hivecast-sdk/packages/cli/tests/integration/fp30-registry-command.spec.ts

193 lines
6.4 KiB
TypeScript
Raw Permalink Normal View History

import { afterEach, describe, it } from 'node:test';
import { strict as assert } from 'node:assert';
import * as fs from 'node:fs';
import * as http from 'node:http';
import * as os from 'node:os';
import * as path from 'node:path';
import { runMxCli, parseLastJson, assertExitCode } from '../helpers/cli-harness.js';
function addressPort(server: http.Server): number {
const address = server.address();
if (!address || typeof address === 'string') {
throw new Error('Fixture registry did not bind a TCP port');
}
return address.port;
}
function asRecord(value: unknown): Record<string, unknown> {
assert.ok(value !== null && typeof value === 'object' && !Array.isArray(value));
return value as Record<string, unknown>;
}
async function startRegistryFixture(): Promise<{ readonly registryUrl: string; readonly close: () => Promise<void> }> {
const server = http.createServer((req, res) => {
const registryUrl = `http://127.0.0.1:${addressPort(server)}/api/packages/open-matrix/npm/`;
const url = new URL(req.url ?? '/', registryUrl);
if (req.method === 'GET' && url.pathname.endsWith('/-/whoami')) {
res.writeHead(200, { 'content-type': 'application/json' });
res.end(JSON.stringify({ username: 'alice' }));
return;
}
res.writeHead(200, { 'content-type': 'application/json' });
res.end(JSON.stringify({ ok: true }));
});
await new Promise<void>((resolve, reject) => {
server.once('error', reject);
server.listen(0, '127.0.0.1', () => {
server.off('error', reject);
resolve();
});
});
return {
registryUrl: `http://127.0.0.1:${addressPort(server)}/api/packages/open-matrix/npm/`,
close: async () => {
await new Promise<void>((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()));
});
},
};
}
describe('FP-30 mx registry commands', { concurrency: false }, () => {
const tempDirs: string[] = [];
const restoreEnvFns: Array<() => void> = [];
const fixtureClosers: Array<() => Promise<void>> = [];
afterEach(async () => {
while (fixtureClosers.length > 0) {
const close = fixtureClosers.pop();
if (close) {
await close();
}
}
while (restoreEnvFns.length > 0) {
const restore = restoreEnvFns.pop();
if (restore) {
restore();
}
}
while (tempDirs.length > 0) {
const dir = tempDirs.pop();
if (dir) {
fs.rmSync(dir, { recursive: true, force: true });
}
}
});
function useCleanEnv(root: string): void {
const previousHome = process.env.HOME;
const previousMatrixHome = process.env.MATRIX_HOME;
const previousCliConfig = process.env.MATRIX_CLI_CONFIG;
const previousPackageRegistry = process.env.MATRIX_PACKAGE_REGISTRY;
const previousRegistryUrl = process.env.MATRIX_REGISTRY_URL;
const previousNpmUserConfig = process.env.NPM_CONFIG_USERCONFIG;
const previousNodeAuthToken = process.env.NODE_AUTH_TOKEN;
const previousNpmToken = process.env.NPM_TOKEN;
const home = path.join(root, 'home');
fs.mkdirSync(home, { recursive: true });
process.env.HOME = home;
process.env.MATRIX_HOME = path.join(root, '.matrix');
delete process.env.MATRIX_CLI_CONFIG;
delete process.env.MATRIX_PACKAGE_REGISTRY;
delete process.env.MATRIX_REGISTRY_URL;
delete process.env.NPM_CONFIG_USERCONFIG;
delete process.env.NODE_AUTH_TOKEN;
delete process.env.NPM_TOKEN;
restoreEnvFns.push(() => {
restoreEnv('HOME', previousHome);
restoreEnv('MATRIX_HOME', previousMatrixHome);
restoreEnv('MATRIX_CLI_CONFIG', previousCliConfig);
restoreEnv('MATRIX_PACKAGE_REGISTRY', previousPackageRegistry);
restoreEnv('MATRIX_REGISTRY_URL', previousRegistryUrl);
restoreEnv('NPM_CONFIG_USERCONFIG', previousNpmUserConfig);
restoreEnv('NODE_AUTH_TOKEN', previousNodeAuthToken);
restoreEnv('NPM_TOKEN', previousNpmToken);
});
}
function restoreEnv(key: string, value: string | undefined): void {
if (value === undefined) {
delete process.env[key];
return;
}
process.env[key] = value;
}
it('uses a local Gitea registry profile and diagnoses scope config without reading poisoned user npmrc', async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'mx-registry-command-'));
tempDirs.push(root);
useCleanEnv(root);
fs.writeFileSync(path.join(process.env.HOME!, '.npmrc'), [
'@matrix:registry=https://wrong.example.invalid/',
'@open-matrix:registry=https://wrong.example.invalid/',
'@omega:registry=https://wrong.example.invalid/',
'',
].join('\n'), 'utf8');
const fixture = await startRegistryFixture();
fixtureClosers.push(fixture.close);
const credentialsFile = path.join(root, 'credentials.json');
const useResult = await runMxCli([
'node',
'mx',
'registry',
'use',
'local-gitea',
'--registry',
fixture.registryUrl,
'--json',
], { cwd: root });
assertExitCode(useResult, 0);
assert.equal((parseLastJson(useResult.logs) as Record<string, unknown>).registry, fixture.registryUrl);
const loginResult = await runMxCli([
'node',
'mx',
'registry',
'login',
'--registry',
fixture.registryUrl,
'--username',
'alice',
'--token',
'token-registry-command',
'--credentials-file',
credentialsFile,
'--json',
], { cwd: root });
assertExitCode(loginResult, 0);
const doctorResult = await runMxCli([
'node',
'mx',
'registry',
'doctor',
'--registry',
fixture.registryUrl,
'--credentials-file',
credentialsFile,
'--timeout-ms',
'5000',
'--json',
], { cwd: root });
assertExitCode(doctorResult, 0);
const doctor = asRecord(parseLastJson(doctorResult.logs));
const registry = asRecord(doctor.registry);
const credentials = asRecord(doctor.credentials);
const npm = asRecord(doctor.npm);
assert.equal(doctor.ok, true);
assert.equal(registry.url, fixture.registryUrl);
assert.equal(credentials.found, true);
assert.equal(credentials.authOk, true);
assert.equal(npm.allScopesMatch, true);
assert.equal(npm.poisonedUserConfigIgnored, true);
assert.deepEqual(npm.scopeResolution, {
'@open-matrix': fixture.registryUrl,
'@matrix': fixture.registryUrl,
'@omega': fixture.registryUrl,
});
});
});