import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import { buildApp } from '@/app'; import { prismaClient } from '@/infrastructure/database'; import { FastifyInstance } from 'fastify'; import { loginAs, authHeader } from '../helpers/auth'; import { encryptCredential, generateCredentialSecret, issueIntegrationToken, } from '@/modules/catalog/products'; import { escalationService } from '@/modules/orchestration/escalation'; /** * specs/016-load-concurrency-testing User Story 3 / FR-003 / SC-003: the same escalation * trigger delivered more than once for the same ticket (e.g. two overlapping breach-sweep * passes, or a re-delivered job) must result in exactly one EscalationEvent and exactly one * resulting reassignment — never two. Before research.md §3's fix (the * `escalation_events_ticket_rule_unique` partial unique index plus * EscalationEventRepository.create's catch-and-absorb), `EscalationService.fire` unconditionally * created a new event and reassigned on every call, with no dedup mechanism at all. */ describe('Escalation idempotency (User Story 3)', () => { let app: FastifyInstance; let authToken: string; const externalProductId = `TEST_ESCALATION_IDEMPOTENCY_PROD_${Date.now()}`; const skillTag = `escalation_idempotency_skill_${Date.now()}`; let productId: string; let teamId: string; let agentId: string; let nodeId: string; let policyId: string; let escalationPolicyId: string; let secret: string; const createdTicketIds: string[] = []; async function createTicketAndAssign(): Promise { const token = issueIntegrationToken(secret, { externalProductId, tenantId: 'tenant-1', userId: 'user-1', }); const created = await app.inject({ method: 'POST', url: '/v1/support/requests', headers: { authorization: `Bearer ${token}` }, payload: { productId: externalProductId, tenantId: 'tenant-1', userId: 'user-1', source: 'test', problem: `Escalation idempotency test ${Date.now()}-${Math.random()}`, }, }); expect(created.statusCode).toBe(202); const ticketId = created.json().data.ticketId as string; createdTicketIds.push(ticketId); const ticket = await prismaClient.ticket.findUniqueOrThrow({ where: { id: ticketId } }); await app.inject({ method: 'PATCH', url: `/tickets/${ticketId}/status`, headers: authHeader(authToken), payload: { status: 'HUMAN_ESCALATION', expectedVersion: ticket.version }, }); // Force the run overdue — the concrete condition handleBreach fires for in production, via // the breach sweep. await prismaClient.sLARun.update({ where: { ticketId }, data: { resolutionDueAt: new Date(Date.now() - 60_000) }, }); return ticketId; } beforeAll(async () => { app = await buildApp(); authToken = await loginAs(app, 'ADMIN'); const product = await prismaClient.product.create({ data: { externalProductId, name: 'Escalation Idempotency Test Product', status: 'active' }, }); productId = product.id; secret = generateCredentialSecret(); await prismaClient.productIntegration.create({ data: { productId, credentialRef: encryptCredential(secret), authMechanism: 'signed_token', allowedScope: { tenantIds: ['tenant-1'] }, status: 'active', rateLimitPerMinute: 1000, rateLimitPerUserPerMinute: 1000, }, }); const team = await app.inject({ method: 'POST', url: '/admin/teams', headers: authHeader(authToken), payload: { name: `Escalation Idempotency Team ${Date.now()}` }, }); teamId = team.json().data.id; const agent = await app.inject({ method: 'POST', url: `/admin/teams/${teamId}/agents`, headers: authHeader(authToken), payload: { name: 'Escalation Idempotency Agent' }, }); agentId = agent.json().data.id; await app.inject({ method: 'PUT', url: `/admin/agents/${agentId}/skills/${skillTag}`, headers: authHeader(authToken), payload: { level: 3 }, }); const node = await app.inject({ method: 'POST', url: '/admin/hierarchy-nodes', headers: authHeader(authToken), payload: { name: 'Escalation Idempotency Node', order: 0, productScope: [externalProductId], skills: [skillTag], assignmentStrategy: 'ROUND_ROBIN', }, }); nodeId = node.json().data.id; const policy = await app.inject({ method: 'POST', url: '/admin/sla-policies', headers: authHeader(authToken), payload: { name: 'Escalation Idempotency SLA Policy', productId, firstResponseMinutes: 30, resolutionMinutes: 240, }, }); policyId = policy.json().data.id; const escPolicy = await app.inject({ method: 'POST', url: '/admin/escalation-policies', headers: authHeader(authToken), payload: { name: 'Escalation Idempotency Policy', productId }, }); escalationPolicyId = escPolicy.json().data.id; await app.inject({ method: 'POST', url: `/admin/escalation-policies/${escalationPolicyId}/rules`, headers: authHeader(authToken), payload: { triggerType: 'resolution_breach', condition: {}, targetNodeId: nodeId, notify: {}, }, }); }); afterAll(async () => { const ticketFilter = { ticketId: { in: createdTicketIds } }; await prismaClient.escalationEvent.deleteMany({ where: ticketFilter }); await prismaClient.escalationRule.deleteMany({ where: { targetNodeId: nodeId } }); await prismaClient.escalationPolicy.deleteMany({ where: { id: escalationPolicyId } }); await prismaClient.sLARun.deleteMany({ where: ticketFilter }); await prismaClient.sLAPolicy.deleteMany({ where: { id: policyId } }); await prismaClient.assignmentHistory.deleteMany({ where: ticketFilter }); await prismaClient.assignment.deleteMany({ where: ticketFilter }); await prismaClient.hierarchyNode.deleteMany({ where: { id: nodeId } }); await prismaClient.agentSkill.deleteMany({ where: { agentId } }); await prismaClient.agent.deleteMany({ where: { teamId } }); await prismaClient.team.deleteMany({ where: { id: teamId } }); await prismaClient.ticketMessage.deleteMany({ where: ticketFilter }); await prismaClient.ticket.deleteMany({ where: { id: { in: createdTicketIds } } }); await prismaClient.problem.deleteMany({ where: { productId } }); await prismaClient.productIntegration.deleteMany({ where: { productId } }); await prismaClient.product.deleteMany({ where: { id: productId } }); await app.close(); }); it('results in exactly one escalation event and one assignment when the same trigger fires twice concurrently', async () => { const ticketId = await createTicketAndAssign(); await Promise.all([ escalationService.handleBreach(ticketId, 'resolution_breach'), escalationService.handleBreach(ticketId, 'resolution_breach'), ]); const events = await prismaClient.escalationEvent.findMany({ where: { ticketId } }); expect(events.length).toBe(1); const currentAssignments = await prismaClient.assignment.findMany({ where: { ticketId, isCurrent: true }, }); expect(currentAssignments.length).toBe(1); }); it( 'holds consistently across 10 repeated runs (SC-003: zero duplicate outcomes)', async () => { for (let run = 0; run < 10; run++) { const ticketId = await createTicketAndAssign(); await Promise.all([ escalationService.handleBreach(ticketId, 'resolution_breach'), escalationService.handleBreach(ticketId, 'resolution_breach'), escalationService.handleBreach(ticketId, 'resolution_breach'), ]); const events = await prismaClient.escalationEvent.findMany({ where: { ticketId } }); expect(events.length).toBe(1); const currentAssignments = await prismaClient.assignment.findMany({ where: { ticketId, isCurrent: true }, }); expect(currentAssignments.length).toBe(1); } }, 60000, ); });