Клиент — замороженный объект с семью методами GET, без универсального запроса и тела; денежные поля проверяются на строковый тип (fail-closed). Каталог джойнит /api/models и /v1/models: модальность и рубли из первого, USD и доступность ключу из второго. Инструменты: list_models, pick_model, estimate_cost, get_balance, get_usage, list_api_keys. Точка входа stdio уводит console.log/info в stderr первым импортом. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
51 lines
2.8 KiB
TypeScript
51 lines
2.8 KiB
TypeScript
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import type { ApiKeyEntry, UsageResponse } from "../src/client.js";
|
|
import { FAILED_NOTE, summarizeUsage, usageWindow } from "../src/tools/billing.js";
|
|
import { KEYS_TOOL_DESCRIPTION, presentKeys } from "../src/tools/keys.js";
|
|
import { fixture } from "./helpers.js";
|
|
|
|
test("usageWindow: to = сейчас, from = сейчас − days, без миллисекунд", () => {
|
|
const w = usageWindow(7, new Date("2026-10-24T03:39:57.123Z"));
|
|
assert.deepEqual(w, { from: "2026-10-17T03:39:57Z", to: "2026-10-24T03:39:57Z" });
|
|
});
|
|
|
|
test("summarizeUsage: total по всем строкам до обрезки, decimal-сложение", () => {
|
|
const resp = fixture<UsageResponse>("api-usage");
|
|
const s = summarizeUsage(resp, 2);
|
|
// 0.8124417 + 0.18412076 + 0.24310558 + 0.05120934 + 0.00371284 = 1.29459022
|
|
assert.equal(s.total_rub, "1.29459022");
|
|
assert.equal((s.items as unknown[]).length, 2);
|
|
assert.equal(s.truncated, true);
|
|
assert.equal(s.failed_note, null);
|
|
const failed = summarizeUsage({ ...resp, failed_events: 3 }, 50);
|
|
assert.equal(failed.failed_note, FAILED_NOTE);
|
|
assert.equal(failed.truncated, false);
|
|
});
|
|
|
|
test("presentKeys: ключ не выводится, текущий отмечен по префиксу, отозванные скрыты", () => {
|
|
const { keys } = fixture<{ keys: ApiKeyEntry[] }>("api-keys");
|
|
const current = "orr_live_Gh5iJkSECRETSECRETSECRETSECRET1";
|
|
const withRevoked = [...keys, { ...(keys[0] as ApiKeyEntry), id: "rev", revoked_at: "2026-10-01T00:00:00Z" }];
|
|
const out = presentKeys(withRevoked, current, false);
|
|
assert.equal(out.length, 3);
|
|
assert.deepEqual(out.map((k) => k.is_current_key), [false, true, false]);
|
|
assert.ok(!JSON.stringify(out).includes(current));
|
|
assert.equal(out[0]?.all_models_allowed, true);
|
|
assert.equal(out[0]?.allowed_models, null);
|
|
assert.deepEqual(out[1]?.allowed_models, ["openai/gpt-4o-mini"]);
|
|
assert.equal(out[1]?.all_models_allowed, false);
|
|
assert.equal(out[0]?.spend_used_rub, "12.5784961");
|
|
assert.equal(out[0]?.key_prefix, "orr_live_Ab3dEf");
|
|
assert.equal(presentKeys(withRevoked, current, true).length, 4);
|
|
});
|
|
|
|
test("описание list_api_keys дословно говорит о запрете управления ключами", () => {
|
|
assert.ok(
|
|
KEYS_TOOL_DESCRIPTION.includes(
|
|
"Создать, отозвать или изменить ключ через MCP нельзя — такие действия требуют браузерной сессии на openrusrouter.ru. " +
|
|
"Это сознательное ограничение безопасности: ключ, которым пользуется MCP, не должен уметь управлять другими ключами или собой",
|
|
),
|
|
);
|
|
});
|