import { expect, tap } from '@git.zone/tstest/tapbundle'; import * as smartregistry from '../ts/index.js'; import * as qenv from '@push.rocks/qenv'; const testQenv = new qenv.Qenv('./', './.nogit'); let registry: smartregistry.SmartRegistry; let testToken: string; tap.test('should create SmartRegistry instance', async () => { // Create mock callbacks for testing const loginCallback: smartregistry.TLoginCallback = async (credentials) => { // Simple mock: return a fake JWT token const tokenPayload = { iss: 'test-registry', sub: credentials.username, aud: 'test-service', exp: Math.floor(Date.now() / 1000) + 3600, nbf: Math.floor(Date.now() / 1000), iat: Math.floor(Date.now() / 1000), access: [ { type: 'repository' as const, name: 'test/repo', actions: ['*'] as smartregistry.TRegistryAction[], }, ], }; // In production, this would be a real JWT return JSON.stringify(tokenPayload); }; const authCallback: smartregistry.TAuthCallback = async (token, repository, action) => { // Simple mock: allow all actions for testing try { const payload = JSON.parse(token); // Check if token has access to the repository const hasAccess = payload.access.some( (acc: any) => acc.name === repository && (acc.actions.includes(action) || acc.actions.includes('*')) ); return hasAccess; } catch { return false; } }; // Read S3 config from env.json const s3AccessKey = await testQenv.getEnvVarOnDemand('S3_ACCESS_KEY'); const s3SecretKey = await testQenv.getEnvVarOnDemand('S3_SECRET_KEY'); const s3Endpoint = await testQenv.getEnvVarOnDemand('S3_ENDPOINT'); const s3Port = await testQenv.getEnvVarOnDemand('S3_PORT'); const config: smartregistry.IRegistryConfig = { storage: { accessKey: s3AccessKey || 'minioadmin', accessSecret: s3SecretKey || 'minioadmin', endpoint: s3Endpoint || 'localhost', port: parseInt(s3Port || '9000', 10), useSsl: false, region: 'us-east-1', bucketName: 'test-registry', }, serviceName: 'test-registry', tokenRealm: 'https://auth.example.com/token', loginCallback, authCallback, }; registry = new smartregistry.SmartRegistry(config); await registry.init(); expect(registry).toBeInstanceOf(smartregistry.SmartRegistry); }); tap.test('should login and get token', async () => { testToken = await registry.login({ username: 'testuser', password: 'testpass', }); expect(testToken).toBeTypeOf('string'); expect(testToken.length).toBeGreaterThan(0); }); tap.test('should upload a blob via chunked upload', async () => { const testData = Buffer.from('Hello, OCI Registry!', 'utf-8'); const crypto = await import('crypto'); const digest = `sha256:${crypto.createHash('sha256').update(testData).digest('hex')}`; // Initiate upload const initResult = await registry.initiateUpload('test/repo', testToken); expect(initResult).toHaveProperty('uploadId'); if ('uploadId' in initResult) { const uploadId = initResult.uploadId; // Upload chunk const chunkResult = await registry.uploadChunk( uploadId, testData, `0-${testData.length - 1}`, testToken ); expect(chunkResult).toHaveProperty('location'); // Complete upload const completeResult = await registry.completeUpload(uploadId, digest, testToken); expect(completeResult).toHaveProperty('digest'); if ('digest' in completeResult) { expect(completeResult.digest).toEqual(digest); } } }); tap.test('should retrieve a blob', async () => { const testData = Buffer.from('Hello, OCI Registry!', 'utf-8'); const crypto = await import('crypto'); const digest = `sha256:${crypto.createHash('sha256').update(testData).digest('hex')}`; const result = await registry.getBlob('test/repo', digest, testToken); expect(result).toHaveProperty('data'); if ('data' in result) { expect(result.data.toString('utf-8')).toEqual('Hello, OCI Registry!'); } }); tap.test('should check if blob exists (HEAD)', async () => { const testData = Buffer.from('Hello, OCI Registry!', 'utf-8'); const crypto = await import('crypto'); const digest = `sha256:${crypto.createHash('sha256').update(testData).digest('hex')}`; const result = await registry.headBlob('test/repo', digest, testToken); expect(result).toHaveProperty('exists'); if ('exists' in result) { expect(result.exists).toEqual(true); expect(result.size).toEqual(testData.length); } }); tap.test('should upload a manifest', async () => { const testManifest: smartregistry.IOciManifest = { schemaVersion: 2, mediaType: 'application/vnd.oci.image.manifest.v1+json', config: { mediaType: 'application/vnd.oci.image.config.v1+json', size: 123, digest: 'sha256:' + '0'.repeat(64), }, layers: [ { mediaType: 'application/vnd.oci.image.layer.v1.tar+gzip', size: 456, digest: 'sha256:' + '1'.repeat(64), }, ], }; const result = await registry.putManifest( 'test/repo', 'latest', testManifest, 'application/vnd.oci.image.manifest.v1+json', testToken ); expect(result).toHaveProperty('digest'); if ('digest' in result) { expect(result.digest).toMatch(/^sha256:[a-f0-9]{64}$/); } }); tap.test('should retrieve a manifest by tag', async () => { const result = await registry.getManifest('test/repo', 'latest', testToken); expect(result).toHaveProperty('data'); if ('data' in result) { const manifest = JSON.parse(result.data.toString('utf-8')); expect(manifest).toHaveProperty('schemaVersion'); expect(manifest.schemaVersion).toEqual(2); } }); tap.test('should list tags', async () => { const result = await registry.listTags('test/repo', testToken); expect(result).toHaveProperty('tags'); if ('tags' in result) { expect(result.tags).toBeInstanceOf(Array); expect(result.tags).toContain('latest'); } }); tap.test('should generate auth challenge', async () => { const challenge = registry.getAuthChallenge('test/repo', ['pull', 'push']); expect(challenge).toInclude('Bearer'); expect(challenge).toInclude('realm='); expect(challenge).toInclude('service='); expect(challenge).toInclude('scope='); }); tap.test('should handle unauthorized access', async () => { const result = await registry.getBlob('test/repo', 'sha256:invalid', 'invalid-token'); expect(result).toHaveProperty('errors'); if ('errors' in result) { expect(result.errors[0].code).toEqual('DENIED'); } }); export default tap.start();