Files
wehub-resource-sync 7088f49b01
Build dist / build (push) Has been cancelled
CI / test (18.x) (push) Has been cancelled
CI / test (20.x) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:39:33 +08:00

397 lines
12 KiB
JavaScript

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { access, mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises';
import * as fs from 'node:fs';
import * as path from 'node:path';
import { tmpdir } from 'node:os';
import { DEFAULT_CONFIG } from '../dist/config.js';
import { getUsageFromStdin } from '../dist/stdin.js';
import { getUsageFromExternalSnapshot, writeExternalUsageSnapshot } from '../dist/external-usage.js';
async function withTempFile(content) {
const dir = await mkdtemp(path.join(tmpdir(), 'claude-hud-external-usage-'));
const filePath = path.join(dir, 'usage.json');
await writeFile(filePath, content, 'utf8');
return {
filePath,
cleanup: async () => rm(dir, { recursive: true, force: true }),
};
}
function makeConfig(filePath, freshnessMs = 300000) {
return {
...DEFAULT_CONFIG,
display: {
...DEFAULT_CONFIG.display,
externalUsagePath: filePath,
externalUsageFreshnessMs: freshnessMs,
},
};
}
function makeWriteConfig(filePath) {
return {
...DEFAULT_CONFIG,
display: {
...DEFAULT_CONFIG.display,
externalUsageWritePath: filePath,
},
};
}
async function pathExists(filePath) {
try {
await access(filePath);
return true;
} catch {
return false;
}
}
function makeUsage(overrides = {}) {
return {
fiveHour: 42,
sevenDay: 85,
fiveHourResetAt: new Date('2026-04-20T15:00:00.000Z'),
sevenDayResetAt: new Date('2026-04-27T12:00:00.000Z'),
...overrides,
};
}
test('getUsageFromExternalSnapshot returns null without a configured path', () => {
const usage = getUsageFromExternalSnapshot(DEFAULT_CONFIG, Date.now());
assert.equal(usage, null);
});
test('getUsageFromExternalSnapshot parses a fresh snapshot', async () => {
const updatedAt = Date.UTC(2026, 3, 20, 12, 0, 0);
const resetAt = '2026-04-20T15:00:00.000Z';
const { filePath, cleanup } = await withTempFile(JSON.stringify({
updated_at: new Date(updatedAt).toISOString(),
five_hour: { used_percentage: 42.4, resets_at: resetAt },
seven_day: { used_percentage: 84.6, resets_at: '2026-04-27T12:00:00.000Z' },
}));
try {
const usage = getUsageFromExternalSnapshot(makeConfig(filePath), updatedAt + 60_000);
assert.deepEqual(usage, {
fiveHour: 42,
sevenDay: 85,
fiveHourResetAt: new Date(resetAt),
sevenDayResetAt: new Date('2026-04-27T12:00:00.000Z'),
});
} finally {
await cleanup();
}
});
test('getUsageFromExternalSnapshot ignores relative read paths', () => {
const usage = getUsageFromExternalSnapshot(makeConfig('usage.json'), Date.now());
assert.equal(usage, null);
});
test('writeExternalUsageSnapshot writes stdin rate limits to the configured path', async () => {
const dir = await mkdtemp(path.join(tmpdir(), 'claude-hud-external-usage-write-'));
const filePath = path.join(dir, 'usage.json');
const now = Date.UTC(2026, 3, 20, 12, 0, 0);
const usage = getUsageFromStdin({
rate_limits: {
five_hour: { used_percentage: 42.4, resets_at: 1776697200 },
seven_day: { used_percentage: 84.6, resets_at: 1777291200 },
},
});
try {
const wrote = writeExternalUsageSnapshot(makeWriteConfig(filePath), usage, now);
const snapshot = JSON.parse(await readFile(filePath, 'utf8'));
const fileMode = (await stat(filePath)).mode & 0o777;
assert.equal(wrote, true);
assert.equal(fileMode, 0o600);
assert.deepEqual(snapshot, {
updated_at: new Date(now).toISOString(),
five_hour: {
used_percentage: 42,
resets_at: '2026-04-20T15:00:00.000Z',
},
seven_day: {
used_percentage: 85,
resets_at: '2026-04-27T12:00:00.000Z',
},
});
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test('writeExternalUsageSnapshot skips identical snapshots within the throttle window', async () => {
const dir = await mkdtemp(path.join(tmpdir(), 'claude-hud-external-usage-write-'));
const filePath = path.join(dir, 'usage.json');
const config = makeWriteConfig(filePath);
const usage = makeUsage();
try {
assert.equal(writeExternalUsageSnapshot(config, usage, Date.now()), true);
const firstStat = await stat(filePath);
assert.equal(writeExternalUsageSnapshot(config, usage, Date.now() + 1000), false);
const secondStat = await stat(filePath);
assert.equal(secondStat.mtimeMs, firstStat.mtimeMs);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test('writeExternalUsageSnapshot is a no-op without a configured write path', async () => {
const dir = await mkdtemp(path.join(tmpdir(), 'claude-hud-external-usage-write-'));
const filePath = path.join(dir, 'usage.json');
try {
const wrote = writeExternalUsageSnapshot(DEFAULT_CONFIG, makeUsage(), Date.now());
assert.equal(wrote, false);
assert.equal(await pathExists(filePath), false);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test('writeExternalUsageSnapshot ignores relative write paths', () => {
const throwingDeps = {
chmodSync: () => {
throw new Error('unexpected chmod');
},
existsSync: () => {
throw new Error('unexpected exists');
},
readFileSync: () => {
throw new Error('unexpected read');
},
renameSync: () => {
throw new Error('unexpected rename');
},
rmSync: () => {
throw new Error('unexpected rm');
},
statSync: () => {
throw new Error('unexpected stat');
},
writeFileSync: () => {
throw new Error('unexpected write');
},
};
assert.equal(
writeExternalUsageSnapshot(makeWriteConfig('usage.json'), makeUsage(), Date.now(), throwingDeps),
false,
);
});
test('writeExternalUsageSnapshot ignores non-json write paths', () => {
const throwingDeps = {
chmodSync: () => {
throw new Error('unexpected chmod');
},
existsSync: () => {
throw new Error('unexpected exists');
},
readFileSync: () => {
throw new Error('unexpected read');
},
renameSync: () => {
throw new Error('unexpected rename');
},
rmSync: () => {
throw new Error('unexpected rm');
},
statSync: () => {
throw new Error('unexpected stat');
},
writeFileSync: () => {
throw new Error('unexpected write');
},
};
assert.equal(
writeExternalUsageSnapshot(makeWriteConfig(path.join(tmpdir(), 'usage.txt')), makeUsage(), Date.now(), throwingDeps),
false,
);
});
test('writeExternalUsageSnapshot does not create missing parent directories', async () => {
const dir = await mkdtemp(path.join(tmpdir(), 'claude-hud-external-usage-write-'));
const missingDir = path.join(dir, 'missing');
const filePath = path.join(missingDir, 'usage.json');
try {
const wrote = writeExternalUsageSnapshot(makeWriteConfig(filePath), makeUsage(), Date.now());
assert.equal(wrote, false);
assert.equal(await pathExists(missingDir), false);
assert.equal(await pathExists(filePath), false);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test('writeExternalUsageSnapshot is a no-op without parsed stdin rate limits', async () => {
const dir = await mkdtemp(path.join(tmpdir(), 'claude-hud-external-usage-write-'));
const filePath = path.join(dir, 'usage.json');
try {
const wrote = writeExternalUsageSnapshot(
makeWriteConfig(filePath),
getUsageFromStdin({ rate_limits: null }),
Date.now(),
);
assert.equal(wrote, false);
assert.equal(await pathExists(filePath), false);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test('writeExternalUsageSnapshot removes temp files when atomic rename fails', async () => {
const dir = await mkdtemp(path.join(tmpdir(), 'claude-hud-external-usage-write-'));
const filePath = path.join(dir, 'usage.json');
const deps = {
chmodSync: fs.chmodSync,
existsSync: fs.existsSync,
readFileSync: fs.readFileSync,
renameSync: () => {
throw new Error('rename failed');
},
rmSync: fs.rmSync,
statSync: fs.statSync,
writeFileSync: fs.writeFileSync,
};
try {
const wrote = writeExternalUsageSnapshot(makeWriteConfig(filePath), makeUsage(), Date.now(), deps);
const files = await fs.promises.readdir(dir);
assert.equal(wrote, false);
assert.equal(await pathExists(filePath), false);
assert.deepEqual(files, []);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test('writeExternalUsageSnapshot emits only supported snapshot keys', async () => {
const dir = await mkdtemp(path.join(tmpdir(), 'claude-hud-external-usage-write-'));
const filePath = path.join(dir, 'usage.json');
const usage = getUsageFromStdin({
rate_limits: {
five_hour: { used_percentage: 12, resets_at: 1776697200, extra: 'ignored' },
seven_day: { used_percentage: 34, resets_at: 1777291200, extra: 'ignored' },
extra_window: { used_percentage: 99 },
},
});
try {
writeExternalUsageSnapshot(makeWriteConfig(filePath), usage, Date.now());
const snapshot = JSON.parse(await readFile(filePath, 'utf8'));
assert.deepEqual(Object.keys(snapshot).sort(), ['five_hour', 'seven_day', 'updated_at']);
assert.deepEqual(Object.keys(snapshot.five_hour).sort(), ['resets_at', 'used_percentage']);
assert.deepEqual(Object.keys(snapshot.seven_day).sort(), ['resets_at', 'used_percentage']);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test('writeExternalUsageSnapshot output can be read by getUsageFromExternalSnapshot', async () => {
const dir = await mkdtemp(path.join(tmpdir(), 'claude-hud-external-usage-write-'));
const filePath = path.join(dir, 'usage.json');
const now = Date.UTC(2026, 3, 20, 12, 0, 0);
try {
writeExternalUsageSnapshot(makeWriteConfig(filePath), makeUsage(), now);
const usage = getUsageFromExternalSnapshot(makeConfig(filePath), now + 1000);
assert.deepEqual(usage, makeUsage());
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test('getUsageFromExternalSnapshot parses optional balance labels', async () => {
const updatedAt = Date.UTC(2026, 3, 20, 12, 0, 0);
const { filePath, cleanup } = await withTempFile(JSON.stringify({
updated_at: new Date(updatedAt).toISOString(),
balance_label: ' ¥6.35 ',
}));
try {
const usage = getUsageFromExternalSnapshot(makeConfig(filePath), updatedAt + 60_000);
assert.deepEqual(usage, {
fiveHour: null,
sevenDay: null,
fiveHourResetAt: null,
sevenDayResetAt: null,
balanceLabel: '¥6.35',
});
} finally {
await cleanup();
}
});
test('getUsageFromExternalSnapshot sanitizes balance labels before rendering', async () => {
const updatedAt = Date.UTC(2026, 3, 20, 12, 0, 0);
const { filePath, cleanup } = await withTempFile(JSON.stringify({
updated_at: new Date(updatedAt).toISOString(),
five_hour: { used_percentage: 42 },
balance_label: '\u001b]8;;https://evil.example\u0007click\u001b]8;;\u0007\u202E',
}));
try {
const usage = getUsageFromExternalSnapshot(makeConfig(filePath), updatedAt + 60_000);
assert.equal(usage?.balanceLabel, 'click');
} finally {
await cleanup();
}
});
test('getUsageFromExternalSnapshot ignores stale snapshots', async () => {
const updatedAt = Date.UTC(2026, 3, 20, 12, 0, 0);
const { filePath, cleanup } = await withTempFile(JSON.stringify({
updated_at: new Date(updatedAt).toISOString(),
five_hour: { used_percentage: 42, resets_at: '2026-04-20T15:00:00.000Z' },
}));
try {
const usage = getUsageFromExternalSnapshot(makeConfig(filePath, 1000), updatedAt + 1001);
assert.equal(usage, null);
} finally {
await cleanup();
}
});
test('getUsageFromExternalSnapshot rejects invalid schema data', async () => {
const updatedAt = Date.UTC(2026, 3, 20, 12, 0, 0);
const { filePath, cleanup } = await withTempFile(JSON.stringify({
updated_at: new Date(updatedAt).toISOString(),
five_hour: { used_percentage: 42, resets_at: 'not-a-date' },
}));
try {
const usage = getUsageFromExternalSnapshot(makeConfig(filePath), updatedAt + 1);
assert.equal(usage, null);
} finally {
await cleanup();
}
});
test('getUsageFromExternalSnapshot returns null for invalid JSON', async () => {
const { filePath, cleanup } = await withTempFile('{');
try {
const usage = getUsageFromExternalSnapshot(makeConfig(filePath), Date.now());
assert.equal(usage, null);
} finally {
await cleanup();
}
});