From 2920d61f184fa7515f3a104f150a45b2f416bd57 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 13 Mar 2026 18:10:40 +0000 Subject: [PATCH] test: tighten minimax usage coverage --- .../provider-usage.fetch.minimax.test.ts | 179 ++++++++++-------- src/infra/provider-usage.test.ts | 67 ++++--- 2 files changed, 141 insertions(+), 105 deletions(-) diff --git a/src/infra/provider-usage.fetch.minimax.test.ts b/src/infra/provider-usage.fetch.minimax.test.ts index 1c13619b8db..ceb1c5439b0 100644 --- a/src/infra/provider-usage.fetch.minimax.test.ts +++ b/src/infra/provider-usage.fetch.minimax.test.ts @@ -2,45 +2,70 @@ import { describe, expect, it } from "vitest"; import { createProviderUsageFetch, makeResponse } from "../test-utils/provider-usage-fetch.js"; import { fetchMinimaxUsage } from "./provider-usage.fetch.minimax.js"; +async function expectMinimaxUsageResult(params: { + payload: unknown; + expected: { + plan?: string; + windows: Array<{ label: string; usedPercent: number; resetAt?: number }>; + }; +}) { + const mockFetch = createProviderUsageFetch(async (_url, init) => { + const headers = (init?.headers as Record | undefined) ?? {}; + expect(headers.Authorization).toBe("Bearer key"); + expect(headers["MM-API-Source"]).toBe("OpenClaw"); + return makeResponse(200, params.payload); + }); + + const result = await fetchMinimaxUsage("key", 5000, mockFetch); + expect(result.plan).toBe(params.expected.plan); + expect(result.windows).toEqual(params.expected.windows); +} + describe("fetchMinimaxUsage", () => { - it("returns HTTP errors for failed requests", async () => { - const mockFetch = createProviderUsageFetch(async () => makeResponse(502, "bad gateway")); + it.each([ + { + name: "returns HTTP errors for failed requests", + response: () => makeResponse(502, "bad gateway"), + expectedError: "HTTP 502", + }, + { + name: "returns invalid JSON when payload cannot be parsed", + response: () => makeResponse(200, "{not-json"), + expectedError: "Invalid JSON", + }, + { + name: "returns trimmed API errors from base_resp", + response: () => + makeResponse(200, { + base_resp: { + status_code: 1007, + status_msg: " auth denied ", + }, + }), + expectedError: "auth denied", + }, + { + name: "falls back to a generic API error when base_resp message is blank", + response: () => + makeResponse(200, { + base_resp: { + status_code: 1007, + status_msg: " ", + }, + }), + expectedError: "API error", + }, + ])("$name", async ({ response, expectedError }) => { + const mockFetch = createProviderUsageFetch(async () => response()); const result = await fetchMinimaxUsage("key", 5000, mockFetch); - - expect(result.error).toBe("HTTP 502"); + expect(result.error).toBe(expectedError); expect(result.windows).toHaveLength(0); }); - it("returns invalid JSON when payload cannot be parsed", async () => { - const mockFetch = createProviderUsageFetch(async () => makeResponse(200, "{not-json")); - const result = await fetchMinimaxUsage("key", 5000, mockFetch); - - expect(result.error).toBe("Invalid JSON"); - expect(result.windows).toHaveLength(0); - }); - - it("returns API errors from base_resp", async () => { - const mockFetch = createProviderUsageFetch(async () => - makeResponse(200, { - base_resp: { - status_code: 1007, - status_msg: " auth denied ", - }, - }), - ); - const result = await fetchMinimaxUsage("key", 5000, mockFetch); - - expect(result.error).toBe("auth denied"); - expect(result.windows).toHaveLength(0); - }); - - it("derives usage from used/total fields and includes reset + plan", async () => { - const mockFetch = createProviderUsageFetch(async (_url, init) => { - const headers = (init?.headers as Record | undefined) ?? {}; - expect(headers.Authorization).toBe("Bearer key"); - expect(headers["MM-API-Source"]).toBe("OpenClaw"); - - return makeResponse(200, { + it.each([ + { + name: "derives usage from used/total fields and includes reset + plan", + payload: { data: { used: 35, total: 100, @@ -48,52 +73,36 @@ describe("fetchMinimaxUsage", () => { reset_at: 1_700_000_000, plan_name: "Pro Max", }, - }); - }); - - const result = await fetchMinimaxUsage("key", 5000, mockFetch); - - expect(result.plan).toBe("Pro Max"); - expect(result.windows).toEqual([ - { - label: "3h", - usedPercent: 35, - resetAt: 1_700_000_000_000, }, - ]); - }); - - it("supports usage ratio strings with minute windows and ISO reset strings", async () => { - const resetIso = "2026-01-08T00:00:00Z"; - const mockFetch = createProviderUsageFetch(async () => - makeResponse(200, { + expected: { + plan: "Pro Max", + windows: [{ label: "3h", usedPercent: 35, resetAt: 1_700_000_000_000 }], + }, + }, + { + name: "supports usage ratio strings with minute windows and ISO reset strings", + payload: { data: { nested: [ { usage_ratio: "0.25", window_minutes: "30", - reset_time: resetIso, + reset_time: "2026-01-08T00:00:00Z", plan: "Starter", }, ], }, - }), - ); - - const result = await fetchMinimaxUsage("key", 5000, mockFetch); - expect(result.plan).toBe("Starter"); - expect(result.windows).toEqual([ - { - label: "30m", - usedPercent: 25, - resetAt: new Date(resetIso).getTime(), }, - ]); - }); - - it("derives used from total and remaining counts", async () => { - const mockFetch = createProviderUsageFetch(async () => - makeResponse(200, { + expected: { + plan: "Starter", + windows: [ + { label: "30m", usedPercent: 25, resetAt: new Date("2026-01-08T00:00:00Z").getTime() }, + ], + }, + }, + { + name: "derives used from total and remaining counts", + payload: { data: { total: "200", remaining: "50", @@ -101,18 +110,28 @@ describe("fetchMinimaxUsage", () => { reset_at: 1_700_000_000_000, plan_name: "Team", }, - }), - ); - - const result = await fetchMinimaxUsage("key", 5000, mockFetch); - expect(result.plan).toBe("Team"); - expect(result.windows).toEqual([ - { - label: "5h", - usedPercent: 75, - resetAt: 1_700_000_000_000, }, - ]); + expected: { + plan: "Team", + windows: [{ label: "5h", usedPercent: 75, resetAt: 1_700_000_000_000 }], + }, + }, + { + name: "falls back to payload-level reset and plan when nested usage records omit them", + payload: { + data: { + plan_name: "Payload Plan", + reset_at: 1_700_000_100, + nested: [{ usage_ratio: 0.4, window_hours: 2 }], + }, + }, + expected: { + plan: "Payload Plan", + windows: [{ label: "2h", usedPercent: 40, resetAt: 1_700_000_100_000 }], + }, + }, + ])("$name", async ({ payload, expected }) => { + await expectMinimaxUsageResult({ payload, expected }); }); it("returns unsupported response shape when no usage fields are present", async () => { diff --git a/src/infra/provider-usage.test.ts b/src/infra/provider-usage.test.ts index f84a4bb25d0..d8f94d04646 100644 --- a/src/infra/provider-usage.test.ts +++ b/src/infra/provider-usage.test.ts @@ -48,17 +48,21 @@ function createMinimaxOnlyFetch(payload: unknown) { async function expectMinimaxUsage( payload: unknown, - expectedUsedPercent: number, - expectedPlan?: string, + expected: { + usedPercent: number; + plan?: string; + label?: string; + }, ) { const mockFetch = createMinimaxOnlyFetch(payload); const summary = await loadUsageWithAuth([{ provider: "minimax", token: "token-1b" }], mockFetch); const minimax = summary.providers.find((p) => p.provider === "minimax"); - expect(minimax?.windows[0]?.usedPercent).toBe(expectedUsedPercent); - if (expectedPlan !== undefined) { - expect(minimax?.plan).toBe(expectedPlan); + expect(minimax?.windows[0]?.usedPercent).toBe(expected.usedPercent); + expect(minimax?.windows[0]?.label).toBe(expected.label ?? "5h"); + if (expected.plan !== undefined) { + expect(minimax?.plan).toBe(expected.plan); } expect(mockFetch).toHaveBeenCalled(); } @@ -181,9 +185,10 @@ describe("provider usage loading", () => { expect(mockFetch).toHaveBeenCalled(); }); - it("handles nested MiniMax usage payloads", async () => { - await expectMinimaxUsage( - { + it.each([ + { + name: "handles nested MiniMax usage payloads", + payload: { base_resp: { status_code: 0, status_msg: "ok" }, data: { plan_name: "Coding Plan", @@ -194,14 +199,11 @@ describe("provider usage loading", () => { }, }, }, - 75, - "Coding Plan", - ); - }); - - it("prefers MiniMax count-based usage when percent looks inverted", async () => { - await expectMinimaxUsage( - { + expected: { usedPercent: 75, plan: "Coding Plan" }, + }, + { + name: "prefers MiniMax count-based usage when percent looks inverted", + payload: { base_resp: { status_code: 0, status_msg: "ok" }, data: { prompt_limit: 200, @@ -210,13 +212,11 @@ describe("provider usage loading", () => { next_reset_time: "2026-01-07T05:00:00Z", }, }, - 25, - ); - }); - - it("handles MiniMax model_remains usage payloads", async () => { - await expectMinimaxUsage( - { + expected: { usedPercent: 25 }, + }, + { + name: "handles MiniMax model_remains usage payloads", + payload: { base_resp: { status_code: 0, status_msg: "ok" }, model_remains: [ { @@ -229,8 +229,25 @@ describe("provider usage loading", () => { }, ], }, - 25, - ); + expected: { usedPercent: 25 }, + }, + { + name: "keeps payload-level MiniMax plan metadata when the usage candidate is nested", + payload: { + base_resp: { status_code: 0, status_msg: "ok" }, + data: { + plan_name: "Payload Plan", + nested: { + usage_ratio: "0.4", + window_hours: 2, + next_reset_time: "2026-01-07T05:00:00Z", + }, + }, + }, + expected: { usedPercent: 40, plan: "Payload Plan", label: "2h" }, + }, + ])("$name", async ({ payload, expected }) => { + await expectMinimaxUsage(payload, expected); }); it("discovers Claude usage from token auth profiles", async () => {