diff --git a/server/tests/test_agent_asset_service.py b/server/tests/test_agent_asset_service.py index 91b7efe..aaa4793 100644 --- a/server/tests/test_agent_asset_service.py +++ b/server/tests/test_agent_asset_service.py @@ -25,10 +25,12 @@ from app.schemas.agent_asset import ( AgentAssetReviewCreate, AgentAssetVersionCreate, ) +from app.api.deps import CurrentUserContext from app.services.agent_assets import AgentAssetService from app.services.agent_runs import AgentRunService from app.services.audit import AuditLogService from app.services.expense_rule_runtime import ExpenseRuleRuntimeService +from app.services.settings import OnlyOfficeRuntimeConfig def build_session() -> Session: @@ -173,6 +175,36 @@ def test_rule_working_version_does_not_replace_published_version_until_activatio assert detail.latest_review is None +def test_pending_review_can_name_new_working_version_before_submission() -> None: + with build_session() as db: + service = AgentAssetService(db) + rule = next( + item + for item in service.list_assets(asset_type=AgentAssetType.RULE.value) + if item.code == "rule.expense.travel_risk_control_standard" + ) + + review = service.create_review( + rule.id, + AgentAssetReviewCreate( + version="v1.2.0", + reviewer="manager_user", + review_status=AgentReviewStatus.PENDING, + review_note="请审核", + ), + actor="finance_user", + ) + detail = service.get_asset(rule.id) + + assert review.version == "v1.2.0" + assert detail is not None + assert detail.current_version == "v1.2.0" + assert detail.working_version == "v1.2.0" + assert detail.published_version == "v1.1.0" + assert detail.latest_review is not None + assert detail.latest_review.reviewer == "manager_user" + + def test_expense_rule_runtime_uses_published_version_instead_of_working_version() -> None: with build_session() as db: service = AgentAssetService(db) @@ -296,6 +328,78 @@ def test_working_spreadsheet_version_reads_immutable_snapshot_instead_of_live_co live_path.write_bytes(original_live_bytes) +def test_spreadsheet_change_records_return_recent_edit_details() -> None: + with build_session() as db: + service = AgentAssetService(db) + rule = next( + item + for item in service.list_assets(asset_type=AgentAssetType.RULE.value) + if item.code == "rule.expense.company_travel_expense_reimbursement" + ) + service.audit_service.log_action( + actor="manager_user", + action="edit_rule_spreadsheet", + resource_type=rule.asset_type, + resource_id=rule.id, + after_json={ + "summary": "在线编辑:共 1 处改动。", + "changed_sheet_count": 1, + "changed_cell_count": 1, + "sheet_changes": [], + "cell_changes": [ + { + "sheet_name": "规则表", + "cell": "B2", + "change_type": "modified", + "before_value": 500, + "after_value": 550, + } + ], + }, + ) + + records = service.list_spreadsheet_change_records(rule.id) + + assert len(records) == 1 + assert records[0].actor == "manager_user" + assert records[0].changed_cell_count == 1 + assert records[0].cell_changes[0].cell == "B2" + + +def test_editable_spreadsheet_onlyoffice_config_enables_forcesave(monkeypatch) -> None: + with build_session() as db: + monkeypatch.setattr( + "app.services.agent_assets.resolve_onlyoffice_settings", + lambda: OnlyOfficeRuntimeConfig( + enabled=True, + public_url="http://onlyoffice.example.com", + backend_url="http://backend.example.com", + jwt_secret="secret", + ), + ) + + service = AgentAssetService(db) + rule = next( + item + for item in service.list_assets(asset_type=AgentAssetType.RULE.value) + if item.code == "rule.expense.company_travel_expense_reimbursement" + ) + + config = service.build_rule_spreadsheet_onlyoffice_config( + rule.id, + CurrentUserContext( + username="finance_user", + name="财务人员", + role_codes=["finance"], + is_admin=False, + ), + ) + + customization = config.config["editorConfig"]["customization"] + assert config.config["editorConfig"]["mode"] == "edit" + assert customization["forcesave"] is True + + def test_version_timeline_contains_created_review_and_publish_events() -> None: with build_session() as db: service = AgentAssetService(db) diff --git a/web/tests/spreadsheet-change-records.test.mjs b/web/tests/spreadsheet-change-records.test.mjs new file mode 100644 index 0000000..9fe24db --- /dev/null +++ b/web/tests/spreadsheet-change-records.test.mjs @@ -0,0 +1,92 @@ +import assert from 'node:assert/strict' + +import { + buildSpreadsheetChangeRecords, + resolveSpreadsheetOperationLabel +} from '../src/views/scripts/spreadsheetChangeRecords.js' + +function testResolveSpreadsheetOperationLabelFromHistorySource() { + assert.equal( + resolveSpreadsheetOperationLabel({ + spreadsheetMeta: { source: 'onlyoffice' }, + note: 'ONLYOFFICE 编辑保存规则表。' + }), + '在线编辑保存' + ) + + assert.equal( + resolveSpreadsheetOperationLabel({ + spreadsheetMeta: { source: 'content-import' } + }), + '导入表格' + ) +} + +function testBuildSpreadsheetChangeRecordsPrefersUnpublishedHistory() { + const records = buildSpreadsheetChangeRecords({ + publishedVersion: 'v1.0.0', + history: [ + { + version: 'v1.0.1', + note: 'ONLYOFFICE 编辑保存规则表。', + createdBy: '张三', + createdAt: '2026-05-18T10:00:00Z', + isWorking: true, + spreadsheetMeta: { source: 'onlyoffice' } + }, + { + version: 'v1.0.0', + note: '首个上线版本', + createdBy: '系统', + createdAt: '2026-05-18T09:00:00Z', + isWorking: false, + spreadsheetMeta: { source: 'upload' } + } + ] + }) + + assert.deepEqual( + records.map((item) => [item.version, item.operationLabel]), + [['v1.0.1', '在线编辑保存']] + ) +} + +function testBuildSpreadsheetChangeRecordsKeepsPendingLocalEditFirst() { + const records = buildSpreadsheetChangeRecords({ + publishedVersion: 'v1.0.0', + localRecords: [ + { + version: 'v1.0.1', + operationLabel: '编辑中', + operationActor: '李四', + note: '检测到未保存的表格改动,保存后会生成新版本并可查看差异。', + time: '2026-05-18T10:30:00Z', + isPendingLocalEdit: true, + disabledReason: '当前是本地未保存修改,保存后才会生成可对比的版本。' + } + ], + history: [ + { + version: 'v1.0.1', + note: 'ONLYOFFICE 编辑保存规则表。', + createdBy: '李四', + createdAt: '2026-05-18T10:00:00Z', + isWorking: true, + spreadsheetMeta: { source: 'onlyoffice' } + } + ] + }) + + assert.equal(records[0].operationLabel, '编辑中') + assert.equal(records[0].isPendingLocalEdit, true) + assert.equal(records[1].operationLabel, '在线编辑保存') +} + +function run() { + testResolveSpreadsheetOperationLabelFromHistorySource() + testBuildSpreadsheetChangeRecordsPrefersUnpublishedHistory() + testBuildSpreadsheetChangeRecordsKeepsPendingLocalEditFirst() + console.log('spreadsheet change record tests passed') +} + +run()