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