Files
pnoker 2b9c188b85 fix(cli): enforce the output contract, exit codes, and timeouts
All transport goes through one shared http seam with a 30s abort timeout and network or auth error mapping, add replaces create as the verb with a one-release alias, login and logout stop swallowing auth and network failures, the global --profile and --format options are actually honored together with the persisted output_format setting, config set validates numeric input instead of poisoning the schema and a corrupt config now warns, backs itself up and never silently reverts to defaults, paging and url parameters are validated and encoded, and the dead crypto helper is gone. The contract is pinned by a dedicated test suite.
2026-10-03 13:44:00 +08:00

66 lines
2.1 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { decodeJwt, isTokenExpired, tokenTtl } from '../src/utils/jwt';
describe('jwt utils', () => {
// A valid-looking JWT with known payload
const futureJwt = [
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9', // header
Buffer.from(JSON.stringify({
iss: 'test:123',
sub: 'test:user',
iat: Math.floor(Date.now() / 1000) - 3600,
exp: Math.floor(Date.now() / 1000) + 3600 * 12, // 12h from now
})).toString('base64url'),
'signature',
].join('.');
const expiredJwt = [
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9',
Buffer.from(JSON.stringify({
iss: 'test:123',
sub: 'test:user',
iat: Math.floor(Date.now() / 1000) - 3600 * 24,
exp: Math.floor(Date.now() / 1000) - 3600, // 1h ago
})).toString('base64url'),
'signature',
].join('.');
it('decodeJwt should parse payload', () => {
const payload = decodeJwt(futureJwt);
expect(payload.iss).toBe('test:123');
expect(payload.sub).toBe('test:user');
expect(payload.exp).toBeGreaterThan(Date.now() / 1000);
});
it('isTokenExpired should return false for future token', () => {
expect(isTokenExpired(futureJwt)).toBe(false);
});
it('isTokenExpired should return true for expired token', () => {
expect(isTokenExpired(expiredJwt)).toBe(true);
});
it('isTokenExpired with threshold', () => {
// Future token should NOT be expired with a reasonable threshold
expect(isTokenExpired(futureJwt, 3600)).toBe(false);
// But should be "expired" if threshold exceeds remaining time
expect(isTokenExpired(futureJwt, 3600 * 24)).toBe(true);
});
it('tokenTtl should return positive for future token', () => {
const ttl = tokenTtl(futureJwt);
expect(ttl).toBeGreaterThan(0);
expect(ttl).toBeLessThan(3600 * 13); // less than 13h
});
it('tokenTtl should return negative for expired token', () => {
const ttl = tokenTtl(expiredJwt);
expect(ttl).toBeLessThan(0);
});
it('decodeJwt should throw for malformed JWT', () => {
expect(() => decodeJwt('not-a-jwt')).toThrow();
expect(() => decodeJwt('a.b')).toThrow();
});
});