import { expect, tap } from '@git.zone/tstest/tapbundle'; import { SmartRegistry } from '../ts/index.js'; import { createTestRegistry, createTestTokens, createComposerZip } from './helpers/registry.js'; let registry: SmartRegistry; let composerToken: string; let userId: string; // Test data const testPackageName = 'vendor/test-package'; const testVersion = '1.0.0'; let testZipData: Buffer; tap.test('Composer: should create registry instance', async () => { registry = await createTestRegistry(); const tokens = await createTestTokens(registry); composerToken = tokens.composerToken; userId = tokens.userId; expect(registry).toBeInstanceOf(SmartRegistry); expect(composerToken).toBeTypeOf('string'); }); tap.test('Composer: should create test ZIP package', async () => { testZipData = await createComposerZip(testPackageName, testVersion, { description: 'Test Composer package for registry', license: ['MIT'], authors: [{ name: 'Test Author', email: 'test@example.com' }], }); expect(testZipData).toBeInstanceOf(Buffer); expect(testZipData.length).toBeGreaterThan(0); }); tap.test('Composer: should return packages.json (GET /packages.json)', async () => { const response = await registry.handleRequest({ method: 'GET', path: '/composer/packages.json', headers: {}, query: {}, }); expect(response.status).toEqual(200); expect(response.body).toHaveProperty('metadata-url'); expect(response.body).toHaveProperty('available-packages'); expect(response.body['available-packages']).toBeInstanceOf(Array); }); tap.test('Composer: should upload a package (PUT /packages/{vendor/package})', async () => { const response = await registry.handleRequest({ method: 'PUT', path: `/composer/packages/${testPackageName}`, headers: { Authorization: `Bearer ${composerToken}`, 'Content-Type': 'application/zip', }, query: {}, body: testZipData, }); expect(response.status).toEqual(201); expect(response.body.status).toEqual('success'); expect(response.body.package).toEqual(testPackageName); expect(response.body.version).toEqual(testVersion); }); tap.test('Composer: should retrieve package metadata (GET /p2/{vendor/package}.json)', async () => { const response = await registry.handleRequest({ method: 'GET', path: `/composer/p2/${testPackageName}.json`, headers: {}, query: {}, }); expect(response.status).toEqual(200); expect(response.body).toHaveProperty('packages'); expect(response.body.packages[testPackageName]).toBeInstanceOf(Array); expect(response.body.packages[testPackageName].length).toEqual(1); const packageData = response.body.packages[testPackageName][0]; expect(packageData.name).toEqual(testPackageName); expect(packageData.version).toEqual(testVersion); expect(packageData.version_normalized).toEqual('1.0.0.0'); expect(packageData).toHaveProperty('dist'); expect(packageData.dist.type).toEqual('zip'); expect(packageData.dist).toHaveProperty('url'); expect(packageData.dist).toHaveProperty('shasum'); expect(packageData.dist).toHaveProperty('reference'); }); tap.test('Composer: should download package ZIP (GET /dists/{vendor/package}/{ref}.zip)', async () => { // First get metadata to find reference const metadataResponse = await registry.handleRequest({ method: 'GET', path: `/composer/p2/${testPackageName}.json`, headers: {}, query: {}, }); const reference = metadataResponse.body.packages[testPackageName][0].dist.reference; const response = await registry.handleRequest({ method: 'GET', path: `/composer/dists/${testPackageName}/${reference}.zip`, headers: {}, query: {}, }); expect(response.status).toEqual(200); expect(response.body).toBeInstanceOf(Buffer); expect(response.headers['Content-Type']).toEqual('application/zip'); expect(response.headers['Content-Disposition']).toContain('attachment'); }); tap.test('Composer: should list packages (GET /packages/list.json)', async () => { const response = await registry.handleRequest({ method: 'GET', path: '/composer/packages/list.json', headers: {}, query: {}, }); expect(response.status).toEqual(200); expect(response.body).toHaveProperty('packageNames'); expect(response.body.packageNames).toBeInstanceOf(Array); expect(response.body.packageNames).toContain(testPackageName); }); tap.test('Composer: should filter package list (GET /packages/list.json?filter=vendor/*)', async () => { const response = await registry.handleRequest({ method: 'GET', path: '/composer/packages/list.json', headers: {}, query: { filter: 'vendor/*' }, }); expect(response.status).toEqual(200); expect(response.body.packageNames).toBeInstanceOf(Array); expect(response.body.packageNames).toContain(testPackageName); }); tap.test('Composer: should prevent duplicate version upload', async () => { const response = await registry.handleRequest({ method: 'PUT', path: `/composer/packages/${testPackageName}`, headers: { Authorization: `Bearer ${composerToken}`, 'Content-Type': 'application/zip', }, query: {}, body: testZipData, }); expect(response.status).toEqual(409); expect(response.body.status).toEqual('error'); expect(response.body.message).toContain('already exists'); }); tap.test('Composer: should upload a second version', async () => { const testVersion2 = '1.1.0'; const testZipData2 = await createComposerZip(testPackageName, testVersion2); const response = await registry.handleRequest({ method: 'PUT', path: `/composer/packages/${testPackageName}`, headers: { Authorization: `Bearer ${composerToken}`, 'Content-Type': 'application/zip', }, query: {}, body: testZipData2, }); expect(response.status).toEqual(201); expect(response.body.status).toEqual('success'); expect(response.body.version).toEqual(testVersion2); }); tap.test('Composer: should return multiple versions in metadata', async () => { const response = await registry.handleRequest({ method: 'GET', path: `/composer/p2/${testPackageName}.json`, headers: {}, query: {}, }); expect(response.status).toEqual(200); expect(response.body.packages[testPackageName]).toBeInstanceOf(Array); expect(response.body.packages[testPackageName].length).toEqual(2); const versions = response.body.packages[testPackageName].map((p: any) => p.version); expect(versions).toContain('1.0.0'); expect(versions).toContain('1.1.0'); }); tap.test('Composer: should delete a specific version (DELETE /packages/{vendor/package}/{version})', async () => { const response = await registry.handleRequest({ method: 'DELETE', path: `/composer/packages/${testPackageName}/1.0.0`, headers: { Authorization: `Bearer ${composerToken}`, }, query: {}, }); expect(response.status).toEqual(204); // Verify version was removed const metadataResponse = await registry.handleRequest({ method: 'GET', path: `/composer/p2/${testPackageName}.json`, headers: {}, query: {}, }); expect(metadataResponse.body.packages[testPackageName].length).toEqual(1); expect(metadataResponse.body.packages[testPackageName][0].version).toEqual('1.1.0'); }); tap.test('Composer: should require auth for package upload', async () => { const testZipData3 = await createComposerZip('vendor/unauth-package', '1.0.0'); const response = await registry.handleRequest({ method: 'PUT', path: '/composer/packages/vendor/unauth-package', headers: { 'Content-Type': 'application/zip', }, query: {}, body: testZipData3, }); expect(response.status).toEqual(401); expect(response.body.status).toEqual('error'); }); tap.test('Composer: should reject invalid ZIP (no composer.json)', async () => { const invalidZip = Buffer.from('invalid zip content'); const response = await registry.handleRequest({ method: 'PUT', path: `/composer/packages/${testPackageName}`, headers: { Authorization: `Bearer ${composerToken}`, 'Content-Type': 'application/zip', }, query: {}, body: invalidZip, }); expect(response.status).toEqual(400); expect(response.body.status).toEqual('error'); expect(response.body.message).toContain('composer.json'); }); tap.test('Composer: should delete entire package (DELETE /packages/{vendor/package})', async () => { const response = await registry.handleRequest({ method: 'DELETE', path: `/composer/packages/${testPackageName}`, headers: { Authorization: `Bearer ${composerToken}`, }, query: {}, }); expect(response.status).toEqual(204); // Verify package was removed const metadataResponse = await registry.handleRequest({ method: 'GET', path: `/composer/p2/${testPackageName}.json`, headers: {}, query: {}, }); expect(metadataResponse.status).toEqual(404); }); tap.test('Composer: should return 404 for non-existent package', async () => { const response = await registry.handleRequest({ method: 'GET', path: '/composer/p2/non/existent.json', headers: {}, query: {}, }); expect(response.status).toEqual(404); }); tap.postTask('cleanup registry', async () => { if (registry) { registry.destroy(); } }); export default tap.start();