fix(core,testing): improve type safety and update tests for latest tstest and storage APIs
This commit is contained in:
+31
-31
@@ -1,4 +1,4 @@
|
||||
import { tap, expect } from '@git.zone/tstest';
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { RegistryStorage } from '../ts/core/classes.registrystorage.js';
|
||||
import { CargoRegistry } from '../ts/cargo/classes.cargoregistry.js';
|
||||
import { AuthManager } from '../ts/core/classes.authmanager.js';
|
||||
@@ -17,21 +17,21 @@ tap.test('should calculate correct index paths for different crate names', async
|
||||
const getPath = (storage as any).getCargoIndexPath.bind(storage);
|
||||
|
||||
// 1-character names
|
||||
expect(getPath('a')).to.equal('cargo/index/1/a');
|
||||
expect(getPath('z')).to.equal('cargo/index/1/z');
|
||||
expect(getPath('a')).toEqual('cargo/index/1/a');
|
||||
expect(getPath('z')).toEqual('cargo/index/1/z');
|
||||
|
||||
// 2-character names
|
||||
expect(getPath('io')).to.equal('cargo/index/2/io');
|
||||
expect(getPath('ab')).to.equal('cargo/index/2/ab');
|
||||
expect(getPath('io')).toEqual('cargo/index/2/io');
|
||||
expect(getPath('ab')).toEqual('cargo/index/2/ab');
|
||||
|
||||
// 3-character names
|
||||
expect(getPath('axo')).to.equal('cargo/index/3/a/axo');
|
||||
expect(getPath('foo')).to.equal('cargo/index/3/f/foo');
|
||||
expect(getPath('axo')).toEqual('cargo/index/3/a/axo');
|
||||
expect(getPath('foo')).toEqual('cargo/index/3/f/foo');
|
||||
|
||||
// 4+ character names
|
||||
expect(getPath('serde')).to.equal('cargo/index/se/rd/serde');
|
||||
expect(getPath('tokio')).to.equal('cargo/index/to/ki/tokio');
|
||||
expect(getPath('my-crate')).to.equal('cargo/index/my/--/my-crate');
|
||||
expect(getPath('serde')).toEqual('cargo/index/se/rd/serde');
|
||||
expect(getPath('tokio')).toEqual('cargo/index/to/ki/tokio');
|
||||
expect(getPath('my-crate')).toEqual('cargo/index/my/--/my-crate');
|
||||
});
|
||||
|
||||
// Test crate file path calculation
|
||||
@@ -46,9 +46,9 @@ tap.test('should calculate correct crate file paths', async () => {
|
||||
// Access private method for testing
|
||||
const getPath = (storage as any).getCargoCratePath.bind(storage);
|
||||
|
||||
expect(getPath('serde', '1.0.0')).to.equal('cargo/crates/serde/serde-1.0.0.crate');
|
||||
expect(getPath('tokio', '1.28.0')).to.equal('cargo/crates/tokio/tokio-1.28.0.crate');
|
||||
expect(getPath('my-crate', '0.1.0')).to.equal('cargo/crates/my-crate/my-crate-0.1.0.crate');
|
||||
expect(getPath('serde', '1.0.0')).toEqual('cargo/crates/serde/serde-1.0.0.crate');
|
||||
expect(getPath('tokio', '1.28.0')).toEqual('cargo/crates/tokio/tokio-1.28.0.crate');
|
||||
expect(getPath('my-crate', '0.1.0')).toEqual('cargo/crates/my-crate/my-crate-0.1.0.crate');
|
||||
});
|
||||
|
||||
// Test crate name validation
|
||||
@@ -73,28 +73,28 @@ tap.test('should validate crate names correctly', async () => {
|
||||
const validate = (registry as any).validateCrateName.bind(registry);
|
||||
|
||||
// Valid names
|
||||
expect(validate('serde')).to.be.true;
|
||||
expect(validate('tokio')).to.be.true;
|
||||
expect(validate('my-crate')).to.be.true;
|
||||
expect(validate('my_crate')).to.be.true;
|
||||
expect(validate('crate123')).to.be.true;
|
||||
expect(validate('a')).to.be.true;
|
||||
expect(validate('serde')).toBeTrue();
|
||||
expect(validate('tokio')).toBeTrue();
|
||||
expect(validate('my-crate')).toBeTrue();
|
||||
expect(validate('my_crate')).toBeTrue();
|
||||
expect(validate('crate123')).toBeTrue();
|
||||
expect(validate('a')).toBeTrue();
|
||||
|
||||
// Invalid names (uppercase not allowed)
|
||||
expect(validate('Serde')).to.be.false;
|
||||
expect(validate('MyCreate')).to.be.false;
|
||||
expect(validate('Serde')).toBeFalse();
|
||||
expect(validate('MyCreate')).toBeFalse();
|
||||
|
||||
// Invalid names (special characters)
|
||||
expect(validate('my.crate')).to.be.false;
|
||||
expect(validate('my@crate')).to.be.false;
|
||||
expect(validate('my crate')).to.be.false;
|
||||
expect(validate('my.crate')).toBeFalse();
|
||||
expect(validate('my@crate')).toBeFalse();
|
||||
expect(validate('my crate')).toBeFalse();
|
||||
|
||||
// Invalid names (too long)
|
||||
const longName = 'a'.repeat(65);
|
||||
expect(validate(longName)).to.be.false;
|
||||
expect(validate(longName)).toBeFalse();
|
||||
|
||||
// Invalid names (empty)
|
||||
expect(validate('')).to.be.false;
|
||||
expect(validate('')).toBeFalse();
|
||||
});
|
||||
|
||||
// Test config.json response
|
||||
@@ -122,12 +122,12 @@ tap.test('should return valid config.json', async () => {
|
||||
query: {},
|
||||
});
|
||||
|
||||
expect(response.status).to.equal(200);
|
||||
expect(response.headers['Content-Type']).to.equal('application/json');
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.headers['Content-Type']).toEqual('application/json');
|
||||
const body = await streamToJson(response.body);
|
||||
expect(body).to.be.an('object');
|
||||
expect(body.dl).to.include('/api/v1/crates/{crate}/{version}/download');
|
||||
expect(body.api).to.equal('http://localhost:5000/cargo');
|
||||
expect(body).toBeTypeOf('object');
|
||||
expect(body.dl).toInclude('/api/v1/crates/{crate}/{version}/download');
|
||||
expect(body.api).toEqual('http://localhost:5000/cargo');
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
|
||||
@@ -26,9 +26,6 @@ tap.test('setup: should create DefaultAuthProvider', async () => {
|
||||
realm: 'https://auth.example.com/token',
|
||||
service: 'test-registry',
|
||||
},
|
||||
mavenTokens: { enabled: true },
|
||||
cargoTokens: { enabled: true },
|
||||
composerTokens: { enabled: true },
|
||||
pypiTokens: { enabled: true },
|
||||
rubygemsTokens: { enabled: true },
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { tapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
|
||||
import { TapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
|
||||
import { SmartRegistry } from '../ts/index.js';
|
||||
import { createTestRegistry, createTestTokens } from './helpers/registry.js';
|
||||
import type { IRequestContext, IResponse } from '../ts/core/interfaces.core.js';
|
||||
@@ -13,6 +13,8 @@ import * as url from 'url';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const tapNodeTools = new TapNodeTools(tap);
|
||||
|
||||
// Test context
|
||||
let registry: SmartRegistry;
|
||||
let server: http.Server;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { tapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
|
||||
import { TapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
|
||||
import { SmartRegistry } from '../ts/index.js';
|
||||
import { createTestRegistry, createTestTokens, createComposerZip, generateTestRunId } from './helpers/registry.js';
|
||||
import type { IRequestContext, IResponse } from '../ts/core/interfaces.core.js';
|
||||
@@ -13,6 +13,8 @@ import * as url from 'url';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const tapNodeTools = new TapNodeTools(tap);
|
||||
|
||||
// Test context
|
||||
let registry: SmartRegistry;
|
||||
let server: http.Server;
|
||||
@@ -208,7 +210,7 @@ async function uploadComposerPackage(
|
||||
'Content-Type': 'application/zip',
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: zipData,
|
||||
body: Uint8Array.from(zipData),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
|
||||
@@ -97,6 +97,9 @@ tap.test('NPM: should publish package to smarts3', async () => {
|
||||
username: 'testuser',
|
||||
password: 'testpass',
|
||||
});
|
||||
if (!userId) {
|
||||
throw new Error('Expected test user authentication to succeed');
|
||||
}
|
||||
const token = await authManager.createNpmToken(userId, false);
|
||||
|
||||
const packageData = {
|
||||
@@ -157,6 +160,9 @@ tap.test('OCI: should store blob in smarts3', async () => {
|
||||
username: 'testuser',
|
||||
password: 'testpass',
|
||||
});
|
||||
if (!userId) {
|
||||
throw new Error('Expected test user authentication to succeed');
|
||||
}
|
||||
const token = await authManager.createOciToken(
|
||||
userId,
|
||||
['oci:repository:test-image:push'],
|
||||
@@ -260,8 +266,11 @@ tap.test('Cargo: should store crate in smarts3', async () => {
|
||||
// Verify stored
|
||||
const retrievedIndex = await storage.getCargoIndex('test-crate');
|
||||
expect(retrievedIndex).toBeDefined();
|
||||
if (!retrievedIndex) {
|
||||
throw new Error('Expected Cargo index to be stored');
|
||||
}
|
||||
expect(retrievedIndex.length).toEqual(1);
|
||||
expect(retrievedIndex[0].name).toEqual('test-crate');
|
||||
expect(retrievedIndex[0]?.name).toEqual('test-crate');
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { tapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
|
||||
import { TapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
|
||||
import { SmartRegistry } from '../ts/index.js';
|
||||
import { createTestRegistry, createTestTokens, createTestPom, createTestJar } from './helpers/registry.js';
|
||||
import type { IRequestContext, IResponse } from '../ts/core/interfaces.core.js';
|
||||
@@ -13,6 +13,8 @@ import * as url from 'url';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const tapNodeTools = new TapNodeTools(tap);
|
||||
|
||||
// Test context
|
||||
let registry: SmartRegistry;
|
||||
let server: http.Server;
|
||||
@@ -270,7 +272,7 @@ tap.test('Maven CLI: should verify mvn is installed', async () => {
|
||||
} catch (error) {
|
||||
console.log('Maven CLI not available, skipping native CLI tests');
|
||||
// Skip remaining tests if Maven is not installed
|
||||
tap.skip.test('Maven CLI: remaining tests skipped - mvn not available');
|
||||
tap.skip.test('Maven CLI: remaining tests skipped - mvn not available', async () => {});
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { tapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
|
||||
import { SmartRegistry } from '../ts/index.js';
|
||||
import { createTestRegistry, createTestTokens, cleanupS3Bucket } from './helpers/registry.js';
|
||||
import type { IRequestContext, IResponse } from '../ts/core/interfaces.core.js';
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { tapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
|
||||
import { TapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
|
||||
import { SmartRegistry } from '../ts/index.js';
|
||||
import type { IRequestContext, IResponse, IRegistryConfig } from '../ts/core/interfaces.core.js';
|
||||
import * as qenv from '@push.rocks/qenv';
|
||||
@@ -13,6 +13,8 @@ import * as url from 'url';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const tapNodeTools = new TapNodeTools(tap);
|
||||
|
||||
const testQenv = new qenv.Qenv('./', './.nogit');
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { tapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
|
||||
import { TapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
|
||||
import { SmartRegistry } from '../ts/index.js';
|
||||
import { createTestRegistry, createTestTokens, createPythonWheel, createPythonSdist, generateTestRunId } from './helpers/registry.js';
|
||||
import type { IRequestContext, IResponse } from '../ts/core/interfaces.core.js';
|
||||
@@ -13,6 +13,8 @@ import * as url from 'url';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const tapNodeTools = new TapNodeTools(tap);
|
||||
|
||||
// Test context
|
||||
let registry: SmartRegistry;
|
||||
let server: http.Server;
|
||||
@@ -291,7 +293,7 @@ tap.test('PyPI CLI: should verify twine is installed', async () => {
|
||||
|
||||
if (!hasPip && !hasTwine) {
|
||||
console.log('Neither pip nor twine available, skipping native CLI tests');
|
||||
tap.skip.test('PyPI CLI: remaining tests skipped - no CLI tools available');
|
||||
tap.skip.test('PyPI CLI: remaining tests skipped - no CLI tools available', async () => {});
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ tap.test('PyPI: should upload wheel file (POST /pypi/)', async () => {
|
||||
formData.append('pyversion', 'py3');
|
||||
formData.append('metadata_version', '2.1');
|
||||
formData.append('sha256_digest', hashes.sha256);
|
||||
formData.append('content', new Blob([testWheelData]), filename);
|
||||
formData.append('content', new Blob([Uint8Array.from(testWheelData)]), filename);
|
||||
|
||||
const response = await registry.handleRequest({
|
||||
method: 'POST',
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { tapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
|
||||
import { TapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
|
||||
import { SmartRegistry } from '../ts/index.js';
|
||||
import { createTestRegistry, createTestTokens, createRubyGem } from './helpers/registry.js';
|
||||
import type { IRequestContext, IResponse } from '../ts/core/interfaces.core.js';
|
||||
@@ -13,6 +13,8 @@ import * as url from 'url';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const tapNodeTools = new TapNodeTools(tap);
|
||||
|
||||
// Test context
|
||||
let registry: SmartRegistry;
|
||||
let server: http.Server;
|
||||
|
||||
@@ -89,6 +89,17 @@ tap.test('setup: should create S3 storage backend', async () => {
|
||||
}
|
||||
return paths;
|
||||
},
|
||||
objectExists: async (key: string): Promise<boolean> => {
|
||||
try {
|
||||
const data = await s3Bucket.fastGet({ path: key });
|
||||
return !!data;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
getMetadata: async (_key: string): Promise<Record<string, string> | null> => {
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
expect(storageBackend).toBeTruthy();
|
||||
@@ -584,9 +595,8 @@ tap.test('cleanup: should stop cache and clean up bucket', async () => {
|
||||
// Clean up test bucket
|
||||
if (s3Bucket) {
|
||||
try {
|
||||
const files = await s3Bucket.fastList({});
|
||||
for (const file of files) {
|
||||
await s3Bucket.fastRemove({ path: file.name });
|
||||
for await (const filePath of s3Bucket.listAllObjects('')) {
|
||||
await s3Bucket.fastRemove({ path: filePath });
|
||||
}
|
||||
await smartBucket.removeBucket(bucketName);
|
||||
} catch {
|
||||
|
||||
@@ -9,9 +9,19 @@ import type {
|
||||
IUpstreamProvider,
|
||||
IUpstreamResolutionContext,
|
||||
IProtocolUpstreamConfig,
|
||||
IUpstreamRegistryConfig,
|
||||
} from '../ts/upstream/interfaces.upstream.js';
|
||||
import type { TRegistryProtocol } from '../ts/core/interfaces.core.js';
|
||||
|
||||
const createUpstream = (id: string, url: string): IUpstreamRegistryConfig => ({
|
||||
id,
|
||||
name: id,
|
||||
url,
|
||||
priority: 1,
|
||||
enabled: true,
|
||||
auth: { type: 'none' },
|
||||
});
|
||||
|
||||
// =============================================================================
|
||||
// StaticUpstreamProvider Tests
|
||||
// =============================================================================
|
||||
@@ -19,7 +29,7 @@ import type { TRegistryProtocol } from '../ts/core/interfaces.core.js';
|
||||
tap.test('StaticUpstreamProvider: should return config for configured protocol', async () => {
|
||||
const npmConfig: IProtocolUpstreamConfig = {
|
||||
enabled: true,
|
||||
upstreams: [{ id: 'npmjs', url: 'https://registry.npmjs.org', priority: 1, enabled: true }],
|
||||
upstreams: [createUpstream('npmjs', 'https://registry.npmjs.org')],
|
||||
};
|
||||
|
||||
const provider = new StaticUpstreamProvider({
|
||||
@@ -43,7 +53,7 @@ tap.test('StaticUpstreamProvider: should return null for unconfigured protocol',
|
||||
const provider = new StaticUpstreamProvider({
|
||||
npm: {
|
||||
enabled: true,
|
||||
upstreams: [{ id: 'npmjs', url: 'https://registry.npmjs.org', priority: 1, enabled: true }],
|
||||
upstreams: [createUpstream('npmjs', 'https://registry.npmjs.org')],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -62,15 +72,15 @@ tap.test('StaticUpstreamProvider: should support multiple protocols', async () =
|
||||
const provider = new StaticUpstreamProvider({
|
||||
npm: {
|
||||
enabled: true,
|
||||
upstreams: [{ id: 'npmjs', url: 'https://registry.npmjs.org', priority: 1, enabled: true }],
|
||||
upstreams: [createUpstream('npmjs', 'https://registry.npmjs.org')],
|
||||
},
|
||||
oci: {
|
||||
enabled: true,
|
||||
upstreams: [{ id: 'dockerhub', url: 'https://registry-1.docker.io', priority: 1, enabled: true }],
|
||||
upstreams: [createUpstream('dockerhub', 'https://registry-1.docker.io')],
|
||||
},
|
||||
maven: {
|
||||
enabled: true,
|
||||
upstreams: [{ id: 'central', url: 'https://repo1.maven.org/maven2', priority: 1, enabled: true }],
|
||||
upstreams: [createUpstream('central', 'https://repo1.maven.org/maven2')],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -113,7 +123,7 @@ tap.test('Provider Integration: should create registry with upstream provider',
|
||||
trackingProvider = createTrackingUpstreamProvider({
|
||||
npm: {
|
||||
enabled: true,
|
||||
upstreams: [{ id: 'test-npm', url: 'https://registry.npmjs.org', priority: 1, enabled: true }],
|
||||
upstreams: [createUpstream('test-npm', 'https://registry.npmjs.org')],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -200,13 +210,13 @@ tap.test('Custom Provider: should support dynamic resolution based on context',
|
||||
// Internal packages go to private registry
|
||||
return {
|
||||
enabled: true,
|
||||
upstreams: [{ id: 'private', url: 'https://private.registry.com', priority: 1, enabled: true }],
|
||||
upstreams: [createUpstream('private', 'https://private.registry.com')],
|
||||
};
|
||||
}
|
||||
// Everything else goes to public registry
|
||||
return {
|
||||
enabled: true,
|
||||
upstreams: [{ id: 'public', url: 'https://registry.npmjs.org', priority: 1, enabled: true }],
|
||||
upstreams: [createUpstream('public', 'https://registry.npmjs.org')],
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -237,12 +247,12 @@ tap.test('Custom Provider: should support actor-based resolution', async () => {
|
||||
if (context.actor?.orgId === 'enterprise-org') {
|
||||
return {
|
||||
enabled: true,
|
||||
upstreams: [{ id: 'enterprise', url: 'https://enterprise.registry.com', priority: 1, enabled: true }],
|
||||
upstreams: [createUpstream('enterprise', 'https://enterprise.registry.com')],
|
||||
};
|
||||
}
|
||||
return {
|
||||
enabled: true,
|
||||
upstreams: [{ id: 'default', url: 'https://registry.npmjs.org', priority: 1, enabled: true }],
|
||||
upstreams: [createUpstream('default', 'https://registry.npmjs.org')],
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -277,7 +287,7 @@ tap.test('Custom Provider: should support disabling upstream for specific resour
|
||||
}
|
||||
return {
|
||||
enabled: true,
|
||||
upstreams: [{ id: 'public', url: 'https://registry.npmjs.org', priority: 1, enabled: true }],
|
||||
upstreams: [createUpstream('public', 'https://registry.npmjs.org')],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user