mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-20 06:02:14 +03:00
Co-authored-by: Markus Hartung <mail@hartmark.se>
This commit is contained in:
committed by
GitHub
parent
41877978e0
commit
b755dd5e74
@@ -0,0 +1 @@
|
||||
- fix(db): filter `getProviderMetrics()` to providers with a live `provider_connections` row so a deleted provider stops permanently ghost-haunting the Home "Provider Topology" widget (#10714)
|
||||
@@ -56,7 +56,10 @@ export interface SearchProviderCountRow {
|
||||
|
||||
/**
|
||||
* Returns one row per provider with call-level aggregates plus last-status
|
||||
* subselects. Excludes rows where provider is NULL or '-'.
|
||||
* subselects. Excludes rows where provider is NULL or '-', and excludes
|
||||
* providers with no live row in `provider_connections` — a deleted provider
|
||||
* connection must not keep surfacing as a ghost topology node forever from
|
||||
* its retained historical call_logs rows. See #10714.
|
||||
*/
|
||||
export function getProviderMetrics(): ProviderMetricRow[] {
|
||||
const db = getDbInstance();
|
||||
@@ -96,6 +99,9 @@ export function getProviderMetrics(): ProviderMetricRow[] {
|
||||
) as lastErrorStatus
|
||||
FROM call_logs c
|
||||
WHERE c.provider IS NOT NULL AND c.provider != '-'
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM provider_connections pc WHERE pc.provider = c.provider
|
||||
)
|
||||
GROUP BY c.provider`
|
||||
)
|
||||
.all() as ProviderMetricRow[];
|
||||
|
||||
@@ -93,6 +93,17 @@ test.after(() => {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test("#3500 getProviderMetrics — aggregates totals and latency per provider", () => {
|
||||
// #10714: getProviderMetrics() only surfaces providers with a live
|
||||
// provider_connections row — seed openai/anthropic connections so their
|
||||
// call_logs rows are not filtered out as ghost/deleted providers.
|
||||
const db0 = core.getDbInstance();
|
||||
db0.prepare(
|
||||
`INSERT INTO provider_connections (id, provider, created_at, updated_at) VALUES (?, ?, ?, ?)`
|
||||
).run("conn-3500-openai", "openai", new Date().toISOString(), new Date().toISOString());
|
||||
db0.prepare(
|
||||
`INSERT INTO provider_connections (id, provider, created_at, updated_at) VALUES (?, ?, ?, ?)`
|
||||
).run("conn-3500-anthropic", "anthropic", new Date().toISOString(), new Date().toISOString());
|
||||
|
||||
// Two openai rows: one success, one error with error_summary
|
||||
const ts1 = "2025-06-01T10:00:00.000Z";
|
||||
const ts2 = "2025-06-01T11:00:00.000Z";
|
||||
|
||||
61
tests/unit/provider-metrics-deleted-provider.test.ts
Normal file
61
tests/unit/provider-metrics-deleted-provider.test.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
const TEST_DATA_DIR = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), "omniroute-provider-metrics-deleted-")
|
||||
);
|
||||
const ORIGINAL_DATA_DIR = process.env.DATA_DIR;
|
||||
process.env.DATA_DIR = TEST_DATA_DIR;
|
||||
|
||||
const core = await import("../../src/lib/db/core.ts");
|
||||
const providerMetricsRoute = await import("../../src/app/api/provider-metrics/route.ts");
|
||||
|
||||
type ProviderMetricsResponse = {
|
||||
metrics: Record<string, unknown>;
|
||||
topology: { providers: string[]; lastProvider: string; errorProvider: string };
|
||||
};
|
||||
|
||||
function resetStorage() {
|
||||
core.resetDbInstance();
|
||||
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
|
||||
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
test.beforeEach(() => {
|
||||
resetStorage();
|
||||
});
|
||||
test.after(() => {
|
||||
core.resetDbInstance();
|
||||
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
|
||||
if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR;
|
||||
else process.env.DATA_DIR = ORIGINAL_DATA_DIR;
|
||||
});
|
||||
|
||||
test("#10714: a provider deleted from provider_connections must NOT keep showing as an errored topology node", async () => {
|
||||
const db = core.getDbInstance();
|
||||
|
||||
db.prepare(
|
||||
`INSERT INTO provider_connections (id, provider, created_at, updated_at) VALUES (?, ?, ?, ?)`
|
||||
).run("conn-openai-1", "openai", "2026-08-19T09:00:00.000Z", "2026-08-19T09:00:00.000Z");
|
||||
db.prepare(
|
||||
`INSERT INTO call_logs (id, timestamp, provider, status, duration, error_summary) VALUES (?, ?, ?, ?, ?, ?)`
|
||||
).run("openai-error", "2026-08-19T10:00:00.000Z", "openai", 500, 80, "upstream error");
|
||||
|
||||
// Deleted provider: historical call_logs rows exist, but no provider_connections row.
|
||||
db.prepare(
|
||||
`INSERT INTO call_logs (id, timestamp, provider, status, duration, error_summary) VALUES (?, ?, ?, ?, ?, ?)`
|
||||
).run("g4f-pollinations-error", "2026-08-19T11:00:00.000Z", "g4f-pollinations", 402, 50, "payment required");
|
||||
|
||||
const response = await providerMetricsRoute.GET();
|
||||
const body = (await response.json()) as ProviderMetricsResponse;
|
||||
|
||||
assert.equal(response.status, 200);
|
||||
assert.equal(body.topology.providers.includes("g4f-pollinations"), false);
|
||||
assert.equal(body.metrics["g4f-pollinations"], undefined);
|
||||
assert.notEqual(body.topology.errorProvider, "g4f-pollinations");
|
||||
assert.equal(body.topology.providers.includes("openai"), true);
|
||||
assert.equal(body.topology.errorProvider, "openai");
|
||||
});
|
||||
@@ -52,8 +52,17 @@ test.after(() => {
|
||||
}
|
||||
});
|
||||
|
||||
function seedProviderConnection(provider: string) {
|
||||
const db = core.getDbInstance();
|
||||
db.prepare(
|
||||
`INSERT INTO provider_connections (id, provider, created_at, updated_at) VALUES (?, ?, ?, ?)`
|
||||
).run(`conn-${provider}`, provider, new Date().toISOString(), new Date().toISOString());
|
||||
}
|
||||
|
||||
test("GET /api/provider-metrics includes provider recency and error topology", async () => {
|
||||
const db = core.getDbInstance();
|
||||
seedProviderConnection("openai");
|
||||
seedProviderConnection("anthropic");
|
||||
db.prepare(
|
||||
`INSERT INTO call_logs (id, timestamp, provider, status, duration, error_summary)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`
|
||||
@@ -91,6 +100,8 @@ test("GET /api/provider-metrics errorProvider must NOT flag a provider whose mos
|
||||
// Bug (pre-fix): errorProvider = "providerA" because lastErrorAt > 0.
|
||||
// Fix (post-fix): errorProvider = "" because lastStatus for providerA is 200.
|
||||
const db = core.getDbInstance();
|
||||
seedProviderConnection("providerA");
|
||||
seedProviderConnection("providerB");
|
||||
db.prepare(
|
||||
`INSERT INTO call_logs (id, timestamp, provider, status, duration, error_summary)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`
|
||||
|
||||
Reference in New Issue
Block a user