const { test, before, after } = require('node:test'); const assert = require('node:assert/strict'); const { spawnSync, execFileSync } = require('node:child_process'); const fs = require('node:fs'); const os = require('node:os'); const path = require('node:path'); const CLI = path.join(__dirname, '..', 'src', 'cli.js'); const TOKEN = 'stoke-secret-token-for-clone-tests'; // A local stand-in for the forge: a directory holding bare repositories laid // out as /.git, so config.url can point at it with a file:// URL // and `repo clone` exercises real git clones without any network. let root; let remote; let work; let cfg; function git(args, cwd) { return execFileSync('git', args, { cwd: cwd || root, encoding: 'utf8' }); } function run(args, cwd) { return spawnSync(process.execPath, [CLI, ...args], { cwd: cwd || work, encoding: 'utf8', env: { ...process.env, STOKE_CONFIG_FILE: cfg }, }); } before(() => { root = fs.mkdtempSync(path.join(os.tmpdir(), 'stoke-clone-test-')); remote = path.join(root, 'remote'); work = path.join(root, 'work'); const seed = path.join(root, 'seed'); fs.mkdirSync(path.join(remote, 'o'), { recursive: true }); fs.mkdirSync(work); git(['init', '-b', 'main', seed]); fs.writeFileSync(path.join(seed, 'README.md'), 'hello from seed\n'); git(['-C', seed, 'add', 'README.md']); git(['-C', seed, '-c', 'user.name=Tester', '-c', 'user.email=tester@example.com', 'commit', '-m', 'initial']); git(['clone', '--bare', seed, path.join(remote, 'o', 'r.git')]); cfg = path.join(root, 'config.json'); fs.writeFileSync(cfg, JSON.stringify({ url: `file://${remote}`, token: TOKEN, login: 'tester' })); }); after(() => { fs.rmSync(root, { recursive: true, force: true }); }); test('repo clone rejects an invalid --depth before running git', () => { const res = run(['repo', 'clone', '-o', 'o', '-r', 'r', '--depth', 'zero']); assert.equal(res.status, 1); assert.match(res.stderr, /Depth must be a positive integer/); }); test('repo clone defaults the destination to the repository name', () => { const res = run(['repo', 'clone', '-o', 'o', '-r', 'r']); assert.equal(res.status, 0, res.stderr); const dest = path.join(work, 'r'); assert.ok(fs.existsSync(path.join(dest, '.git'))); assert.equal(fs.readFileSync(path.join(dest, 'README.md'), 'utf8'), 'hello from seed\n'); }); test('repo clone honors an explicit destination directory', () => { const res = run(['repo', 'clone', '-o', 'o', '-r', 'r', 'custom-dir']); assert.equal(res.status, 0, res.stderr); assert.ok(fs.existsSync(path.join(work, 'custom-dir', '.git'))); }); test('repo clone fails with git\'s status when the destination is not empty', () => { const dest = path.join(work, 'occupied'); fs.mkdirSync(dest); fs.writeFileSync(path.join(dest, 'file.txt'), 'in the way\n'); const res = run(['repo', 'clone', '-o', 'o', '-r', 'r', 'occupied']); assert.equal(res.status, 128); assert.match(res.stderr, /already exists and is not an empty directory/); }); test('repo clone propagates git\'s failure for a missing repository', () => { const res = run(['repo', 'clone', '-o', 'o', '-r', 'nonexistent']); assert.equal(res.status, 128); assert.match(res.stderr, /does not appear to be a git repository|repository.*does not exist/i); }); test('repo clone --origin sets the remote name', () => { const res = run(['repo', 'clone', '-o', 'o', '-r', 'r', '--origin', 'upstream', 'named-origin']); assert.equal(res.status, 0, res.stderr); const url = git(['-C', path.join(work, 'named-origin'), 'config', 'remote.upstream.url']); assert.ok(url.trim().endsWith('/o/r.git')); }); test('repo clone never exposes the token in output or repository config', () => { const ok = run(['repo', 'clone', '-o', 'o', '-r', 'r', 'redacted']); assert.equal(ok.status, 0, ok.stderr); const fail = run(['repo', 'clone', '-o', 'o', '-r', 'nonexistent']); for (const output of [ok.stdout, ok.stderr, fail.stdout, fail.stderr]) { assert.ok(!output.includes(TOKEN), 'token leaked into CLI output'); } const dest = path.join(work, 'redacted'); const gitConfig = fs.readFileSync(path.join(dest, '.git', 'config'), 'utf8'); assert.ok(!gitConfig.includes(TOKEN), 'token persisted in .git/config'); const remoteUrl = git(['-C', dest, 'config', 'remote.origin.url']); assert.ok(!remoteUrl.includes(TOKEN), 'token persisted in the remote URL'); });