Files
OmniRoute/tests/unit/routing-otel.test.ts
Praveen K Palaniswamy 65e81158ab fix(ollama): route models by advertised capability (#11088)
Landed with the design call resolved per the owner's pick — **option 1**: the synced store is now endpoint-agnostic (persistDiscoveredModels and managedModelImport no longer drop non-chat models at write time), and chat selectability moved to read time (auto-pool expansion in autoStrategy applies filterChatSelectableModels; the models-route projection already had its chatOnly filter). Your discovery test now passes end-to-end (3/3): /api/show capabilities persist per connection and image/embedding requests route through the advertising host.

Reconciliation notes: conflicted areas merged onto the current tip (adobe discovery import, requestedModel preflight signature, resolvedProvider fast-path coexists with the synced-route override — explicit resolution wins); carried base-red drains (#10055 memoization, #11071 test variants) dropped as already-landed; the managed-model-import exclusion test was propagated to the new contract (image/video models persist; the read filter still hides them from chat pickers — pinned by a new assertion). Full battery: 205/206 focused (the one red is a confirmed periodic-timer timing flake on the loaded devbox — 20/20 isolated), autoCombo vitest 30/30, combo suites 46/46, gates + typecheck clean.

Thank you @yourspraveen — the capability probe + routing design was right; it just needed the store contract opened up. Fixes #11087.
2026-08-23 11:45:01 -03:00

130 lines
4.6 KiB
TypeScript

/**
* tests/unit/routing-otel.test.ts
*
* Optional OpenTelemetry sink (open-sse/services/routing/otel.ts):
* - disabled unless an endpoint is configured
* - buildOtlpTracesPayload emits GenAI semantic-convention spans
* - record() enqueues without performing I/O; stop() flushes via fetch
* - dropped events are counted when the buffer overflows
*/
import test from "node:test";
import assert from "node:assert/strict";
import {
buildOtlpTracesPayload,
isRoutingOtelEnabled,
OtlpHttpsEventSink,
} from "../../open-sse/services/routing/otel.ts";
import type { RoutingEvent } from "../../open-sse/services/routing/events.ts";
function event(partial: Partial<RoutingEvent> = {}): RoutingEvent {
return {
requestId: "req-1",
provider: "openai",
model: "gpt-4o",
strategy: "auto",
latencyMs: 120,
ttftMs: 40,
inputTokens: 10,
outputTokens: 20,
cost: 0.01,
retries: 1,
fallbackUsed: true,
outcome: "success",
status: 200,
finishReason: "stop",
connectionId: "conn-1",
ts: 1_700_000_000_000,
...partial,
};
}
test("isRoutingOtelEnabled is false without an endpoint", () => {
assert.equal(isRoutingOtelEnabled({}), false);
assert.equal(isRoutingOtelEnabled({ OMNIROUTE_OTEL_ENDPOINT: " " }), false);
});
test("isRoutingOtelEnabled honors OMNIROUTE_OTEL_ENDPOINT and OTLP env", () => {
assert.equal(isRoutingOtelEnabled({ OMNIROUTE_OTEL_ENDPOINT: "http://collector:4318" }), true);
assert.equal(
isRoutingOtelEnabled({ OTEL_EXPORTER_OTLP_ENDPOINT: "https://collector:4318" }),
true
);
});
test("buildOtlpTracesPayload emits GenAI semantic-convention spans", () => {
const payload = buildOtlpTracesPayload([event()], "omniroute-test") as {
resourceSpans: Array<{
scopeSpans: Array<{
spans: Array<{
attributes: Array<{ key: string; value: { stringValue?: string; intValue?: string } }>;
}>;
}>;
}>;
};
const span = payload.resourceSpans[0].scopeSpans[0].spans[0];
const attrs = Object.fromEntries(
span.attributes.map((a) => [a.key, a.value.stringValue ?? a.value.intValue])
);
assert.equal(attrs["gen_ai.provider.name"], "openai");
assert.equal(attrs["gen_ai.request.model"], "gpt-4o");
assert.equal(attrs["gen_ai.system"], "auto");
assert.equal(attrs["gen_ai.usage.input_tokens"], "10");
assert.equal(attrs["gen_ai.usage.output_tokens"], "20");
assert.equal(attrs["gen_ai.completion.finish_reason"], "stop");
assert.equal(attrs["omniroute.routing.outcome"], "success");
assert.equal(attrs["omniroute.routing.status"], "200");
assert.equal(attrs["omniroute.routing.retries"], "1");
assert.equal(attrs["omniroute.routing.fallback_used"], "1");
assert.equal(attrs["omniroute.connection_id"], "conn-1");
assert.ok(BigInt(span.startTimeUnixNano) > 0n);
});
test("OtlpHttpsEventSink record() enqueues without I/O and flush sends via fetch", async () => {
const calls: Array<{ url: string; body: string }> = [];
const originalFetch = global.fetch;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
global.fetch = (async (url: any, init: any) => {
calls.push({ url: String(url), body: String(init?.body ?? "") });
return { ok: true } as Response;
}) as typeof fetch;
const sink = new OtlpHttpsEventSink({
endpoint: "http://collector:4318",
flushIntervalMs: 1_000_000, // effectively never auto-flush in the test
});
try {
sink.record(event());
sink.record(event({ requestId: "req-2" }));
assert.equal(sink.getStats().buffered, 2);
// Force an explicit flush via stop().
await new Promise((r) => setTimeout(r, 20));
sink.stop();
await new Promise((r) => setTimeout(r, 50));
assert.equal(calls.length, 1, "one flush should have been sent");
assert.ok(calls[0].url.endsWith("/v1/traces"), calls[0].url);
const body = JSON.parse(calls[0].body);
assert.ok(body.resourceSpans[0].scopeSpans[0].spans.length === 2);
assert.equal(sink.getStats().buffered, 0);
} finally {
global.fetch = originalFetch;
}
});
test("OtlpHttpsEventSink drops oldest when the buffer is saturated", async () => {
const originalFetch = global.fetch;
global.fetch = (async () => ({ ok: true }) as Response) as typeof fetch;
const sink = new OtlpHttpsEventSink({
endpoint: "http://collector:4318",
maxBatchSize: 2,
flushIntervalMs: 1_000_000,
});
try {
for (let i = 0; i < 20; i++) sink.record(event({ requestId: `r-${i}` }));
const stats = sink.getStats();
assert.ok(stats.dropped > 0, "overload must drop events, never block");
sink.stop();
} finally {
global.fetch = originalFetch;
}
});