Files
wehub-resource-sync 64ed910d3f
CI / Backend Checks (push) Has been cancelled
CI / Frontend Checks (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 11:57:20 +08:00

145 lines
4.0 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
flattenModels,
formatPrice,
normalizeModelIdForPricing,
} from "@/components/usage/ModelsDevPickerDialog";
describe("normalizeModelIdForPricing", () => {
it("keeps already-normalized ids unchanged", () => {
expect(normalizeModelIdForPricing("claude-opus-4-5")).toBe(
"claude-opus-4-5",
);
});
it("strips the vendor prefix before the last slash", () => {
expect(normalizeModelIdForPricing("z-ai/glm-4.7")).toBe("glm-4.7");
expect(normalizeModelIdForPricing("clarifai/main/models/mm-poly-8b")).toBe(
"mm-poly-8b",
);
});
it("lowercases the id", () => {
expect(normalizeModelIdForPricing("MiniMaxAI/MiniMax-M2.1")).toBe(
"minimax-m2.1",
);
});
it("truncates colon suffixes", () => {
expect(normalizeModelIdForPricing("claude-sonnet-4-thinking:8192")).toBe(
"claude-sonnet-4-thinking",
);
});
it("maps @ to -", () => {
expect(normalizeModelIdForPricing("claude-sonnet-4@20250514")).toBe(
"claude-sonnet-4-20250514",
);
});
it("strips the [1m] context marker", () => {
expect(normalizeModelIdForPricing("claude-sonnet-4-5[1m]")).toBe(
"claude-sonnet-4-5",
);
});
it("combines all rules", () => {
expect(normalizeModelIdForPricing("Vendor/Claude-Sonnet-4@2025:free")).toBe(
"claude-sonnet-4-2025",
);
});
});
describe("formatPrice", () => {
it("formats integers without a decimal point", () => {
expect(formatPrice(5)).toBe("5");
expect(formatPrice(25)).toBe("25");
});
it("trims trailing zeros", () => {
expect(formatPrice(0.5)).toBe("0.5");
expect(formatPrice(6.25)).toBe("6.25");
expect(formatPrice(1.0395)).toBe("1.0395");
});
it("keeps up to six decimal places", () => {
expect(formatPrice(0.000001)).toBe("0.000001");
expect(formatPrice(0.0000004)).toBe("0");
});
it("returns 0 for zero, negative and non-finite values", () => {
expect(formatPrice(0)).toBe("0");
expect(formatPrice(-1)).toBe("0");
expect(formatPrice(NaN)).toBe("0");
expect(formatPrice(Infinity)).toBe("0");
});
it("never produces exponent notation", () => {
// 后端 Decimal::from_str 不接受科学计数法
expect(formatPrice(1e-8)).toBe("0");
expect(formatPrice(1e21)).toBe("0");
for (const value of [5, 0.5, 0.000123, 123456.789]) {
expect(formatPrice(value)).toMatch(/^\d+(\.\d+)?$/);
}
});
});
describe("flattenModels", () => {
it("flattens providers, fills defaults and sorts by release date desc", () => {
const entries = flattenModels({
acme: {
id: "acme",
name: "Acme AI",
models: {
"old-model": {
id: "old-model",
name: "Old Model",
release_date: "2024-01-01",
cost: { input: 1, output: 2 },
},
"new-model": {
id: "new-model",
name: "New Model",
release_date: "2025-06-01",
cost: { input: 3, output: 6, cache_read: 0.3, cache_write: 3.75 },
},
"free-model": {
id: "free-model",
name: "No Cost Model",
},
},
},
bare: {
models: {
"Vendor/Some-Model:free": {
release_date: "2025-01",
cost: { input: 0.1 },
},
},
},
});
expect(entries.map((e) => e.key)).toEqual([
"acme/new-model",
"bare/Vendor/Some-Model:free",
"acme/old-model",
]);
const newModel = entries[0];
expect(newModel.normalizedId).toBe("new-model");
expect(newModel.cacheRead).toBe(0.3);
expect(newModel.cacheWrite).toBe(3.75);
// 没有 name 的 provider 用 id 兜底;缺失的成本字段补 0
const bareModel = entries[1];
expect(bareModel.providerName).toBe("bare");
expect(bareModel.normalizedId).toBe("some-model");
expect(bareModel.output).toBe(0);
expect(bareModel.cacheRead).toBe(0);
// 完全没有定价的模型被过滤
expect(entries.some((e) => e.modelId === "free-model")).toBe(false);
});
});