From 2d88f5f02a0a808243fefd0b3d817c887793cece Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Sat, 14 Mar 2026 07:19:11 -0500 Subject: [PATCH] test: add update-check exit code regression tests Guards against the "exits 1 when up to date" bug that broke skill preambles. Two new tests: real VERSION + unreachable remote, and multi-call sequence verifying exit 0 in all states. Co-Authored-By: Claude Opus 4.6 --- browse/test/gstack-update-check.test.ts | 48 +++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/browse/test/gstack-update-check.test.ts b/browse/test/gstack-update-check.test.ts index ac674b3d6d8d10133ef7c6dfbd5296824fd83d48..a0fefb73810639f75e817c19feb0fed020e3fecd 100644 --- a/browse/test/gstack-update-check.test.ts +++ b/browse/test/gstack-update-check.test.ts @@ -185,4 +185,52 @@ describe('gstack-update-check', () => { expect(exitCode).toBe(0); expect(existsSync(join(newStateDir, 'last-update-check'))).toBe(true); }); + + // ─── E2E regression: always exit 0 ─────────────────────────── + // Agents call this on every skill invocation. Exit code 1 breaks + // the preamble and confuses the agent. This test guards against + // regressions like the "exits 1 when up to date" bug. + test('exits 0 with real project VERSION and unreachable remote', () => { + // Simulate agent context: real VERSION file, network unavailable + const projectRoot = join(import.meta.dir, '..', '..'); + const versionFile = join(projectRoot, 'VERSION'); + if (!existsSync(versionFile)) return; // skip if no VERSION + const version = readFileSync(versionFile, 'utf-8').trim(); + + // Copy VERSION into test dir + writeFileSync(join(gstackDir, 'VERSION'), version + '\n'); + + // Remote is unreachable (simulates offline / CI / sandboxed agent) + const { exitCode, stdout } = run({ + GSTACK_REMOTE_URL: 'file:///nonexistent/path/VERSION', + }); + expect(exitCode).toBe(0); + // Should write UP_TO_DATE cache (not crash) + const cache = readFileSync(join(stateDir, 'last-update-check'), 'utf-8'); + expect(cache).toContain('UP_TO_DATE'); + }); + + test('exits 0 when up to date (not exit 1)', () => { + // Regression test: script previously exited 1 when versions matched. + // This broke every skill preamble that called it without || true. + writeFileSync(join(gstackDir, 'VERSION'), '0.3.3\n'); + writeFileSync(join(gstackDir, 'REMOTE_VERSION'), '0.3.3\n'); + + // First call: fetches remote, writes cache + const first = run(); + expect(first.exitCode).toBe(0); + expect(first.stdout).toBe(''); + + // Second call: reads fresh cache + const second = run(); + expect(second.exitCode).toBe(0); + expect(second.stdout).toBe(''); + + // Third call with upgrade available: still exit 0 + writeFileSync(join(gstackDir, 'REMOTE_VERSION'), '0.4.0\n'); + rmSync(join(stateDir, 'last-update-check')); // force re-fetch + const third = run(); + expect(third.exitCode).toBe(0); + expect(third.stdout).toBe('UPGRADE_AVAILABLE 0.3.3 0.4.0'); + }); });