Files
9router/tests/unit/freebuff-usage.test.js
T

314 lines
9.5 KiB
JavaScript

import { describe, it, expect, vi, beforeEach } from "vitest";
vi.mock("../../open-sse/utils/proxyFetch.js", () => ({
proxyAwareFetch: vi.fn(),
}));
import { proxyAwareFetch } from "../../open-sse/utils/proxyFetch.js";
import { getUsageForProvider } from "../../open-sse/services/usage.js";
import { PROVIDERS } from "../../open-sse/providers/index.js";
import { USAGE_SUPPORTED_PROVIDERS } from "../../src/shared/constants/providers.js";
import { parseQuotaData } from "../../src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js";
const SESSION_URL = "https://www.codebuff.com/api/v1/freebuff/session";
function jsonResponse(body, status = 200) {
return new Response(JSON.stringify(body), {
status,
headers: { "Content-Type": "application/json" },
});
}
const PRE_JOIN = {
status: "none",
accessTier: "limited",
rateLimitsByModel: {
"deepseek/deepseek-v4-flash": {
limit: 6,
recentCount: 4.1,
period: "pacific_day",
resetTimeZone: "America/Los_Angeles",
resetAt: "2026-08-06T07:00:00.000Z",
entitlementBreakdown: { base: 6, referral: 0, streak: 0 },
},
"openai/gpt-5.6-luna": {
limit: 6,
recentCount: 1,
period: "pacific_day",
resetTimeZone: "America/Los_Angeles",
resetAt: "2026-08-06T07:00:00.000Z",
},
},
};
describe("freebuff registry usage flag", () => {
it("exposes the session endpoint as transport.usage url", () => {
const cfg = PROVIDERS["freebuff"];
expect(cfg.usage?.url).toBe(SESSION_URL);
});
it("is listed in USAGE_SUPPORTED_PROVIDERS (features.usage)", () => {
expect(USAGE_SUPPORTED_PROVIDERS).toContain("freebuff");
});
});
describe("getUsageForProvider(freebuff)", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("GETs the session endpoint and normalizes per-model session quotas", async () => {
proxyAwareFetch.mockResolvedValueOnce(jsonResponse(PRE_JOIN));
const usage = await getUsageForProvider({
provider: "freebuff",
accessToken: "tok-1",
});
expect(usage.message).toBeUndefined();
expect(usage.plan).toBe("Freebuff (Limited)");
expect(usage.quotas["deepseek/deepseek-v4-flash"]).toMatchObject({
used: 4.1,
total: 6,
resetAt: "2026-08-06T07:00:00.000Z",
recurring: true,
unlimited: false,
displayName: "DeepSeek V4 Flash",
});
expect(usage.quotas["openai/gpt-5.6-luna"]).toMatchObject({
used: 1,
total: 6,
displayName: "GPT-5.6 Luna",
});
// Quota reads MUST be GET (a POST would claim a session and burn quota).
const [url, opts] = proxyAwareFetch.mock.calls[0];
expect(url).toBe(SESSION_URL);
expect(opts.method).toBe("GET");
expect(opts.headers.Authorization).toBe("Bearer tok-1");
});
it("folds the active session's own rateLimit into the shared map", async () => {
proxyAwareFetch.mockResolvedValueOnce(
jsonResponse({
status: "active",
accessTier: "full",
instanceId: "inst-1",
model: "meta/muse-spark-1.3-contributor",
expiresAt: new Date(Date.now() + 3600000).toISOString(),
rateLimit: {
limit: 6,
recentCount: 2.4,
period: "pacific_day",
resetAt: "2026-08-06T07:00:00.000Z",
},
rateLimitsByModel: {},
}),
);
const usage = await getUsageForProvider({
provider: "freebuff",
accessToken: "tok-1",
});
expect(usage.plan).toBe("Freebuff");
expect(usage.quotas["meta/muse-spark-1.3-contributor"]).toMatchObject({
used: 2.4,
total: 6,
displayName: "Muse Spark 1.3",
});
});
it("reports the Freebucks daily pool under each priced model for metered accounts (no rateLimitsByModel)", async () => {
proxyAwareFetch.mockResolvedValueOnce(
jsonResponse({
status: "none",
accessTier: "limited",
rateLimitsByModel: {},
freebucks: {
balance: 10,
daily: { limit: 25, spent: 15, remaining: 10, resetAt: "2026-09-08T07:00:00.000Z" },
wallet: { balance: 0, monthlyBonus: 0 },
spend: { limitUsd: 4, resetAt: "2026-09-08T07:00:00.000Z" },
monthly: { limitUsd: 10, spentUsd: 3.5, remainingUsd: 6.5, resetAt: "2026-10-01T07:00:00.000Z" },
planId: null,
prices: { "deepseek/deepseek-v4-flash": 15, "z-ai/glm-5.3-flash": 5 },
},
}),
);
const usage = await getUsageForProvider({
provider: "freebuff",
accessToken: "tok-1",
});
expect(usage.message).toBeUndefined();
expect(usage.quotas["deepseek/deepseek-v4-flash"]).toMatchObject({
used: 15,
total: 25,
resetAt: "2026-09-08T07:00:00.000Z",
recurring: true,
unlimited: false,
price: 15,
displayName: "DeepSeek V4 Flash",
});
expect(usage.quotas["z-ai/glm-5.3-flash"]).toMatchObject({
used: 15,
total: 25,
price: 5,
displayName: "GLM 5.3 Flash",
});
// No session pools → only the freebucks-derived rows exist.
expect(Object.keys(usage.quotas)).toEqual([
"deepseek/deepseek-v4-flash",
"z-ai/glm-5.3-flash",
]);
// Account summary rides the response for the card header (server data,
// nothing hardcoded client-side).
expect(usage.freebucks).toEqual({
balance: 10,
daily: {
limit: 25,
spent: 15,
remaining: 10,
resetAt: "2026-09-08T07:00:00.000Z",
},
wallet: { balance: 0 },
monthly: { remainingUsd: 6.5, limitUsd: 10, resetAt: "2026-10-01T07:00:00.000Z" },
});
});
it("folds the server's announced priceChanges into the live price (promos expire without a client release)", async () => {
proxyAwareFetch.mockResolvedValueOnce(
jsonResponse({
status: "none",
accessTier: "full",
rateLimitsByModel: {},
freebucks: {
balance: 20,
daily: { limit: 25, spent: 5, remaining: 20, resetAt: "2026-09-08T07:00:00.000Z" },
wallet: { balance: 0, monthlyBonus: 0 },
planId: null,
prices: { "upstage/solar-pro4": 0 },
priceNotices: { "upstage/solar-pro4": "0 Freebucks · Labor Day weekend (through Sep 7 PT)" },
priceChanges: [
{
at: "2026-01-01T00:00:00.000Z", // already due
modelId: "upstage/solar-pro4",
price: 5,
tagline: "Limited-time trial",
},
],
},
}),
);
const usage = await getUsageForProvider({
provider: "freebuff",
accessToken: "tok-1",
});
// The due change is folded in: price 5 + new tagline, schedule emptied.
expect(usage.quotas["upstage/solar-pro4"]).toMatchObject({
price: 5,
priceNote: "Limited-time trial",
displayName: "Solar Pro 4",
});
});
it("attaches no price to legacy session-quota rows", async () => {
proxyAwareFetch.mockResolvedValueOnce(jsonResponse(PRE_JOIN));
const usage = await getUsageForProvider({
provider: "freebuff",
accessToken: "tok-1",
});
expect(usage.quotas["deepseek/deepseek-v4-flash"].price).toBeUndefined();
expect(usage.freebucks).toBeUndefined();
});
it("surfaces a re-login message on 401", async () => {
proxyAwareFetch.mockResolvedValueOnce(jsonResponse({ error: "unauthorized" }, 401));
const usage = await getUsageForProvider({
provider: "freebuff",
accessToken: "expired",
});
expect(usage.message).toMatch(/expired|re-login/i);
expect(usage.quotas).toBeUndefined();
});
it("surfaces a region message on 403 country_blocked (not a re-login hint)", async () => {
proxyAwareFetch.mockResolvedValueOnce(
jsonResponse({ status: "country_blocked", countryCode: "XX" }, 403),
);
const usage = await getUsageForProvider({
provider: "freebuff",
accessToken: "tok-1",
});
expect(usage.message).toMatch(/not available in your region/i);
});
it("treats 404 (no session row) as pre-join with no quota", async () => {
proxyAwareFetch.mockResolvedValueOnce(jsonResponse({}, 404));
const usage = await getUsageForProvider({
provider: "freebuff",
accessToken: "tok-1",
});
expect(usage.message).toMatch(/no session quota/i);
});
it("returns a message when the session response carries no quota map", async () => {
proxyAwareFetch.mockResolvedValueOnce(jsonResponse({ status: "none", accessTier: "full" }));
const usage = await getUsageForProvider({
provider: "freebuff",
accessToken: "tok-1",
});
expect(usage.plan).toBe("Freebuff");
expect(usage.message).toMatch(/no session quota/i);
});
it("does not throw when the session fetch fails", async () => {
proxyAwareFetch.mockRejectedValueOnce(new Error("network down"));
const usage = await getUsageForProvider({
provider: "freebuff",
accessToken: "tok-1",
});
expect(usage.message).toMatch(/usage error/i);
});
});
describe("parseQuotaData(freebuff)", () => {
it("uses displayName for the row label and keeps modelKey for ordering", () => {
const rows = parseQuotaData("freebuff", {
plan: "Freebuff (Limited)",
quotas: {
"deepseek/deepseek-v4-flash": {
used: 4.1,
total: 6,
resetAt: "2026-08-06T07:00:00.000Z",
displayName: "DeepSeek V4 Flash",
},
},
});
expect(rows).toHaveLength(1);
expect(rows[0]).toMatchObject({
name: "DeepSeek V4 Flash",
modelKey: "deepseek/deepseek-v4-flash",
used: 4.1,
total: 6,
});
});
});