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 { assert.ok(value !== null && typeof value === 'object' && !Array.isArray(value)); return value as Record; } async function startRegistryFixture(): Promise<{ readonly registryUrl: string; readonly close: () => Promise }> { 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((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((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> = []; 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).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, }); }); });