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:
232
test/unit/models/apitoken.test.ts
Normal file
232
test/unit/models/apitoken.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
220
test/unit/models/organization.test.ts
Normal file
220
test/unit/models/organization.test.ts
Normal file
@@ -0,0 +1,220 @@
|
||||
/**
|
||||
* Organization model unit tests
|
||||
*/
|
||||
|
||||
import { assertEquals, assertExists, assertRejects } 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 { Organization } from '../../../ts/models/organization.ts';
|
||||
|
||||
describe('Organization Model', () => {
|
||||
let testUserId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
await setupTestDb();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await teardownTestDb();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await cleanupTestDb();
|
||||
const { user } = await createTestUser();
|
||||
testUserId = user.id;
|
||||
});
|
||||
|
||||
describe('createOrganization', () => {
|
||||
it('should create an organization with valid data', async () => {
|
||||
const org = await Organization.createOrganization({
|
||||
name: 'test-org',
|
||||
displayName: 'Test Organization',
|
||||
description: 'A test organization',
|
||||
createdById: testUserId,
|
||||
});
|
||||
|
||||
assertExists(org.id);
|
||||
assertEquals(org.name, 'test-org');
|
||||
assertEquals(org.displayName, 'Test Organization');
|
||||
assertEquals(org.description, 'A test organization');
|
||||
assertEquals(org.createdById, testUserId);
|
||||
assertEquals(org.isPublic, false);
|
||||
assertEquals(org.memberCount, 0);
|
||||
assertEquals(org.plan, 'free');
|
||||
});
|
||||
|
||||
it('should allow dots in org name (domain-like)', async () => {
|
||||
const org = await Organization.createOrganization({
|
||||
name: 'push.rocks',
|
||||
displayName: 'Push Rocks',
|
||||
createdById: testUserId,
|
||||
});
|
||||
|
||||
assertEquals(org.name, 'push.rocks');
|
||||
});
|
||||
|
||||
it('should allow hyphens in org name', async () => {
|
||||
const org = await Organization.createOrganization({
|
||||
name: 'my-awesome-org',
|
||||
displayName: 'My Awesome Org',
|
||||
createdById: testUserId,
|
||||
});
|
||||
|
||||
assertEquals(org.name, 'my-awesome-org');
|
||||
});
|
||||
|
||||
it('should reject uppercase names (must be lowercase)', async () => {
|
||||
await assertRejects(
|
||||
async () => {
|
||||
await Organization.createOrganization({
|
||||
name: 'UPPERCASE',
|
||||
displayName: 'Uppercase Org',
|
||||
createdById: testUserId,
|
||||
});
|
||||
},
|
||||
Error,
|
||||
'lowercase alphanumeric'
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject invalid names starting with dot', async () => {
|
||||
await assertRejects(
|
||||
async () => {
|
||||
await Organization.createOrganization({
|
||||
name: '.invalid',
|
||||
displayName: 'Invalid',
|
||||
createdById: testUserId,
|
||||
});
|
||||
},
|
||||
Error,
|
||||
'lowercase alphanumeric'
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject invalid names ending with dot', async () => {
|
||||
await assertRejects(
|
||||
async () => {
|
||||
await Organization.createOrganization({
|
||||
name: 'invalid.',
|
||||
displayName: 'Invalid',
|
||||
createdById: testUserId,
|
||||
});
|
||||
},
|
||||
Error,
|
||||
'lowercase alphanumeric'
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject names with special characters', async () => {
|
||||
await assertRejects(
|
||||
async () => {
|
||||
await Organization.createOrganization({
|
||||
name: 'invalid@org',
|
||||
displayName: 'Invalid',
|
||||
createdById: testUserId,
|
||||
});
|
||||
},
|
||||
Error,
|
||||
'lowercase alphanumeric'
|
||||
);
|
||||
});
|
||||
|
||||
it('should set default settings', async () => {
|
||||
const org = await Organization.createOrganization({
|
||||
name: 'defaults',
|
||||
displayName: 'Defaults Test',
|
||||
createdById: testUserId,
|
||||
});
|
||||
|
||||
assertEquals(org.settings.requireMfa, false);
|
||||
assertEquals(org.settings.allowPublicRepositories, true);
|
||||
assertEquals(org.settings.defaultRepositoryVisibility, 'private');
|
||||
assertEquals(org.settings.allowedProtocols.length, 7);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should find organization by ID', async () => {
|
||||
const created = await Organization.createOrganization({
|
||||
name: 'findable',
|
||||
displayName: 'Findable Org',
|
||||
createdById: testUserId,
|
||||
});
|
||||
|
||||
const found = await Organization.findById(created.id);
|
||||
assertExists(found);
|
||||
assertEquals(found.id, created.id);
|
||||
});
|
||||
|
||||
it('should return null for non-existent ID', async () => {
|
||||
const found = await Organization.findById('non-existent-id');
|
||||
assertEquals(found, null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByName', () => {
|
||||
it('should find organization by name (case-insensitive)', async () => {
|
||||
await Organization.createOrganization({
|
||||
name: 'byname',
|
||||
displayName: 'By Name',
|
||||
createdById: testUserId,
|
||||
});
|
||||
|
||||
const found = await Organization.findByName('BYNAME');
|
||||
assertExists(found);
|
||||
assertEquals(found.name, 'byname');
|
||||
});
|
||||
});
|
||||
|
||||
describe('storage quota', () => {
|
||||
it('should have default 5GB quota', async () => {
|
||||
const org = await Organization.createOrganization({
|
||||
name: 'quota-test',
|
||||
displayName: 'Quota Test',
|
||||
createdById: testUserId,
|
||||
});
|
||||
|
||||
assertEquals(org.storageQuotaBytes, 5 * 1024 * 1024 * 1024);
|
||||
assertEquals(org.usedStorageBytes, 0);
|
||||
});
|
||||
|
||||
it('should check available storage', async () => {
|
||||
const org = await Organization.createOrganization({
|
||||
name: 'storage-check',
|
||||
displayName: 'Storage Check',
|
||||
createdById: testUserId,
|
||||
});
|
||||
|
||||
assertEquals(org.hasStorageAvailable(1024), true);
|
||||
assertEquals(org.hasStorageAvailable(6 * 1024 * 1024 * 1024), false);
|
||||
});
|
||||
|
||||
it('should allow unlimited storage with -1 quota', async () => {
|
||||
const org = await Organization.createOrganization({
|
||||
name: 'unlimited',
|
||||
displayName: 'Unlimited',
|
||||
createdById: testUserId,
|
||||
});
|
||||
org.storageQuotaBytes = -1;
|
||||
|
||||
assertEquals(org.hasStorageAvailable(1000 * 1024 * 1024 * 1024), true);
|
||||
});
|
||||
|
||||
it('should update storage usage', async () => {
|
||||
const org = await Organization.createOrganization({
|
||||
name: 'usage-test',
|
||||
displayName: 'Usage Test',
|
||||
createdById: testUserId,
|
||||
});
|
||||
|
||||
await org.updateStorageUsage(1000);
|
||||
assertEquals(org.usedStorageBytes, 1000);
|
||||
|
||||
await org.updateStorageUsage(500);
|
||||
assertEquals(org.usedStorageBytes, 1500);
|
||||
|
||||
await org.updateStorageUsage(-2000);
|
||||
assertEquals(org.usedStorageBytes, 0); // Should not go negative
|
||||
});
|
||||
});
|
||||
});
|
||||
240
test/unit/models/package.test.ts
Normal file
240
test/unit/models/package.test.ts
Normal file
@@ -0,0 +1,240 @@
|
||||
/**
|
||||
* Package 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,
|
||||
createOrgWithOwner,
|
||||
createTestRepository,
|
||||
} from '../../helpers/index.ts';
|
||||
import { Package } from '../../../ts/models/package.ts';
|
||||
import type { IPackageVersion } from '../../../ts/interfaces/package.interfaces.ts';
|
||||
|
||||
describe('Package Model', () => {
|
||||
let testUserId: string;
|
||||
let testOrgId: string;
|
||||
let testRepoId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
await setupTestDb();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await teardownTestDb();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await cleanupTestDb();
|
||||
const { user } = await createTestUser();
|
||||
testUserId = user.id;
|
||||
const { organization } = await createOrgWithOwner(testUserId);
|
||||
testOrgId = organization.id;
|
||||
const repo = await createTestRepository({
|
||||
organizationId: testOrgId,
|
||||
createdById: testUserId,
|
||||
protocol: 'npm',
|
||||
});
|
||||
testRepoId = repo.id;
|
||||
});
|
||||
|
||||
function createVersion(version: string): IPackageVersion {
|
||||
return {
|
||||
version,
|
||||
publishedAt: new Date(),
|
||||
publishedBy: testUserId,
|
||||
size: 1024,
|
||||
checksum: `sha256-${crypto.randomUUID()}`,
|
||||
checksumAlgorithm: 'sha256',
|
||||
downloads: 0,
|
||||
metadata: {},
|
||||
};
|
||||
}
|
||||
|
||||
async function createPackage(name: string, versions: string[] = ['1.0.0']): Promise<Package> {
|
||||
const pkg = new Package();
|
||||
pkg.id = Package.generateId('npm', testOrgId, name);
|
||||
pkg.organizationId = testOrgId;
|
||||
pkg.repositoryId = testRepoId;
|
||||
pkg.protocol = 'npm';
|
||||
pkg.name = name;
|
||||
pkg.createdById = testUserId;
|
||||
pkg.createdAt = new Date();
|
||||
pkg.updatedAt = new Date();
|
||||
|
||||
for (const v of versions) {
|
||||
pkg.addVersion(createVersion(v));
|
||||
}
|
||||
pkg.distTags['latest'] = versions[versions.length - 1];
|
||||
|
||||
await pkg.save();
|
||||
return pkg;
|
||||
}
|
||||
|
||||
describe('generateId', () => {
|
||||
it('should generate correct format', () => {
|
||||
const id = Package.generateId('npm', 'my-org', 'my-package');
|
||||
assertEquals(id, 'npm:my-org:my-package');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should find package by ID', async () => {
|
||||
const created = await createPackage('findable');
|
||||
|
||||
const found = await Package.findById(created.id);
|
||||
assertExists(found);
|
||||
assertEquals(found.name, 'findable');
|
||||
});
|
||||
|
||||
it('should return null for non-existent ID', async () => {
|
||||
const found = await Package.findById('npm:fake:package');
|
||||
assertEquals(found, null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByName', () => {
|
||||
it('should find package by protocol, org, and name', async () => {
|
||||
await createPackage('by-name');
|
||||
|
||||
const found = await Package.findByName('npm', testOrgId, 'by-name');
|
||||
assertExists(found);
|
||||
assertEquals(found.name, 'by-name');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOrgPackages', () => {
|
||||
it('should return all packages in organization', async () => {
|
||||
await createPackage('pkg1');
|
||||
await createPackage('pkg2');
|
||||
await createPackage('pkg3');
|
||||
|
||||
const packages = await Package.getOrgPackages(testOrgId);
|
||||
assertEquals(packages.length, 3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('search', () => {
|
||||
it('should find packages by name', async () => {
|
||||
await createPackage('search-me');
|
||||
await createPackage('find-this');
|
||||
await createPackage('other');
|
||||
|
||||
const results = await Package.search('search');
|
||||
assertEquals(results.length, 1);
|
||||
assertEquals(results[0].name, 'search-me');
|
||||
});
|
||||
|
||||
it('should find packages by description', async () => {
|
||||
const pkg = await createPackage('described');
|
||||
pkg.description = 'A unique description for testing';
|
||||
await pkg.save();
|
||||
|
||||
const results = await Package.search('unique description');
|
||||
assertEquals(results.length, 1);
|
||||
});
|
||||
|
||||
it('should filter by protocol', async () => {
|
||||
await createPackage('npm-pkg');
|
||||
|
||||
const results = await Package.search('npm', { protocol: 'oci' });
|
||||
assertEquals(results.length, 0);
|
||||
});
|
||||
|
||||
it('should apply pagination', async () => {
|
||||
await createPackage('page1');
|
||||
await createPackage('page2');
|
||||
await createPackage('page3');
|
||||
|
||||
const firstPage = await Package.search('page', { limit: 2, offset: 0 });
|
||||
assertEquals(firstPage.length, 2);
|
||||
|
||||
const secondPage = await Package.search('page', { limit: 2, offset: 2 });
|
||||
assertEquals(secondPage.length, 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('versions', () => {
|
||||
it('should add version and update storage', async () => {
|
||||
const pkg = await createPackage('versioned', []);
|
||||
|
||||
pkg.addVersion(createVersion('1.0.0'));
|
||||
|
||||
assertEquals(Object.keys(pkg.versions).length, 1);
|
||||
assertEquals(pkg.storageBytes, 1024);
|
||||
});
|
||||
|
||||
it('should get specific version', async () => {
|
||||
const pkg = await createPackage('multi-version', ['1.0.0', '1.1.0', '2.0.0']);
|
||||
|
||||
const v1 = pkg.getVersion('1.0.0');
|
||||
assertExists(v1);
|
||||
assertEquals(v1.version, '1.0.0');
|
||||
|
||||
const v2 = pkg.getVersion('2.0.0');
|
||||
assertExists(v2);
|
||||
assertEquals(v2.version, '2.0.0');
|
||||
});
|
||||
|
||||
it('should return undefined for non-existent version', async () => {
|
||||
const pkg = await createPackage('single', ['1.0.0']);
|
||||
|
||||
const missing = pkg.getVersion('9.9.9');
|
||||
assertEquals(missing, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLatestVersion', () => {
|
||||
it('should return version from distTags.latest', async () => {
|
||||
const pkg = await createPackage('tagged', ['1.0.0', '2.0.0']);
|
||||
pkg.distTags['latest'] = '1.0.0'; // Set older version as latest
|
||||
await pkg.save();
|
||||
|
||||
const latest = pkg.getLatestVersion();
|
||||
assertExists(latest);
|
||||
assertEquals(latest.version, '1.0.0');
|
||||
});
|
||||
|
||||
it('should fallback to last version if no latest tag', async () => {
|
||||
const pkg = await createPackage('untagged', ['1.0.0', '2.0.0']);
|
||||
delete pkg.distTags['latest'];
|
||||
|
||||
const latest = pkg.getLatestVersion();
|
||||
assertExists(latest);
|
||||
assertEquals(latest.version, '2.0.0');
|
||||
});
|
||||
|
||||
it('should return undefined for empty versions', async () => {
|
||||
const pkg = await createPackage('empty', []);
|
||||
delete pkg.distTags['latest'];
|
||||
|
||||
const latest = pkg.getLatestVersion();
|
||||
assertEquals(latest, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('incrementDownloads', () => {
|
||||
it('should increment total download count', async () => {
|
||||
const pkg = await createPackage('downloads');
|
||||
|
||||
await pkg.incrementDownloads();
|
||||
assertEquals(pkg.downloadCount, 1);
|
||||
|
||||
await pkg.incrementDownloads();
|
||||
await pkg.incrementDownloads();
|
||||
assertEquals(pkg.downloadCount, 3);
|
||||
});
|
||||
|
||||
it('should increment version-specific downloads', async () => {
|
||||
const pkg = await createPackage('version-downloads', ['1.0.0', '2.0.0']);
|
||||
|
||||
await pkg.incrementDownloads('1.0.0');
|
||||
assertEquals(pkg.versions['1.0.0'].downloads, 1);
|
||||
assertEquals(pkg.versions['2.0.0'].downloads, 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
285
test/unit/models/repository.test.ts
Normal file
285
test/unit/models/repository.test.ts
Normal file
@@ -0,0 +1,285 @@
|
||||
/**
|
||||
* Repository model unit tests
|
||||
*/
|
||||
|
||||
import { assertEquals, assertExists, assertRejects } from 'jsr:@std/assert';
|
||||
import { describe, it, beforeAll, afterAll, beforeEach } from 'jsr:@std/testing/bdd';
|
||||
import {
|
||||
setupTestDb,
|
||||
teardownTestDb,
|
||||
cleanupTestDb,
|
||||
createTestUser,
|
||||
createOrgWithOwner,
|
||||
} from '../../helpers/index.ts';
|
||||
import { Repository } from '../../../ts/models/repository.ts';
|
||||
|
||||
describe('Repository Model', () => {
|
||||
let testUserId: string;
|
||||
let testOrgId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
await setupTestDb();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await teardownTestDb();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await cleanupTestDb();
|
||||
const { user } = await createTestUser();
|
||||
testUserId = user.id;
|
||||
const { organization } = await createOrgWithOwner(testUserId);
|
||||
testOrgId = organization.id;
|
||||
});
|
||||
|
||||
describe('createRepository', () => {
|
||||
it('should create a repository with valid data', async () => {
|
||||
const repo = await Repository.createRepository({
|
||||
organizationId: testOrgId,
|
||||
name: 'test-repo',
|
||||
description: 'A test repository',
|
||||
protocol: 'npm',
|
||||
createdById: testUserId,
|
||||
});
|
||||
|
||||
assertExists(repo.id);
|
||||
assertEquals(repo.name, 'test-repo');
|
||||
assertEquals(repo.organizationId, testOrgId);
|
||||
assertEquals(repo.protocol, 'npm');
|
||||
assertEquals(repo.visibility, 'private');
|
||||
assertEquals(repo.downloadCount, 0);
|
||||
assertEquals(repo.starCount, 0);
|
||||
});
|
||||
|
||||
it('should allow dots and underscores in name', async () => {
|
||||
const repo = await Repository.createRepository({
|
||||
organizationId: testOrgId,
|
||||
name: 'my.test_repo',
|
||||
protocol: 'npm',
|
||||
createdById: testUserId,
|
||||
});
|
||||
|
||||
assertEquals(repo.name, 'my.test_repo');
|
||||
});
|
||||
|
||||
it('should lowercase the name', async () => {
|
||||
const repo = await Repository.createRepository({
|
||||
organizationId: testOrgId,
|
||||
name: 'UPPERCASE',
|
||||
protocol: 'npm',
|
||||
createdById: testUserId,
|
||||
});
|
||||
|
||||
assertEquals(repo.name, 'uppercase');
|
||||
});
|
||||
|
||||
it('should set correct storage namespace', async () => {
|
||||
const repo = await Repository.createRepository({
|
||||
organizationId: testOrgId,
|
||||
name: 'packages',
|
||||
protocol: 'npm',
|
||||
createdById: testUserId,
|
||||
});
|
||||
|
||||
assertEquals(repo.storageNamespace, `npm/${testOrgId}/packages`);
|
||||
});
|
||||
|
||||
it('should reject duplicate name+protocol in same org', async () => {
|
||||
await Repository.createRepository({
|
||||
organizationId: testOrgId,
|
||||
name: 'unique',
|
||||
protocol: 'npm',
|
||||
createdById: testUserId,
|
||||
});
|
||||
|
||||
await assertRejects(
|
||||
async () => {
|
||||
await Repository.createRepository({
|
||||
organizationId: testOrgId,
|
||||
name: 'unique',
|
||||
protocol: 'npm',
|
||||
createdById: testUserId,
|
||||
});
|
||||
},
|
||||
Error,
|
||||
'already exists'
|
||||
);
|
||||
});
|
||||
|
||||
it('should allow same name with different protocol', async () => {
|
||||
await Repository.createRepository({
|
||||
organizationId: testOrgId,
|
||||
name: 'packages',
|
||||
protocol: 'npm',
|
||||
createdById: testUserId,
|
||||
});
|
||||
|
||||
const ociRepo = await Repository.createRepository({
|
||||
organizationId: testOrgId,
|
||||
name: 'packages',
|
||||
protocol: 'oci',
|
||||
createdById: testUserId,
|
||||
});
|
||||
|
||||
assertEquals(ociRepo.name, 'packages');
|
||||
assertEquals(ociRepo.protocol, 'oci');
|
||||
});
|
||||
|
||||
it('should reject invalid names', async () => {
|
||||
await assertRejects(
|
||||
async () => {
|
||||
await Repository.createRepository({
|
||||
organizationId: testOrgId,
|
||||
name: '-invalid',
|
||||
protocol: 'npm',
|
||||
createdById: testUserId,
|
||||
});
|
||||
},
|
||||
Error,
|
||||
'lowercase alphanumeric'
|
||||
);
|
||||
});
|
||||
|
||||
it('should set visibility when provided', async () => {
|
||||
const repo = await Repository.createRepository({
|
||||
organizationId: testOrgId,
|
||||
name: 'public-repo',
|
||||
protocol: 'npm',
|
||||
visibility: 'public',
|
||||
createdById: testUserId,
|
||||
});
|
||||
|
||||
assertEquals(repo.visibility, 'public');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByName', () => {
|
||||
it('should find repository by org, name, and protocol', async () => {
|
||||
await Repository.createRepository({
|
||||
organizationId: testOrgId,
|
||||
name: 'findable',
|
||||
protocol: 'npm',
|
||||
createdById: testUserId,
|
||||
});
|
||||
|
||||
const found = await Repository.findByName(testOrgId, 'FINDABLE', 'npm');
|
||||
assertExists(found);
|
||||
assertEquals(found.name, 'findable');
|
||||
});
|
||||
|
||||
it('should return null for wrong protocol', async () => {
|
||||
await Repository.createRepository({
|
||||
organizationId: testOrgId,
|
||||
name: 'npm-only',
|
||||
protocol: 'npm',
|
||||
createdById: testUserId,
|
||||
});
|
||||
|
||||
const found = await Repository.findByName(testOrgId, 'npm-only', 'oci');
|
||||
assertEquals(found, null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOrgRepositories', () => {
|
||||
it('should return all org repositories', async () => {
|
||||
await Repository.createRepository({
|
||||
organizationId: testOrgId,
|
||||
name: 'repo1',
|
||||
protocol: 'npm',
|
||||
createdById: testUserId,
|
||||
});
|
||||
await Repository.createRepository({
|
||||
organizationId: testOrgId,
|
||||
name: 'repo2',
|
||||
protocol: 'oci',
|
||||
createdById: testUserId,
|
||||
});
|
||||
await Repository.createRepository({
|
||||
organizationId: testOrgId,
|
||||
name: 'repo3',
|
||||
protocol: 'maven',
|
||||
createdById: testUserId,
|
||||
});
|
||||
|
||||
const repos = await Repository.getOrgRepositories(testOrgId);
|
||||
assertEquals(repos.length, 3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPublicRepositories', () => {
|
||||
it('should return only public repositories', async () => {
|
||||
await Repository.createRepository({
|
||||
organizationId: testOrgId,
|
||||
name: 'public1',
|
||||
protocol: 'npm',
|
||||
visibility: 'public',
|
||||
createdById: testUserId,
|
||||
});
|
||||
await Repository.createRepository({
|
||||
organizationId: testOrgId,
|
||||
name: 'private1',
|
||||
protocol: 'npm',
|
||||
visibility: 'private',
|
||||
createdById: testUserId,
|
||||
});
|
||||
|
||||
const repos = await Repository.getPublicRepositories();
|
||||
assertEquals(repos.length, 1);
|
||||
assertEquals(repos[0].name, 'public1');
|
||||
});
|
||||
|
||||
it('should filter by protocol when provided', async () => {
|
||||
await Repository.createRepository({
|
||||
organizationId: testOrgId,
|
||||
name: 'npm-public',
|
||||
protocol: 'npm',
|
||||
visibility: 'public',
|
||||
createdById: testUserId,
|
||||
});
|
||||
await Repository.createRepository({
|
||||
organizationId: testOrgId,
|
||||
name: 'oci-public',
|
||||
protocol: 'oci',
|
||||
visibility: 'public',
|
||||
createdById: testUserId,
|
||||
});
|
||||
|
||||
const repos = await Repository.getPublicRepositories('npm');
|
||||
assertEquals(repos.length, 1);
|
||||
assertEquals(repos[0].protocol, 'npm');
|
||||
});
|
||||
});
|
||||
|
||||
describe('incrementDownloads', () => {
|
||||
it('should increment download count', async () => {
|
||||
const repo = await Repository.createRepository({
|
||||
organizationId: testOrgId,
|
||||
name: 'downloads',
|
||||
protocol: 'npm',
|
||||
createdById: testUserId,
|
||||
});
|
||||
|
||||
await repo.incrementDownloads();
|
||||
assertEquals(repo.downloadCount, 1);
|
||||
|
||||
await repo.incrementDownloads();
|
||||
await repo.incrementDownloads();
|
||||
assertEquals(repo.downloadCount, 3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFullPath', () => {
|
||||
it('should return org/repo path', async () => {
|
||||
const repo = await Repository.createRepository({
|
||||
organizationId: testOrgId,
|
||||
name: 'my-package',
|
||||
protocol: 'npm',
|
||||
createdById: testUserId,
|
||||
});
|
||||
|
||||
const path = repo.getFullPath('my-org');
|
||||
assertEquals(path, 'my-org/my-package');
|
||||
});
|
||||
});
|
||||
});
|
||||
142
test/unit/models/session.test.ts
Normal file
142
test/unit/models/session.test.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* Session 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 { Session } from '../../../ts/models/session.ts';
|
||||
|
||||
describe('Session Model', () => {
|
||||
let testUserId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
await setupTestDb();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await teardownTestDb();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await cleanupTestDb();
|
||||
const { user } = await createTestUser();
|
||||
testUserId = user.id;
|
||||
});
|
||||
|
||||
describe('createSession', () => {
|
||||
it('should create a session with valid data', async () => {
|
||||
const session = await Session.createSession({
|
||||
userId: testUserId,
|
||||
userAgent: 'Mozilla/5.0',
|
||||
ipAddress: '192.168.1.1',
|
||||
});
|
||||
|
||||
assertExists(session.id);
|
||||
assertEquals(session.userId, testUserId);
|
||||
assertEquals(session.userAgent, 'Mozilla/5.0');
|
||||
assertEquals(session.ipAddress, '192.168.1.1');
|
||||
assertEquals(session.isValid, true);
|
||||
assertExists(session.createdAt);
|
||||
assertExists(session.lastActivityAt);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findValidSession', () => {
|
||||
it('should find valid session by ID', async () => {
|
||||
const created = await Session.createSession({
|
||||
userId: testUserId,
|
||||
userAgent: 'Test Agent',
|
||||
ipAddress: '127.0.0.1',
|
||||
});
|
||||
|
||||
const found = await Session.findValidSession(created.id);
|
||||
assertExists(found);
|
||||
assertEquals(found.id, created.id);
|
||||
});
|
||||
|
||||
it('should not find invalidated session', async () => {
|
||||
const session = await Session.createSession({
|
||||
userId: testUserId,
|
||||
userAgent: 'Test Agent',
|
||||
ipAddress: '127.0.0.1',
|
||||
});
|
||||
await session.invalidate('Logged out');
|
||||
|
||||
const found = await Session.findValidSession(session.id);
|
||||
assertEquals(found, null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUserSessions', () => {
|
||||
it('should return all valid sessions for user', async () => {
|
||||
await Session.createSession({ userId: testUserId, userAgent: 'Agent 1', ipAddress: '1.1.1.1' });
|
||||
await Session.createSession({ userId: testUserId, userAgent: 'Agent 2', ipAddress: '2.2.2.2' });
|
||||
await Session.createSession({ userId: testUserId, userAgent: 'Agent 3', ipAddress: '3.3.3.3' });
|
||||
|
||||
const sessions = await Session.getUserSessions(testUserId);
|
||||
assertEquals(sessions.length, 3);
|
||||
});
|
||||
|
||||
it('should not return invalidated sessions', async () => {
|
||||
await Session.createSession({ userId: testUserId, userAgent: 'Valid', ipAddress: '1.1.1.1' });
|
||||
const invalid = await Session.createSession({
|
||||
userId: testUserId,
|
||||
userAgent: 'Invalid',
|
||||
ipAddress: '2.2.2.2',
|
||||
});
|
||||
await invalid.invalidate('test');
|
||||
|
||||
const sessions = await Session.getUserSessions(testUserId);
|
||||
assertEquals(sessions.length, 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalidate', () => {
|
||||
it('should invalidate session with reason', async () => {
|
||||
const session = await Session.createSession({
|
||||
userId: testUserId,
|
||||
userAgent: 'Test',
|
||||
ipAddress: '127.0.0.1',
|
||||
});
|
||||
|
||||
await session.invalidate('User logged out');
|
||||
|
||||
assertEquals(session.isValid, false);
|
||||
assertExists(session.invalidatedAt);
|
||||
assertEquals(session.invalidatedReason, 'User logged out');
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalidateAllUserSessions', () => {
|
||||
it('should invalidate all user sessions', async () => {
|
||||
await Session.createSession({ userId: testUserId, userAgent: 'Agent 1', ipAddress: '1.1.1.1' });
|
||||
await Session.createSession({ userId: testUserId, userAgent: 'Agent 2', ipAddress: '2.2.2.2' });
|
||||
await Session.createSession({ userId: testUserId, userAgent: 'Agent 3', ipAddress: '3.3.3.3' });
|
||||
|
||||
const count = await Session.invalidateAllUserSessions(testUserId, 'Security logout');
|
||||
assertEquals(count, 3);
|
||||
|
||||
const remaining = await Session.getUserSessions(testUserId);
|
||||
assertEquals(remaining.length, 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('touchActivity', () => {
|
||||
it('should update lastActivityAt', async () => {
|
||||
const session = await Session.createSession({
|
||||
userId: testUserId,
|
||||
userAgent: 'Test',
|
||||
ipAddress: '127.0.0.1',
|
||||
});
|
||||
const originalActivity = session.lastActivityAt;
|
||||
|
||||
// Wait a bit to ensure time difference
|
||||
await new Promise((r) => setTimeout(r, 10));
|
||||
|
||||
await session.touchActivity();
|
||||
|
||||
assertEquals(session.lastActivityAt > originalActivity, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
228
test/unit/models/user.test.ts
Normal file
228
test/unit/models/user.test.ts
Normal file
@@ -0,0 +1,228 @@
|
||||
/**
|
||||
* User model unit tests
|
||||
*/
|
||||
|
||||
import { assertEquals, assertExists, assertRejects } from 'jsr:@std/assert';
|
||||
import { describe, it, beforeAll, afterAll, beforeEach } from 'jsr:@std/testing/bdd';
|
||||
import { setupTestDb, teardownTestDb, cleanupTestDb } from '../../helpers/index.ts';
|
||||
import { User } from '../../../ts/models/user.ts';
|
||||
|
||||
describe('User Model', () => {
|
||||
beforeAll(async () => {
|
||||
await setupTestDb();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await teardownTestDb();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await cleanupTestDb();
|
||||
});
|
||||
|
||||
describe('createUser', () => {
|
||||
it('should create a user with valid data', async () => {
|
||||
const passwordHash = await User.hashPassword('testpassword');
|
||||
const user = await User.createUser({
|
||||
email: 'test@example.com',
|
||||
username: 'testuser',
|
||||
passwordHash,
|
||||
displayName: 'Test User',
|
||||
});
|
||||
|
||||
assertExists(user.id);
|
||||
assertEquals(user.email, 'test@example.com');
|
||||
assertEquals(user.username, 'testuser');
|
||||
assertEquals(user.displayName, 'Test User');
|
||||
assertEquals(user.status, 'pending_verification');
|
||||
assertEquals(user.emailVerified, false);
|
||||
assertEquals(user.isPlatformAdmin, false);
|
||||
});
|
||||
|
||||
it('should lowercase email and username', async () => {
|
||||
const passwordHash = await User.hashPassword('testpassword');
|
||||
const user = await User.createUser({
|
||||
email: 'TEST@EXAMPLE.COM',
|
||||
username: 'TestUser',
|
||||
passwordHash,
|
||||
});
|
||||
|
||||
assertEquals(user.email, 'test@example.com');
|
||||
assertEquals(user.username, 'testuser');
|
||||
});
|
||||
|
||||
it('should use username as displayName if not provided', async () => {
|
||||
const passwordHash = await User.hashPassword('testpassword');
|
||||
const user = await User.createUser({
|
||||
email: 'test2@example.com',
|
||||
username: 'testuser2',
|
||||
passwordHash,
|
||||
});
|
||||
|
||||
assertEquals(user.displayName, 'testuser2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByEmail', () => {
|
||||
it('should find user by email (case-insensitive)', async () => {
|
||||
const passwordHash = await User.hashPassword('testpassword');
|
||||
await User.createUser({
|
||||
email: 'findme@example.com',
|
||||
username: 'findme',
|
||||
passwordHash,
|
||||
});
|
||||
|
||||
const found = await User.findByEmail('FINDME@example.com');
|
||||
assertExists(found);
|
||||
assertEquals(found.email, 'findme@example.com');
|
||||
});
|
||||
|
||||
it('should return null for non-existent email', async () => {
|
||||
const found = await User.findByEmail('nonexistent@example.com');
|
||||
assertEquals(found, null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByUsername', () => {
|
||||
it('should find user by username (case-insensitive)', async () => {
|
||||
const passwordHash = await User.hashPassword('testpassword');
|
||||
await User.createUser({
|
||||
email: 'user@example.com',
|
||||
username: 'findbyname',
|
||||
passwordHash,
|
||||
});
|
||||
|
||||
const found = await User.findByUsername('FINDBYNAME');
|
||||
assertExists(found);
|
||||
assertEquals(found.username, 'findbyname');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should find user by ID', async () => {
|
||||
const passwordHash = await User.hashPassword('testpassword');
|
||||
const created = await User.createUser({
|
||||
email: 'byid@example.com',
|
||||
username: 'byid',
|
||||
passwordHash,
|
||||
});
|
||||
|
||||
const found = await User.findById(created.id);
|
||||
assertExists(found);
|
||||
assertEquals(found.id, created.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('password hashing', () => {
|
||||
it('should hash password with salt', async () => {
|
||||
const hash = await User.hashPassword('mypassword');
|
||||
assertExists(hash);
|
||||
assertEquals(hash.includes(':'), true);
|
||||
|
||||
const [salt, _hashPart] = hash.split(':');
|
||||
assertEquals(salt.length, 32); // 16 bytes = 32 hex chars
|
||||
});
|
||||
|
||||
it('should produce different hashes for same password', async () => {
|
||||
const hash1 = await User.hashPassword('samepassword');
|
||||
const hash2 = await User.hashPassword('samepassword');
|
||||
|
||||
// Different salts should produce different hashes
|
||||
assertEquals(hash1 !== hash2, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('verifyPassword', () => {
|
||||
it('should verify correct password', async () => {
|
||||
const passwordHash = await User.hashPassword('correctpassword');
|
||||
const user = await User.createUser({
|
||||
email: 'verify@example.com',
|
||||
username: 'verifyuser',
|
||||
passwordHash,
|
||||
});
|
||||
|
||||
const isValid = await user.verifyPassword('correctpassword');
|
||||
assertEquals(isValid, true);
|
||||
});
|
||||
|
||||
it('should reject incorrect password', async () => {
|
||||
const passwordHash = await User.hashPassword('correctpassword');
|
||||
const user = await User.createUser({
|
||||
email: 'reject@example.com',
|
||||
username: 'rejectuser',
|
||||
passwordHash,
|
||||
});
|
||||
|
||||
const isValid = await user.verifyPassword('wrongpassword');
|
||||
assertEquals(isValid, false);
|
||||
});
|
||||
|
||||
it('should reject empty password', async () => {
|
||||
const passwordHash = await User.hashPassword('correctpassword');
|
||||
const user = await User.createUser({
|
||||
email: 'empty@example.com',
|
||||
username: 'emptyuser',
|
||||
passwordHash,
|
||||
});
|
||||
|
||||
const isValid = await user.verifyPassword('');
|
||||
assertEquals(isValid, false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isActive', () => {
|
||||
it('should return true for active status', async () => {
|
||||
const passwordHash = await User.hashPassword('test');
|
||||
const user = await User.createUser({
|
||||
email: 'active@example.com',
|
||||
username: 'activeuser',
|
||||
passwordHash,
|
||||
});
|
||||
user.status = 'active';
|
||||
await user.save();
|
||||
|
||||
assertEquals(user.isActive, true);
|
||||
});
|
||||
|
||||
it('should return false for suspended status', async () => {
|
||||
const passwordHash = await User.hashPassword('test');
|
||||
const user = await User.createUser({
|
||||
email: 'suspended@example.com',
|
||||
username: 'suspendeduser',
|
||||
passwordHash,
|
||||
});
|
||||
user.status = 'suspended';
|
||||
|
||||
assertEquals(user.isActive, false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isPlatformAdmin', () => {
|
||||
it('should default to false', async () => {
|
||||
const passwordHash = await User.hashPassword('test');
|
||||
const user = await User.createUser({
|
||||
email: 'notadmin@example.com',
|
||||
username: 'notadmin',
|
||||
passwordHash,
|
||||
});
|
||||
|
||||
assertEquals(user.isPlatformAdmin, false);
|
||||
assertEquals(user.isSystemAdmin, false);
|
||||
});
|
||||
|
||||
it('should be settable to true', async () => {
|
||||
const passwordHash = await User.hashPassword('test');
|
||||
const user = await User.createUser({
|
||||
email: 'admin@example.com',
|
||||
username: 'adminuser',
|
||||
passwordHash,
|
||||
});
|
||||
user.isPlatformAdmin = true;
|
||||
await user.save();
|
||||
|
||||
const found = await User.findById(user.id);
|
||||
assertEquals(found!.isPlatformAdmin, true);
|
||||
assertEquals(found!.isSystemAdmin, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user