Compare commits

..

1 Commits

Author SHA1 Message Date
非法操作
573b4e41cb fix: correctly detect required columns in archived workflow run restore (#33403)
Some checks are pending
autofix.ci / autofix (push) Waiting to run
Build and Push API & Web / build (api, DIFY_API_IMAGE_NAME, linux/amd64, build-api-amd64) (push) Waiting to run
Build and Push API & Web / build (api, DIFY_API_IMAGE_NAME, linux/arm64, build-api-arm64) (push) Waiting to run
Build and Push API & Web / build (web, DIFY_WEB_IMAGE_NAME, linux/amd64, build-web-amd64) (push) Waiting to run
Build and Push API & Web / build (web, DIFY_WEB_IMAGE_NAME, linux/arm64, build-web-arm64) (push) Waiting to run
Build and Push API & Web / create-manifest (api, DIFY_API_IMAGE_NAME, merge-api-images) (push) Blocked by required conditions
Build and Push API & Web / create-manifest (web, DIFY_WEB_IMAGE_NAME, merge-web-images) (push) Blocked by required conditions
Main CI Pipeline / Check Changed Files (push) Waiting to run
Main CI Pipeline / API Tests (push) Blocked by required conditions
Main CI Pipeline / Web Tests (push) Blocked by required conditions
Main CI Pipeline / Style Check (push) Waiting to run
Main CI Pipeline / VDB Tests (push) Blocked by required conditions
Main CI Pipeline / DB Migration Test (push) Blocked by required conditions
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-03-13 23:24:40 +08:00
4 changed files with 118 additions and 27 deletions

View File

@@ -358,21 +358,19 @@ class WorkflowRunRestore:
self,
model: type[DeclarativeBase] | Any,
) -> tuple[set[str], set[str], set[str]]:
columns = list(model.__table__.columns)
table = model.__table__
columns = list(table.columns)
autoincrement_column = getattr(table, "autoincrement_column", None)
def has_insert_default(column: Any) -> bool:
# SQLAlchemy may set column.autoincrement to "auto" on non-PK columns.
# Only treat the resolved autoincrement column as DB-generated.
return column.default is not None or column.server_default is not None or column is autoincrement_column
column_names = {column.key for column in columns}
required_columns = {
column.key
for column in columns
if not column.nullable
and column.default is None
and column.server_default is None
and not column.autoincrement
}
required_columns = {column.key for column in columns if not column.nullable and not has_insert_default(column)}
non_nullable_with_default = {
column.key
for column in columns
if not column.nullable
and (column.default is not None or column.server_default is not None or column.autoincrement)
column.key for column in columns if not column.nullable and has_insert_default(column)
}
return column_names, required_columns, non_nullable_with_default

View File

