289 lines
8.3 KiB
TypeScript
289 lines
8.3 KiB
TypeScript
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
|
import { SmartRegistry } from '../ts/index.js';
|
|
import {
|
|
createTestRegistry,
|
|
createTestTokens,
|
|
createPythonWheel,
|
|
createRubyGem,
|
|
} from './helpers/registry.js';
|
|
|
|
let registry: SmartRegistry;
|
|
let pypiToken: string;
|
|
let rubygemsToken: string;
|
|
|
|
tap.test('Integration: should initialize registry with all protocols', async () => {
|
|
registry = await createTestRegistry();
|
|
const tokens = await createTestTokens(registry);
|
|
pypiToken = tokens.pypiToken;
|
|
rubygemsToken = tokens.rubygemsToken;
|
|
|
|
expect(registry).toBeInstanceOf(SmartRegistry);
|
|
expect(registry.isInitialized()).toEqual(true);
|
|
expect(pypiToken).toBeTypeOf('string');
|
|
expect(rubygemsToken).toBeTypeOf('string');
|
|
});
|
|
|
|
tap.test('Integration: should correctly route PyPI requests', async () => {
|
|
const wheelData = await createPythonWheel('integration-test-py', '1.0.0');
|
|
|
|
const response = await registry.handleRequest({
|
|
method: 'POST',
|
|
path: '/pypi/',
|
|
headers: {
|
|
Authorization: `Bearer ${pypiToken}`,
|
|
'Content-Type': 'multipart/form-data',
|
|
},
|
|
query: {},
|
|
body: {
|
|
':action': 'file_upload',
|
|
protocol_version: '1',
|
|
name: 'integration-test-py',
|
|
version: '1.0.0',
|
|
filetype: 'bdist_wheel',
|
|
pyversion: 'py3',
|
|
metadata_version: '2.1',
|
|
content: wheelData,
|
|
filename: 'integration_test_py-1.0.0-py3-none-any.whl',
|
|
},
|
|
});
|
|
|
|
expect(response.status).toEqual(201);
|
|
});
|
|
|
|
tap.test('Integration: should correctly route RubyGems requests', async () => {
|
|
const gemData = await createRubyGem('integration-test-gem', '1.0.0');
|
|
|
|
const response = await registry.handleRequest({
|
|
method: 'POST',
|
|
path: '/rubygems/api/v1/gems',
|
|
headers: {
|
|
Authorization: rubygemsToken,
|
|
'Content-Type': 'application/octet-stream',
|
|
},
|
|
query: {},
|
|
body: gemData,
|
|
});
|
|
|
|
expect(response.status).toEqual(201);
|
|
});
|
|
|
|
tap.test('Integration: should handle /simple path for PyPI', async () => {
|
|
const response = await registry.handleRequest({
|
|
method: 'GET',
|
|
path: '/simple/',
|
|
headers: {
|
|
Accept: 'text/html',
|
|
},
|
|
query: {},
|
|
});
|
|
|
|
expect(response.status).toEqual(200);
|
|
expect(response.headers['Content-Type']).toEqual('text/html');
|
|
expect(response.body).toContain('integration-test-py');
|
|
});
|
|
|
|
tap.test('Integration: should reject PyPI token for RubyGems endpoint', async () => {
|
|
const gemData = await createRubyGem('unauthorized-gem', '1.0.0');
|
|
|
|
const response = await registry.handleRequest({
|
|
method: 'POST',
|
|
path: '/rubygems/api/v1/gems',
|
|
headers: {
|
|
Authorization: pypiToken, // Using PyPI token for RubyGems endpoint
|
|
'Content-Type': 'application/octet-stream',
|
|
},
|
|
query: {},
|
|
body: gemData,
|
|
});
|
|
|
|
expect(response.status).toEqual(401);
|
|
});
|
|
|
|
tap.test('Integration: should reject RubyGems token for PyPI endpoint', async () => {
|
|
const wheelData = await createPythonWheel('unauthorized-py', '1.0.0');
|
|
|
|
const response = await registry.handleRequest({
|
|
method: 'POST',
|
|
path: '/pypi/',
|
|
headers: {
|
|
Authorization: `Bearer ${rubygemsToken}`, // Using RubyGems token for PyPI endpoint
|
|
'Content-Type': 'multipart/form-data',
|
|
},
|
|
query: {},
|
|
body: {
|
|
':action': 'file_upload',
|
|
protocol_version: '1',
|
|
name: 'unauthorized-py',
|
|
version: '1.0.0',
|
|
filetype: 'bdist_wheel',
|
|
pyversion: 'py3',
|
|
metadata_version: '2.1',
|
|
content: wheelData,
|
|
filename: 'unauthorized_py-1.0.0-py3-none-any.whl',
|
|
},
|
|
});
|
|
|
|
expect(response.status).toEqual(401);
|
|
});
|
|
|
|
tap.test('Integration: should return 404 for unknown paths', async () => {
|
|
const response = await registry.handleRequest({
|
|
method: 'GET',
|
|
path: '/unknown-protocol/endpoint',
|
|
headers: {},
|
|
query: {},
|
|
});
|
|
|
|
expect(response.status).toEqual(404);
|
|
expect(response.body).toHaveProperty('error');
|
|
expect((response.body as any).error).toEqual('NOT_FOUND');
|
|
});
|
|
|
|
tap.test('Integration: should retrieve PyPI registry instance', async () => {
|
|
const pypiRegistry = registry.getRegistry('pypi');
|
|
|
|
expect(pypiRegistry).toBeDefined();
|
|
expect(pypiRegistry).not.toBeNull();
|
|
});
|
|
|
|
tap.test('Integration: should retrieve RubyGems registry instance', async () => {
|
|
const rubygemsRegistry = registry.getRegistry('rubygems');
|
|
|
|
expect(rubygemsRegistry).toBeDefined();
|
|
expect(rubygemsRegistry).not.toBeNull();
|
|
});
|
|
|
|
tap.test('Integration: should retrieve all other protocol instances', async () => {
|
|
const ociRegistry = registry.getRegistry('oci');
|
|
const npmRegistry = registry.getRegistry('npm');
|
|
const mavenRegistry = registry.getRegistry('maven');
|
|
const composerRegistry = registry.getRegistry('composer');
|
|
const cargoRegistry = registry.getRegistry('cargo');
|
|
|
|
expect(ociRegistry).toBeDefined();
|
|
expect(npmRegistry).toBeDefined();
|
|
expect(mavenRegistry).toBeDefined();
|
|
expect(composerRegistry).toBeDefined();
|
|
expect(cargoRegistry).toBeDefined();
|
|
});
|
|
|
|
tap.test('Integration: should share storage across protocols', async () => {
|
|
const storage = registry.getStorage();
|
|
|
|
expect(storage).toBeDefined();
|
|
|
|
// Verify storage has methods for all protocols
|
|
expect(typeof storage.getPypiPackageMetadata).toEqual('function');
|
|
expect(typeof storage.getRubyGemsVersions).toEqual('function');
|
|
expect(typeof storage.getNpmPackument).toEqual('function');
|
|
expect(typeof storage.getOciBlob).toEqual('function');
|
|
});
|
|
|
|
tap.test('Integration: should share auth manager across protocols', async () => {
|
|
const authManager = registry.getAuthManager();
|
|
|
|
expect(authManager).toBeDefined();
|
|
|
|
// Verify auth manager has methods for all protocols
|
|
expect(typeof authManager.createPypiToken).toEqual('function');
|
|
expect(typeof authManager.createRubyGemsToken).toEqual('function');
|
|
expect(typeof authManager.createNpmToken).toEqual('function');
|
|
expect(typeof authManager.createOciToken).toEqual('function');
|
|
});
|
|
|
|
tap.test('Integration: should handle concurrent requests to different protocols', async () => {
|
|
const pypiRequest = registry.handleRequest({
|
|
method: 'GET',
|
|
path: '/simple/',
|
|
headers: {},
|
|
query: {},
|
|
});
|
|
|
|
const rubygemsRequest = registry.handleRequest({
|
|
method: 'GET',
|
|
path: '/rubygems/versions',
|
|
headers: {},
|
|
query: {},
|
|
});
|
|
|
|
const [pypiResponse, rubygemsResponse] = await Promise.all([pypiRequest, rubygemsRequest]);
|
|
|
|
expect(pypiResponse.status).toEqual(200);
|
|
expect(rubygemsResponse.status).toEqual(200);
|
|
});
|
|
|
|
tap.test('Integration: should handle package name conflicts across protocols', async () => {
|
|
const packageName = 'conflict-test';
|
|
|
|
// Upload PyPI package
|
|
const wheelData = await createPythonWheel(packageName, '1.0.0');
|
|
const pypiResponse = await registry.handleRequest({
|
|
method: 'POST',
|
|
path: '/pypi/',
|
|
headers: {
|
|
Authorization: `Bearer ${pypiToken}`,
|
|
'Content-Type': 'multipart/form-data',
|
|
},
|
|
query: {},
|
|
body: {
|
|
':action': 'file_upload',
|
|
protocol_version: '1',
|
|
name: packageName,
|
|
version: '1.0.0',
|
|
filetype: 'bdist_wheel',
|
|
pyversion: 'py3',
|
|
metadata_version: '2.1',
|
|
content: wheelData,
|
|
filename: `${packageName.replace(/-/g, '_')}-1.0.0-py3-none-any.whl`,
|
|
},
|
|
});
|
|
|
|
expect(pypiResponse.status).toEqual(201);
|
|
|
|
// Upload RubyGems package with same name
|
|
const gemData = await createRubyGem(packageName, '1.0.0');
|
|
const rubygemsResponse = await registry.handleRequest({
|
|
method: 'POST',
|
|
path: '/rubygems/api/v1/gems',
|
|
headers: {
|
|
Authorization: rubygemsToken,
|
|
'Content-Type': 'application/octet-stream',
|
|
},
|
|
query: {},
|
|
body: gemData,
|
|
});
|
|
|
|
expect(rubygemsResponse.status).toEqual(201);
|
|
|
|
// Both should exist independently
|
|
const pypiGetResponse = await registry.handleRequest({
|
|
method: 'GET',
|
|
path: `/simple/${packageName}/`,
|
|
headers: {},
|
|
query: {},
|
|
});
|
|
|
|
const rubygemsGetResponse = await registry.handleRequest({
|
|
method: 'GET',
|
|
path: `/rubygems/gems/${packageName}-1.0.0.gem`,
|
|
headers: {},
|
|
query: {},
|
|
});
|
|
|
|
expect(pypiGetResponse.status).toEqual(200);
|
|
expect(rubygemsGetResponse.status).toEqual(200);
|
|
});
|
|
|
|
tap.test('Integration: should properly clean up resources on destroy', async () => {
|
|
// Destroy should clean up all registries
|
|
expect(() => registry.destroy()).not.toThrow();
|
|
});
|
|
|
|
tap.postTask('cleanup registry', async () => {
|
|
if (registry && registry.isInitialized()) {
|
|
registry.destroy();
|
|
}
|
|
});
|
|
|
|
export default tap.start();
|