mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-17 18:32:19 +02:00
test: regression pin for legacy remove mixed-entry filtering + ownership negatives
Coverage-audit iron rule: the rewritten legacy `remove` action filters per-item (pre-v1.67.2 it dropped the whole entry, destroying a user's co-located SessionStart hook) — modified existing behavior, previously untested. Also pins two ownership negatives: an owned basename+relpath under the WRONG matcher stays foreign, and prune-stale on an absent settings file exits 0 with removed 0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
d85fbccf7a
commit
d5626653ac
@@ -470,6 +470,53 @@ describe('add-event: per-item identity re-point', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('legacy remove: per-item (regression)', () => {
|
||||||
|
test('mixed SessionStart entry: user item survives in place, gstack item removed', () => {
|
||||||
|
// REGRESSION pin: the pre-v1.67.2 legacy `remove` dropped the ENTIRE
|
||||||
|
// entry when any item matched gstack-session-update, destroying a user's
|
||||||
|
// co-located hook. The rewrite filters per-item; this is the only test of
|
||||||
|
// that branch (team-mode.test.ts covers single-item entries only).
|
||||||
|
fs.writeFileSync(settingsFile, JSON.stringify({
|
||||||
|
hooks: {
|
||||||
|
SessionStart: [{
|
||||||
|
hooks: [
|
||||||
|
{ type: 'command', command: '/Users/me/my-own-session-hook' },
|
||||||
|
{ type: 'command', command: '/old/install/bin/gstack-session-update' },
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
}, null, 2));
|
||||||
|
const r = runIso(['remove', '/old/install/bin/gstack-session-update']);
|
||||||
|
expect(r.exitCode).toBe(0);
|
||||||
|
const s = settings();
|
||||||
|
expect(s.hooks.SessionStart).toHaveLength(1);
|
||||||
|
expect(s.hooks.SessionStart[0].hooks).toHaveLength(1);
|
||||||
|
expect(s.hooks.SessionStart[0].hooks[0].command).toBe('/Users/me/my-own-session-hook');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ownership negatives', () => {
|
||||||
|
test('owned basename+relpath under the WRONG matcher stays foreign (not re-pointed)', () => {
|
||||||
|
const canon = mkCanon(tmpDir);
|
||||||
|
fs.writeFileSync(settingsFile, JSON.stringify({
|
||||||
|
hooks: {
|
||||||
|
PostToolUse: [hookEntry('/dead/wt/hosts/claude/hooks/question-log-hook', 'Bash')],
|
||||||
|
},
|
||||||
|
}, null, 2));
|
||||||
|
const before = fs.readFileSync(settingsFile, 'utf-8');
|
||||||
|
const r = runIso(['prune-stale', '--repoint', canon]);
|
||||||
|
expect(r.stdout).toMatch(/removed 0 gstack hook entries \(repointed 0\)/);
|
||||||
|
expect(fs.readFileSync(settingsFile, 'utf-8')).toBe(before);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('prune-stale on an absent settings file exits 0 with removed 0', () => {
|
||||||
|
const r = runIso(['prune-stale', '--repoint', '/nonexistent-root']);
|
||||||
|
expect(r.exitCode).toBe(0);
|
||||||
|
expect(r.stdout).toMatch(/removed 0 gstack hook entries \(repointed 0\)/);
|
||||||
|
expect(fs.existsSync(settingsFile)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('remove-source: per-item', () => {
|
describe('remove-source: per-item', () => {
|
||||||
test('mixed tagged entry: gstack item removed, user item survives, tag dropped', () => {
|
test('mixed tagged entry: gstack item removed, user item survives, tag dropped', () => {
|
||||||
fs.writeFileSync(settingsFile, JSON.stringify({
|
fs.writeFileSync(settingsFile, JSON.stringify({
|
||||||
|
|||||||
Reference in New Issue
Block a user