feat(freebuff): enhance Freebucks handling and pricing logic across services

This commit is contained in:
MUH. IQRAM BAHRING
2026-09-07 22:26:28 +08:00
parent eedfdf982b
commit 9c17c5f96c
9 changed files with 352 additions and 19 deletions
+44
View File
@@ -294,6 +294,50 @@ describe("freebuff session pre-flight", () => {
);
});
it("rate_limited with a resetAt locks the account until the daily Pacific reset (skip the day, not retry loops)", async () => {
const resetAt = "2099-01-01T00:00:00.000Z";
fetchMock.mockResolvedValue(
jsonResponse({
status: "rate_limited",
resetAt,
retryAfterMs: 1234,
freebucksShortfall: { price: 15, balance: 0 },
message: "Freebucks exhausted",
}),
);
await expect(requestSession("tok-1", "deepseek/deepseek-v4-flash", null)).rejects.toMatchObject({
status: 429,
resetsAtMs: Date.parse(resetAt),
});
});
it("spend_limited falls back to retryAfterMs when resetAt is absent", async () => {
const retryAfterMs = 90 * 60 * 1000;
fetchMock.mockResolvedValue(
jsonResponse({ status: "spend_limited", retryAfterMs, message: "daily spend cap" }),
);
const before = Date.now();
try {
await requestSession("tok-1", "deepseek/deepseek-v4-flash", null);
throw new Error("should have rejected");
} catch (error) {
expect(error.status).toBe(429);
expect(error.resetsAtMs).toBeGreaterThanOrEqual(before + retryAfterMs - 1000);
expect(error.resetsAtMs).toBeLessThanOrEqual(before + retryAfterMs + 1000);
}
});
it("rate_limited without any reset hint stays a plain error (transient cooldown path)", async () => {
fetchMock.mockResolvedValue(jsonResponse({ status: "rate_limited", message: "busy" }));
try {
await requestSession("tok-1", "deepseek/deepseek-v4-flash", null);
throw new Error("should have rejected");
} catch (error) {
expect(error.status).toBeUndefined();
expect(error.resetsAtMs).toBeUndefined();
}
});
it("throws a friendly error on country_blocked", async () => {
fetchMock.mockResolvedValue(jsonResponse({ status: "country_blocked" }));
await expect(ensureSession("tok-1", "deepseek/deepseek-v4-flash", null)).rejects.toThrow(
+109
View File
@@ -119,6 +119,115 @@ describe("getUsageForProvider(freebuff)", () => {
});
});
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));