mirror of
https://github.com/langflow-ai/langflow.git
synced 2026-07-24 06:42:21 +08:00
The hard_proof marker name was a vibe word that said nothing about what the tests need. Rename it to real_services everywhere: the pytest marker registration, the *_hard_proof.py test files, the Makefile target (real_services_tests), the -m selector in migration-validation.yml, and the CI job. real_services says what these tests require: real Postgres + Redis + worker subprocesses. (integration was already taken for the external-API suite under tests/integration.)
264 lines
9.1 KiB
YAML
264 lines
9.1 KiB
YAML
name: Database Migration Validation
|
|
|
|
on:
|
|
pull_request:
|
|
paths:
|
|
- 'src/backend/base/langflow/alembic/**'
|
|
- 'src/backend/base/langflow/services/database/models/**'
|
|
- 'src/backend/base/langflow/services/database/service.py'
|
|
- 'src/backend/tests/unit/alembic/**'
|
|
- '.github/workflows/migration-validation.yml'
|
|
- 'src/backend/base/langflow/services/background_execution/**'
|
|
- 'src/backend/base/langflow/services/jobs/**'
|
|
- 'src/backend/base/langflow/services/job_queue/**'
|
|
- 'src/backend/tests/unit/background_execution/**'
|
|
|
|
jobs:
|
|
model-migration-consistency:
|
|
name: Model/Migration Consistency
|
|
runs-on: ubuntu-latest
|
|
|
|
services:
|
|
postgres:
|
|
image: postgres:16
|
|
env:
|
|
POSTGRES_USER: langflow
|
|
POSTGRES_PASSWORD: langflow # pragma: allowlist secret
|
|
POSTGRES_DB: langflow
|
|
ports:
|
|
- 5432:5432
|
|
options: >-
|
|
--health-cmd="pg_isready -U langflow"
|
|
--health-interval=10s
|
|
--health-timeout=5s
|
|
--health-retries=5
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Install uv
|
|
uses: astral-sh/setup-uv@v6
|
|
|
|
- name: Setup Python
|
|
uses: actions/setup-python@v6
|
|
with:
|
|
python-version: '3.12'
|
|
|
|
- name: Install dependencies
|
|
run: |
|
|
uv sync --extra postgresql
|
|
|
|
- name: Check model/migration consistency
|
|
env:
|
|
MIGRATION_VALIDATION_CI: "true"
|
|
LANGFLOW_TEST_DATABASE_URI: "postgresql://langflow:langflow@localhost:5432/langflow" # pragma: allowlist secret
|
|
run: |
|
|
uv run pytest src/backend/tests/unit/alembic/test_migration_execution.py -x -v
|
|
|
|
background-real-service:
|
|
name: Background Execution Real-Service Tests (real Postgres + Redis)
|
|
runs-on: ubuntu-latest
|
|
|
|
services:
|
|
postgres:
|
|
image: postgres:16
|
|
env:
|
|
POSTGRES_USER: langflow
|
|
POSTGRES_PASSWORD: langflow # pragma: allowlist secret
|
|
POSTGRES_DB: langflow
|
|
ports:
|
|
- 5432:5432
|
|
options: >-
|
|
--health-cmd="pg_isready -U langflow"
|
|
--health-interval=10s
|
|
--health-timeout=5s
|
|
--health-retries=5
|
|
redis:
|
|
image: redis:7
|
|
ports:
|
|
- 6379:6379
|
|
options: >-
|
|
--health-cmd="redis-cli ping"
|
|
--health-interval=10s
|
|
--health-timeout=5s
|
|
--health-retries=5
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Install uv
|
|
uses: astral-sh/setup-uv@v6
|
|
|
|
- name: Setup Python
|
|
uses: actions/setup-python@v6
|
|
with:
|
|
python-version: '3.12'
|
|
|
|
- name: Install dependencies
|
|
run: |
|
|
uv sync --extra postgresql
|
|
|
|
- name: Run real-service tests
|
|
env:
|
|
LANGFLOW_TEST_DATABASE_URI: "postgresql://langflow:langflow@localhost:5432/langflow" # pragma: allowlist secret
|
|
LANGFLOW_TEST_REDIS_URL: "redis://localhost:6379/0"
|
|
run: |
|
|
uv run pytest src/backend/tests/unit/background_execution -m real_services -x -v
|
|
|
|
validate-migration:
|
|
name: Migration Pattern Validation
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Setup Python
|
|
uses: actions/setup-python@v6
|
|
with:
|
|
python-version: '3.12'
|
|
|
|
- name: Get changed migration files
|
|
id: changed-files
|
|
run: |
|
|
set -euo pipefail
|
|
CHANGED_FILES=$(git diff --name-only origin/main...HEAD | grep -E 'src/backend/base/langflow/alembic/versions/.*\.py$' | grep -v 'test_migrations/' || echo "")
|
|
|
|
if [ -z "$CHANGED_FILES" ]; then
|
|
echo "No migration files changed"
|
|
echo "files=" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "Changed migration files:"
|
|
echo "$CHANGED_FILES"
|
|
echo "files=$(printf '%s' "$CHANGED_FILES" | tr '\n' ' ')" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Validate migration patterns
|
|
if: steps.changed-files.outputs.files != ''
|
|
env:
|
|
MIGRATION_FILES: ${{ steps.changed-files.outputs.files }}
|
|
run: |
|
|
python src/backend/base/langflow/alembic/migration_validator.py $MIGRATION_FILES
|
|
|
|
- name: Generate validation report
|
|
if: always() && steps.changed-files.outputs.files != ''
|
|
env:
|
|
MIGRATION_FILES: ${{ steps.changed-files.outputs.files }}
|
|
run: |
|
|
python src/backend/base/langflow/alembic/migration_validator.py \
|
|
--json $MIGRATION_FILES > validation-report.json 2> validation-stderr.txt || true
|
|
if [ ! -s validation-report.json ]; then
|
|
echo "::error::Validator produced no output. Stderr:"
|
|
cat validation-stderr.txt
|
|
fi
|
|
|
|
- name: Post PR comment with results
|
|
if: always() && steps.changed-files.outputs.files != ''
|
|
uses: actions/github-script@v8
|
|
with:
|
|
script: |
|
|
const fs = require('fs');
|
|
|
|
let message = '';
|
|
let validationPassed = true;
|
|
|
|
try {
|
|
const report = JSON.parse(fs.readFileSync('validation-report.json', 'utf8'));
|
|
|
|
for (const result of report) {
|
|
if (!result.valid) {
|
|
validationPassed = false;
|
|
}
|
|
}
|
|
|
|
if (validationPassed) {
|
|
message = `✅ **Migration Validation Passed**\n\n`;
|
|
message += `All migrations follow the Expand-Contract pattern correctly.\n\n`;
|
|
} else {
|
|
message = `❌ **Migration Validation Failed**\n\n`;
|
|
message += `Your migrations don't follow the Expand-Contract pattern.\n\n`;
|
|
|
|
for (const result of report) {
|
|
if (!result.valid || result.warnings.length > 0) {
|
|
message += `### File: \`${result.file.split('/').pop()}\`\n`;
|
|
message += `**Phase:** ${result.phase}\n\n`;
|
|
|
|
if (result.violations && result.violations.length > 0) {
|
|
message += `**Violations:**\n`;
|
|
for (const v of result.violations) {
|
|
message += `- Line ${v.line}: ${v.message}\n`;
|
|
}
|
|
message += `\n`;
|
|
}
|
|
|
|
if (result.warnings && result.warnings.length > 0) {
|
|
message += `**Warnings:**\n`;
|
|
for (const w of result.warnings) {
|
|
message += `- Line ${w.line}: ${w.message}\n`;
|
|
}
|
|
message += `\n`;
|
|
}
|
|
}
|
|
}
|
|
|
|
message += `### Resources\n`;
|
|
message += `- Review the [DB Migration Guide](./src/backend/base/langflow/alembic/DB-MIGRATION-GUIDE.MD)\n`;
|
|
message += `- Use \`python scripts/generate_migration.py --help\` to generate compliant migrations\n\n`;
|
|
|
|
message += `### Common Issues & Solutions\n`;
|
|
message += `- **New columns must be nullable:** Add \`nullable=True\` or \`server_default\`\n`;
|
|
message += `- **Missing phase marker:** Add \`Phase: EXPAND/MIGRATE/CONTRACT\` to docstring\n`;
|
|
message += `- **Column drops:** Only allowed in CONTRACT phase\n`;
|
|
message += `- **Direct renames:** Use expand-contract pattern instead\n`;
|
|
}
|
|
} catch (error) {
|
|
message = `⚠️ **Migration validation check failed to run properly**\n`;
|
|
message += `Error: ${error.message}\n`;
|
|
validationPassed = false;
|
|
}
|
|
|
|
// Post or update comment (non-critical — don't let API errors mask validation results)
|
|
try {
|
|
const { data: comments } = await github.rest.issues.listComments({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: context.issue.number,
|
|
});
|
|
|
|
const botComment = comments.find(comment =>
|
|
comment.user.type === 'Bot' &&
|
|
comment.body.includes('Migration Validation')
|
|
);
|
|
|
|
if (botComment) {
|
|
await github.rest.issues.updateComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
comment_id: botComment.id,
|
|
body: message
|
|
});
|
|
} else {
|
|
await github.rest.issues.createComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: context.issue.number,
|
|
body: message
|
|
});
|
|
}
|
|
} catch (apiError) {
|
|
core.warning(`Failed to post PR comment: ${apiError.message}`);
|
|
}
|
|
|
|
// Fail the workflow if validation didn't pass
|
|
if (!validationPassed) {
|
|
core.setFailed('Migration validation failed');
|
|
}
|