144 lines
5.1 KiB
JavaScript
144 lines
5.1 KiB
JavaScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import { tmpdir } from 'node:os';
|
|
import { deriveAuthInfo, readAuthInfo, truncateUser, formatAuthSegment } from '../dist/auth.js';
|
|
|
|
const MAX_ACCOUNT = {
|
|
oauthAccount: {
|
|
emailAddress: 'someone.long@example.com',
|
|
displayName: 'Some One',
|
|
organizationType: 'claude_max',
|
|
organizationRateLimitTier: 'default_claude_max_20x',
|
|
},
|
|
};
|
|
|
|
function restoreEnvVar(name, value) {
|
|
if (value === undefined) {
|
|
delete process.env[name];
|
|
} else {
|
|
process.env[name] = value;
|
|
}
|
|
}
|
|
|
|
test('deriveAuthInfo formats claude_max with rate-limit tier', () => {
|
|
const info = deriveAuthInfo(MAX_ACCOUNT, {});
|
|
assert.equal(info.method, 'Claude Max 20x');
|
|
assert.equal(info.user, 'someone.long');
|
|
});
|
|
|
|
test('deriveAuthInfo formats claude_pro without tier', () => {
|
|
const info = deriveAuthInfo({
|
|
oauthAccount: {
|
|
emailAddress: 'a@b.com',
|
|
organizationType: 'claude_pro',
|
|
organizationRateLimitTier: 'default_claude_pro',
|
|
},
|
|
}, {});
|
|
assert.equal(info.method, 'Claude Pro');
|
|
assert.equal(info.user, 'a');
|
|
});
|
|
|
|
test('deriveAuthInfo falls back to displayName without email', () => {
|
|
const info = deriveAuthInfo({
|
|
oauthAccount: {
|
|
displayName: 'Some One',
|
|
organizationType: 'claude_enterprise',
|
|
},
|
|
}, {});
|
|
assert.equal(info.method, 'Claude Enterprise');
|
|
assert.equal(info.user, 'Some One');
|
|
});
|
|
|
|
test('deriveAuthInfo reports API Key when no oauth account but key exported', () => {
|
|
const info = deriveAuthInfo({}, { ANTHROPIC_API_KEY: 'sk-test' });
|
|
assert.equal(info.method, 'API Key');
|
|
assert.equal(info.user, null);
|
|
});
|
|
|
|
test('deriveAuthInfo gives API Key precedence over a stale oauth account', () => {
|
|
const info = deriveAuthInfo(MAX_ACCOUNT, { ANTHROPIC_API_KEY: 'sk-test' });
|
|
assert.deepEqual(info, { method: 'API Key', user: null });
|
|
});
|
|
|
|
test('deriveAuthInfo returns nulls for missing/invalid input', () => {
|
|
assert.deepEqual(deriveAuthInfo(null, {}), { method: null, user: null });
|
|
assert.deepEqual(deriveAuthInfo('junk', {}), { method: null, user: null });
|
|
assert.deepEqual(deriveAuthInfo({ oauthAccount: 42 }, {}), { method: null, user: null });
|
|
});
|
|
|
|
test('deriveAuthInfo strips ANSI sequences and control characters from values', () => {
|
|
const info = deriveAuthInfo({
|
|
oauthAccount: {
|
|
emailAddress: 'evil\x1b[31m@example.com',
|
|
organizationType: 'claude_max',
|
|
},
|
|
}, {});
|
|
assert.equal(info.user, 'evil');
|
|
});
|
|
|
|
test('readAuthInfo honors CLAUDE_CONFIG_DIR and handles unreadable profiles', async () => {
|
|
const tempDir = await mkdtemp(path.join(tmpdir(), 'claude-hud-auth-test-'));
|
|
const configDir = path.join(tempDir, 'profile');
|
|
const originalConfigDir = process.env.CLAUDE_CONFIG_DIR;
|
|
const originalApiKey = process.env.ANTHROPIC_API_KEY;
|
|
|
|
try {
|
|
delete process.env.ANTHROPIC_API_KEY;
|
|
process.env.CLAUDE_CONFIG_DIR = configDir;
|
|
|
|
assert.deepEqual(readAuthInfo(), { method: null, user: null });
|
|
|
|
await writeFile(`${configDir}.json`, JSON.stringify(MAX_ACCOUNT), 'utf8');
|
|
assert.deepEqual(readAuthInfo(), { method: 'Claude Max 20x', user: 'someone.long' });
|
|
|
|
await writeFile(`${configDir}.json`, '{invalid', 'utf8');
|
|
assert.deepEqual(readAuthInfo(), { method: null, user: null });
|
|
} finally {
|
|
restoreEnvVar('CLAUDE_CONFIG_DIR', originalConfigDir);
|
|
restoreEnvVar('ANTHROPIC_API_KEY', originalApiKey);
|
|
await rm(tempDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('readAuthInfo reports an API key without requiring an oauth profile', async () => {
|
|
const tempDir = await mkdtemp(path.join(tmpdir(), 'claude-hud-auth-key-test-'));
|
|
const originalConfigDir = process.env.CLAUDE_CONFIG_DIR;
|
|
const originalApiKey = process.env.ANTHROPIC_API_KEY;
|
|
|
|
try {
|
|
process.env.CLAUDE_CONFIG_DIR = path.join(tempDir, 'missing');
|
|
process.env.ANTHROPIC_API_KEY = 'sk-test';
|
|
assert.deepEqual(readAuthInfo(), { method: 'API Key', user: null });
|
|
} finally {
|
|
restoreEnvVar('CLAUDE_CONFIG_DIR', originalConfigDir);
|
|
restoreEnvVar('ANTHROPIC_API_KEY', originalApiKey);
|
|
await rm(tempDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('truncateUser truncates with ellipsis and honors 0 = full', () => {
|
|
assert.equal(truncateUser('yukinoshita.reimu', 8), 'yukinosh…');
|
|
assert.equal(truncateUser('short', 8), 'short');
|
|
assert.equal(truncateUser('yukinoshita.reimu', 0), 'yukinoshita.reimu');
|
|
});
|
|
|
|
test('formatAuthSegment joins method and truncated user', () => {
|
|
const info = deriveAuthInfo(MAX_ACCOUNT, {});
|
|
assert.equal(
|
|
formatAuthSegment(info, { showAuth: true, showAuthUser: true, authUserLength: 8 }),
|
|
'Claude Max 20x · someone.…',
|
|
);
|
|
assert.equal(
|
|
formatAuthSegment(info, { showAuth: true, showAuthUser: false }),
|
|
'Claude Max 20x',
|
|
);
|
|
assert.equal(
|
|
formatAuthSegment(info, { showAuth: false, showAuthUser: true, authUserLength: 0 }),
|
|
'someone.long',
|
|
);
|
|
assert.equal(formatAuthSegment(info, { showAuth: false, showAuthUser: false }), null);
|
|
assert.equal(formatAuthSegment(null, { showAuth: true, showAuthUser: true }), null);
|
|
});
|