feat: batch resolve public bank conflicts

This commit is contained in:
Codex
2026-06-29 14:18:25 +08:00
parent 215cbd05f7
commit c00992aab5
13 changed files with 621 additions and 28 deletions

View File

@@ -4233,6 +4233,140 @@ async function testPublicQuestionBankAdoption() {
const keptLocalTarget = collectionAfterKeepLocal.items?.find(item => item.id === conflictTarget.id);
assert.equal(keptLocalTarget?.content, keepLocalContent, 'keep-local resolution should preserve tenant customized content');
const batchTargetOne = keptLocalTarget;
const batchTargetTwo = collectionAfterKeepLocal.items?.find(item => item.legacyId === syncedLegacyId);
assert.ok(batchTargetOne?.id && batchTargetTwo?.id, 'batch conflict test needs two adopted tenant question copies');
const batchLocalOneContent = `租户批量冲突本地题一 ${Date.now()}`;
const batchLocalTwoContent = `租户批量冲突本地题二 ${Date.now()}`;
await request('/api/tenant-content/questions', {
tenantId: PARTNER_TENANT_ID,
userId: PARTNER_TENANT_ADMIN_USER_ID,
method: 'PATCH',
body: {
questionId: batchTargetOne.id,
createVersion: true,
content: batchLocalOneContent,
options: batchTargetOne.options || [],
correctOptionIndex: batchTargetOne.correctOptionIndex,
correctOptionIndices: batchTargetOne.correctOptionIndices || [],
answerText: batchTargetOne.answerText,
explanation: '租户批量冲突测试本地版本一。',
sourceHash: `tenant-batch-local-one-${Date.now()}`,
},
});
await request('/api/tenant-content/questions', {
tenantId: PARTNER_TENANT_ID,
userId: PARTNER_TENANT_ADMIN_USER_ID,
method: 'PATCH',
body: {
questionId: batchTargetTwo.id,
createVersion: true,
content: batchLocalTwoContent,
options: batchTargetTwo.options || [],
correctOptionIndex: batchTargetTwo.correctOptionIndex,
correctOptionIndices: batchTargetTwo.correctOptionIndices || [],
answerText: batchTargetTwo.answerText,
explanation: '租户批量冲突测试本地版本二。',
sourceHash: `tenant-batch-local-two-${Date.now()}`,
},
});
const batchPlatformOneContent = '平台公共题库批量更新1 + 3 = ?';
const batchPlatformTwoContent = '平台公共题库批量更新5 + 6 = ?';
await request('/api/tenant-content/questions', {
userId: TENANT_ADMIN_USER_ID,
method: 'PATCH',
body: {
questionId: ids.question,
createVersion: true,
content: batchPlatformOneContent,
options: [
{ label: 'A', text: '3' },
{ label: 'B', text: '4' },
],
correctOptionIndex: 1,
correctOptionIndices: [1],
answerText: '4',
explanation: '平台公共题库批量冲突测试题一。',
sourceHash: `platform-batch-conflict-one-${Date.now()}`,
},
});
await request('/api/tenant-content/questions', {
userId: TENANT_ADMIN_USER_ID,
method: 'PATCH',
body: {
questionId: sourceQuestion.item.id,
createVersion: true,
content: batchPlatformTwoContent,
options: [
{ label: 'A', text: '11' },
{ label: 'B', text: '12' },
],
correctOptionIndex: 0,
correctOptionIndices: [0],
answerText: '11',
explanation: '平台公共题库批量冲突测试题二。',
sourceHash: `platform-batch-conflict-two-${Date.now()}`,
},
});
const batchConflictSync = await request('/api/tenant-content/public-question-banks/sync', {
tenantId: PARTNER_TENANT_ID,
userId: PARTNER_TENANT_ADMIN_USER_ID,
method: 'POST',
body: { adoptionId: adopted.item.id, copyLimit: 20 },
});
assert.equal(batchConflictSync.sync?.status, 'conflict', 'batch setup should produce public bank conflicts');
const batchConflictIds = (batchConflictSync.sync?.results || [])
.filter(item => item.action === 'conflict' && [ids.question, sourceQuestion.item.id].includes(item.sourceQuestionId))
.map(item => item.sourceQuestionId);
assert.deepEqual(new Set(batchConflictIds), new Set([ids.question, sourceQuestion.item.id]), 'batch setup should produce two expected conflicts');
const crossTenantBatchDenied = await request('/api/tenant-content/public-question-banks/conflicts/resolve-batch', {
userId: TENANT_ADMIN_USER_ID,
method: 'POST',
body: {
adoptionId: adopted.item.id,
sourceQuestionIds: [ids.question, sourceQuestion.item.id],
resolution: 'accept_platform',
},
expectStatus: 404,
});
assert.equal(crossTenantBatchDenied.code, 'QUESTION_BANK_ADOPTION_NOT_FOUND', 'public bank batch conflict resolution must be tenant isolated');
const batchAccepted = await request('/api/tenant-content/public-question-banks/conflicts/resolve-batch', {
tenantId: PARTNER_TENANT_ID,
userId: PARTNER_TENANT_ADMIN_USER_ID,
method: 'POST',
body: {
adoptionId: adopted.item.id,
sourceQuestionIds: [ids.question, sourceQuestion.item.id],
resolution: 'accept_platform',
},
});
assert.equal(batchAccepted.item?.resolution, 'accept_platform', 'batch conflict resolution should expose resolution');
assert.equal(batchAccepted.item?.processedCount, 2, 'batch conflict resolution should process both conflicts');
assert.equal(batchAccepted.item?.remainingConflictCount, 0, 'batch conflict resolution should clear all conflicts');
assert.equal(batchAccepted.item?.syncStatus, 'synced', 'batch conflict resolution should mark adoption synced');
const collectionAfterBatchAccept = await request('/api/catalog/question-collections/questions', {
tenantId: PARTNER_TENANT_ID,
userId: false,
query: { collectionId: adopted.item.targetCollectionId, limit: 50 },
});
const batchAcceptedOne = collectionAfterBatchAccept.items?.find(item => item.id === batchTargetOne.id);
const batchAcceptedTwo = collectionAfterBatchAccept.items?.find(item => item.id === batchTargetTwo.id);
assert.equal(batchAcceptedOne?.content, batchPlatformOneContent, 'batch accept should update first tenant copy to platform content');
assert.equal(batchAcceptedTwo?.content, batchPlatformTwoContent, 'batch accept should update second tenant copy to platform content');
const conflictListAfterBatchAccept = await request('/api/tenant-content/public-question-banks/conflicts', {
tenantId: PARTNER_TENANT_ID,
userId: PARTNER_TENANT_ADMIN_USER_ID,
query: { adoptionId: adopted.item.id },
});
assert.equal(conflictListAfterBatchAccept.item?.conflictCount, 0, 'batch-resolved conflict list should have no remaining conflicts');
const mainTenantNotAdopted = await request('/api/tenant-content/public-question-banks', {
userId: TENANT_ADMIN_USER_ID,
query: { onlyNotAdopted: 'true' },