mirror of
https://gitee.com/pnoker/iot-dc3.git
synced 2026-10-31 10:11:49 +08:00
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.
66 lines
2.1 KiB
TypeScript
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();
|
|
});
|
|
});
|