Files
registry/test/e2e/oci.e2e.test.ts

191 lines
5.4 KiB
TypeScript
Raw Permalink Normal View History

/**
* 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);
});
});