* Add MiniMax usage UI and API support * Fix MiniMax usage parsing and refresh UI * Refactor MiniMax usage handling
326 lines
8.3 KiB
TypeScript
326 lines
8.3 KiB
TypeScript
import { describe, expect, test } from 'bun:test'
|
|
import { resolve } from 'node:path'
|
|
|
|
import {
|
|
buildMiniMaxUsageRows,
|
|
getMiniMaxUsageUrls,
|
|
normalizeMiniMaxUsagePayload,
|
|
} from './minimaxUsage.js'
|
|
|
|
const fixture = (name: string) =>
|
|
Bun.file(resolve(import.meta.dir, '__fixtures__', name))
|
|
|
|
describe('normalizeMiniMaxUsagePayload', () => {
|
|
test('normalizes interval and weekly quota payloads', () => {
|
|
const usage = normalizeMiniMaxUsagePayload({
|
|
plan_type: 'plus_highspeed',
|
|
data: {
|
|
'MiniMax-M2.7-highspeed': {
|
|
current_interval_usage_count: 4200,
|
|
max_interval_usage_count: 4500,
|
|
current_weekly_usage_count: 43000,
|
|
max_weekly_usage_count: 45000,
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(usage).toMatchObject({
|
|
availability: 'available',
|
|
planType: 'Plus Highspeed',
|
|
snapshots: [
|
|
{
|
|
limitName: 'MiniMax-M2.7-highspeed',
|
|
windows: [
|
|
{
|
|
label: '5h limit',
|
|
usedPercent: 93,
|
|
remaining: 300,
|
|
total: 4500,
|
|
},
|
|
{
|
|
label: 'Weekly limit',
|
|
usedPercent: 96,
|
|
remaining: 2000,
|
|
total: 45000,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
})
|
|
})
|
|
|
|
test('normalizes daily quota payloads from generic usage records', () => {
|
|
const usage = normalizeMiniMaxUsagePayload({
|
|
models: {
|
|
image_01: {
|
|
daily_remaining: 12,
|
|
daily_quota: 50,
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(usage).toMatchObject({
|
|
availability: 'available',
|
|
snapshots: [
|
|
{
|
|
limitName: 'image_01',
|
|
windows: [
|
|
{
|
|
label: 'Daily limit',
|
|
usedPercent: 76,
|
|
remaining: 12,
|
|
total: 50,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
})
|
|
})
|
|
|
|
test('normalizes MiniMax model_remains payloads from a captured fixture', async () => {
|
|
const payload = await fixture('minimax-model-remains.json').json()
|
|
const originalDateNow = Date.now
|
|
Date.now = () => Date.parse('2026-02-20T15:00:00.000Z')
|
|
|
|
try {
|
|
const usage = normalizeMiniMaxUsagePayload(payload)
|
|
|
|
expect(usage).toMatchObject({
|
|
availability: 'available',
|
|
planType: 'Plus Highspeed',
|
|
snapshots: [
|
|
{
|
|
limitName: 'MiniMax-M2.7',
|
|
windows: [
|
|
{
|
|
label: '5h limit',
|
|
usedPercent: 96,
|
|
remaining: 63,
|
|
total: 1500,
|
|
resetsAt: '2026-02-20T16:00:00.000Z',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
limitName: 'MiniMax-M2.7-highspeed',
|
|
windows: [
|
|
{
|
|
label: '5h limit',
|
|
usedPercent: 50,
|
|
remaining: 1000,
|
|
total: 2000,
|
|
resetsAt: '2026-02-20T16:00:00.000Z',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
})
|
|
} finally {
|
|
Date.now = originalDateNow
|
|
}
|
|
})
|
|
|
|
test('treats current_interval_usage_count as used count for MiniMax subscription payloads', () => {
|
|
const usage = normalizeMiniMaxUsagePayload({
|
|
model_remains: [
|
|
{
|
|
current_interval_total_count: 1500,
|
|
current_interval_usage_count: 1,
|
|
model_name: 'MiniMax-M2.7',
|
|
},
|
|
],
|
|
})
|
|
|
|
expect(usage).toMatchObject({
|
|
availability: 'available',
|
|
snapshots: [
|
|
{
|
|
limitName: 'MiniMax-M2.7',
|
|
windows: [
|
|
{
|
|
label: '5h limit',
|
|
usedPercent: 0,
|
|
remaining: 1499,
|
|
total: 1500,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
})
|
|
})
|
|
|
|
test('treats MiniMax usage_percent as remaining percentage', () => {
|
|
const usage = normalizeMiniMaxUsagePayload({
|
|
model_remains: [
|
|
{
|
|
model_name: 'MiniMax-M2.7-highspeed',
|
|
usage_percent: 96,
|
|
},
|
|
],
|
|
})
|
|
|
|
expect(usage).toMatchObject({
|
|
availability: 'available',
|
|
snapshots: [
|
|
{
|
|
limitName: 'MiniMax-M2.7-highspeed',
|
|
windows: [
|
|
{
|
|
label: '5h limit',
|
|
usedPercent: 4,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
})
|
|
})
|
|
|
|
test('returns unknown availability when no quota windows can be parsed', () => {
|
|
const usage = normalizeMiniMaxUsagePayload({
|
|
message: 'quota status unavailable',
|
|
ok: true,
|
|
})
|
|
|
|
expect(usage).toEqual({
|
|
availability: 'unknown',
|
|
planType: undefined,
|
|
snapshots: [],
|
|
message:
|
|
'Usage details are not available for this MiniMax account. This plan or MiniMax endpoint may not expose quota status.',
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('buildMiniMaxUsageRows', () => {
|
|
test('builds provider-prefixed labels and remaining subtext', () => {
|
|
const rows = buildMiniMaxUsageRows([
|
|
{
|
|
limitName: 'MiniMax-M2.7',
|
|
windows: [
|
|
{
|
|
label: '5h limit',
|
|
usedPercent: 20,
|
|
remaining: 1200,
|
|
total: 1500,
|
|
},
|
|
{
|
|
label: 'Weekly limit',
|
|
usedPercent: 10,
|
|
remaining: 13500,
|
|
total: 15000,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
limitName: 'image_01',
|
|
windows: [
|
|
{
|
|
label: 'Daily limit',
|
|
usedPercent: 76,
|
|
remaining: 12,
|
|
total: 50,
|
|
},
|
|
],
|
|
},
|
|
])
|
|
|
|
expect(rows).toEqual([
|
|
{
|
|
kind: 'text',
|
|
label: 'MiniMax-M2.7 quota',
|
|
value: '',
|
|
},
|
|
{
|
|
kind: 'window',
|
|
label: '5h limit',
|
|
usedPercent: 20,
|
|
resetsAt: undefined,
|
|
extraSubtext: '1200/1500 remaining',
|
|
},
|
|
{
|
|
kind: 'window',
|
|
label: 'Weekly limit',
|
|
usedPercent: 10,
|
|
resetsAt: undefined,
|
|
extraSubtext: '13500/15000 remaining',
|
|
},
|
|
{
|
|
kind: 'window',
|
|
label: 'Image 01 Daily limit',
|
|
usedPercent: 76,
|
|
resetsAt: undefined,
|
|
extraSubtext: '12/50 remaining',
|
|
},
|
|
])
|
|
})
|
|
})
|
|
|
|
describe('MiniMax usage helpers', () => {
|
|
test('keeps usage endpoints on the configured provider host and path', () => {
|
|
expect(
|
|
getMiniMaxUsageUrls('https://proxy.example/providers/minimax/v1'),
|
|
).toEqual([
|
|
'https://proxy.example/providers/minimax/v1/token_plan/remains',
|
|
'https://proxy.example/providers/minimax/v1/api/openplatform/coding_plan/remains',
|
|
])
|
|
})
|
|
|
|
test('falls back to OPENAI_API_BASE when OPENAI_BASE_URL is unset', () => {
|
|
const originalBaseUrl = process.env.OPENAI_BASE_URL
|
|
const originalApiBase = process.env.OPENAI_API_BASE
|
|
delete process.env.OPENAI_BASE_URL
|
|
process.env.OPENAI_API_BASE = 'https://gateway.example/openai/v1'
|
|
|
|
try {
|
|
expect(getMiniMaxUsageUrls()).toEqual([
|
|
'https://gateway.example/openai/v1/token_plan/remains',
|
|
'https://gateway.example/openai/v1/api/openplatform/coding_plan/remains',
|
|
])
|
|
} finally {
|
|
if (originalBaseUrl === undefined) {
|
|
delete process.env.OPENAI_BASE_URL
|
|
} else {
|
|
process.env.OPENAI_BASE_URL = originalBaseUrl
|
|
}
|
|
|
|
if (originalApiBase === undefined) {
|
|
delete process.env.OPENAI_API_BASE
|
|
} else {
|
|
process.env.OPENAI_API_BASE = originalApiBase
|
|
}
|
|
}
|
|
})
|
|
|
|
test('throws when an explicitly configured MiniMax base url is invalid', () => {
|
|
expect(() => getMiniMaxUsageUrls('not a url')).toThrow(
|
|
'MiniMax usage base URL is invalid: not a url',
|
|
)
|
|
})
|
|
|
|
test('uses the default MiniMax base url when no provider base is configured', () => {
|
|
const originalBaseUrl = process.env.OPENAI_BASE_URL
|
|
const originalApiBase = process.env.OPENAI_API_BASE
|
|
delete process.env.OPENAI_BASE_URL
|
|
delete process.env.OPENAI_API_BASE
|
|
|
|
try {
|
|
expect(getMiniMaxUsageUrls()).toEqual([
|
|
'https://api.minimax.io/v1/token_plan/remains',
|
|
'https://api.minimax.io/v1/api/openplatform/coding_plan/remains',
|
|
])
|
|
} finally {
|
|
if (originalBaseUrl === undefined) {
|
|
delete process.env.OPENAI_BASE_URL
|
|
} else {
|
|
process.env.OPENAI_BASE_URL = originalBaseUrl
|
|
}
|
|
|
|
if (originalApiBase === undefined) {
|
|
delete process.env.OPENAI_API_BASE
|
|
} else {
|
|
process.env.OPENAI_API_BASE = originalApiBase
|
|
}
|
|
}
|
|
})
|
|
})
|