import { afterEach, describe, it, expect, vi } from 'vitest'; import { fetchManagedUsage, formatDuration, formatResetTime, isManagedKimiCode, parseManagedUsagePayload, } from '../src/managed-usage'; afterEach(() => { vi.unstubAllGlobals(); }); describe('isManagedKimiCode', () => { it('matches only the kimi-code managed provider', () => { expect(isManagedKimiCode('managed:kimi-code')).toBe(true); expect(isManagedKimiCode('managed:moonshot-ai')).toBe(false); expect(isManagedKimiCode('openai')).toBe(false); expect(isManagedKimiCode('')).toBe(false); expect(isManagedKimiCode(null)).toBe(false); expect(isManagedKimiCode()).toBe(false); }); }); describe('parseManagedUsagePayload', () => { it('returns empty when payload is not an object', () => { expect(parseManagedUsagePayload(null)).toEqual({ summary: null, limits: [], extraUsage: null }); expect(parseManagedUsagePayload('nope')).toEqual({ summary: null, limits: [], extraUsage: null }); }); it('extracts a summary from the `usage` object', () => { const parsed = parseManagedUsagePayload({ usage: { used: 40, limit: 1000, name: 'Weekly limit' }, }); expect(parsed.summary).toEqual({ label: 'Weekly limit', used: 40, limit: 1000, }); expect(parsed.limits).toEqual([]); }); it('falls back to remaining=limit-used when used is absent', () => { const parsed = parseManagedUsagePayload({ usage: { remaining: 200, limit: 1000 } }); expect(parsed.summary).toEqual({ label: 'Weekly limit', used: 800, limit: 1000 }); }); it('labels limits from window duration when no name is given', () => { const parsed = parseManagedUsagePayload({ limits: [ { detail: { used: 1, limit: 100 }, window: { duration: 300, timeUnit: 'MINUTE' } }, { detail: { used: 2, limit: 50 }, window: { duration: 24, timeUnit: 'HOUR' } }, ], }); expect(parsed.limits.map((l) => l.label)).toEqual(['5h limit', '24h limit']); }); it('prefers explicit item.name over window duration label', () => { const parsed = parseManagedUsagePayload({ limits: [ { name: 'Daily cap', detail: { used: 5, limit: 100 }, window: { duration: 1440, timeUnit: 'MINUTE' }, }, ], }); expect(parsed.limits[0]!.label).toBe('Daily cap'); }); it('surfaces reset hints from resetAt timestamps', () => { const future = new Date(Date.now() + 3600_000).toISOString(); const parsed = parseManagedUsagePayload({ usage: { used: 1, limit: 10, resetAt: future } }); expect(parsed.summary?.resetHint).toMatch(/resets in/); }); it('extracts extra usage from boosterWallet.balance', () => { const parsed = parseManagedUsagePayload({ usage: { used: 40, limit: 1000, name: 'Weekly limit' }, boosterWallet: { id: 'wallet_1', balance: { type: 'BOOSTER', amount: '20000000000', amountLeft: '10000000000', unit: 'UNIT_CURRENCY', }, monthlyChargeLimitEnabled: true, monthlyChargeLimit: { currency: 'USD', priceInCents: '20000' }, monthlyUsed: { currency: 'USD', priceInCents: '5000' }, }, }); expect(parsed.extraUsage).toEqual({ balanceCents: 10000, totalCents: 20000, monthlyChargeLimitEnabled: true, monthlyChargeLimitCents: 20000, monthlyUsedCents: 5000, currency: 'USD', }); }); it('treats missing amountLeft as zero balance', () => { const parsed = parseManagedUsagePayload({ usage: { used: 1, limit: 10 }, boosterWallet: { balance: { type: 'BOOSTER', amount: '20000000000' } }, }); expect(parsed.extraUsage).toMatchObject({ totalCents: 20000, balanceCents: 0 }); }); it('defaults monthly limit fields when absent', () => { const parsed = parseManagedUsagePayload({ usage: { used: 1, limit: 10 }, boosterWallet: { balance: { type: 'BOOSTER', amount: '20000000000', amountLeft: '20000000000' }, }, }); expect(parsed.extraUsage).toEqual({ balanceCents: 20000, totalCents: 20000, monthlyChargeLimitEnabled: false, monthlyChargeLimitCents: 0, monthlyUsedCents: 0, currency: 'USD', }); }); it('returns null extra usage when boosterWallet is missing or invalid', () => { expect(parseManagedUsagePayload({ usage: { used: 1, limit: 10 } }).extraUsage).toBeNull(); expect( parseManagedUsagePayload({ usage: { used: 1, limit: 10 }, boosterWallet: { balance: { type: 'OTHER', amount: '100', amountLeft: '50' } }, }).extraUsage, ).toBeNull(); expect( parseManagedUsagePayload({ usage: { used: 1, limit: 10 }, boosterWallet: { balance: { type: 'BOOSTER', amount: '0', amountLeft: '0' } }, }).extraUsage, ).toBeNull(); }); }); describe('fetchManagedUsage', () => { it('sends only Authorization and Accept headers', async () => { const fetchMock = vi.fn( async () => new Response(JSON.stringify({ usage: { used: 1, limit: 10 } }), { status: 200, headers: { 'Content-Type': 'application/json' }, }), ); vi.stubGlobal('fetch', fetchMock); await expect(fetchManagedUsage('https://api.example/usages', 'access-token')).resolves.toEqual({ kind: 'ok', parsed: { summary: { label: 'Weekly limit', used: 1, limit: 10 }, limits: [], extraUsage: null, }, }); const calls = fetchMock.mock.calls as unknown as [string, RequestInit?][]; const init = calls[0]?.[1] ?? {}; const headers = new Headers((init.headers ?? {}) as Record); expect(headers.get('authorization')).toBe('Bearer access-token'); expect(headers.get('accept')).toBe('application/json'); expect(headers.get('user-agent')).toBeNull(); expect(headers.get('x-msh-platform')).toBeNull(); }); it('surfaces JSON API error messages with status', async () => { vi.stubGlobal( 'fetch', vi.fn( async () => new Response(JSON.stringify({ message: 'usage quota unavailable' }), { status: 401, headers: { 'Content-Type': 'application/json' }, }), ), ); const result = await fetchManagedUsage('https://api.example/usages', 'access-token'); expect(result.kind).toBe('error'); if (result.kind !== 'error') return; expect(result.status).toBe(401); expect(result.message).toBe('usage quota unavailable'); }); it('surfaces nested JSON API error messages', async () => { vi.stubGlobal( 'fetch', vi.fn( async () => new Response(JSON.stringify({ error: { message: 'usage endpoint moved' } }), { status: 404, headers: { 'Content-Type': 'application/json' }, }), ), ); const result = await fetchManagedUsage('https://api.example/usages', 'access-token'); expect(result.kind).toBe('error'); if (result.kind !== 'error') return; expect(result.status).toBe(404); expect(result.message).toBe('usage endpoint moved'); }); it('falls back to local usage hints when the API error body is empty', async () => { vi.stubGlobal('fetch', vi.fn(async () => new Response('', { status: 404 }))); const result = await fetchManagedUsage('https://api.example/usages', 'access-token'); expect(result.kind).toBe('error'); if (result.kind !== 'error') return; expect(result.status).toBe(404); expect(result.message).toBe('Usage endpoint not available. Try Kimi For Coding.'); }); }); describe('formatDuration', () => { it('formats days/hours/minutes', () => { expect(formatDuration(0)).toBe('0s'); expect(formatDuration(45)).toBe('45s'); expect(formatDuration(90)).toBe('1m'); expect(formatDuration(3600)).toBe('1h'); expect(formatDuration(3661)).toBe('1h 1m'); expect(formatDuration(86_400 + 7200 + 600)).toBe('1d 2h 10m'); }); }); describe('formatResetTime', () => { it('returns "reset" for past timestamps', () => { const past = new Date(Date.now() - 5000).toISOString(); expect(formatResetTime(past)).toBe('reset'); }); it('returns "resets in X" for future timestamps', () => { const future = new Date(Date.now() + 3600_000).toISOString(); expect(formatResetTime(future)).toMatch(/^resets in /); }); it('falls back when parsing fails', () => { expect(formatResetTime('not-a-date')).toBe('resets at not-a-date'); }); });