mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-26 00:52:18 +03:00
* feat(dashboard): add Type filter and easiest-first sort to Free Provider Rankings (#6915) Adds sort/filter controls keyed on provider auth type (NOAUTH/OAUTH/APIKEY) to /dashboard/free-provider-rankings so zero-setup providers can be surfaced without eyeballing the Type column: - Type filter chips (All / No Signup / OAuth Login / API Key), client-side over the already-fetched rankings (no API change). - "Easiest first" sort toggle groups NOAUTH < OAUTH < APIKEY while preserving the existing score-descending order within each group (stable sort). - Type column legend/tooltip explaining what each auth type means. - Pure filter/sort logic extracted to a new freeProviderRankingsAuthType.ts module (no DB imports) rather than freeProviderRankings.ts, so importing it from the "use client" page does not pull server-only DB wiring (fs/path/better-sqlite3) into the client bundle; freeProviderRankings.ts re-exports both for API/test parity. - i18n keys added to en.json + __MISSING__ placeholders synced to all 42 locale mirrors (consistent with the existing untranslated-key convention). * test(6915): move page test to tests/unit/ui so a runner actually collects it The .test.tsx sat at tests/unit/ top-level, which no runner collects (vitest ui filters on tests/unit/ui) — check:test-discovery flagged it as a new orphan: the test never ran. Moved under tests/unit/ui/ and switched the dynamic import to the @/ alias (the convention of its neighbours). 6/6 now pass under test:vitest:ui. * fix(types): explicit unknown hop on the getProviderConnections cast (#6915) The dashboard-typecheck gate (added by #7203, after this branch was cut) scopes tsc to the src/app/(dashboard) import graph. This PR's page.tsx now imports freeProviderRankingsAuthType, which type-imports freeProviderRankings — dragging that module into the graph for the first time and surfacing its pre-existing TS2352 (JsonRecord[] -> ConnectionState[] is a structural subset). Fixed the cast rather than widening the frozen baseline.
118 lines
3.9 KiB
TypeScript
118 lines
3.9 KiB
TypeScript
/**
|
|
* Unit tests for #6915 — Sort/filter Free Provider Rankings by auth Type.
|
|
*
|
|
* Targets the PURE helpers `filterRankingsByAuthType` + `sortRankingsAuthTypeFirst`
|
|
* (no DB, no I/O) so the new filter/sort logic is exercised in isolation, mirroring
|
|
* `tests/unit/freeProviderRankings-filters.test.ts`.
|
|
*/
|
|
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import {
|
|
filterRankingsByAuthType,
|
|
sortRankingsAuthTypeFirst,
|
|
type FreeProviderRanking,
|
|
type ProviderAuthType,
|
|
} from "../../src/lib/freeProviderRankings.ts";
|
|
|
|
function ranking(id: string, category: ProviderAuthType, score: number): FreeProviderRanking {
|
|
return {
|
|
id,
|
|
name: id,
|
|
icon: "",
|
|
color: "#000",
|
|
category,
|
|
topModel: null,
|
|
averageScore: score,
|
|
modelCount: 1,
|
|
};
|
|
}
|
|
|
|
// ──────────────── filterRankingsByAuthType ────────────────
|
|
|
|
test("filterRankingsByAuthType: 'noauth' keeps only NOAUTH rows", () => {
|
|
const rows = [
|
|
ranking("a", "noauth", 0.9),
|
|
ranking("b", "oauth", 0.8),
|
|
ranking("c", "apikey", 0.7),
|
|
ranking("d", "noauth", 0.6),
|
|
];
|
|
const result = filterRankingsByAuthType(rows, "noauth");
|
|
assert.deepEqual(
|
|
result.map((r) => r.id),
|
|
["a", "d"]
|
|
);
|
|
});
|
|
|
|
test("filterRankingsByAuthType: 'oauth' keeps only OAUTH rows", () => {
|
|
const rows = [ranking("a", "noauth", 0.9), ranking("b", "oauth", 0.8)];
|
|
const result = filterRankingsByAuthType(rows, "oauth");
|
|
assert.deepEqual(
|
|
result.map((r) => r.id),
|
|
["b"]
|
|
);
|
|
});
|
|
|
|
test("filterRankingsByAuthType: 'apikey' keeps only APIKEY rows", () => {
|
|
const rows = [ranking("a", "apikey", 0.9), ranking("b", "oauth", 0.8)];
|
|
const result = filterRankingsByAuthType(rows, "apikey");
|
|
assert.deepEqual(
|
|
result.map((r) => r.id),
|
|
["a"]
|
|
);
|
|
});
|
|
|
|
test("filterRankingsByAuthType: empty-string type returns input unchanged (identity — 'All')", () => {
|
|
const rows = [ranking("a", "noauth", 0.9), ranking("b", "oauth", 0.8)];
|
|
const result = filterRankingsByAuthType(rows, "");
|
|
assert.equal(result, rows);
|
|
});
|
|
|
|
test("filterRankingsByAuthType: undefined type returns input unchanged (identity — 'All')", () => {
|
|
const rows = [ranking("a", "noauth", 0.9), ranking("b", "oauth", 0.8)];
|
|
const result = filterRankingsByAuthType(rows);
|
|
assert.equal(result, rows);
|
|
});
|
|
|
|
// ──────────────── sortRankingsAuthTypeFirst ────────────────
|
|
|
|
test("sortRankingsAuthTypeFirst: groups NOAUTH < OAUTH < APIKEY", () => {
|
|
const rows = [
|
|
ranking("apikey-1", "apikey", 0.95),
|
|
ranking("oauth-1", "oauth", 0.9),
|
|
ranking("noauth-1", "noauth", 0.5),
|
|
];
|
|
const result = sortRankingsAuthTypeFirst(rows);
|
|
assert.deepEqual(
|
|
result.map((r) => r.category),
|
|
["noauth", "oauth", "apikey"]
|
|
);
|
|
});
|
|
|
|
test("sortRankingsAuthTypeFirst: preserves relative (score) order within each group (stable-sort proof)", () => {
|
|
// Input already sorted by score across mixed types (simulating computeFreeProviderRankings output).
|
|
const rows = [
|
|
ranking("apikey-best", "apikey", 0.95),
|
|
ranking("oauth-best", "oauth", 0.9),
|
|
ranking("noauth-best", "noauth", 0.85),
|
|
ranking("apikey-worst", "apikey", 0.8),
|
|
ranking("oauth-worst", "oauth", 0.6),
|
|
ranking("noauth-worst", "noauth", 0.4),
|
|
];
|
|
const result = sortRankingsAuthTypeFirst(rows);
|
|
assert.deepEqual(
|
|
result.map((r) => r.id),
|
|
["noauth-best", "noauth-worst", "oauth-best", "oauth-worst", "apikey-best", "apikey-worst"]
|
|
);
|
|
});
|
|
|
|
test("sortRankingsAuthTypeFirst: does not mutate the input array", () => {
|
|
const rows = [ranking("apikey-1", "apikey", 0.95), ranking("noauth-1", "noauth", 0.5)];
|
|
const original = [...rows];
|
|
sortRankingsAuthTypeFirst(rows);
|
|
assert.deepEqual(rows, original);
|
|
});
|
|
|
|
test("sortRankingsAuthTypeFirst: empty input returns empty output", () => {
|
|
assert.deepEqual(sortRankingsAuthTypeFirst([]), []);
|
|
});
|