feat: add public bank sync failure ops

This commit is contained in:
Codex
2026-06-30 09:12:45 +08:00
parent d1cb341351
commit 959e612211
14 changed files with 484 additions and 44 deletions

View File

@@ -230,8 +230,111 @@ async function main() {
const secondOutput = await runWorkerOnce();
assert.match(secondOutput, /processed=0/, 'worker should skip already synced public bank when source has not changed');
await pool.query(
'update public.question_bank_grants set status = $2, updated_at = now() where id = $1',
[ids.grant, 'disabled'],
);
await pool.query(
`
update public.tenant_question_bank_adoptions
set status = 'active',
sync_status = 'pending',
metadata = metadata || '{"source":"public_bank_worker_failure_test"}'::jsonb,
updated_at = now()
where tenant_id = $1 and id = $2
`,
[PARTNER_TENANT_ID, ids.adoption],
);
const failedOutput = await runWorkerOnce();
assert.match(failedOutput, /processed=1/, 'worker should claim forced pending adoption');
assert.match(failedOutput, /failed=1/, 'worker should count unavailable grant as failed');
const failedAdoption = await pool.query(
`
select status, sync_status, metadata
from public.tenant_question_bank_adoptions
where tenant_id = $1 and id = $2
`,
[PARTNER_TENANT_ID, ids.adoption],
);
assert.equal(failedAdoption.rows[0]?.status, 'active', 'failed worker sync should return adoption to active');
assert.equal(failedAdoption.rows[0]?.sync_status, 'failed', 'failed worker sync should mark adoption failed');
assert.equal(
failedAdoption.rows[0]?.metadata?.publicBankSyncWorker?.lastError?.code,
'QUESTION_BANK_GRANT_NOT_AVAILABLE',
'failed worker sync should persist a stable error code',
);
const failureNotifications = await pool.query(
`
select notification_type, status, severity, message, metadata
from public.tenant_content_notifications
where tenant_id = $1 and adoption_id = $2 and notification_type = 'public_question_bank_sync_failed'
order by created_at desc
limit 5
`,
[PARTNER_TENANT_ID, ids.adoption],
);
const failureNotification = failureNotifications.rows[0];
assert.equal(failureNotification?.status, 'unread', 'failed worker sync should create an unread tenant notification');
assert.equal(failureNotification?.severity, 'error', 'failed worker sync notification should use error severity');
assert.equal(
failureNotification?.metadata?.errorCode,
'QUESTION_BANK_GRANT_NOT_AVAILABLE',
'failed worker sync notification should expose stable error code metadata',
);
assert.ok(
!JSON.stringify(failureNotification).includes('integration-second-region-v1'),
'failed worker sync notification should not leak source question details',
);
await pool.query(
'update public.question_bank_grants set status = $2, updated_at = now() where id = $1',
[ids.grant, 'active'],
);
await pool.query(
`
update public.tenant_question_bank_adoptions
set status = 'active', sync_status = 'pending', updated_at = now()
where tenant_id = $1 and id = $2
`,
[PARTNER_TENANT_ID, ids.adoption],
);
const recoveryOutput = await runWorkerOnce();
assert.match(recoveryOutput, /processed=1/, 'worker should retry adoption after grant recovers');
const recoveredAdoption = await pool.query(
`
select sync_status
from public.tenant_question_bank_adoptions
where tenant_id = $1 and id = $2
`,
[PARTNER_TENANT_ID, ids.adoption],
);
assert.equal(recoveredAdoption.rows[0]?.sync_status, 'synced', 'worker should recover failed sync after grant becomes available');
const resolvedFailureNotifications = await pool.query(
`
select status, resolved_at
from public.tenant_content_notifications
where tenant_id = $1 and adoption_id = $2 and notification_type = 'public_question_bank_sync_failed'
order by updated_at desc
limit 1
`,
[PARTNER_TENANT_ID, ids.adoption],
);
assert.equal(
resolvedFailureNotifications.rows[0]?.status,
'resolved',
'successful recovery should resolve previous public bank sync failure notifications',
);
assert.ok(resolvedFailureNotifications.rows[0]?.resolved_at, 'resolved failure notification should record resolved_at');
console.log('Public question bank sync worker integration test complete.');
} finally {
await pool.query('update public.question_bank_grants set status = $2, updated_at = now() where id = $1', [ids.grant, 'active']).catch(() => {});
await cleanup(pool).catch(() => {});
await pool.end();
}