diff --git a/gitnexus/test/unit/git-clone.test.ts b/gitnexus/test/unit/git-clone.test.ts index 020bc0d88..9d496cbea 100644 --- a/gitnexus/test/unit/git-clone.test.ts +++ b/gitnexus/test/unit/git-clone.test.ts @@ -32,6 +32,15 @@ import fs from 'node:fs/promises'; import { spawn } from 'node:child_process'; import { getRemoteOriginUrl } from '../../src/storage/git.js'; +// CLONE_ROOT now derives from getGlobalDir() (GITNEXUS_HOME || ~/.gitnexus), so +// assertions must mirror that derivation rather than hardcoding ~/.gitnexus — +// otherwise an ambient GITNEXUS_HOME (e.g. a CI runner that sets it) makes the +// "direct child of the clone root" assertions fail. Computed at module load, +// the same point CLONE_ROOT is frozen, so the two always agree. +const EXPECTED_CLONE_ROOT = path.resolve( + path.join(process.env.GITNEXUS_HOME || path.join(os.homedir(), '.gitnexus'), 'repos'), +); + describe('git-clone', () => { describe('extractRepoName', () => { it('extracts name from HTTPS URL', () => { @@ -112,11 +121,9 @@ describe('git-clone', () => { }); describe('getCloneDir', () => { - it('returns path under ~/.gitnexus/repos/', () => { + it('returns path under the clone root (getGlobalDir()/repos/)', () => { const dir = getCloneDir('my-repo'); - expect(dir).toContain('.gitnexus'); - expect(dir).toMatch(/repos/); - expect(dir).toContain('my-repo'); + expect(dir).toBe(path.join(EXPECTED_CLONE_ROOT, 'my-repo')); }); it('rejects ".." to prevent path-traversal escape from the clone root', () => { @@ -131,7 +138,7 @@ describe('git-clone', () => { }); it('returned path is always a direct child of the clone root', () => { - const cloneRoot = path.resolve(path.join(os.homedir(), '.gitnexus', 'repos')); + const cloneRoot = EXPECTED_CLONE_ROOT; const dir = getCloneDir('my-repo'); const rel = path.relative(cloneRoot, path.resolve(dir)); // path.relative from the parent to the child must be just the child name — @@ -476,7 +483,7 @@ describe('git-clone', () => { // // These tests do NOT mock spawn — the barrier throws synchronously // before git is invoked, so the rejection is observable directly. - const cloneRoot = path.resolve(path.join(os.homedir(), '.gitnexus', 'repos')); + const cloneRoot = EXPECTED_CLONE_ROOT; it('rejects an absolute target outside CLONE_ROOT', async () => { await expect(cloneOrPull('https://github.com/a/b.git', '/etc/passwd')).rejects.toThrow( diff --git a/gitnexus/test/unit/gitnexus-home-roots.test.ts b/gitnexus/test/unit/gitnexus-home-roots.test.ts index eeb6393e4..ba51507a7 100644 --- a/gitnexus/test/unit/gitnexus-home-roots.test.ts +++ b/gitnexus/test/unit/gitnexus-home-roots.test.ts @@ -67,4 +67,34 @@ describe('GITNEXUS_HOME path roots', () => { ); expect(UPLOAD_ROOT).toBe(path.resolve(os.homedir(), '.gitnexus', 'uploads')); }); + + it('server-mapping falls back to ~/.gitnexus when GITNEXUS_HOME is unset', async () => { + // os.homedir() honors $HOME (and $USERPROFILE on Windows), so redirect the + // home dir to a tmp path to exercise the real fallback (getGlobalDir() -> + // ~/.gitnexus) without writing into the developer's actual + // ~/.gitnexus/server-mapping.json. + const fakeHome = path.join(os.tmpdir(), 'gitnexus-fallback-home'); + const savedHome = process.env.HOME; + const savedUserProfile = process.env.USERPROFILE; + delete process.env.GITNEXUS_HOME; + process.env.HOME = fakeHome; + process.env.USERPROFILE = fakeHome; + try { + await fs.mkdir(path.join(fakeHome, '.gitnexus'), { recursive: true }); + await fs.writeFile( + path.join(fakeHome, '.gitnexus', 'server-mapping.json'), + JSON.stringify({ 'my-repo': 'fallback-service' }), + 'utf-8', + ); + vi.resetModules(); + const { readServerMapping } = await import('../../src/core/embeddings/server-mapping.js'); + expect(await readServerMapping('my-repo')).toBe('fallback-service'); + } finally { + if (savedHome === undefined) delete process.env.HOME; + else process.env.HOME = savedHome; + if (savedUserProfile === undefined) delete process.env.USERPROFILE; + else process.env.USERPROFILE = savedUserProfile; + await fs.rm(fakeHome, { recursive: true, force: true }); + } + }); });