Files
support_backend/tests/concurrency/round-robin.test.ts
T
saqib mirandClaude Sonnet 5 40687f68fa feat(010-identity-auth): real staff login, session verification, and role gating
Replaces the no-op fastify.authenticate stub and the never-implemented
identity/auth login with real bcrypt password verification, JWT session
issuance/verification (reusing the existing JWT_SECRET), and a Redis-backed
revocation denylist for logout. Adds requireRole('ADMIN') to admin-only
configuration writes across 002-009 that previously relied on a decorator
that never actually checked anything. Adds self-identity (GET /auth/me,
re-validated against live account state) and admin-provisioned accounts
(POST /admin/users).

Making the auth check genuinely reject invalid/missing tokens exposed that
~18 pre-existing integration test files called already-gated routes with no
Authorization header (safe against the old no-op stub, broken against a real
one) — fixed via a shared tests/helpers/auth.ts (loginAs/authHeader) and a
file-by-file pass, plus two related SLA-run cleanup races exposed once admin
setup calls in those files' own beforeAll blocks started actually succeeding.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 12:45:37 +05:30

81 lines
2.7 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { redisClient } from '@/infrastructure/cache';
import {
selectViaRoundRobin,
roundRobinSelectIndex,
} from '@/modules/orchestration/assignments/strategies/round-robin.strategy';
/**
* Constitution Principle VII / FR-006 / SC-002: round robin must be concurrency-safe under
* genuinely concurrent selection attempts, not just sequential calls — this is the first
* dedicated multi-writer-race test in this codebase since 003-ticketing's optimistic
* ticket-status concurrency (research.md).
*/
describe('ROUND_ROBIN concurrency safety', () => {
function fakeAgent(id: string) {
return {
id,
teamId: 't',
name: id,
active: true,
userId: null,
createdAt: new Date(),
updatedAt: new Date(),
skills: [],
};
}
it('never selects the same index twice for concurrent calls against the same key, and the final counter matches the call count', async () => {
const nodeId = `concurrency-test-node-${Date.now()}`;
const key = `ticketing:round_robin:${nodeId}`;
await redisClient.del(key);
const eligible = [
fakeAgent('a'),
fakeAgent('b'),
fakeAgent('c'),
fakeAgent('d'),
fakeAgent('e'),
];
const callCount = 25;
const results = await Promise.all(
Array.from({ length: callCount }, () =>
selectViaRoundRobin(eligible, { hierarchyNodeId: nodeId, requiredSkills: [] }),
),
);
// Every call must have selected somebody — never null for a non-empty eligible set.
expect(results.every((r) => r !== null)).toBe(true);
// Across every complete cycle through the 5 agents, each agent must be selected exactly
// callCount/5 times — if two concurrent INCRs had ever collided (both reading the same
// pre-increment value), some agent would be over- or under-selected relative to this exact
// count, since callCount (25) is a clean multiple of the eligible set size (5).
const counts = new Map<string, number>();
for (const r of results) {
if (!r) continue;
counts.set(r.id, (counts.get(r.id) ?? 0) + 1);
}
for (const agent of eligible) {
expect(counts.get(agent.id)).toBe(callCount / eligible.length);
}
const finalCount = await redisClient.get(key);
expect(Number(finalCount)).toBe(callCount);
await redisClient.del(key);
});
it('the pure index function never repeats an index within one full cycle', () => {
const length = 7;
const seen = new Set<number>();
for (let count = 1; count <= length; count++) {
const index = roundRobinSelectIndex(count, length);
expect(seen.has(index)).toBe(false);
seen.add(index);
}
expect(seen.size).toBe(length);
});
});