/** * ApiToken model unit tests */ import { assertEquals, assertExists } 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 { ApiToken } from '../../../ts/models/apitoken.ts'; describe('ApiToken Model', () => { let testUserId: string; beforeAll(async () => { await setupTestDb(); }); afterAll(async () => { await teardownTestDb(); }); beforeEach(async () => { await cleanupTestDb(); const { user } = await createTestUser(); testUserId = user.id; }); async function createToken(overrides: Partial = {}): Promise { const token = new ApiToken(); token.id = await ApiToken.getNewId(); token.userId = overrides.userId || testUserId; token.name = overrides.name || 'test-token'; token.tokenHash = overrides.tokenHash || `hash-${crypto.randomUUID()}`; token.tokenPrefix = overrides.tokenPrefix || 'srg_test'; token.protocols = overrides.protocols || ['npm', 'oci']; token.scopes = overrides.scopes || [{ protocol: '*', actions: ['read', 'write'] }]; token.createdAt = new Date(); if (overrides.expiresAt) token.expiresAt = overrides.expiresAt; if (overrides.isRevoked) token.isRevoked = overrides.isRevoked; if (overrides.organizationId) token.organizationId = overrides.organizationId; await token.save(); return token; } describe('findByHash', () => { it('should find token by hash', async () => { const created = await createToken({ tokenHash: 'unique-hash-123' }); const found = await ApiToken.findByHash('unique-hash-123'); assertExists(found); assertEquals(found.id, created.id); }); it('should not find revoked tokens', async () => { await createToken({ tokenHash: 'revoked-hash', isRevoked: true, }); const found = await ApiToken.findByHash('revoked-hash'); assertEquals(found, null); }); }); describe('getUserTokens', () => { it('should return all user tokens', async () => { await createToken({ name: 'token1' }); await createToken({ name: 'token2' }); const tokens = await ApiToken.getUserTokens(testUserId); assertEquals(tokens.length, 2); }); it('should not return revoked tokens', async () => { await createToken({ name: 'active' }); await createToken({ name: 'revoked', isRevoked: true }); const tokens = await ApiToken.getUserTokens(testUserId); assertEquals(tokens.length, 1); assertEquals(tokens[0].name, 'active'); }); }); describe('getOrgTokens', () => { it('should return organization tokens', async () => { const orgId = 'org-123'; await createToken({ name: 'org-token', organizationId: orgId }); await createToken({ name: 'personal-token' }); // No org const tokens = await ApiToken.getOrgTokens(orgId); assertEquals(tokens.length, 1); assertEquals(tokens[0].name, 'org-token'); }); }); describe('isValid', () => { it('should return true for valid token', async () => { const token = await createToken(); assertEquals(token.isValid(), true); }); it('should return false for revoked token', async () => { const token = await createToken({ isRevoked: true }); assertEquals(token.isValid(), false); }); it('should return false for expired token', async () => { const pastDate = new Date(); pastDate.setDate(pastDate.getDate() - 1); const token = await createToken({ expiresAt: pastDate }); assertEquals(token.isValid(), false); }); it('should return true for non-expired token', async () => { const futureDate = new Date(); futureDate.setDate(futureDate.getDate() + 30); const token = await createToken({ expiresAt: futureDate }); assertEquals(token.isValid(), true); }); }); describe('recordUsage', () => { it('should update usage stats', async () => { const token = await createToken(); await token.recordUsage('192.168.1.1'); assertExists(token.lastUsedAt); assertEquals(token.lastUsedIp, '192.168.1.1'); assertEquals(token.usageCount, 1); }); it('should increment usage count', async () => { const token = await createToken(); await token.recordUsage(); await token.recordUsage(); await token.recordUsage(); assertEquals(token.usageCount, 3); }); }); describe('revoke', () => { it('should revoke token with reason', async () => { const token = await createToken(); await token.revoke('Security concern'); assertEquals(token.isRevoked, true); assertExists(token.revokedAt); assertEquals(token.revokedReason, 'Security concern'); }); it('should revoke token without reason', async () => { const token = await createToken(); await token.revoke(); assertEquals(token.isRevoked, true); assertExists(token.revokedAt); assertEquals(token.revokedReason, undefined); }); }); describe('hasProtocol', () => { it('should return true for allowed protocol', async () => { const token = await createToken({ protocols: ['npm', 'oci'] }); assertEquals(token.hasProtocol('npm'), true); assertEquals(token.hasProtocol('oci'), true); }); it('should return false for disallowed protocol', async () => { const token = await createToken({ protocols: ['npm'] }); assertEquals(token.hasProtocol('maven'), false); }); }); describe('hasScope', () => { it('should allow wildcard protocol scope', async () => { const token = await createToken({ scopes: [{ protocol: '*', actions: ['read', 'write'] }], }); assertEquals(token.hasScope('npm'), true); assertEquals(token.hasScope('oci'), true); assertEquals(token.hasScope('maven'), true); }); it('should restrict by specific protocol', async () => { const token = await createToken({ scopes: [{ protocol: 'npm', actions: ['read'] }], }); assertEquals(token.hasScope('npm'), true); assertEquals(token.hasScope('oci'), false); }); it('should restrict by organization', async () => { const token = await createToken({ scopes: [{ protocol: '*', organizationId: 'org-123', actions: ['read'] }], }); assertEquals(token.hasScope('npm', 'org-123'), true); assertEquals(token.hasScope('npm', 'org-456'), false); }); it('should check action permissions', async () => { const token = await createToken({ scopes: [{ protocol: '*', actions: ['read'] }], }); assertEquals(token.hasScope('npm', undefined, undefined, 'read'), true); assertEquals(token.hasScope('npm', undefined, undefined, 'write'), false); }); it('should allow wildcard action', async () => { const token = await createToken({ scopes: [{ protocol: '*', actions: ['*'] }], }); assertEquals(token.hasScope('npm', undefined, undefined, 'read'), true); assertEquals(token.hasScope('npm', undefined, undefined, 'write'), true); assertEquals(token.hasScope('npm', undefined, undefined, 'delete'), true); }); }); });