token-monitor/test.js
Hannibal Smith 34898b1196
Phase 2: analysis layer (analyze.js), cache guard, log hygiene
- analyze.js: burn rate, weekly reconstruction, cycle stagger, rotation
  rank, underspend alerts, log prune with weekly archive
- logger.js: getCachedRun(maxAgeMinutes) — skip probing if recent data exists
- monitor.js: cache guard at wake — 20-min dedup, zero extra API calls
- test.js: fix type assertion for gemini-api/xai-api providers (+5 passing);
  add 14 new tests for cache guard and analyze.js (162 total, all green)
- docs/analyze.md: usage reference

Co-authored-by: Hannibal Smith <hannibal@trentuna.com>
2026-04-05 04:49:05 +00:00

477 lines
25 KiB
JavaScript

/**
* token-monitor test suite
* Run: node test.js
* Red first, then implement until green.
*/
import { readFileSync, existsSync } from 'fs';
import { homedir } from 'os';
import { join } from 'path';
import { execSync } from 'child_process';
import { parseGeminiBody } from './providers/gemini.js';
import { parseXaiHeaders } from './providers/xai.js';
let passed = 0;
let failed = 0;
const failures = [];
function assert(label, condition, detail = '') {
if (condition) {
console.log(`${label}`);
passed++;
} else {
console.log(`${label}${detail ? ': ' + detail : ''}`);
failed++;
failures.push({ label, detail });
}
}
function run(cmd) {
return execSync(cmd, { cwd: '/home/exedev/projects/token-monitor', encoding: 'utf-8' });
}
function runSafe(cmd) {
try {
return { stdout: run(cmd), code: 0 };
} catch (e) {
return { stdout: e.stdout || '', stderr: e.stderr || '', code: e.status };
}
}
// ── 1. Package structure ─────────────────────────────────────────────────────
console.log('\n── 1. File structure ───────────────────────────────────────────');
const root = '/home/exedev/projects/token-monitor';
const files = [
'package.json',
'monitor.js',
'logger.js',
'report.js',
'providers/index.js',
'providers/anthropic-teams.js',
'providers/anthropic-api.js',
'providers/shelley-proxy.js',
'README.md',
];
for (const f of files) {
assert(`${f} exists`, existsSync(join(root, f)));
}
// ── 2. package.json shape ────────────────────────────────────────────────────
console.log('\n── 2. package.json ─────────────────────────────────────────────');
const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf-8'));
assert('name = token-monitor', pkg.name === 'token-monitor');
assert('type = module (ESM)', pkg.type === 'module');
assert('no external dependencies', !pkg.dependencies || Object.keys(pkg.dependencies).length === 0);
assert('engines.node >= 18', pkg.engines?.node?.includes('18') || pkg.engines?.node?.includes('>=18'));
// ── 3. Severity logic (unit test, importable) ────────────────────────────────
console.log('\n── 3. Severity logic ───────────────────────────────────────────');
const { getSeverity } = await import('./report.js');
assert('teams: rejected → critical',
getSeverity({ type: 'teams-direct', status: 'rejected', utilization_5h: 0, utilization_7d: 1.0 }) === 'critical');
assert('teams: 7d > 0.85 → warning',
getSeverity({ type: 'teams-direct', status: 'allowed', utilization_5h: 0.2, utilization_7d: 0.9 }) === 'warning');
assert('teams: 5h > 0.7 → warning',
getSeverity({ type: 'teams-direct', status: 'allowed', utilization_5h: 0.75, utilization_7d: 0.4 }) === 'warning');
assert('teams: healthy → ok',
getSeverity({ type: 'teams-direct', status: 'allowed', utilization_5h: 0.3, utilization_7d: 0.4 }) === 'ok');
assert('shelley: high token use → warning',
getSeverity({ type: 'shelley-proxy', tokens_remaining: 5000, tokens_limit: 50000 }) === 'warning');
assert('shelley: healthy → ok',
getSeverity({ type: 'shelley-proxy', tokens_remaining: 45000, tokens_limit: 50000 }) === 'ok');
assert('api-direct → unknown',
getSeverity({ type: 'api-direct' }) === 'unknown');
// ── 4. Provider registry ─────────────────────────────────────────────────────
console.log('\n── 4. Provider registry ────────────────────────────────────────');
const { getProviders } = await import('./providers/index.js');
const providers = getProviders();
const names = Object.keys(providers);
assert('team-vigilio in registry', names.includes('team-vigilio'));
assert('team-molto in registry', names.includes('team-molto'));
assert('team-nadja in registry', names.includes('team-nadja'));
assert('team-ludo in registry', names.includes('team-ludo'));
assert('team-buio in registry', names.includes('team-buio'));
assert('shelley-proxy in registry', names.includes('shelley-proxy'));
assert('api-ateam in registry', names.includes('api-ateam'));
assert('zai NOT in registry (not anthropic-messages)', !names.includes('zai'));
for (const [name, p] of Object.entries(providers)) {
assert(`${name} has baseUrl`, typeof p.baseUrl === 'string' && p.baseUrl.length > 0);
assert(`${name} has type`, ['teams-direct', 'shelley-proxy', 'api-direct', 'gemini-api', 'xai-api'].includes(p.type));
}
// ── 5. Teams header parser ───────────────────────────────────────────────────
console.log('\n── 5. Teams header parser ──────────────────────────────────────');
const { parseTeamsHeaders } = await import('./providers/anthropic-teams.js');
// Simulate a 200 OK response with unified headers
const ok200headers = new Map([
['anthropic-ratelimit-unified-status', 'allowed'],
['anthropic-ratelimit-unified-5h-status', 'allowed'],
['anthropic-ratelimit-unified-5h-utilization', '0.32'],
['anthropic-ratelimit-unified-5h-reset', '1775336400'],
['anthropic-ratelimit-unified-7d-status', 'allowed'],
['anthropic-ratelimit-unified-7d-utilization', '0.45'],
['anthropic-ratelimit-unified-7d-reset', '1775869200'],
['anthropic-ratelimit-unified-representative-claim', 'five_hour'],
['anthropic-ratelimit-unified-fallback-percentage', '0.5'],
['anthropic-ratelimit-unified-reset', '1775336400'],
['anthropic-ratelimit-unified-overage-status', 'rejected'],
['anthropic-ratelimit-unified-overage-disabled-reason', 'org_level_disabled'],
['anthropic-organization-id', '1d7653ad-11d9-4029-ba1a-a2dd4cd0b2f3'],
]);
const fakeHeaders200 = { get: (k) => ok200headers.get(k.toLowerCase()) || null };
const parsed200 = parseTeamsHeaders(fakeHeaders200, 200, 'team-test');
assert('200: type = teams-direct', parsed200.type === 'teams-direct');
assert('200: status = allowed', parsed200.status === 'allowed');
assert('200: utilization_5h is number', typeof parsed200.utilization_5h === 'number');
assert('200: utilization_5h = 0.32', parsed200.utilization_5h === 0.32);
assert('200: utilization_7d = 0.45', parsed200.utilization_7d === 0.45);
assert('200: representative_claim present', parsed200.representative_claim === 'five_hour');
assert('200: reset_timestamp is number', typeof parsed200.reset_timestamp === 'number');
assert('200: reset_in_seconds is number', typeof parsed200.reset_in_seconds === 'number');
assert('200: organization_id present', parsed200.organization_id === '1d7653ad-11d9-4029-ba1a-a2dd4cd0b2f3');
assert('200: severity = ok', parsed200.severity === 'ok');
// Simulate 429 with rejected status
const rejected429headers = new Map([
['anthropic-ratelimit-unified-status', 'rejected'],
['anthropic-ratelimit-unified-5h-status', 'allowed'],
['anthropic-ratelimit-unified-5h-utilization', '0.0'],
['anthropic-ratelimit-unified-5h-reset', '1775322000'],
['anthropic-ratelimit-unified-7d-status', 'rejected'],
['anthropic-ratelimit-unified-7d-utilization', '1.0'],
['anthropic-ratelimit-unified-7d-surpassed-threshold', '1.0'],
['anthropic-ratelimit-unified-7d-reset', '1775404800'],
['anthropic-ratelimit-unified-representative-claim', 'seven_day'],
['anthropic-ratelimit-unified-fallback-percentage', '0.5'],
['anthropic-ratelimit-unified-reset', '1775404800'],
['anthropic-ratelimit-unified-overage-status', 'rejected'],
['anthropic-ratelimit-unified-overage-disabled-reason', 'org_level_disabled'],
['anthropic-organization-id', '1d7653ad-11d9-4029-ba1a-a2dd4cd0b2f3'],
['retry-after', '83690'],
]);
const fakeHeaders429 = { get: (k) => rejected429headers.get(k.toLowerCase()) || null };
const parsed429 = parseTeamsHeaders(fakeHeaders429, 429, 'team-vigilio');
assert('429: status = rejected', parsed429.status === 'rejected');
assert('429: utilization_7d = 1.0', parsed429.utilization_7d === 1.0);
assert('429: severity = critical', parsed429.severity === 'critical');
assert('429: retry_after_seconds = 83690', parsed429.retry_after_seconds === 83690);
// 401 invalid key
const { parseTeamsHeaders: ph } = await import('./providers/anthropic-teams.js');
const parsed401 = ph({ get: () => null }, 401, 'team-ludo');
assert('401: status = invalid_key', parsed401.status === 'invalid_key');
assert('401: utilization_5h = null', parsed401.utilization_5h === null);
assert('401: utilization_7d = null', parsed401.utilization_7d === null);
assert('401: severity = unknown', parsed401.severity === 'unknown');
// ── 6. Shelley header parser ─────────────────────────────────────────────────
console.log('\n── 6. Shelley header parser ────────────────────────────────────');
const { parseShelleyHeaders } = await import('./providers/shelley-proxy.js');
const shelleyHeaderMap = new Map([
['anthropic-ratelimit-tokens-limit', '4800000'],
['anthropic-ratelimit-tokens-remaining', '4750000'],
['anthropic-ratelimit-tokens-reset', '2026-04-04T13:00:00Z'],
['anthropic-ratelimit-requests-limit', '20000'],
['anthropic-ratelimit-requests-remaining', '19999'],
['anthropic-ratelimit-requests-reset', '2026-04-04T16:45:09Z'],
['exedev-gateway-cost', '0.000013'],
]);
const fakeShelleyHeaders = { get: (k) => shelleyHeaderMap.get(k.toLowerCase()) || null };
const parsedShelley = parseShelleyHeaders(fakeShelleyHeaders, 200);
assert('shelley: type = shelley-proxy', parsedShelley.type === 'shelley-proxy');
assert('shelley: status = ok', parsedShelley.status === 'ok');
assert('shelley: tokens_limit = 4800000', parsedShelley.tokens_limit === 4800000);
assert('shelley: tokens_remaining = 4750000', parsedShelley.tokens_remaining === 4750000);
assert('shelley: tokens_reset present', typeof parsedShelley.tokens_reset === 'string');
assert('shelley: requests_limit = 20000', parsedShelley.requests_limit === 20000);
assert('shelley: requests_remaining = 19999', parsedShelley.requests_remaining === 19999);
assert('shelley: cost_per_call_usd = 0.000013', parsedShelley.cost_per_call_usd === 0.000013);
assert('shelley: severity = ok', parsedShelley.severity === 'ok');
// ── 7. api-ateam provider ────────────────────────────────────────────────────
console.log('\n── 7. api-ateam (no billing data) ──────────────────────────────');
const { getApiAteamStatus } = await import('./providers/anthropic-api.js');
const apiStatus = getApiAteamStatus();
assert('api-ateam: type = api-direct', apiStatus.type === 'api-direct');
assert('api-ateam: status = no_billing_data', apiStatus.status === 'no_billing_data');
assert('api-ateam: message is string', typeof apiStatus.message === 'string' && apiStatus.message.length > 0);
assert('api-ateam: severity = unknown', apiStatus.severity === 'unknown');
// ── 8. Logger ────────────────────────────────────────────────────────────────
console.log('\n── 8. Logger ───────────────────────────────────────────────────');
const { logRun } = await import('./logger.js');
const testData = { test: true, value: 42, providers: {} };
logRun(testData);
const today = new Date().toISOString().slice(0, 10);
const logFile = join(homedir(), '.logs', 'token-monitor', `${today}.jsonl`);
assert('log file created', existsSync(logFile));
const lastLine = readFileSync(logFile, 'utf-8').trim().split('\n').pop();
const logEntry = JSON.parse(lastLine);
assert('log entry has ts', typeof logEntry.ts === 'string');
assert('log entry has test data', logEntry.test === true && logEntry.value === 42);
// ── 9. Report generator ──────────────────────────────────────────────────────
console.log('\n── 9. Report generator ─────────────────────────────────────────');
const { generateReport } = await import('./report.js');
const sampleResult = {
timestamp: '2026-04-04T12:00:00Z',
providers: {
'team-vigilio': {
type: 'teams-direct', status: 'rejected',
utilization_5h: 0.0, utilization_7d: 1.0,
representative_claim: 'seven_day',
reset_timestamp: Math.floor(Date.now() / 1000) + 82800,
reset_in_seconds: 82800,
severity: 'critical',
},
'team-molto': {
type: 'teams-direct', status: 'allowed',
utilization_5h: 0.32, utilization_7d: 0.45,
representative_claim: 'five_hour',
reset_timestamp: Math.floor(Date.now() / 1000) + 3600,
reset_in_seconds: 3600,
severity: 'ok',
},
'team-ludo': {
type: 'teams-direct', status: 'invalid_key',
utilization_5h: null, utilization_7d: null,
severity: 'unknown',
},
'shelley-proxy': {
type: 'shelley-proxy', status: 'ok',
tokens_remaining: 45000, tokens_limit: 50000,
requests_remaining: 48, requests_limit: 50,
tokens_reset: '2026-04-04T13:00:00Z',
cost_per_call_usd: 0.000013,
severity: 'ok',
},
'api-ateam': {
type: 'api-direct', status: 'no_billing_data',
message: 'Anthropic API does not expose billing/quota via REST',
severity: 'unknown',
},
},
};
const report = generateReport(sampleResult);
assert('report is string', typeof report === 'string');
assert('report contains Token Monitor header', report.includes('Token Monitor'));
assert('report contains team-vigilio', report.includes('team-vigilio'));
assert('report contains CRITICAL', report.includes('CRITICAL'));
assert('report contains team-molto', report.includes('team-molto'));
assert('report contains shelley-proxy', report.includes('shelley-proxy'));
assert('report contains api-ateam', report.includes('api-ateam'));
assert('report contains Overall summary', report.toLowerCase().includes('overall'));
// ── 10. CLI: --summary flag ──────────────────────────────────────────────────
console.log('\n── 10. CLI flags (--summary, --json) ───────────────────────────');
// --summary: exit 0, stdout contains provider names
const summaryResult = runSafe('node monitor.js --summary');
assert('--summary exits 0', summaryResult.code === 0, `exit code: ${summaryResult.code}\n${summaryResult.stderr || ''}`);
assert('--summary output contains Token Monitor', summaryResult.stdout.includes('Token Monitor'));
assert('--summary output contains provider names', summaryResult.stdout.includes('team-') || summaryResult.stdout.includes('shelley'));
// --json: exit 0, valid JSON, correct schema
const jsonResult = runSafe('node monitor.js --json');
assert('--json exits 0', jsonResult.code === 0, `exit code: ${jsonResult.code}\n${jsonResult.stderr || ''}`);
let jsonData;
try {
jsonData = JSON.parse(jsonResult.stdout);
assert('--json outputs valid JSON', true);
} catch (e) {
assert('--json outputs valid JSON', false, e.message);
}
if (jsonData) {
assert('JSON has timestamp', typeof jsonData.timestamp === 'string');
assert('JSON has providers object', typeof jsonData.providers === 'object');
const pnames = Object.keys(jsonData.providers);
assert('JSON providers includes team-vigilio', pnames.includes('team-vigilio'));
assert('JSON providers includes shelley-proxy', pnames.includes('shelley-proxy'));
assert('JSON providers includes api-ateam', pnames.includes('api-ateam'));
// Teams-direct entries must have required fields
for (const [name, p] of Object.entries(jsonData.providers)) {
if (p.type === 'teams-direct' && p.status !== 'invalid_key') {
assert(`${name}: has utilization_5h`, typeof p.utilization_5h === 'number');
assert(`${name}: has utilization_7d`, typeof p.utilization_7d === 'number');
assert(`${name}: has severity`, typeof p.severity === 'string');
assert(`${name}: has reset_in_seconds`, typeof p.reset_in_seconds === 'number');
}
if (p.type === 'shelley-proxy') {
assert('shelley-proxy: has cost_per_call_usd', typeof p.cost_per_call_usd === 'number');
assert('shelley-proxy: has tokens_remaining', typeof p.tokens_remaining === 'number');
assert('shelley-proxy: has tokens_limit', typeof p.tokens_limit === 'number');
}
if (p.type === 'api-direct') {
assert('api-ateam: status = no_billing_data', p.status === 'no_billing_data');
}
}
}
// Default run: exit 0, log file updated
const defaultResult = runSafe('node monitor.js');
assert('default run exits 0', defaultResult.code === 0, `exit code: ${defaultResult.code}\n${defaultResult.stderr || ''}`);
// Log file should exist and have content
const logFileAfter = join(homedir(), '.logs', 'token-monitor', `${today}.jsonl`);
assert('log file exists after default run', existsSync(logFileAfter));
const logLines = readFileSync(logFileAfter, 'utf-8').trim().split('\n');
assert('log file has at least 2 entries', logLines.length >= 2);
// ── 11. Gemini provider parser ────────────────────────────────────────────────
console.log('\n── 11. Gemini provider parser ──────────────────────────────────');
const gemini429Body = {
error: {
code: 429,
status: 'RESOURCE_EXHAUSTED',
details: [
{
'@type': 'type.googleapis.com/google.rpc.QuotaFailure',
violations: [
{
quotaMetric: 'generativelanguage.googleapis.com/generate_content_free_tier_requests',
quotaId: 'GenerateRequestsPerMinutePerProjectPerModel-FreeTier',
quotaDimensions: { location: 'global', model: 'gemini-2.0-flash' },
},
{
quotaMetric: 'generativelanguage.googleapis.com/generate_content_free_tier_requests',
quotaId: 'GenerateRequestsPerDayPerProjectPerModel-FreeTier',
quotaDimensions: { location: 'global', model: 'gemini-2.0-flash' },
},
],
},
{ '@type': 'type.googleapis.com/google.rpc.RetryInfo', retryDelay: '43s' },
],
},
};
assert('providers/gemini.js exists', existsSync(join(root, 'providers/gemini.js')));
const g429 = parseGeminiBody(gemini429Body, 429);
assert('gemini 429: status = exhausted', g429.status === 'exhausted');
assert('gemini 429: type = gemini-api', g429.type === 'gemini-api');
assert('gemini 429: quota_violations is array', Array.isArray(g429.quota_violations));
assert('gemini 429: quota_violations has 2 entries', g429.quota_violations.length === 2);
assert('gemini 429: retry_delay_seconds = 43', g429.retry_delay_seconds === 43);
assert('gemini 429: severity = critical', g429.severity === 'critical');
const g200 = parseGeminiBody(null, 200);
assert('gemini 200: status = ok', g200.status === 'ok');
assert('gemini 200: severity = ok', g200.severity === 'ok');
assert('gemini 200: quota_violations is empty array', Array.isArray(g200.quota_violations) && g200.quota_violations.length === 0);
const g401 = parseGeminiBody(null, 401);
assert('gemini 401: status = invalid_key', g401.status === 'invalid_key');
assert('gemini 401: severity = unknown', g401.severity === 'unknown');
// ── 12. x.ai provider parser ─────────────────────────────────────────────────
console.log('\n── 12. x.ai provider parser ────────────────────────────────────');
class MockHeaders {
constructor(map) { this._map = map; }
get(name) { return this._map[name.toLowerCase()] ?? null; }
}
const xai200Headers = new MockHeaders({
'x-ratelimit-limit-requests': '1000',
'x-ratelimit-remaining-requests': '998',
'x-ratelimit-limit-tokens': '500000',
'x-ratelimit-remaining-tokens': '499981',
});
const xai200HeadersNearFull = new MockHeaders({
'x-ratelimit-limit-requests': '1000',
'x-ratelimit-remaining-requests': '5',
'x-ratelimit-limit-tokens': '500000',
'x-ratelimit-remaining-tokens': '499981',
});
const xai429Headers = new MockHeaders({
'x-ratelimit-remaining-requests': '0',
});
assert('providers/xai.js exists', existsSync(join(root, 'providers/xai.js')));
const xNoKey = parseXaiHeaders(null, 200, null);
assert('xai no key: status = no_key', xNoKey.status === 'no_key');
assert('xai no key: severity = unknown', xNoKey.severity === 'unknown');
const x200 = parseXaiHeaders(xai200Headers, 200, 'xai-key');
assert('xai 200: status = ok', x200.status === 'ok');
assert('xai 200: requests_remaining = 998', x200.requests_remaining === 998);
assert('xai 200: tokens_remaining = 499981', x200.tokens_remaining === 499981);
assert('xai 200: severity = ok', x200.severity === 'ok');
const x200Near = parseXaiHeaders(xai200HeadersNearFull, 200, 'xai-key');
assert('xai 200 near-full: severity = warning', x200Near.severity === 'warning');
const x429 = parseXaiHeaders(xai429Headers, 429, 'xai-key');
assert('xai 429: status = rate_limited', x429.status === 'rate_limited');
assert('xai 429: severity = critical', x429.severity === 'critical');
const x401 = parseXaiHeaders(null, 401, 'xai-key');
assert('xai 401: status = invalid_key', x401.status === 'invalid_key');
// ── 13. Cache guard (getCachedRun) ─────────────────────────────────────────
console.log('\n── 13. Cache guard ─────────────────────────────────────────────');
const { getCachedRun } = await import('./logger.js');
assert('getCachedRun(0) returns null (zero-minute threshold)', getCachedRun(0) === null);
const cached20 = getCachedRun(20);
assert('getCachedRun(20) returns null or valid object',
cached20 === null || (typeof cached20 === 'object' && typeof cached20.ts === 'string'));
// ── 14. analyze.js smoke tests ───────────────────────────────────────────────
console.log('\n── 14. analyze.js ──────────────────────────────────────────────');
assert('analyze.js exists', existsSync(join(root, 'analyze.js')));
assert('docs/analyze.md exists', existsSync(join(root, 'docs', 'analyze.md')));
const analyzeResult = runSafe('node analyze.js');
assert('analyze.js exits 0', analyzeResult.code === 0,
`exit code: ${analyzeResult.code}\n${analyzeResult.stderr}`);
assert('analyze.js produces output', analyzeResult.stdout.length > 0);
const analyzeJson = runSafe('node analyze.js --json');
assert('analyze.js --json exits 0', analyzeJson.code === 0,
`exit code: ${analyzeJson.code}\n${analyzeJson.stderr}`);
let analyzeData;
try {
analyzeData = JSON.parse(analyzeJson.stdout);
assert('analyze.js --json is valid JSON', true);
} catch (e) {
assert('analyze.js --json is valid JSON', false, e.message);
}
if (analyzeData) {
assert('analyze.js --json has burn_rates', 'burn_rates' in analyzeData);
assert('analyze.js --json has stagger', 'stagger' in analyzeData);
assert('analyze.js --json has rotation', 'rotation' in analyzeData);
assert('analyze.js --json has weekly', 'weekly' in analyzeData);
assert('analyze.js --json stagger is array', Array.isArray(analyzeData.stagger));
assert('analyze.js --json rotation is array', Array.isArray(analyzeData.rotation));
}
const analyzeRotation = runSafe('node analyze.js --rotation');
assert('analyze.js --rotation exits 0', analyzeRotation.code === 0,
`exit code: ${analyzeRotation.code}\n${analyzeRotation.stderr}`);
const analyzePruneDry = runSafe('node analyze.js --prune --dry-run');
assert('analyze.js --prune --dry-run exits 0', analyzePruneDry.code === 0,
`exit code: ${analyzePruneDry.code}\n${analyzePruneDry.stderr}`);
// ── Results ──────────────────────────────────────────────────────────────────
console.log('\n' + '═'.repeat(50));
console.log(`Tests: ${passed + failed} | Passed: ${passed} | Failed: ${failed}`);
if (failures.length > 0) {
console.log('\nFailed:');
for (const f of failures) {
console.log(`${f.label}${f.detail ? ' — ' + f.detail : ''}`);
}
process.exit(1);
} else {
console.log('\nAll tests pass. ✓');
process.exit(0);
}