From 284329c191a84797ae5bb8d2fd058ac729628624 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Mon, 24 Nov 2025 00:15:29 +0000 Subject: [PATCH] feat(smarts3): Add local smarts3 testing support and documentation --- changelog.md | 7 + package.json | 1 + pnpm-lock.yaml | 58 +++-- readme.md | 76 +++++++ test/test.integration.smarts3.node.ts | 291 ++++++++++++++++++++++++++ ts/00_commitinfo_data.ts | 2 +- 6 files changed, 414 insertions(+), 21 deletions(-) create mode 100644 test/test.integration.smarts3.node.ts diff --git a/changelog.md b/changelog.md index 2b6deda..9e3aa5e 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ # Changelog +## 2025-11-24 - 1.8.0 - feat(smarts3) +Add local smarts3 testing support and documentation + +- Added @push.rocks/smarts3 ^5.1.0 to devDependencies to enable a local S3-compatible test server. +- Updated README with a new "Testing with smarts3" section including a Quick Start example and integration test commands. +- Documented benefits and CI-friendly usage for running registry integration tests locally without cloud credentials. + ## 2025-11-23 - 1.7.0 - feat(core) Standardize S3 storage config using @tsclass/tsclass IS3Descriptor and wire it into RegistryStorage and plugins exports; update README and package dependencies. diff --git a/package.json b/package.json index 3708027..846beba 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@git.zone/tsbundle": "^2.0.5", "@git.zone/tsrun": "^2.0.0", "@git.zone/tstest": "^3.1.0", + "@push.rocks/smarts3": "^5.1.0", "@types/node": "^24.10.1" }, "repository": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 46d61b5..f3e9408 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -39,6 +39,9 @@ importers: '@git.zone/tstest': specifier: ^3.1.0 version: 3.1.0(socks@2.8.7)(typescript@5.9.3) + '@push.rocks/smarts3': + specifier: ^5.1.0 + version: 5.1.0 '@types/node': specifier: ^24.10.1 version: 24.10.1 @@ -576,7 +579,6 @@ packages: '@koa/router@9.4.0': resolution: {integrity: sha512-dOOXgzqaDoHu5qqMEPLKEgLz5CeIA7q8+1W62mCvFVCOqeC71UoTGJ4u1xUSOpIl2J1x2pqrNULkFteUeZW3/A==} engines: {node: '>= 8.0.0'} - deprecated: '**IMPORTANT 10x+ PERFORMANCE UPGRADE**: Please upgrade to v12.0.1+ as we have fixed an issue with debuglog causing 10x slower router benchmark performance, see https://github.com/koajs/router/pull/173' '@leichtgewicht/ip-codec@2.0.5': resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==} @@ -763,6 +765,9 @@ packages: '@push.rocks/smartfile@11.2.7': resolution: {integrity: sha512-8Yp7/sAgPpWJBHohV92ogHWKzRomI5MEbSG6b5W2n18tqwfAmjMed0rQvsvGrSBlnEWCKgoOrYIIZbLO61+J0Q==} + '@push.rocks/smartfs@1.1.0': + resolution: {integrity: sha512-fg8JIjFUPPX5laRoBpTaGwhMfZ3Y8mFT4fUaW54Y4J/BfOBa/y0+rIFgvgvqcOZgkQlyZU+FIfL8Z6zezqxyTg==} + '@push.rocks/smartguard@3.1.0': resolution: {integrity: sha512-J23q84f1O+TwFGmd4lrO9XLHUh2DaLXo9PN/9VmTWYzTkQDv5JehmifXVI0esophXcCIfbdIu6hbt7/aHlDF4A==} @@ -850,6 +855,9 @@ packages: '@push.rocks/smarts3@2.2.7': resolution: {integrity: sha512-9ZXGMlmUL2Wd+YJO0xOB8KyqPf4V++fWJvTq4s76bnqEuaCr9OLfq6czhban+i4cD3ZdIjehfuHqctzjuLw8Jw==} + '@push.rocks/smarts3@5.1.0': + resolution: {integrity: sha512-jmoSaJkdWOWxiS5aiTXvE6+zS7n6+OZe1jxIOq3weX54tPmDCjpLLTl12rdgvvpDE1ai5ayftirWhLGk96hkaw==} + '@push.rocks/smartshell@3.3.0': resolution: {integrity: sha512-m0w618H6YBs+vXGz1CgS4nPi5CUAnqRtckcS9/koGwfcIx1IpjqmiP47BoCTbdgcv0IPUxQVBG1IXTHPuZ8Z5g==} @@ -1754,7 +1762,7 @@ packages: engines: {node: '>=12'} co@4.6.0: - resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + resolution: {integrity: sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} color-convert@1.9.3: @@ -1769,7 +1777,7 @@ packages: engines: {node: '>=14.6'} color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + resolution: {integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=} color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} @@ -1894,7 +1902,7 @@ packages: engines: {node: '>=10'} deep-equal@1.0.1: - resolution: {integrity: sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==} + resolution: {integrity: sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=} deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} @@ -1925,10 +1933,10 @@ packages: engines: {node: '>=0.4.0'} delegates@1.0.0: - resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + resolution: {integrity: sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=} depd@1.1.2: - resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} + resolution: {integrity: sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=} engines: {node: '>= 0.6'} depd@2.0.0: @@ -1980,7 +1988,7 @@ packages: resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} encodeurl@1.0.2: - resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + resolution: {integrity: sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=} engines: {node: '>= 0.8'} encodeurl@2.0.0: @@ -2041,7 +2049,7 @@ packages: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + resolution: {integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=} engines: {node: '>=0.8.0'} escape-string-regexp@5.0.0: @@ -2202,7 +2210,7 @@ packages: engines: {node: '>= 0.6'} fresh@0.5.2: - resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + resolution: {integrity: sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=} engines: {node: '>= 0.6'} fresh@2.0.0: @@ -2291,7 +2299,7 @@ packages: engines: {node: '>=18.0.0'} has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + resolution: {integrity: sha1-tdRU3CGZriJWmfNGfloH87lVuv0=} engines: {node: '>=4'} has-property-descriptors@1.0.2: @@ -2367,7 +2375,7 @@ packages: resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} humanize-number@0.0.2: - resolution: {integrity: sha512-un3ZAcNQGI7RzaWGZzQDH47HETM4Wrj6z6E4TId8Yeq9w5ZKUVB1nrT2jwFheTUjEmqcgTjXDc959jum+ai1kQ==} + resolution: {integrity: sha1-EcCvakcWQ2M1iFiASPF5lUFInBg=} iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} @@ -2496,7 +2504,7 @@ packages: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} jsonfile@4.0.0: - resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + resolution: {integrity: sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=} jsonfile@6.2.0: resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} @@ -2504,7 +2512,6 @@ packages: keygrip@1.1.0: resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} engines: {node: '>= 0.6'} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -2686,7 +2693,7 @@ packages: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} media-typer@0.3.0: - resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + resolution: {integrity: sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=} engines: {node: '>= 0.6'} media-typer@1.1.0: @@ -2701,7 +2708,7 @@ packages: engines: {node: '>=18'} methods@1.1.2: - resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + resolution: {integrity: sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=} engines: {node: '>= 0.6'} micromark-core-commonmark@2.0.3: @@ -2954,7 +2961,7 @@ packages: resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} only@0.0.2: - resolution: {integrity: sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==} + resolution: {integrity: sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=} open@8.4.2: resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} @@ -3026,7 +3033,7 @@ packages: engines: {node: '>= 0.8'} passthrough-counter@1.0.0: - resolution: {integrity: sha512-Wy8PXTLqPAN0oEgBrlnsXPMww3SYJ44tQ8aVrGAI4h4JZYCS0oYqsPqtPR8OhJpv6qFbpbB7XAn0liKV7EXubA==} + resolution: {integrity: sha1-GWfZ5m2lcrXAI8eH2xEqOHqxZvo=} path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} @@ -3352,10 +3359,10 @@ packages: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} stack-trace@0.0.10: - resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} + resolution: {integrity: sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=} statuses@1.5.0: - resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + resolution: {integrity: sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=} engines: {node: '>= 0.6'} statuses@2.0.1: @@ -3367,7 +3374,7 @@ packages: engines: {node: '>= 0.8'} streamsearch@0.1.2: - resolution: {integrity: sha512-jos8u++JKm0ARcSUTAZXOVC0mSox7Bhn6sBgty73P1f3JGf7yG2clTbBNHUdde/kdvP2FESam+vM6l8jBrNxHA==} + resolution: {integrity: sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=} engines: {node: '>=0.8.0'} streamx@2.23.0: @@ -5446,6 +5453,10 @@ snapshots: glob: 11.1.0 js-yaml: 4.1.1 + '@push.rocks/smartfs@1.1.0': + dependencies: + '@push.rocks/smartpath': 6.0.0 + '@push.rocks/smartguard@3.1.0': dependencies: '@push.rocks/smartpromise': 4.2.3 @@ -5694,6 +5705,13 @@ snapshots: - aws-crt - supports-color + '@push.rocks/smarts3@5.1.0': + dependencies: + '@push.rocks/smartfs': 1.1.0 + '@push.rocks/smartpath': 6.0.0 + '@push.rocks/smartxml': 2.0.0 + '@tsclass/tsclass': 9.3.0 + '@push.rocks/smartshell@3.3.0': dependencies: '@push.rocks/smartdelay': 3.0.5 diff --git a/readme.md b/readme.md index 0731101..c21ca52 100644 --- a/readme.md +++ b/readme.md @@ -1024,6 +1024,82 @@ pnpm run build pnpm test ``` +## ๐Ÿงช Testing with smarts3 + +smartregistry works seamlessly with [@push.rocks/smarts3](https://code.foss.global/push.rocks/smarts3), a local S3-compatible server for testing. This allows you to test the registry without needing cloud credentials or external services. + +### Quick Start with smarts3 + +```typescript +import { Smarts3 } from '@push.rocks/smarts3'; +import { SmartRegistry } from '@push.rocks/smartregistry'; + +// Start local S3 server +const s3Server = await Smarts3.createAndStart({ + server: { port: 3456 }, + storage: { cleanSlate: true }, +}); + +// Manually create IS3Descriptor matching smarts3 configuration +// Note: smarts3 v5.1.0 doesn't properly expose getS3Descriptor() yet +const s3Descriptor = { + endpoint: 'localhost', + port: 3456, + accessKey: 'test', + accessSecret: 'test', + useSsl: false, + region: 'us-east-1', +}; + +// Create registry with smarts3 configuration +const registry = new SmartRegistry({ + storage: { + ...s3Descriptor, + bucketName: 'my-test-registry', + }, + auth: { + jwtSecret: 'test-secret', + tokenStore: 'memory', + npmTokens: { enabled: true }, + ociTokens: { + enabled: true, + realm: 'https://auth.example.com/token', + service: 'my-registry', + }, + }, + npm: { enabled: true, basePath: '/npm' }, + oci: { enabled: true, basePath: '/oci' }, + pypi: { enabled: true, basePath: '/pypi' }, + cargo: { enabled: true, basePath: '/cargo' }, +}); + +await registry.init(); + +// Use registry... +// Your tests here + +// Cleanup +await s3Server.stop(); +``` + +### Benefits of Testing with smarts3 + +- โœ… **Zero Setup** - No cloud credentials or external services needed +- โœ… **Fast** - Local filesystem storage, no network latency +- โœ… **Isolated** - Clean slate per test run, no shared state +- โœ… **CI/CD Ready** - Works in automated pipelines without configuration +- โœ… **Full Compatibility** - Implements S3 API, works with IS3Descriptor + +### Running Integration Tests + +```bash +# Run smarts3 integration test +pnpm exec tstest test/test.integration.smarts3.node.ts --verbose + +# Run all tests (includes smarts3) +pnpm test +``` + ## License and Legal Information This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. diff --git a/test/test.integration.smarts3.node.ts b/test/test.integration.smarts3.node.ts new file mode 100644 index 0000000..0b9acad --- /dev/null +++ b/test/test.integration.smarts3.node.ts @@ -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(); diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index da0f77d..4f46650 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@push.rocks/smartregistry', - version: '1.7.0', + version: '1.8.0', description: 'A composable TypeScript library implementing OCI, NPM, Maven, Cargo, Composer, PyPI, and RubyGems registries for building unified container and package registries' }