292 lines
7.5 KiB
TypeScript
292 lines
7.5 KiB
TypeScript
|
|
/**
|
||
|
|
* Integration test for smartregistry with smarts3
|
||
|
|
* Verifies that smartregistry works with a local S3-compatible server
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||
|
|
import * as smarts3Module from '@push.rocks/smarts3';
|
||
|
|
import { SmartRegistry } from '../ts/classes.smartregistry.js';
|
||
|
|
import type { IRegistryConfig } from '../ts/core/interfaces.core.js';
|
||
|
|
import * as crypto from 'crypto';
|
||
|
|
|
||
|
|
let s3Server: smarts3Module.Smarts3;
|
||
|
|
let registry: SmartRegistry;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Setup: Start smarts3 server
|
||
|
|
*/
|
||
|
|
tap.test('should start smarts3 server', async () => {
|
||
|
|
s3Server = await smarts3Module.Smarts3.createAndStart({
|
||
|
|
server: {
|
||
|
|
port: 3456, // Use different port to avoid conflicts with other tests
|
||
|
|
host: '0.0.0.0',
|
||
|
|
},
|
||
|
|
storage: {
|
||
|
|
cleanSlate: true, // Fresh storage for each test run
|
||
|
|
bucketsDir: './.nogit/smarts3-test-buckets',
|
||
|
|
},
|
||
|
|
logging: {
|
||
|
|
silent: true, // Reduce test output noise
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
expect(s3Server).toBeDefined();
|
||
|
|
});
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Setup: Create SmartRegistry with smarts3 configuration
|
||
|
|
*/
|
||
|
|
tap.test('should create SmartRegistry instance with smarts3 IS3Descriptor', async () => {
|
||
|
|
// Manually construct IS3Descriptor based on smarts3 configuration
|
||
|
|
// Note: smarts3.getS3Descriptor() returns empty object as of v5.1.0
|
||
|
|
// This is a known limitation - smarts3 doesn't expose its config properly
|
||
|
|
const s3Descriptor = {
|
||
|
|
endpoint: 'localhost',
|
||
|
|
port: 3456,
|
||
|
|
accessKey: 'test', // smarts3 doesn't require real credentials
|
||
|
|
accessSecret: 'test',
|
||
|
|
useSsl: false,
|
||
|
|
region: 'us-east-1',
|
||
|
|
};
|
||
|
|
|
||
|
|
const config: IRegistryConfig = {
|
||
|
|
storage: {
|
||
|
|
...s3Descriptor,
|
||
|
|
bucketName: 'test-registry-smarts3',
|
||
|
|
},
|
||
|
|
auth: {
|
||
|
|
jwtSecret: 'test-secret-key',
|
||
|
|
tokenStore: 'memory',
|
||
|
|
npmTokens: {
|
||
|
|
enabled: true,
|
||
|
|
},
|
||
|
|
ociTokens: {
|
||
|
|
enabled: true,
|
||
|
|
realm: 'https://auth.example.com/token',
|
||
|
|
service: 'test-registry-smarts3',
|
||
|
|
},
|
||
|
|
pypiTokens: {
|
||
|
|
enabled: true,
|
||
|
|
},
|
||
|
|
rubygemsTokens: {
|
||
|
|
enabled: true,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
npm: {
|
||
|
|
enabled: true,
|
||
|
|
basePath: '/npm',
|
||
|
|
},
|
||
|
|
oci: {
|
||
|
|
enabled: true,
|
||
|
|
basePath: '/oci',
|
||
|
|
},
|
||
|
|
pypi: {
|
||
|
|
enabled: true,
|
||
|
|
basePath: '/pypi',
|
||
|
|
},
|
||
|
|
cargo: {
|
||
|
|
enabled: true,
|
||
|
|
basePath: '/cargo',
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
registry = new SmartRegistry(config);
|
||
|
|
await registry.init();
|
||
|
|
|
||
|
|
expect(registry).toBeDefined();
|
||
|
|
});
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Test NPM protocol with smarts3
|
||
|
|
*/
|
||
|
|
tap.test('NPM: should publish package to smarts3', async () => {
|
||
|
|
const authManager = registry.getAuthManager();
|
||
|
|
const userId = await authManager.authenticate({
|
||
|
|
username: 'testuser',
|
||
|
|
password: 'testpass',
|
||
|
|
});
|
||
|
|
const token = await authManager.createNpmToken(userId, false);
|
||
|
|
|
||
|
|
const packageData = {
|
||
|
|
name: 'test-package-smarts3',
|
||
|
|
'dist-tags': {
|
||
|
|
latest: '1.0.0',
|
||
|
|
},
|
||
|
|
versions: {
|
||
|
|
'1.0.0': {
|
||
|
|
name: 'test-package-smarts3',
|
||
|
|
version: '1.0.0',
|
||
|
|
description: 'Test package for smarts3 integration',
|
||
|
|
},
|
||
|
|
},
|
||
|
|
_attachments: {
|
||
|
|
'test-package-smarts3-1.0.0.tgz': {
|
||
|
|
content_type: 'application/octet-stream',
|
||
|
|
data: Buffer.from('test tarball content').toString('base64'),
|
||
|
|
length: 20,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
const response = await registry.handleRequest({
|
||
|
|
method: 'PUT',
|
||
|
|
path: '/npm/test-package-smarts3',
|
||
|
|
headers: {
|
||
|
|
'Authorization': `Bearer ${token}`,
|
||
|
|
'Content-Type': 'application/json',
|
||
|
|
},
|
||
|
|
query: {},
|
||
|
|
body: packageData,
|
||
|
|
});
|
||
|
|
|
||
|
|
expect(response.status).toEqual(201); // 201 Created is correct for publishing
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('NPM: should retrieve package from smarts3', async () => {
|
||
|
|
const response = await registry.handleRequest({
|
||
|
|
method: 'GET',
|
||
|
|
path: '/npm/test-package-smarts3',
|
||
|
|
headers: {},
|
||
|
|
query: {},
|
||
|
|
});
|
||
|
|
|
||
|
|
expect(response.status).toEqual(200);
|
||
|
|
expect(response.body).toHaveProperty('name');
|
||
|
|
expect(response.body.name).toEqual('test-package-smarts3');
|
||
|
|
});
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Test OCI protocol with smarts3
|
||
|
|
*/
|
||
|
|
tap.test('OCI: should store blob in smarts3', async () => {
|
||
|
|
const authManager = registry.getAuthManager();
|
||
|
|
const userId = await authManager.authenticate({
|
||
|
|
username: 'testuser',
|
||
|
|
password: 'testpass',
|
||
|
|
});
|
||
|
|
const token = await authManager.createOciToken(
|
||
|
|
userId,
|
||
|
|
['oci:repository:test-image:push'],
|
||
|
|
3600
|
||
|
|
);
|
||
|
|
|
||
|
|
// Initiate blob upload
|
||
|
|
const initiateResponse = await registry.handleRequest({
|
||
|
|
method: 'POST',
|
||
|
|
path: '/oci/v2/test-image/blobs/uploads/',
|
||
|
|
headers: {
|
||
|
|
'Authorization': `Bearer ${token}`,
|
||
|
|
},
|
||
|
|
query: {},
|
||
|
|
});
|
||
|
|
|
||
|
|
expect(initiateResponse.status).toEqual(202);
|
||
|
|
expect(initiateResponse.headers).toHaveProperty('Location');
|
||
|
|
|
||
|
|
// Extract upload ID from location
|
||
|
|
const location = initiateResponse.headers['Location'];
|
||
|
|
const uploadId = location.split('/').pop();
|
||
|
|
|
||
|
|
// Upload blob data
|
||
|
|
const blobData = Buffer.from('test blob content');
|
||
|
|
const digest = 'sha256:' + crypto
|
||
|
|
.createHash('sha256')
|
||
|
|
.update(blobData)
|
||
|
|
.digest('hex');
|
||
|
|
|
||
|
|
const uploadResponse = await registry.handleRequest({
|
||
|
|
method: 'PUT',
|
||
|
|
path: `/oci/v2/test-image/blobs/uploads/${uploadId}`,
|
||
|
|
headers: {
|
||
|
|
'Authorization': `Bearer ${token}`,
|
||
|
|
'Content-Type': 'application/octet-stream',
|
||
|
|
},
|
||
|
|
query: { digest },
|
||
|
|
body: blobData,
|
||
|
|
});
|
||
|
|
|
||
|
|
expect(uploadResponse.status).toEqual(201);
|
||
|
|
});
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Test PyPI protocol with smarts3
|
||
|
|
*/
|
||
|
|
tap.test('PyPI: should upload package to smarts3', async () => {
|
||
|
|
const authManager = registry.getAuthManager();
|
||
|
|
const userId = await authManager.authenticate({
|
||
|
|
username: 'testuser',
|
||
|
|
password: 'testpass',
|
||
|
|
});
|
||
|
|
const token = await authManager.createPypiToken(userId, false);
|
||
|
|
|
||
|
|
// Note: In a real test, this would be multipart/form-data
|
||
|
|
// For simplicity, we're testing the storage layer
|
||
|
|
const storage = registry.getStorage();
|
||
|
|
|
||
|
|
// Store a test package file
|
||
|
|
const packageContent = Buffer.from('test wheel content');
|
||
|
|
await storage.putPypiPackageFile(
|
||
|
|
'test-package',
|
||
|
|
'test_package-1.0.0-py3-none-any.whl',
|
||
|
|
packageContent
|
||
|
|
);
|
||
|
|
|
||
|
|
// Store metadata
|
||
|
|
const metadata = {
|
||
|
|
name: 'test-package',
|
||
|
|
version: '1.0.0',
|
||
|
|
files: [
|
||
|
|
{
|
||
|
|
filename: 'test_package-1.0.0-py3-none-any.whl',
|
||
|
|
url: '/packages/test-package/test_package-1.0.0-py3-none-any.whl',
|
||
|
|
hashes: { sha256: 'abc123' },
|
||
|
|
},
|
||
|
|
],
|
||
|
|
};
|
||
|
|
await storage.putPypiPackageMetadata('test-package', metadata);
|
||
|
|
|
||
|
|
// Verify stored
|
||
|
|
const retrievedMetadata = await storage.getPypiPackageMetadata('test-package');
|
||
|
|
expect(retrievedMetadata).toBeDefined();
|
||
|
|
expect(retrievedMetadata.name).toEqual('test-package');
|
||
|
|
});
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Test Cargo protocol with smarts3
|
||
|
|
*/
|
||
|
|
tap.test('Cargo: should store crate in smarts3', async () => {
|
||
|
|
const storage = registry.getStorage();
|
||
|
|
|
||
|
|
// Store a test crate index entry
|
||
|
|
const indexEntry = {
|
||
|
|
name: 'test-crate',
|
||
|
|
vers: '1.0.0',
|
||
|
|
deps: [],
|
||
|
|
cksum: 'abc123',
|
||
|
|
features: {},
|
||
|
|
yanked: false,
|
||
|
|
};
|
||
|
|
|
||
|
|
await storage.putCargoIndex('test-crate', [indexEntry]);
|
||
|
|
|
||
|
|
// Store the actual .crate file
|
||
|
|
const crateContent = Buffer.from('test crate tarball');
|
||
|
|
await storage.putCargoCrate('test-crate', '1.0.0', crateContent);
|
||
|
|
|
||
|
|
// Verify stored
|
||
|
|
const retrievedIndex = await storage.getCargoIndex('test-crate');
|
||
|
|
expect(retrievedIndex).toBeDefined();
|
||
|
|
expect(retrievedIndex.length).toEqual(1);
|
||
|
|
expect(retrievedIndex[0].name).toEqual('test-crate');
|
||
|
|
});
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Cleanup: Stop smarts3 server
|
||
|
|
*/
|
||
|
|
tap.test('should stop smarts3 server', async () => {
|
||
|
|
await s3Server.stop();
|
||
|
|
expect(true).toEqual(true); // Just verify it completes without error
|
||
|
|
});
|
||
|
|
|
||
|
|
export default tap.start();
|