mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-02 03:35:09 +02:00
28257b7bc8
CI's "Check VERSION is not stale vs queue" job (job 73105686380) failed with: "VERSION drift: PR #1234 claims v1.15.1.0 but the queue has moved — next free slot is v1.16.1.0." PR #1233 (garrytan/browserharness) entered the queue claiming v1.16.0.0 between when this branch's prior /ship ran and when CI evaluated, so v1.15.1.0 is stale. Rebumping on top. Files updated: - VERSION 1.15.1.0 → 1.16.1.0 - package.json 1.15.1.0 → 1.16.1.0 - CHANGELOG.md heading + Before/After columns 1.15.1.0 → 1.16.1.0 - CHANGELOG removal target (consumers.json + config keys) 1.16.0.0 → 1.17.0.0 - gstack-upgrade/migrations/v1.15.1.0.sh → renamed v1.16.1.0.sh + log prefix - bin/gstack-brain-consumer "DEPRECATED in" + "removal in" 1.15.1.0/1.16.0.0 → 1.16.1.0/1.17.0.0 - bin/gstack-brain-uninstall "since vX.Y.Z.W" 1.15.1.0 → 1.16.1.0 - test/gstack-upgrade-migration-v1_15_1_0.test.ts → renamed v1_16_1_0.test.ts No behavior change. 26/26 wireup + migration tests still pass on the rename. Full bun test suite: exit 0, 0 failures. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
152 lines
5.2 KiB
TypeScript
152 lines
5.2 KiB
TypeScript
/**
|
|
* gstack-upgrade/migrations/v1.16.1.0.sh — migration script unit tests.
|
|
*
|
|
* The migration runs on /gstack-upgrade for users with brain-sync configured but
|
|
* never wired up to gbrain. It has 4 skip conditions and one happy path.
|
|
*
|
|
* Strategy: stub gstack-config and gstack-gbrain-source-wireup binaries on PATH
|
|
* so each skip condition can be triggered independently. The migration script
|
|
* itself is plain bash — we exercise it directly.
|
|
*/
|
|
|
|
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
|
import * as fs from 'fs';
|
|
import * as os from 'os';
|
|
import * as path from 'path';
|
|
import { spawnSync } from 'child_process';
|
|
|
|
const ROOT = path.resolve(import.meta.dir, '..');
|
|
const MIGRATION = path.join(ROOT, 'gstack-upgrade', 'migrations', 'v1.16.1.0.sh');
|
|
|
|
let tmpHome: string;
|
|
let fakeBinDir: string;
|
|
let stubLog: string;
|
|
|
|
function makeFakeStubs(opts: {
|
|
configValue?: string; // value gstack-config returns for gbrain_sync_mode
|
|
configMissing?: boolean; // gstack-config binary itself missing (test edge)
|
|
wireupMissing?: boolean; // wireup binary missing
|
|
wireupExitCode?: number;
|
|
}) {
|
|
const skillsBin = path.join(tmpHome, '.claude', 'skills', 'gstack', 'bin');
|
|
fs.mkdirSync(skillsBin, { recursive: true });
|
|
|
|
if (!opts.configMissing) {
|
|
const cfg = `#!/bin/bash
|
|
echo "gstack-config $@" >> "${stubLog}"
|
|
[ "$1" = "get" ] && [ "$2" = "gbrain_sync_mode" ] && echo "${opts.configValue ?? ''}"
|
|
exit 0
|
|
`;
|
|
fs.writeFileSync(path.join(skillsBin, 'gstack-config'), cfg, { mode: 0o755 });
|
|
}
|
|
|
|
if (!opts.wireupMissing) {
|
|
const wu = `#!/bin/bash
|
|
echo "gstack-gbrain-source-wireup $@" >> "${stubLog}"
|
|
exit ${opts.wireupExitCode ?? 0}
|
|
`;
|
|
fs.writeFileSync(path.join(skillsBin, 'gstack-gbrain-source-wireup'), wu, { mode: 0o755 });
|
|
}
|
|
}
|
|
|
|
function makeBrainGitRepo() {
|
|
const gstackHome = path.join(tmpHome, '.gstack');
|
|
fs.mkdirSync(path.join(gstackHome, '.git'), { recursive: true });
|
|
}
|
|
|
|
function run(opts: { env?: Record<string, string> } = {}) {
|
|
const env = {
|
|
PATH: '/usr/bin:/bin:/opt/homebrew/bin',
|
|
HOME: tmpHome,
|
|
...(opts.env || {}),
|
|
};
|
|
return spawnSync('bash', [MIGRATION], {
|
|
env,
|
|
encoding: 'utf-8',
|
|
cwd: tmpHome,
|
|
});
|
|
}
|
|
|
|
function stubCalls(): string[] {
|
|
if (!fs.existsSync(stubLog)) return [];
|
|
return fs.readFileSync(stubLog, 'utf-8').split('\n').filter((l) => l.trim());
|
|
}
|
|
|
|
beforeEach(() => {
|
|
tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-migration-test-'));
|
|
fakeBinDir = path.join(tmpHome, 'fake-bin');
|
|
fs.mkdirSync(fakeBinDir, { recursive: true });
|
|
stubLog = path.join(tmpHome, 'stub-calls.log');
|
|
});
|
|
|
|
afterEach(() => {
|
|
try {
|
|
fs.rmSync(tmpHome, { recursive: true, force: true });
|
|
} catch {}
|
|
});
|
|
|
|
describe('migrations/v1.16.1.0.sh', () => {
|
|
test('HOME unset: prints message + exit 0 (defensive)', () => {
|
|
// Override HOME to empty string. Bash's [ -z "${HOME:-}" ] guard should fire.
|
|
const r = run({ env: { HOME: '' } });
|
|
expect(r.status).toBe(0);
|
|
expect(r.stderr).toContain('HOME is unset or empty');
|
|
});
|
|
|
|
test('gbrain_sync_mode = off: exit 0 silently (no helper invoked)', () => {
|
|
makeFakeStubs({ configValue: 'off' });
|
|
const r = run();
|
|
expect(r.status).toBe(0);
|
|
// Helper should not have been invoked
|
|
const calls = stubCalls();
|
|
expect(calls.some((c) => c.startsWith('gstack-gbrain-source-wireup'))).toBe(false);
|
|
});
|
|
|
|
test('gbrain_sync_mode unset/empty: exit 0 silently', () => {
|
|
makeFakeStubs({ configValue: '' }); // empty string return
|
|
const r = run();
|
|
expect(r.status).toBe(0);
|
|
const calls = stubCalls();
|
|
expect(calls.some((c) => c.startsWith('gstack-gbrain-source-wireup'))).toBe(false);
|
|
});
|
|
|
|
test('no ~/.gstack/.git: exit 0 silently (no brain-sync configured)', () => {
|
|
makeFakeStubs({ configValue: 'full' });
|
|
// Do NOT call makeBrainGitRepo() — no .gstack/.git directory exists
|
|
const r = run();
|
|
expect(r.status).toBe(0);
|
|
const calls = stubCalls();
|
|
expect(calls.some((c) => c.startsWith('gstack-gbrain-source-wireup'))).toBe(false);
|
|
});
|
|
|
|
test('helper missing on PATH: prints warning, exit 0 (defensive)', () => {
|
|
makeFakeStubs({ configValue: 'full', wireupMissing: true });
|
|
makeBrainGitRepo();
|
|
const r = run();
|
|
expect(r.status).toBe(0);
|
|
expect(r.stderr).toContain('missing or non-executable');
|
|
});
|
|
|
|
test('happy path: invokes the helper', () => {
|
|
makeFakeStubs({ configValue: 'full' });
|
|
makeBrainGitRepo();
|
|
const r = run();
|
|
expect(r.status).toBe(0);
|
|
const calls = stubCalls();
|
|
expect(calls.some((c) => c.startsWith('gstack-gbrain-source-wireup'))).toBe(true);
|
|
// Note: migration invokes WITHOUT --strict (benign-skip semantics for batch upgrade)
|
|
const helperCall = calls.find((c) => c.startsWith('gstack-gbrain-source-wireup'));
|
|
expect(helperCall).not.toContain('--strict');
|
|
});
|
|
|
|
test('helper exits non-zero: migration prints retry hint, exit 0 (non-blocking)', () => {
|
|
// The migration uses `|| { echo retry-hint; }` so non-zero helper still
|
|
// exits 0 and prints a retry hint to stderr.
|
|
makeFakeStubs({ configValue: 'full', wireupExitCode: 2 });
|
|
makeBrainGitRepo();
|
|
const r = run();
|
|
expect(r.status).toBe(0); // migration is non-blocking
|
|
expect(r.stderr).toContain('Wireup exited non-zero');
|
|
});
|
|
});
|