🛠️ Add default header management methods to API client

This commit is contained in:
2026-01-28 22:48:06 +00:00
parent 56578917e8
commit e51d484083
2 changed files with 164 additions and 1 deletions

View File

@@ -191,7 +191,16 @@ export function createApiClient(config = {}) {
body: JSON.stringify(data)
}),
delete: (url, options = {}) =>
request(url, { ...options, method: 'DELETE' })
request(url, { ...options, method: 'DELETE' }),
// Header management
setDefaultHeader: (name, value) => {
defaultHeaders[name] = value;
},
removeDefaultHeader: (name) => {
delete defaultHeaders[name];
},
getDefaultHeaders: () => ({ ...defaultHeaders })
};
}

View File

@@ -0,0 +1,154 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import {
createToken,
verifyToken,
decodeToken,
isTokenExpired,
getTokenExpiresIn
} from '../../server/utils/jwt-utils.js';
describe('JWT Utilities', () => {
const testSecret = 'test-secret-key';
let token;
beforeEach(() => {
// Create a test token
token = createToken(
{
userId: '123',
isAdmin: true,
permissions: ['admin']
},
testSecret,
3600 // 1 hour
);
});
describe('createToken', () => {
it('creates a valid token', () => {
expect(token).toBeDefined();
expect(typeof token).toBe('string');
expect(token.split('.')).toHaveLength(3);
});
it('includes payload data', () => {
const decoded = decodeToken(token);
expect(decoded.userId).toBe('123');
expect(decoded.isAdmin).toBe(true);
expect(decoded.permissions).toContain('admin');
});
it('includes timestamps', () => {
const decoded = decodeToken(token);
expect(decoded.iat).toBeDefined();
expect(decoded.exp).toBeDefined();
expect(decoded.exp).toBeGreaterThan(decoded.iat);
});
it('respects custom expiration time', () => {
const shortToken = createToken(
{ userId: '123' },
testSecret,
60 // 1 minute
);
const longToken = createToken(
{ userId: '123' },
testSecret,
7200 // 2 hours
);
const shortDecoded = decodeToken(shortToken);
const longDecoded = decodeToken(longToken);
expect(longDecoded.exp - longDecoded.iat).toBeGreaterThan(
shortDecoded.exp - shortDecoded.iat
);
});
});
describe('verifyToken', () => {
it('verifies a valid token', () => {
const decoded = verifyToken(token, testSecret);
expect(decoded.userId).toBe('123');
expect(decoded.isAdmin).toBe(true);
});
it('throws on invalid secret', () => {
expect(() => {
verifyToken(token, 'wrong-secret');
}).toThrow();
});
it('throws on malformed token', () => {
expect(() => {
verifyToken('not.a.token', testSecret);
}).toThrow();
});
it('throws on expired token', () => {
// Create an already expired token
const expiredToken = createToken(
{ userId: '123' },
testSecret,
-1 // Already expired
);
expect(() => {
verifyToken(expiredToken, testSecret);
}).toThrow();
});
});
describe('decodeToken', () => {
it('decodes token without verification', () => {
const decoded = decodeToken(token);
expect(decoded.userId).toBe('123');
expect(decoded.isAdmin).toBe(true);
});
it('returns null for invalid token', () => {
const result = decodeToken('invalid');
expect(result).toBeNull();
});
});
describe('isTokenExpired', () => {
it('returns false for valid token', () => {
expect(isTokenExpired(token)).toBe(false);
});
it('returns true for expired token', () => {
const expiredToken = createToken(
{ userId: '123' },
testSecret,
-1
);
expect(isTokenExpired(expiredToken)).toBe(true);
});
it('returns true for invalid token', () => {
expect(isTokenExpired('invalid')).toBe(true);
});
});
describe('getTokenExpiresIn', () => {
it('returns remaining time in milliseconds', () => {
const remaining = getTokenExpiresIn(token);
expect(remaining).toBeGreaterThan(0);
expect(remaining).toBeLessThanOrEqual(3600000); // 1 hour in ms
});
it('returns 0 for expired token', () => {
const expiredToken = createToken(
{ userId: '123' },
testSecret,
-1
);
expect(getTokenExpiresIn(expiredToken)).toBe(0);
});
it('returns 0 for invalid token', () => {
expect(getTokenExpiresIn('invalid')).toBe(0);
});
});
});