Files
OmniRoute/tests/unit/routing-events-concurrency.test.ts
3g0r1ch d87b97a786 feat(routing): adaptive feedback loop v2 — operational/semantic quality, confidence, TTFT/ITL, end-to-end test (#10881)
Obrigado — feature substancial e bem estruturada: separa qualidade operacional (comportamento de wire: 4xx/5xx, 429, respostas malformadas, stream interrompido) de qualidade semântica (só setada por avaliadores externos, nunca inferida do sucesso HTTP), com confidence/sample-awareness para não deixar poucos sucessos de sorte dominarem o ranking. Instrumentação de streaming (TTFT/ITL) threaded até RoutingEvent, endpoint de explicabilidade, e teste E2E determinístico cobrindo degradação→recuperação→blip.

Validação (worktree própria a partir de origin/release/v3.8.50, merge limpo, 0 conflitos):
- typecheck:core limpo, complexity/cognitive-complexity dentro do baseline
- 59/59 testes passando (mlx-provider, routing-adaptive-e2e, routing-events(-concurrency), routing-otel, routing-quality, routing-scoring-quality, stream-timing, auto-combo-scoring-clamp)
2026-08-20 17:28:30 -03:00

173 lines
5.5 KiB
TypeScript

/**
* tests/unit/routing-events-concurrency.test.ts
*
* Stress the routing-event system (Phase 4):
* - thousands of events through the real dispatch + quality + memory sinks
* - ring-buffer boundedness under sustained load (newest retained)
* - a throwing sink under load is isolated (other sinks keep working)
* - quality tracker updates stay consistent under interleaved async bursts
* - simultaneous reset() during inserts does not throw or corrupt state
*
* Node's event loop is single-threaded, so "concurrency" here is interleaved
* async execution; these tests assert correctness under bursts, not true
* parallelism.
*/
import test from "node:test";
import assert from "node:assert/strict";
import {
dispatchRoutingEvent,
MemoryRoutingEventStore,
registerRoutingEventSink,
clearRoutingEventSinks,
createRoutingEvent,
type RoutingEvent,
type RoutingEventSink,
} from "../../open-sse/services/routing/events.ts";
import {
recordQualityEvent,
getQualityScore,
resetQualityTracker,
getProviderQuality,
} from "../../open-sse/services/routing/quality.ts";
function makeEvent(i: number): RoutingEvent {
return createRoutingEvent({
requestId: `r-${i}`,
provider: i % 2 === 0 ? "openai" : "anthropic",
model: "m",
strategy: "auto",
latencyMs: 50 + (i % 100),
outcome: i % 100 === 0 ? "malformed" : "success",
status: 200,
finishReason: "stop",
outputTokens: 5,
});
}
test("sustained burst of thousands of events is bounded and consistent", async () => {
clearRoutingEventSinks();
resetQualityTracker();
const store = new MemoryRoutingEventStore(100);
registerRoutingEventSink(store);
registerRoutingEventSink({
name: "quality",
record: (e) => recordQualityEvent(e),
});
const N = 10_000;
for (let i = 0; i < N; i++) dispatchRoutingEvent(makeEvent(i));
assert.equal(store.size, 100, "ring buffer must stay bounded at capacity");
const recent = store.recent(5);
assert.equal(recent[0].requestId, `r-${N - 1}`, "newest event must be retained");
const q = getProviderQuality("openai", "m");
assert.equal(q.samples, N / 2, "even-indexed events all landed in the quality tracker");
assert.ok(q.operational > 0.5, "mostly-successful provider should be above neutral");
clearRoutingEventSinks();
});
test("a throwing sink under load does not break other sinks", () => {
clearRoutingEventSinks();
resetQualityTracker();
const seen: string[] = [];
registerRoutingEventSink({
name: "thrower",
record: () => {
throw new Error("sink boom");
},
});
registerRoutingEventSink({
name: "collector",
record: (e) => void seen.push(e.requestId),
});
for (let i = 0; i < 2000; i++) dispatchRoutingEvent(makeEvent(i));
assert.equal(seen.length, 2000, "all events must still reach the good sink");
clearRoutingEventSinks();
});
test("interleaved async bursts keep quality math consistent", async () => {
resetQualityTracker();
const bursts = Array.from({ length: 8 }, (_, b) =>
(async () => {
for (let i = 0; i < 500; i++) {
recordQualityEvent(makeEvent(b * 500 + i));
// Yield occasionally to interleave with the other bursts.
if (i % 50 === 0) await new Promise((r) => setImmediate(r));
}
})()
);
await Promise.all(bursts);
const q = getProviderQuality("openai", "m");
assert.equal(q.samples, 2000, "4 bursts * 500 with i%2==0 → 2000 openai samples");
assert.ok(Number.isFinite(q.operational) && q.operational >= 0 && q.operational <= 1);
// Recency should be non-null and tiny (events were just recorded).
assert.ok(q.recencyMs !== null && q.recencyMs < 5000);
});
test("reset during inserts is safe and state re-initializes cleanly", async () => {
resetQualityTracker();
const store = new MemoryRoutingEventStore(50);
registerRoutingEventSink(store);
registerRoutingEventSink({ name: "quality", record: (e) => recordQualityEvent(e) });
const writer = (async () => {
for (let i = 0; i < 2000; i++) {
dispatchRoutingEvent(makeEvent(i));
if (i % 200 === 0) await new Promise((r) => setImmediate(r));
}
})();
// Fire several resets while the writer is mid-flight.
const resets = Array.from({ length: 3 }, (_, k) =>
(async () => {
await new Promise((r) => setImmediate(r));
resetQualityTracker();
store.clear();
})()
);
await Promise.all(resets);
await writer;
// After a reset the tracker is empty for the reset epoch; post-reset events
// must still record without throwing. We assert non-negative, finite state.
const q = getProviderQuality("openai", "m");
assert.ok(q.samples >= 0);
assert.ok(Number.isFinite(q.operational));
clearRoutingEventSinks();
resetQualityTracker();
});
test("quality score never goes NaN or out of [0,1] under adversarial events", () => {
resetQualityTracker();
const bad: RoutingEvent[] = [
makeEvent(0),
createRoutingEvent({
requestId: "nan-1",
provider: "nanp",
model: "nanm",
latencyMs: NaN,
outcome: "error",
status: NaN,
ttftMs: NaN,
outputTokens: NaN,
}),
];
for (const e of bad) dispatchRoutingEvent(makeEvent(1));
recordQualityEvent({
provider: "nanp",
model: "nanm",
outcome: "error",
status: NaN,
latencyMs: NaN,
ttftMs: NaN,
outputTokens: NaN,
});
const score = getQualityScore("nanp", "nanm");
assert.ok(Number.isFinite(score), `score must be finite, got ${score}`);
assert.ok(score >= 0 && score <= 1, `score in [0,1], got ${score}`);
});