Files
OmniRoute/tests/unit/freeProviderRankings-authtype-6915.test.ts
Diego Rodrigues de Sa e Souza 3cd04afe62 feat: add Type filter and easiest-first sort to Free Provider Rankings (#6915) (#7240)
* 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.
2026-07-17 10:40:37 -03:00

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([]), []);
});