Files
gbrain/test/git-remote-durable.serial.test.ts
T
Garry Tan 3e6f76f341 test(durability): unit + integration coverage for brain-repo durability
git helpers, core harden/unharden, hook+helper E2E (real background push),
cron generators. 41 tests across 4 files.
2026-06-16 22:35:27 -07:00

154 lines
6.4 KiB
TypeScript

/**
* git-remote durability helpers (v0.42.44): divergenceSafePull, detectDefaultBranch,
* pushProbe, isWorkingTreeDirty. Real git against local bare remotes.
*
* Local file transport is enabled via GBRAIN_GIT_ALLOW_FILE_TRANSPORT=1 (the
* documented escape hatch the durability paths honor).
*/
import { describe, test, expect, beforeAll, afterAll, beforeEach, afterEach } from 'bun:test';
import { mkdtempSync, rmSync, writeFileSync, existsSync, mkdirSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import { execFileSync } from 'child_process';
import {
divergenceSafePull, detectDefaultBranch, pushProbe, isWorkingTreeDirty,
} from '../src/core/git-remote.ts';
function git(cwd: string, ...args: string[]): string {
return execFileSync('git', ['-C', cwd, '-c', 'protocol.file.allow=always', ...args], {
stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf-8',
}).trim();
}
function gitIn(cwd: string, ...args: string[]): string {
return execFileSync('git', [...args], { cwd, stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf-8' }).trim();
}
let root: string;
let bare: string;
/** A bare origin + a working clone with one commit on `main`. */
function makePair(): { bare: string; work: string } {
const b = mkdtempSync(join(root, 'origin-')) + '.git';
execFileSync('git', ['init', '-q', '--bare', '-b', 'main', b], { stdio: 'ignore' });
const w = mkdtempSync(join(root, 'work-'));
execFileSync('git', ['-c', 'protocol.file.allow=always', 'clone', '-q', b, w], { stdio: 'ignore' });
git(w, 'config', 'user.email', 't@t.t');
git(w, 'config', 'user.name', 'tester');
writeFileSync(join(w, 'README.md'), 'init\n');
git(w, 'add', 'README.md');
git(w, 'commit', '-qm', 'init');
git(w, 'push', '-q', 'origin', 'main');
// Set origin/HEAD so detectDefaultBranch can resolve it.
try { git(w, 'remote', 'set-head', 'origin', 'main'); } catch { /* */ }
return { bare: b, work: w };
}
function secondClone(b: string): string {
const w = mkdtempSync(join(root, 'work2-'));
execFileSync('git', ['-c', 'protocol.file.allow=always', 'clone', '-q', b, w], { stdio: 'ignore' });
git(w, 'config', 'user.email', 'u@u.u');
git(w, 'config', 'user.name', 'tester2');
return w;
}
beforeAll(() => { process.env.GBRAIN_GIT_ALLOW_FILE_TRANSPORT = '1'; });
afterAll(() => { delete process.env.GBRAIN_GIT_ALLOW_FILE_TRANSPORT; });
beforeEach(() => { root = mkdtempSync(join(tmpdir(), 'gd-')); });
afterEach(() => { rmSync(root, { recursive: true, force: true }); });
describe('detectDefaultBranch', () => {
test('resolves origin/HEAD', () => {
const { work } = makePair();
expect(detectDefaultBranch(work)).toBe('main');
});
test('falls back to main when nothing resolves', () => {
const empty = mkdtempSync(join(root, 'bare-')); // not a git repo
expect(detectDefaultBranch(empty)).toBe('main');
});
});
describe('isWorkingTreeDirty', () => {
test('false when clean, true after an edit', () => {
const { work } = makePair();
expect(isWorkingTreeDirty(work)).toBe(false);
writeFileSync(join(work, 'README.md'), 'changed\n');
expect(isWorkingTreeDirty(work)).toBe(true);
});
});
describe('divergenceSafePull', () => {
test('up_to_date when already current', () => {
const { work } = makePair();
expect(divergenceSafePull(work, 'main').status).toBe('up_to_date');
});
test('advanced when origin has a new commit', () => {
const { bare, work } = makePair();
const other = secondClone(bare);
writeFileSync(join(other, 'b.txt'), 'b\n');
git(other, 'add', 'b.txt'); git(other, 'commit', '-qm', 'b'); git(other, 'push', '-q', 'origin', 'main');
const out = divergenceSafePull(work, 'main');
expect(out.status).toBe('advanced');
expect(existsSync(join(work, 'b.txt'))).toBe(true);
});
test('skipped_dirty when the working tree is dirty', () => {
const { work } = makePair();
writeFileSync(join(work, 'README.md'), 'local edit\n');
expect(divergenceSafePull(work, 'main').status).toBe('skipped_dirty');
});
test('rebases local commits over origin', () => {
const { bare, work } = makePair();
const other = secondClone(bare);
writeFileSync(join(other, 'remote.txt'), 'r\n');
git(other, 'add', 'remote.txt'); git(other, 'commit', '-qm', 'remote'); git(other, 'push', '-q', 'origin', 'main');
// local commit on a DIFFERENT file → clean rebase
writeFileSync(join(work, 'local.txt'), 'l\n');
git(work, 'add', 'local.txt'); git(work, 'commit', '-qm', 'local');
const out = divergenceSafePull(work, 'main');
expect(out.status).toBe('advanced');
expect(existsSync(join(work, 'remote.txt'))).toBe(true);
expect(existsSync(join(work, 'local.txt'))).toBe(true);
});
test('conflict_aborted leaves NO rebase state', () => {
const { bare, work } = makePair();
const other = secondClone(bare);
writeFileSync(join(other, 'README.md'), 'remote version\n');
git(other, 'add', 'README.md'); git(other, 'commit', '-qm', 'remote'); git(other, 'push', '-q', 'origin', 'main');
// local commit touching the SAME line → rebase conflict
writeFileSync(join(work, 'README.md'), 'local version\n');
git(work, 'add', 'README.md'); git(work, 'commit', '-qm', 'local');
const out = divergenceSafePull(work, 'main');
expect(out.status).toBe('conflict_aborted');
// The "never mid-rebase" invariant:
expect(existsSync(join(work, '.git', 'rebase-merge'))).toBe(false);
expect(existsSync(join(work, '.git', 'rebase-apply'))).toBe(false);
// Working tree is usable (HEAD is the local commit, not a conflicted state).
expect(gitIn(work, 'rev-parse', '--abbrev-ref', 'HEAD')).toBe('main');
});
});
describe('pushProbe', () => {
test('ok against a writable remote', () => {
const { work } = makePair();
expect(pushProbe(work, 'main')).toEqual({ ok: true });
});
test('not ok when origin is unreachable', () => {
const { work } = makePair();
git(work, 'remote', 'set-url', 'origin', join(root, 'does-not-exist.git'));
const r = pushProbe(work, 'main');
expect(r.ok).toBe(false);
});
test('redactDetail scrubs a token from the failure detail', () => {
const { work } = makePair();
git(work, 'remote', 'set-url', 'origin', join(root, 'nope-ghp_SECRETTOKEN.git'));
const r = pushProbe(work, 'main', { redactDetail: (s) => s.replaceAll('ghp_SECRETTOKEN', '***') });
expect(r.ok).toBe(false);
if (!r.ok) expect(r.detail.includes('ghp_SECRETTOKEN')).toBe(false);
});
});