297 lines
9.1 KiB
TypeScript
297 lines
9.1 KiB
TypeScript
|
|
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();
|