- Implemented unit tests for the Package model, covering methods such as generateId, findById, findByName, and version management. - Created unit tests for the Repository model, including repository creation, name validation, and retrieval methods. - Added tests for the Session model, focusing on session creation, validation, and invalidation. - Developed unit tests for the User model, ensuring user creation, password hashing, and retrieval methods function correctly. - Implemented AuthService tests, validating login, token refresh, and session management. - Added TokenService tests, covering token creation, validation, and revocation processes.
261 lines
7.9 KiB
TypeScript
261 lines
7.9 KiB
TypeScript
/**
|
|
* TokenService unit tests
|
|
*/
|
|
|
|
import { assertEquals, assertExists, assertMatch } from 'jsr:@std/assert';
|
|
import { describe, it, beforeAll, afterAll, beforeEach } from 'jsr:@std/testing/bdd';
|
|
import { setupTestDb, teardownTestDb, cleanupTestDb, createTestUser } from '../../helpers/index.ts';
|
|
import { TokenService } from '../../../ts/services/token.service.ts';
|
|
import { ApiToken } from '../../../ts/models/apitoken.ts';
|
|
|
|
describe('TokenService', () => {
|
|
let tokenService: TokenService;
|
|
let testUserId: string;
|
|
|
|
beforeAll(async () => {
|
|
await setupTestDb();
|
|
tokenService = new TokenService();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await teardownTestDb();
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
await cleanupTestDb();
|
|
const { user } = await createTestUser();
|
|
testUserId = user.id;
|
|
});
|
|
|
|
describe('createToken', () => {
|
|
it('should create token with correct format', async () => {
|
|
const result = await tokenService.createToken({
|
|
userId: testUserId,
|
|
name: 'test-token',
|
|
protocols: ['npm', 'oci'],
|
|
scopes: [{ protocol: '*', actions: ['read', 'write'] }],
|
|
});
|
|
|
|
assertExists(result.rawToken);
|
|
assertExists(result.token);
|
|
|
|
// Check token format: srg_{prefix}_{random}
|
|
assertMatch(result.rawToken, /^srg_[a-z0-9]+_[a-z0-9]+$/);
|
|
assertEquals(result.token.name, 'test-token');
|
|
assertEquals(result.token.protocols.includes('npm'), true);
|
|
assertEquals(result.token.protocols.includes('oci'), true);
|
|
});
|
|
|
|
it('should store hashed token', async () => {
|
|
const result = await tokenService.createToken({
|
|
userId: testUserId,
|
|
name: 'hashed-token',
|
|
protocols: ['npm'],
|
|
scopes: [{ protocol: 'npm', actions: ['read'] }],
|
|
});
|
|
|
|
// The stored token should be hashed
|
|
assertEquals(result.token.tokenHash !== result.rawToken, true);
|
|
assertEquals(result.token.tokenHash.length, 64); // SHA-256 hex
|
|
});
|
|
|
|
it('should set expiration when provided', async () => {
|
|
const result = await tokenService.createToken({
|
|
userId: testUserId,
|
|
name: 'expiring-token',
|
|
protocols: ['npm'],
|
|
scopes: [{ protocol: 'npm', actions: ['read'] }],
|
|
expiresInDays: 30,
|
|
});
|
|
|
|
assertExists(result.token.expiresAt);
|
|
|
|
const expectedExpiry = new Date();
|
|
expectedExpiry.setDate(expectedExpiry.getDate() + 30);
|
|
|
|
// Should be within a few seconds of expected
|
|
const diff = Math.abs(result.token.expiresAt.getTime() - expectedExpiry.getTime());
|
|
assertEquals(diff < 5000, true);
|
|
});
|
|
|
|
it('should create org-owned token', async () => {
|
|
const orgId = 'test-org-123';
|
|
const result = await tokenService.createToken({
|
|
userId: testUserId,
|
|
organizationId: orgId,
|
|
name: 'org-token',
|
|
protocols: ['npm'],
|
|
scopes: [{ protocol: 'npm', organizationId: orgId, actions: ['read', 'write'] }],
|
|
});
|
|
|
|
assertEquals(result.token.organizationId, orgId);
|
|
});
|
|
});
|
|
|
|
describe('validateToken', () => {
|
|
it('should validate correct token', async () => {
|
|
const { rawToken } = await tokenService.createToken({
|
|
userId: testUserId,
|
|
name: 'valid-token',
|
|
protocols: ['npm'],
|
|
scopes: [{ protocol: 'npm', actions: ['read'] }],
|
|
});
|
|
|
|
const validation = await tokenService.validateToken(rawToken, '127.0.0.1');
|
|
|
|
assertExists(validation);
|
|
assertEquals(validation.userId, testUserId);
|
|
assertEquals(validation.protocols.includes('npm'), true);
|
|
});
|
|
|
|
it('should reject invalid token format', async () => {
|
|
const validation = await tokenService.validateToken('invalid-format', '127.0.0.1');
|
|
|
|
assertEquals(validation, null);
|
|
});
|
|
|
|
it('should reject non-existent token', async () => {
|
|
const validation = await tokenService.validateToken('srg_abc123_def456', '127.0.0.1');
|
|
|
|
assertEquals(validation, null);
|
|
});
|
|
|
|
it('should reject revoked token', async () => {
|
|
const { rawToken, token } = await tokenService.createToken({
|
|
userId: testUserId,
|
|
name: 'revoked-token',
|
|
protocols: ['npm'],
|
|
scopes: [{ protocol: 'npm', actions: ['read'] }],
|
|
});
|
|
|
|
await token.revoke('Test revocation');
|
|
|
|
const validation = await tokenService.validateToken(rawToken, '127.0.0.1');
|
|
|
|
assertEquals(validation, null);
|
|
});
|
|
|
|
it('should reject expired token', async () => {
|
|
const { rawToken, token } = await tokenService.createToken({
|
|
userId: testUserId,
|
|
name: 'expired-token',
|
|
protocols: ['npm'],
|
|
scopes: [{ protocol: 'npm', actions: ['read'] }],
|
|
expiresInDays: 1,
|
|
});
|
|
|
|
// Manually set expiry to past
|
|
token.expiresAt = new Date(Date.now() - 86400000);
|
|
await token.save();
|
|
|
|
const validation = await tokenService.validateToken(rawToken, '127.0.0.1');
|
|
|
|
assertEquals(validation, null);
|
|
});
|
|
|
|
it('should record usage on validation', async () => {
|
|
const { rawToken, token } = await tokenService.createToken({
|
|
userId: testUserId,
|
|
name: 'usage-token',
|
|
protocols: ['npm'],
|
|
scopes: [{ protocol: 'npm', actions: ['read'] }],
|
|
});
|
|
|
|
await tokenService.validateToken(rawToken, '192.168.1.100');
|
|
|
|
// Reload token from DB
|
|
const updated = await ApiToken.findByHash(token.tokenHash);
|
|
assertExists(updated);
|
|
assertExists(updated.lastUsedAt);
|
|
assertEquals(updated.lastUsedIp, '192.168.1.100');
|
|
assertEquals(updated.usageCount, 1);
|
|
});
|
|
});
|
|
|
|
describe('getUserTokens', () => {
|
|
it('should return all user tokens', async () => {
|
|
await tokenService.createToken({
|
|
userId: testUserId,
|
|
name: 'token1',
|
|
protocols: ['npm'],
|
|
scopes: [{ protocol: 'npm', actions: ['read'] }],
|
|
});
|
|
await tokenService.createToken({
|
|
userId: testUserId,
|
|
name: 'token2',
|
|
protocols: ['oci'],
|
|
scopes: [{ protocol: 'oci', actions: ['read'] }],
|
|
});
|
|
|
|
const tokens = await tokenService.getUserTokens(testUserId);
|
|
|
|
assertEquals(tokens.length, 2);
|
|
});
|
|
|
|
it('should not return revoked tokens', async () => {
|
|
const { token } = await tokenService.createToken({
|
|
userId: testUserId,
|
|
name: 'revoked',
|
|
protocols: ['npm'],
|
|
scopes: [{ protocol: 'npm', actions: ['read'] }],
|
|
});
|
|
await tokenService.createToken({
|
|
userId: testUserId,
|
|
name: 'active',
|
|
protocols: ['npm'],
|
|
scopes: [{ protocol: 'npm', actions: ['read'] }],
|
|
});
|
|
|
|
await token.revoke('test');
|
|
|
|
const tokens = await tokenService.getUserTokens(testUserId);
|
|
|
|
assertEquals(tokens.length, 1);
|
|
assertEquals(tokens[0].name, 'active');
|
|
});
|
|
});
|
|
|
|
describe('revokeToken', () => {
|
|
it('should revoke token with reason', async () => {
|
|
const { token } = await tokenService.createToken({
|
|
userId: testUserId,
|
|
name: 'to-revoke',
|
|
protocols: ['npm'],
|
|
scopes: [{ protocol: 'npm', actions: ['read'] }],
|
|
});
|
|
|
|
await tokenService.revokeToken(token.id, 'Security concern');
|
|
|
|
const updated = await ApiToken.findByPrefix(token.tokenPrefix);
|
|
assertExists(updated);
|
|
assertEquals(updated.isRevoked, true);
|
|
assertEquals(updated.revokedReason, 'Security concern');
|
|
});
|
|
});
|
|
|
|
describe('getOrgTokens', () => {
|
|
it('should return organization tokens', async () => {
|
|
const orgId = 'org-123';
|
|
|
|
await tokenService.createToken({
|
|
userId: testUserId,
|
|
organizationId: orgId,
|
|
name: 'org-token',
|
|
protocols: ['npm'],
|
|
scopes: [{ protocol: 'npm', actions: ['read'] }],
|
|
});
|
|
await tokenService.createToken({
|
|
userId: testUserId,
|
|
name: 'personal-token',
|
|
protocols: ['npm'],
|
|
scopes: [{ protocol: 'npm', actions: ['read'] }],
|
|
});
|
|
|
|
const tokens = await tokenService.getOrgTokens(orgId);
|
|
|
|
assertEquals(tokens.length, 1);
|
|
assertEquals(tokens[0].organizationId, orgId);
|
|
});
|
|
});
|
|
});
|