import * as qenv from '@push.rocks/qenv'; import * as crypto from 'crypto'; import * as smartarchive from '@push.rocks/smartarchive'; import { SmartRegistry } from '../../ts/classes.smartregistry.js'; import type { IRegistryConfig } from '../../ts/core/interfaces.core.js'; const testQenv = new qenv.Qenv('./', './.nogit'); /** * Create a test SmartRegistry instance with all protocols enabled */ export async function createTestRegistry(): Promise { // Read S3 config from env.json const s3AccessKey = await testQenv.getEnvVarOnDemand('S3_ACCESSKEY'); const s3SecretKey = await testQenv.getEnvVarOnDemand('S3_SECRETKEY'); const s3Endpoint = await testQenv.getEnvVarOnDemand('S3_ENDPOINT'); const s3Port = await testQenv.getEnvVarOnDemand('S3_PORT'); const config: IRegistryConfig = { storage: { accessKey: s3AccessKey || 'minioadmin', accessSecret: s3SecretKey || 'minioadmin', endpoint: s3Endpoint || 'localhost', port: parseInt(s3Port || '9000', 10), useSsl: false, region: 'us-east-1', bucketName: 'test-registry', }, auth: { jwtSecret: 'test-secret-key', tokenStore: 'memory', npmTokens: { enabled: true, }, ociTokens: { enabled: true, realm: 'https://auth.example.com/token', service: 'test-registry', }, pypiTokens: { enabled: true, }, rubygemsTokens: { enabled: true, }, }, oci: { enabled: true, basePath: '/oci', }, npm: { enabled: true, basePath: '/npm', }, maven: { enabled: true, basePath: '/maven', }, composer: { enabled: true, basePath: '/composer', }, cargo: { enabled: true, basePath: '/cargo', }, pypi: { enabled: true, basePath: '/pypi', }, rubygems: { enabled: true, basePath: '/rubygems', }, }; const registry = new SmartRegistry(config); await registry.init(); return registry; } /** * Helper to create test authentication tokens */ export async function createTestTokens(registry: SmartRegistry) { const authManager = registry.getAuthManager(); // Authenticate and create tokens const userId = await authManager.authenticate({ username: 'testuser', password: 'testpass', }); if (!userId) { throw new Error('Failed to authenticate test user'); } // Create NPM token const npmToken = await authManager.createNpmToken(userId, false); // Create OCI token with full access const ociToken = await authManager.createOciToken( userId, ['oci:repository:*:*'], 3600 ); // Create Maven token with full access const mavenToken = await authManager.createMavenToken(userId, false); // Create Composer token with full access const composerToken = await authManager.createComposerToken(userId, false); // Create Cargo token with full access const cargoToken = await authManager.createCargoToken(userId, false); // Create PyPI token with full access const pypiToken = await authManager.createPypiToken(userId, false); // Create RubyGems token with full access const rubygemsToken = await authManager.createRubyGemsToken(userId, false); return { npmToken, ociToken, mavenToken, composerToken, cargoToken, pypiToken, rubygemsToken, userId }; } /** * Helper to calculate SHA-256 digest in OCI format */ export function calculateDigest(data: Buffer): string { const hash = crypto.createHash('sha256').update(data).digest('hex'); return `sha256:${hash}`; } /** * Helper to create a minimal valid OCI manifest */ export function createTestManifest(configDigest: string, layerDigest: string) { return { schemaVersion: 2, mediaType: 'application/vnd.oci.image.manifest.v1+json', config: { mediaType: 'application/vnd.oci.image.config.v1+json', size: 123, digest: configDigest, }, layers: [ { mediaType: 'application/vnd.oci.image.layer.v1.tar+gzip', size: 456, digest: layerDigest, }, ], }; } /** * Helper to create a minimal valid NPM packument */ export function createTestPackument(packageName: string, version: string, tarballData: Buffer) { const shasum = crypto.createHash('sha1').update(tarballData).digest('hex'); const integrity = `sha512-${crypto.createHash('sha512').update(tarballData).digest('base64')}`; return { name: packageName, versions: { [version]: { name: packageName, version: version, description: 'Test package', main: 'index.js', scripts: {}, dist: { shasum: shasum, integrity: integrity, tarball: `http://localhost:5000/npm/${packageName}/-/${packageName}-${version}.tgz`, }, }, }, 'dist-tags': { latest: version, }, _attachments: { [`${packageName}-${version}.tgz`]: { content_type: 'application/octet-stream', data: tarballData.toString('base64'), length: tarballData.length, }, }, }; } /** * Helper to create a minimal valid Maven POM file */ export function createTestPom( groupId: string, artifactId: string, version: string, packaging: string = 'jar' ): string { return ` 4.0.0 ${groupId} ${artifactId} ${version} ${packaging} ${artifactId} Test Maven artifact `; } /** * Helper to create a test JAR file (minimal ZIP with manifest) */ export function createTestJar(): Buffer { // Create a simple JAR structure (just a manifest) // In practice, this is a ZIP file with at least META-INF/MANIFEST.MF const manifestContent = `Manifest-Version: 1.0 Created-By: SmartRegistry Test `; // For testing, we'll just create a buffer with dummy content // Real JAR would be a proper ZIP archive return Buffer.from(manifestContent, 'utf-8'); } /** * Helper to calculate Maven checksums */ export function calculateMavenChecksums(data: Buffer) { return { md5: crypto.createHash('md5').update(data).digest('hex'), sha1: crypto.createHash('sha1').update(data).digest('hex'), sha256: crypto.createHash('sha256').update(data).digest('hex'), sha512: crypto.createHash('sha512').update(data).digest('hex'), }; } /** * Helper to create a Composer package ZIP using smartarchive */ export async function createComposerZip( vendorPackage: string, version: string, options?: { description?: string; license?: string[]; authors?: Array<{ name: string; email?: string }>; } ): Promise { const zipTools = new smartarchive.ZipTools(); const composerJson = { name: vendorPackage, version: version, type: 'library', description: options?.description || 'Test Composer package', license: options?.license || ['MIT'], authors: options?.authors || [{ name: 'Test Author', email: 'test@example.com' }], require: { php: '>=7.4', }, autoload: { 'psr-4': { 'Vendor\\TestPackage\\': 'src/', }, }, }; // Add a test PHP file const [vendor, pkg] = vendorPackage.split('/'); const namespace = `${vendor.charAt(0).toUpperCase() + vendor.slice(1)}\\${pkg.charAt(0).toUpperCase() + pkg.slice(1).replace(/-/g, '')}`; const testPhpContent = ` { const zipTools = new smartarchive.ZipTools(); const normalizedName = packageName.replace(/-/g, '_'); const distInfoDir = `${normalizedName}-${version}.dist-info`; // Create METADATA file const metadata = `Metadata-Version: 2.1 Name: ${packageName} Version: ${version} Summary: Test Python package Home-page: https://example.com Author: Test Author Author-email: test@example.com License: MIT Platform: UNKNOWN Classifier: Programming Language :: Python :: 3 Requires-Python: >=3.7 Description-Content-Type: text/markdown # ${packageName} Test package for SmartRegistry `; // Create WHEEL file const wheelContent = `Wheel-Version: 1.0 Generator: test 1.0.0 Root-Is-Purelib: true Tag: ${pyVersion}-none-any `; // Create a simple Python module const moduleContent = `"""${packageName} module""" __version__ = "${version}" def hello(): return "Hello from ${packageName}!" `; const entries: smartarchive.IArchiveEntry[] = [ { archivePath: `${distInfoDir}/METADATA`, content: Buffer.from(metadata, 'utf-8'), }, { archivePath: `${distInfoDir}/WHEEL`, content: Buffer.from(wheelContent, 'utf-8'), }, { archivePath: `${distInfoDir}/RECORD`, content: Buffer.from('', 'utf-8'), }, { archivePath: `${distInfoDir}/top_level.txt`, content: Buffer.from(normalizedName, 'utf-8'), }, { archivePath: `${normalizedName}/__init__.py`, content: Buffer.from(moduleContent, 'utf-8'), }, ]; return zipTools.createZip(entries); } /** * Helper to create a test Python source distribution (sdist) using smartarchive */ export async function createPythonSdist( packageName: string, version: string ): Promise { const tarTools = new smartarchive.TarTools(); const normalizedName = packageName.replace(/-/g, '_'); const dirPrefix = `${packageName}-${version}`; // PKG-INFO const pkgInfo = `Metadata-Version: 2.1 Name: ${packageName} Version: ${version} Summary: Test Python package Home-page: https://example.com Author: Test Author Author-email: test@example.com License: MIT `; // setup.py const setupPy = `from setuptools import setup, find_packages setup( name="${packageName}", version="${version}", packages=find_packages(), python_requires=">=3.7", ) `; // Module file const moduleContent = `"""${packageName} module""" __version__ = "${version}" def hello(): return "Hello from ${packageName}!" `; const entries: smartarchive.IArchiveEntry[] = [ { archivePath: `${dirPrefix}/PKG-INFO`, content: Buffer.from(pkgInfo, 'utf-8'), }, { archivePath: `${dirPrefix}/setup.py`, content: Buffer.from(setupPy, 'utf-8'), }, { archivePath: `${dirPrefix}/${normalizedName}/__init__.py`, content: Buffer.from(moduleContent, 'utf-8'), }, ]; return tarTools.packFilesToTarGz(entries); } /** * Helper to calculate PyPI file hashes */ export function calculatePypiHashes(data: Buffer) { return { md5: crypto.createHash('md5').update(data).digest('hex'), sha256: crypto.createHash('sha256').update(data).digest('hex'), blake2b: crypto.createHash('blake2b512').update(data).digest('hex'), }; } /** * Helper to create a test RubyGem file (minimal tar.gz structure) using smartarchive */ export async function createRubyGem( gemName: string, version: string, platform: string = 'ruby' ): Promise { const tarTools = new smartarchive.TarTools(); const gzipTools = new smartarchive.GzipTools(); // Create metadata.gz (simplified) const metadataYaml = `--- !ruby/object:Gem::Specification name: ${gemName} version: !ruby/object:Gem::Version version: ${version} platform: ${platform} authors: - Test Author autorequire: bindir: bin cert_chain: [] date: ${new Date().toISOString().split('T')[0]} dependencies: [] description: Test RubyGem email: test@example.com executables: [] extensions: [] extra_rdoc_files: [] files: - lib/${gemName}.rb homepage: https://example.com licenses: - MIT metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '2.7' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubygems_version: 3.0.0 signing_key: specification_version: 4 summary: Test gem for SmartRegistry test_files: [] `; const metadataGz = await gzipTools.compress(Buffer.from(metadataYaml, 'utf-8')); // Create data.tar.gz content const libContent = `# ${gemName} module ${gemName.charAt(0).toUpperCase() + gemName.slice(1).replace(/-/g, '')} VERSION = "${version}" def self.hello "Hello from #{gemName}!" end end `; const dataEntries: smartarchive.IArchiveEntry[] = [ { archivePath: `lib/${gemName}.rb`, content: Buffer.from(libContent, 'utf-8'), }, ]; const dataTarGz = await tarTools.packFilesToTarGz(dataEntries); // Create the outer gem (tar.gz containing metadata.gz and data.tar.gz) const gemEntries: smartarchive.IArchiveEntry[] = [ { archivePath: 'metadata.gz', content: metadataGz, }, { archivePath: 'data.tar.gz', content: dataTarGz, }, ]; // RubyGems .gem files are plain tar archives (NOT gzipped), containing metadata.gz and data.tar.gz return tarTools.packFiles(gemEntries); } /** * Helper to calculate RubyGems checksums */ export function calculateRubyGemsChecksums(data: Buffer) { return { md5: crypto.createHash('md5').update(data).digest('hex'), sha256: crypto.createHash('sha256').update(data).digest('hex'), }; }