126 lines
3.7 KiB
TypeScript
126 lines
3.7 KiB
TypeScript
|
|
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<Partial<IUpstreamRegistryConfig>, 'id' | 'url' | 'priority' | 'enabled'> &
|
||
|
|
Pick<IUpstreamRegistryConfig, 'id' | 'url' | 'priority' | 'enabled'>;
|
||
|
|
|
||
|
|
type TTestProtocolUpstreamConfig = Omit<IProtocolUpstreamConfig, 'upstreams'> & {
|
||
|
|
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<Record<TRegistryProtocol, TTestProtocolUpstreamConfig>>
|
||
|
|
): {
|
||
|
|
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>): IAuthProvider {
|
||
|
|
const tokens = new Map<string, IAuthToken>();
|
||
|
|
|
||
|
|
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,
|
||
|
|
};
|
||
|
|
}
|