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

290
test/e2e/npm.e2e.test.ts Normal file
View File

@@ -0,0 +1,290 @@
/**
* NPM Protocol E2E Tests
*
* Tests the full NPM package lifecycle: publish -> fetch -> delete
* Requires: npm CLI, running registry, Docker test infrastructure
*/
import { assertEquals, assertExists } from 'jsr:@std/assert';
import { describe, it, beforeAll, afterAll, beforeEach } from 'jsr:@std/testing/bdd';
import * as path from '@std/path';
import {
setupTestDb,
teardownTestDb,
cleanupTestDb,
createTestUser,
createOrgWithOwner,
createTestRepository,
createTestApiToken,
clients,
skipIfMissing,
runCommand,
testConfig,
} from '../helpers/index.ts';
const FIXTURE_DIR = path.join(
path.dirname(path.fromFileUrl(import.meta.url)),
'../fixtures/npm/@stack-test/demo-package'
);
describe('NPM E2E: Full lifecycle', () => {
let testUserId: string;
let testOrgName: string;
let apiToken: string;
let registryUrl: string;
let shouldSkip = false;
beforeAll(async () => {
// Check if npm is available
shouldSkip = await skipIfMissing('npm');
if (shouldSkip) return;
await setupTestDb();
registryUrl = testConfig.registry.url;
});
afterAll(async () => {
if (!shouldSkip) {
await teardownTestDb();
}
});
beforeEach(async () => {
if (shouldSkip) return;
await cleanupTestDb();
// Create test user and org
const { user } = await createTestUser({ status: 'active' });
testUserId = user.id;
const { organization } = await createOrgWithOwner(testUserId, { name: 'npm-test' });
testOrgName = organization.name;
// Create repository for npm packages
await createTestRepository({
organizationId: organization.id,
createdById: testUserId,
name: 'packages',
protocol: 'npm',
});
// Create API token with npm permissions
const { rawToken } = await createTestApiToken({
userId: testUserId,
name: 'npm-publish-token',
protocols: ['npm'],
scopes: [{ protocol: 'npm', actions: ['read', 'write', 'delete'] }],
});
apiToken = rawToken;
});
it('should publish package', async function () {
if (shouldSkip) {
console.log('Skipping: npm not available');
return;
}
// Configure npm to use our registry
const npmrcPath = path.join(FIXTURE_DIR, '.npmrc');
const npmrcContent = `
//${new URL(registryUrl).host}/-/npm/${testOrgName}/:_authToken=${apiToken}
@stack-test:registry=${registryUrl}/-/npm/${testOrgName}/
`;
await Deno.writeTextFile(npmrcPath, npmrcContent);
try {
const result = await clients.npm.publish(
FIXTURE_DIR,
`${registryUrl}/-/npm/${testOrgName}/`,
apiToken
);
assertEquals(result.success, true, `npm publish failed: ${result.stderr}`);
} finally {
// Cleanup .npmrc
try {
await Deno.remove(npmrcPath);
} catch {
// Ignore
}
}
});
it('should fetch package metadata', async function () {
if (shouldSkip) {
console.log('Skipping: npm not available');
return;
}
// First publish
const npmrcPath = path.join(FIXTURE_DIR, '.npmrc');
const npmrcContent = `//${new URL(registryUrl).host}/-/npm/${testOrgName}/:_authToken=${apiToken}`;
await Deno.writeTextFile(npmrcPath, npmrcContent);
try {
await clients.npm.publish(
FIXTURE_DIR,
`${registryUrl}/-/npm/${testOrgName}/`,
apiToken
);
// Fetch metadata via npm view
const viewResult = await runCommand(
['npm', 'view', '@stack-test/demo-package', '--registry', `${registryUrl}/-/npm/${testOrgName}/`],
{ env: { npm_config__authToken: apiToken } }
);
assertEquals(viewResult.success, true, `npm view failed: ${viewResult.stderr}`);
assertEquals(viewResult.stdout.includes('@stack-test/demo-package'), true);
} finally {
try {
await Deno.remove(npmrcPath);
} catch {
// Ignore
}
}
});
it('should install package', async function () {
if (shouldSkip) {
console.log('Skipping: npm not available');
return;
}
// Create temp directory for installation
const tempDir = await Deno.makeTempDir({ prefix: 'npm-e2e-' });
try {
// First publish
const npmrcPath = path.join(FIXTURE_DIR, '.npmrc');
const npmrcContent = `//${new URL(registryUrl).host}/-/npm/${testOrgName}/:_authToken=${apiToken}`;
await Deno.writeTextFile(npmrcPath, npmrcContent);
await clients.npm.publish(
FIXTURE_DIR,
`${registryUrl}/-/npm/${testOrgName}/`,
apiToken
);
// Create package.json in temp dir
await Deno.writeTextFile(
path.join(tempDir, 'package.json'),
JSON.stringify({ name: 'test-install', version: '1.0.0' })
);
// Create .npmrc in temp dir
await Deno.writeTextFile(
path.join(tempDir, '.npmrc'),
`@stack-test:registry=${registryUrl}/-/npm/${testOrgName}/\n//${new URL(registryUrl).host}/-/npm/${testOrgName}/:_authToken=${apiToken}`
);
// Install
const installResult = await clients.npm.install(
'@stack-test/demo-package@1.0.0',
`${registryUrl}/-/npm/${testOrgName}/`,
tempDir
);
assertEquals(installResult.success, true, `npm install failed: ${installResult.stderr}`);
// Verify installed
const pkgPath = path.join(tempDir, 'node_modules/@stack-test/demo-package');
const stat = await Deno.stat(pkgPath);
assertEquals(stat.isDirectory, true);
// Cleanup fixture .npmrc
try {
await Deno.remove(npmrcPath);
} catch {
// Ignore
}
} finally {
await Deno.remove(tempDir, { recursive: true });
}
});
it('should unpublish package', async function () {
if (shouldSkip) {
console.log('Skipping: npm not available');
return;
}
// First publish
const npmrcPath = path.join(FIXTURE_DIR, '.npmrc');
const npmrcContent = `//${new URL(registryUrl).host}/-/npm/${testOrgName}/:_authToken=${apiToken}`;
await Deno.writeTextFile(npmrcPath, npmrcContent);
try {
await clients.npm.publish(
FIXTURE_DIR,
`${registryUrl}/-/npm/${testOrgName}/`,
apiToken
);
// Unpublish
const unpublishResult = await clients.npm.unpublish(
'@stack-test/demo-package@1.0.0',
`${registryUrl}/-/npm/${testOrgName}/`,
apiToken
);
assertEquals(
unpublishResult.success,
true,
`npm unpublish failed: ${unpublishResult.stderr}`
);
// Verify package is gone
const viewResult = await runCommand(
['npm', 'view', '@stack-test/demo-package', '--registry', `${registryUrl}/-/npm/${testOrgName}/`],
{ env: { npm_config__authToken: apiToken } }
);
// Should fail since package was unpublished
assertEquals(viewResult.success, false);
} finally {
try {
await Deno.remove(npmrcPath);
} catch {
// Ignore
}
}
});
});
describe('NPM E2E: Edge cases', () => {
let shouldSkip = false;
beforeAll(async () => {
shouldSkip = await skipIfMissing('npm');
});
it('should handle scoped packages correctly', async function () {
if (shouldSkip) {
console.log('Skipping: npm not available');
return;
}
// Test scoped package name handling
const scopedName = '@stack-test/demo-package';
assertEquals(scopedName.startsWith('@'), true);
assertEquals(scopedName.includes('/'), true);
});
it('should reject invalid package names', async function () {
if (shouldSkip) {
console.log('Skipping: npm not available');
return;
}
// npm has strict naming rules
const invalidNames = ['UPPERCASE', '..dots..', 'spaces here', '_underscore'];
for (const name of invalidNames) {
// Just verify these are considered invalid by npm standards
assertEquals(!/^[a-z0-9][-a-z0-9._]*$/.test(name), true);
}
});
});

