mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-14 02:42:24 +03:00
Lets the combo dashboard builder order models manually/by-provider/by-score/by-name — the choice is stored in config.modelSort and re-applied on load and after adding models. Score-based ordering fetches provider rankings from the existing /api/free-provider-rankings endpoint; the field is inert on execution (client-side hint only). 9/9 focused tests passing (schema, sort logic, and rendered component). Thanks!
113 lines
3.6 KiB
TypeScript
113 lines
3.6 KiB
TypeScript
// tests/unit/combo/comboSort.test.ts
|
|
import { describe, it } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import {
|
|
PROVIDER_ORDER,
|
|
sortComboStepsSync,
|
|
sortComboStepsByScore,
|
|
reapplyCurrentSort,
|
|
type ComboStep,
|
|
} from "@/lib/combos/comboSort";
|
|
|
|
const m = (id: string, providerId: string, model: string): ComboStep => ({
|
|
id,
|
|
kind: "model",
|
|
model,
|
|
providerId,
|
|
weight: 0,
|
|
});
|
|
const ref = (id: string, comboName: string): ComboStep => ({
|
|
id,
|
|
kind: "combo-ref",
|
|
comboName,
|
|
weight: 0,
|
|
});
|
|
|
|
describe("sortComboStepsSync", () => {
|
|
it("manual returns the input unchanged", () => {
|
|
const steps = [m("a", "openai", "gpt"), m("b", "anthropic", "claude")];
|
|
assert.equal(sortComboStepsSync(steps, "manual"), steps);
|
|
});
|
|
|
|
it("provider groups by PROVIDER_ORDER, stable intra-group, combo-ref at end", () => {
|
|
const steps = [
|
|
m("x", "anthropic", "claude-3"),
|
|
m("y", "openai", "gpt-4"),
|
|
ref("r", "other-combo"),
|
|
m("z", "anthropic", "claude-2"),
|
|
];
|
|
const out = sortComboStepsSync(steps, "provider");
|
|
// combo-ref always last (no providerId).
|
|
assert.equal(out[out.length - 1].id, "r");
|
|
const nonRef = out.filter((s) => s.id !== "r").map((s) => s.id);
|
|
// all three model steps are present, and the two anthropic steps stay grouped.
|
|
assert.deepEqual(nonRef.slice().sort(), ["x", "y", "z"]);
|
|
const ai = nonRef.indexOf("x");
|
|
const zi = nonRef.indexOf("z");
|
|
assert.ok(Math.abs(ai - zi) === 1, "steps of the same provider are adjacent");
|
|
});
|
|
|
|
it("name sorts alphabetically with a stable tiebreak", () => {
|
|
const steps = [m("a", "openai", "zeta"), m("b", "openai", "alpha"), m("c", "openai", "alpha")];
|
|
const out = sortComboStepsSync(steps, "name");
|
|
assert.deepEqual(
|
|
out.map((s) => s.id),
|
|
["b", "c", "a"]
|
|
);
|
|
});
|
|
|
|
it("PROVIDER_ORDER is non-empty and stable", () => {
|
|
assert.ok(PROVIDER_ORDER.length > 0);
|
|
});
|
|
});
|
|
|
|
describe("score sort", () => {
|
|
it("sorts scored steps descending, unscored stable at end", async () => {
|
|
const steps = [
|
|
m("a", "openai", "gpt"), // score 90
|
|
m("b", "anthropic", "claude"), // no score
|
|
m("c", "google", "gemini"), // score 70
|
|
ref("r", "other"), // no providerId
|
|
];
|
|
const rankings = new Map<string, number>([
|
|
["openai", 90],
|
|
["google", 70],
|
|
]);
|
|
const out = await sortComboStepsByScore(steps, rankings);
|
|
assert.deepEqual(
|
|
out.map((s) => s.id),
|
|
["a", "c", "b", "r"]
|
|
);
|
|
});
|
|
|
|
it("reapplyCurrentSort applies score async and sync methods", async () => {
|
|
const steps = [m("a", "openai", "gpt"), m("b", "claude", "claude")];
|
|
const syncOut = await reapplyCurrentSort(steps, "provider");
|
|
assert.deepEqual(
|
|
syncOut.map((s) => s.id),
|
|
["b", "a"]
|
|
); // claude (anthropic) before openai
|
|
const rankOut = await reapplyCurrentSort(steps, "score", async () => new Map([["openai", 5]]));
|
|
assert.deepEqual(
|
|
rankOut.map((s) => s.id),
|
|
["a", "b"]
|
|
);
|
|
});
|
|
|
|
it("reapplyCurrentSort after an add keeps provider grouping", async () => {
|
|
const base = [m("a", "anthropic", "claude")];
|
|
const added = [...base, m("b", "openai", "gpt")];
|
|
const out = await reapplyCurrentSort(added, "provider");
|
|
// Expected order derived from PROVIDER_ORDER (robust to provider precedence).
|
|
const expected = ["a", "b"].sort((x, y) => {
|
|
const px = x === "a" ? "anthropic" : "openai";
|
|
const py = y === "a" ? "anthropic" : "openai";
|
|
return PROVIDER_ORDER.indexOf(px) - PROVIDER_ORDER.indexOf(py);
|
|
});
|
|
assert.deepEqual(
|
|
out.map((s) => s.id),
|
|
expected
|
|
);
|
|
});
|
|
});
|