Add unit tests for models and services

- 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.
This commit is contained in:
2025-11-28 15:27:04 +00:00
parent 61324ba195
commit 44e92d48f2
50 changed files with 4403 additions and 108 deletions

View File

@@ -0,0 +1,232 @@
/**
* 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<ApiToken> = {}): Promise<ApiToken> {
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);
});
});
});