feat(smarts3): Add local smarts3 testing support and documentation

This commit is contained in:
2025-11-24 00:15:29 +00:00
parent 4f662ff611
commit 284329c191
6 changed files with 414 additions and 21 deletions

View File

@@ -0,0 +1,291 @@
/**
* 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();