@@ -13,6 +13,7 @@ from datetime import datetime
from unittest.mock import Mock, create_autospec, patch
import pytest
from sqlalchemy import Column, Integer, MetaData, String, Table
from libs.archive_storage import ArchiveStorageNotConfiguredError
from models.trigger import WorkflowTriggerLog
@@ -127,10 +128,41 @@ class WorkflowRunRestoreTestDataFactory:
if tables_data is None:
tables_data = {
"workflow_runs": [{"id": "run-123", "tenant_id": "tenant-123"}],
"workflow_runs": [
{
"id": "run-123",
"tenant_id": "tenant-123",
"app_id": "app-123",
"workflow_id": "workflow-123",
"type": "workflow",
"triggered_from": "app",
"version": "1",
"status": "succeeded",
"created_by_role": "account",
"created_by": "user-123",
}
],
"workflow_app_logs": [
{"id": "log-1", "workflow_run_id": "run-123"},
{"id": "log-2", "workflow_run_id": "run-123"},
{
"id": "log-1",
"tenant_id": "tenant-123",
"app_id": "app-123",
"workflow_id": "workflow-123",
"workflow_run_id": "run-123",
"created_from": "app",
"created_by_role": "account",
"created_by": "user-123",
},
{
"id": "log-2",
"tenant_id": "tenant-123",
"app_id": "app-123",
"workflow_id": "workflow-123",
"workflow_run_id": "run-123",
"created_from": "app",
"created_by_role": "account",
"created_by": "user-123",
},
],
}
@@ -406,14 +438,48 @@ class TestGetModelColumnInfo:
assert "created_by" in column_names
assert "status" in column_names
# WorkflowRun model has no required columns (all have defaults or are auto-generated)
assert required_columns == set()
# Columns without defaults should be required for restore inserts.
assert {
"tenant_id",
"app_id",
"workflow_id",
"type",
"triggered_from",
"version",
"status",
"created_by_role",
"created_by",
}.issubset(required_columns)
assert "id" not in required_columns
assert "created_at" not in required_columns
# Check columns with defaults or server defaults
assert "id" in non_nullable_with_default
assert "created_at" in non_nullable_with_default
assert "elapsed_time" in non_nullable_with_default
assert "total_tokens" in non_nullable_with_default
assert "tenant_id" not in non_nullable_with_default
def test_non_pk_auto_autoincrement_column_is_still_required(self):
"""`autoincrement='auto'` should not mark non-PK columns as defaulted."""
restore = WorkflowRunRestore()
test_table = Table(
"test_autoincrement",
MetaData(),
Column("id", Integer, primary_key=True, autoincrement=True),
Column("required_field", String(255), nullable=False),
Column("defaulted_field", String(255), nullable=False, default="x"),
)
class MockModel:
__table__ = test_table
_, required_columns, non_nullable_with_default = restore._get_model_column_info(MockModel)
assert required_columns == {"required_field"}
assert "id" in non_nullable_with_default
assert "defaulted_field" in non_nullable_with_default
# ---------------------------------------------------------------------------
@@ -465,7 +531,32 @@ class TestRestoreTableRecords:
mock_stmt.on_conflict_do_nothing.return_value = mock_stmt
mock_pg_insert.return_value = mock_stmt
records = [{"id": "test1", "tenant_id": "tenant-123"}, {"id": "test2", "tenant_id": "tenant-123"}]
records = [
{
"id": "test1",
"tenant_id": "tenant-123",
"app_id": "app-123",
"workflow_id": "workflow-123",
"type": "workflow",
"triggered_from": "app",
"version": "1",
"status": "succeeded",
"created_by_role": "account",
"created_by": "user-123",
},
{
"id": "test2",
"tenant_id": "tenant-123",
"app_id": "app-123",
"workflow_id": "workflow-123",
"type": "workflow",
"triggered_from": "app",
"version": "1",
"status": "succeeded",
"created_by_role": "account",
"created_by": "user-123",
},
]
result = restore._restore_table_records(mock_session, "workflow_runs", records, schema_version="1.0")
@@ -477,8 +568,7 @@ class TestRestoreTableRecords:
restore = WorkflowRunRestore()
mock_session = Mock()
# Since WorkflowRun has no required columns, we need to test with a different model
# Let's test with a mock model that has required columns
# Use a dedicated mock model to isolate required-column validation behavior.
mock_model = Mock()
# Mock a required column
@@ -965,6 +1055,13 @@ class TestIntegration:
"id": "run-123",
"tenant_id": "tenant-123",
"app_id": "app-123",
"workflow_id": "workflow-123",
"type": "workflow",
"triggered_from": "app",
"version": "1",
"status": "succeeded",
"created_by_role": "account",
"created_by": "user-123",
"created_at": "2024-01-01T12:00:00",
}
],

View File

@@ -82,11 +82,8 @@ vi.mock('@/app/components/base/voice-input', () => {
}
})
vi.stubGlobal('requestAnimationFrame', (cb: FrameRequestCallback) => {
cb(Date.now())
return 0
})
vi.stubGlobal('cancelAnimationFrame', vi.fn())
vi.stubGlobal('requestAnimationFrame', (cb: FrameRequestCallback) => setTimeout(() => cb(Date.now()), 16))
vi.stubGlobal('cancelAnimationFrame', (id: number) => clearTimeout(id))
vi.stubGlobal('devicePixelRatio', 1)
// Mock Canvas

View File

@@ -82,7 +82,6 @@ export default defineConfig(({ mode }) => {
// Vitest config
test: {
detectAsyncLeaks: true,
environment: 'jsdom',
globals: true,
setupFiles: ['./vitest.setup.ts'],