190
test/e2e/oci.e2e.test.ts Normal file
View File

@@ -0,0 +1,190 @@
/**
* OCI Protocol E2E Tests
*
* Tests the full OCI container image lifecycle: push -> pull -> delete
* Requires: docker CLI, running registry, Docker test infrastructure
*/
import { assertEquals } from 'jsr:@std/assert';
import { describe, it, beforeAll, afterAll, beforeEach } from 'jsr:@std/testing/bdd';
import * as path from '@std/path';
import {
setupTestDb,
teardownTestDb,
cleanupTestDb,
createTestUser,
createOrgWithOwner,
createTestRepository,
createTestApiToken,
clients,
skipIfMissing,
testConfig,
} from '../helpers/index.ts';
const FIXTURE_DIR = path.join(
path.dirname(path.fromFileUrl(import.meta.url)),
'../fixtures/oci'
);
describe('OCI E2E: Full lifecycle', () => {
let testUserId: string;
let testOrgName: string;
let apiToken: string;
let registryHost: string;
let shouldSkip = false;
beforeAll(async () => {
// Check if docker is available
shouldSkip = await skipIfMissing('docker');
if (shouldSkip) return;
await setupTestDb();
const url = new URL(testConfig.registry.url);
registryHost = url.host;
});
afterAll(async () => {
if (!shouldSkip) {
await teardownTestDb();
}
});
beforeEach(async () => {
if (shouldSkip) return;
await cleanupTestDb();
// Create test user and org
const { user } = await createTestUser({ status: 'active' });
testUserId = user.id;
const { organization } = await createOrgWithOwner(testUserId, { name: 'oci-test' });
testOrgName = organization.name;
// Create repository for OCI images
await createTestRepository({
organizationId: organization.id,
createdById: testUserId,
name: 'images',
protocol: 'oci',
});
// Create API token with OCI permissions
const { rawToken } = await createTestApiToken({
userId: testUserId,
name: 'oci-push-token',
protocols: ['oci'],
scopes: [{ protocol: 'oci', actions: ['read', 'write', 'delete'] }],
});
apiToken = rawToken;
});
it('should build and push image', async function () {
if (shouldSkip) {
console.log('Skipping: docker not available');
return;
}
const imageName = `${registryHost}/v2/${testOrgName}/demo:1.0.0`;
const dockerfile = path.join(FIXTURE_DIR, 'Dockerfile.simple');
try {
// Build image
const buildResult = await clients.docker.build(dockerfile, imageName, FIXTURE_DIR);
assertEquals(buildResult.success, true, `docker build failed: ${buildResult.stderr}`);
// Login to registry
const loginResult = await clients.docker.login(registryHost, 'token', apiToken);
assertEquals(loginResult.success, true, `docker login failed: ${loginResult.stderr}`);
// Push image
const pushResult = await clients.docker.push(imageName);
assertEquals(pushResult.success, true, `docker push failed: ${pushResult.stderr}`);
} finally {
// Cleanup local image
await clients.docker.rmi(imageName, true);
}
});
it('should pull image', async function () {
if (shouldSkip) {
console.log('Skipping: docker not available');
return;
}
const imageName = `${registryHost}/v2/${testOrgName}/demo:1.0.0`;
const dockerfile = path.join(FIXTURE_DIR, 'Dockerfile.simple');
try {
// Build and push first
await clients.docker.build(dockerfile, imageName, FIXTURE_DIR);
await clients.docker.login(registryHost, 'token', apiToken);
await clients.docker.push(imageName);
// Remove local image
await clients.docker.rmi(imageName, true);
// Pull from registry
const pullResult = await clients.docker.pull(imageName);
assertEquals(pullResult.success, true, `docker pull failed: ${pullResult.stderr}`);
} finally {
await clients.docker.rmi(imageName, true);
}
});
it('should handle multi-layer images', async function () {
if (shouldSkip) {
console.log('Skipping: docker not available');
return;
}
const imageName = `${registryHost}/v2/${testOrgName}/multi:1.0.0`;
const dockerfile = path.join(FIXTURE_DIR, 'Dockerfile.multi-layer');
try {
// Build multi-stage image
const buildResult = await clients.docker.build(dockerfile, imageName, FIXTURE_DIR);
assertEquals(buildResult.success, true, `docker build failed: ${buildResult.stderr}`);
// Login and push
await clients.docker.login(registryHost, 'token', apiToken);
const pushResult = await clients.docker.push(imageName);
assertEquals(pushResult.success, true, `docker push failed: ${pushResult.stderr}`);
} finally {
await clients.docker.rmi(imageName, true);
}
});
});
describe('OCI E2E: Tags and versions', () => {
let shouldSkip = false;
beforeAll(async () => {
shouldSkip = await skipIfMissing('docker');
});
it('should handle multiple tags for same image', async function () {
if (shouldSkip) {
console.log('Skipping: docker not available');
return;
}
// Verify tag handling logic
const tags = ['1.0.0', '1.0', '1', 'latest'];
for (const tag of tags) {
assertEquals(/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(tag), true);
}
});
it('should handle SHA256 digests', async function () {
if (shouldSkip) {
console.log('Skipping: docker not available');
return;
}
// Verify digest format
const digest = 'sha256:' + 'a'.repeat(64);
assertEquals(digest.startsWith('sha256:'), true);
assertEquals(digest.length, 71);
});
});