import type { IAuthToken, TRegistryProtocol } from '../../ts/core/interfaces.core.js'; import type { IAuthProvider } from '../../ts/core/interfaces.auth.js'; import type { IUpstreamProvider, IUpstreamRegistryConfig, IUpstreamResolutionContext, IProtocolUpstreamConfig, } from '../../ts/upstream/interfaces.upstream.js'; type TTestUpstreamRegistryConfig = Omit, 'id' | 'url' | 'priority' | 'enabled'> & Pick; type TTestProtocolUpstreamConfig = Omit & { upstreams: TTestUpstreamRegistryConfig[]; }; function normalizeUpstreamRegistryConfig( upstream: TTestUpstreamRegistryConfig ): IUpstreamRegistryConfig { return { ...upstream, name: upstream.name ?? upstream.id, auth: upstream.auth ?? { type: 'none' }, }; } function normalizeProtocolUpstreamConfig( config: TTestProtocolUpstreamConfig | undefined ): IProtocolUpstreamConfig | null { if (!config) { return null; } return { ...config, upstreams: config.upstreams.map(normalizeUpstreamRegistryConfig), }; } /** * Create a mock upstream provider that tracks all calls for testing */ export function createTrackingUpstreamProvider( baseConfig?: Partial> ): { provider: IUpstreamProvider; calls: IUpstreamResolutionContext[]; } { const calls: IUpstreamResolutionContext[] = []; const provider: IUpstreamProvider = { async resolveUpstreamConfig(context: IUpstreamResolutionContext) { calls.push({ ...context }); return normalizeProtocolUpstreamConfig(baseConfig?.[context.protocol]); }, }; return { provider, calls }; } /** * Create a mock auth provider for testing pluggable authentication. * Allows customizing behavior for different test scenarios. */ export function createMockAuthProvider(overrides?: Partial): IAuthProvider { const tokens = new Map(); return { init: async () => {}, authenticate: async (credentials) => { return credentials.username; }, validateToken: async (token, protocol) => { const stored = tokens.get(token); if (stored && (!protocol || stored.type === protocol)) { return stored; } if (token === 'valid-mock-token') { return { type: 'npm' as TRegistryProtocol, userId: 'mock-user', scopes: ['npm:*:*:*'], }; } return null; }, createToken: async (userId, protocol, options) => { const tokenId = `mock-${protocol}-${Date.now()}`; const authToken: IAuthToken = { type: protocol, userId, scopes: options?.scopes || [`${protocol}:*:*:*`], readonly: options?.readonly, expiresAt: options?.expiresIn ? new Date(Date.now() + options.expiresIn * 1000) : undefined, }; tokens.set(tokenId, authToken); return tokenId; }, revokeToken: async (token) => { tokens.delete(token); }, authorize: async (token, resource, action) => { if (!token) return false; if (token.readonly && ['write', 'push', 'delete'].includes(action)) { return false; } return true; }, listUserTokens: async (userId) => { const result: Array<{ key: string; readonly: boolean; created: string; protocol?: TRegistryProtocol }> = []; for (const [key, token] of tokens.entries()) { if (token.userId === userId) { result.push({ key: `hash-${key.substring(0, 8)}`, readonly: token.readonly || false, created: new Date().toISOString(), protocol: token.type, }); } } return result; }, ...overrides, }; }