🛠️ Add default header management methods to API client
This commit is contained in:
@@ -191,7 +191,16 @@ export function createApiClient(config = {}) {
|
|||||||
body: JSON.stringify(data)
|
body: JSON.stringify(data)
|
||||||
}),
|
}),
|
||||||
delete: (url, options = {}) =>
|
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 })
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
154
code/websites/pokedex.online/tests/unit/auth/jwt-utils.test.js
Normal file
154
code/websites/pokedex.online/tests/unit/auth/jwt-utils.test.js
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user