test: add concurrent resolveSnapshot race test (API-AI-R01)
All checks were successful
Deploy API Server / build-and-deploy (push) Successful in 47s

- Simulates two concurrent getSnapshot calls with no valid snapshot
- Documents the known race: both trigger buildSnapshot, second update overwrites first
- Verifies both calls complete without error (accepted-risk behavior)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
wangdl 2026-06-18 11:41:35 +08:00
parent 7725b3d2ea
commit c0594c518d

View File

@ -329,6 +329,38 @@ describe('RuntimeInternalService', () => {
}); });
}); });
// ═══════════════════════════════════════════════════════════════════
// resolveSnapshot: concurrent race (API-AI-R01)
// ═══════════════════════════════════════════════════════════════════
describe('resolveSnapshot concurrent race', () => {
it('handles concurrent getSnapshot without error (known race, accepted risk)', async () => {
// Simulate: two calls with no valid snapshot → both trigger buildSnapshot
const job = { id: 'j1', userId: 'u1', targetType: 'material', targetId: 'm1', snapshotId: null };
mockAiRuntimeJob.findUnique.mockResolvedValue(job);
// Each buildSnapshot call returns a new id (simulating two builds)
mockSnapshotBuilder.buildSnapshot
.mockResolvedValueOnce({ ...mockSnapshot, id: 'snap-A' })
.mockResolvedValueOnce({ ...mockSnapshot, id: 'snap-B' });
// First update writes snap-A, second overwrites with snap-B (the race)
mockAiRuntimeJob.update.mockResolvedValue({});
const [r1, r2] = await Promise.all([
service.getSnapshot('j1'),
service.getSnapshot('j1'),
]);
// Both calls succeed; one snapshot may be orphaned (documented risk)
expect(r1.snapshotId).toBeDefined();
expect(r2.snapshotId).toBeDefined();
// Two buildSnapshot calls issued (the race)
expect(mockSnapshotBuilder.buildSnapshot).toHaveBeenCalledTimes(2);
// Job updated twice (second overwrites first — orphan snap-A)
expect(mockAiRuntimeJob.update).toHaveBeenCalledTimes(2);
});
});
// ═══════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════
// resolveCredential // resolveCredential
// ═══════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════