mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-09 17:32:41 -10:00
test(daemon): failing tests for slice 3.5 Codex feature flag (HG-5.1-5.4)
Per IMPL-PRD §5 + velocity-guard cadence boundary (d). 7 tests, all currently FAIL (TDD red). Replaces activity-hook-injection-coupled upsertCodexHooksFeature() being removed in next commit. 7 tests cover: - HG-5.1: runtime.codex.hooks_enabled is in SETTINGS_VALID_KEYS - HG-5.2: ensureCodexFeatureFlag(true) writes codex_hooks = true to ~/.codex/config.toml when file missing AND when file has prior content (preserves prior content) - HG-5.3: idempotent (running twice doesn't duplicate); pre-existing [features] block with other flags is preserved when adding codex_hooks - HG-5.4: ensureCodexFeatureFlag(false) does NOT touch ~/.codex/config.toml (file missing OR existing — user owns it) Next commit: - Add 'runtime.codex.hooks_enabled' to SETTINGS_VALID_KEYS + ENV_MAP + KEY_TO_PATH (and lockstep CLI ConfigStore) - Add ensureCodexFeatureFlag(enabled: boolean) method on CodexRuntimeAdapter (reuses existing upsertCodexHooksFeature TOML helper that's about to move from coupled location) - Wire into daemon startup Per IMPL-PRD §5.x + plugin-primitive Phase 3a slice 3.5.
This commit is contained in:
1 parent
fc73fd345f
commit
6f6804267e
1 file changed
+134
@@ -0,0 +1,134 @@
|
||||
// Test suite for plugin-primitive Phase 3a slice 3.5 — Codex feature flag
|
||||
// (codex_hooks = true). Per IMPL-PRD §5 + velocity-guard cadence boundary
|
||||
// (d) 2026-05-10.
|
||||
//
|
||||
// Replaces the activity-hook-injection-coupled upsertCodexHooksFeature()
|
||||
// being removed in Phase 3a slice 3.1. New shape:
|
||||
//
|
||||
// 1. OpenRig setting `runtime.codex.hooks_enabled` (default true)
|
||||
// 2. CodexRuntimeAdapter exposes ensureCodexFeatureFlag(setting) that
|
||||
// writes codex_hooks = true to ~/.codex/config.toml when setting=true
|
||||
// 3. Idempotent: running twice produces no duplication
|
||||
// 4. User override (setting=false) → daemon does NOT mutate Codex config
|
||||
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { CodexRuntimeAdapter, type CodexAdapterFsOps } from "../src/adapters/codex-runtime-adapter.js";
|
||||
import { SETTINGS_VALID_KEYS } from "../src/domain/user-settings/settings-store.js";
|
||||
|
||||
function mockTmux() {
|
||||
return {
|
||||
sessionExists: vi.fn().mockResolvedValue(true),
|
||||
sendKeys: vi.fn().mockResolvedValue(undefined),
|
||||
capturePaneContent: vi.fn().mockResolvedValue(""),
|
||||
getPaneCommand: vi.fn().mockResolvedValue(""),
|
||||
listSessions: vi.fn().mockResolvedValue([]),
|
||||
runCommandInSession: vi.fn().mockResolvedValue({ stdout: "", stderr: "", exitCode: 0 }),
|
||||
setEnvVar: vi.fn().mockResolvedValue(undefined),
|
||||
} as unknown as ConstructorParameters<typeof CodexRuntimeAdapter>[0]["tmux"];
|
||||
}
|
||||
|
||||
function mockCodexFs(files?: Record<string, string>): CodexAdapterFsOps & { _store: Record<string, string> } {
|
||||
const store: Record<string, string> = { ...files };
|
||||
return {
|
||||
readFile: (p: string) => { if (p in store) return store[p]!; throw new Error(`Not found: ${p}`); },
|
||||
writeFile: (p: string, c: string) => { store[p] = c; },
|
||||
exists: (p: string) => p in store,
|
||||
mkdirp: () => {},
|
||||
listFiles: (dir: string) => Object.keys(store).filter((k) => k.startsWith(dir + "/")).map((k) => k.slice(dir.length + 1)),
|
||||
homedir: "/home/test",
|
||||
_store: store,
|
||||
} as CodexAdapterFsOps & { _store: Record<string, string> };
|
||||
}
|
||||
|
||||
describe("Slice 3.5 — Codex feature flag (runtime.codex.hooks_enabled)", () => {
|
||||
// ============================================================
|
||||
// HG-5.1: setting exists in canonical valid-keys lists
|
||||
// ============================================================
|
||||
|
||||
it("HG-5.1 — runtime.codex.hooks_enabled is in SETTINGS_VALID_KEYS", () => {
|
||||
expect(SETTINGS_VALID_KEYS as readonly string[]).toContain("runtime.codex.hooks_enabled");
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// HG-5.2: ensureCodexFeatureFlag writes codex_hooks = true
|
||||
// ============================================================
|
||||
|
||||
it("HG-5.2 — ensureCodexFeatureFlag(true) writes codex_hooks = true to ~/.codex/config.toml when file missing", () => {
|
||||
const fs = mockCodexFs(/* no existing file */);
|
||||
const adapter = new CodexRuntimeAdapter({ tmux: mockTmux(), fsOps: fs });
|
||||
|
||||
adapter.ensureCodexFeatureFlag(true);
|
||||
|
||||
const written = fs._store["/home/test/.codex/config.toml"];
|
||||
expect(written).toBeDefined();
|
||||
expect(written).toContain("[features]");
|
||||
expect(written).toContain("codex_hooks = true");
|
||||
});
|
||||
|
||||
it("HG-5.2 — ensureCodexFeatureFlag(true) sets codex_hooks = true while preserving existing config content", () => {
|
||||
const existing = `[model_provider]\nname = "openai"\n\n[other_section]\nfoo = "bar"\n`;
|
||||
const fs = mockCodexFs({ "/home/test/.codex/config.toml": existing });
|
||||
const adapter = new CodexRuntimeAdapter({ tmux: mockTmux(), fsOps: fs });
|
||||
|
||||
adapter.ensureCodexFeatureFlag(true);
|
||||
|
||||
const written = fs._store["/home/test/.codex/config.toml"]!;
|
||||
expect(written).toContain('name = "openai"');
|
||||
expect(written).toContain('foo = "bar"');
|
||||
expect(written).toContain("[features]");
|
||||
expect(written).toContain("codex_hooks = true");
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// HG-5.3: idempotent
|
||||
// ============================================================
|
||||
|
||||
it("HG-5.3 — ensureCodexFeatureFlag(true) is idempotent (running twice doesn't duplicate codex_hooks)", () => {
|
||||
const fs = mockCodexFs();
|
||||
const adapter = new CodexRuntimeAdapter({ tmux: mockTmux(), fsOps: fs });
|
||||
|
||||
adapter.ensureCodexFeatureFlag(true);
|
||||
adapter.ensureCodexFeatureFlag(true);
|
||||
|
||||
const written = fs._store["/home/test/.codex/config.toml"]!;
|
||||
const matches = written.match(/codex_hooks = true/g) ?? [];
|
||||
expect(matches).toHaveLength(1);
|
||||
const featuresMatches = written.match(/\[features\]/g) ?? [];
|
||||
expect(featuresMatches).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("HG-5.3 — pre-existing [features] block with other flags is preserved when adding codex_hooks", () => {
|
||||
const existing = `[features]\nother_flag = false\n`;
|
||||
const fs = mockCodexFs({ "/home/test/.codex/config.toml": existing });
|
||||
const adapter = new CodexRuntimeAdapter({ tmux: mockTmux(), fsOps: fs });
|
||||
|
||||
adapter.ensureCodexFeatureFlag(true);
|
||||
|
||||
const written = fs._store["/home/test/.codex/config.toml"]!;
|
||||
expect(written).toContain("other_flag = false");
|
||||
expect(written).toContain("codex_hooks = true");
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// HG-5.4: user override (false) → daemon does not mutate
|
||||
// ============================================================
|
||||
|
||||
it("HG-5.4 — ensureCodexFeatureFlag(false) does NOT touch ~/.codex/config.toml when file missing", () => {
|
||||
const fs = mockCodexFs();
|
||||
const adapter = new CodexRuntimeAdapter({ tmux: mockTmux(), fsOps: fs });
|
||||
|
||||
adapter.ensureCodexFeatureFlag(false);
|
||||
|
||||
expect(fs._store["/home/test/.codex/config.toml"]).toBeUndefined();
|
||||
});
|
||||
|
||||
it("HG-5.4 — ensureCodexFeatureFlag(false) does NOT touch existing ~/.codex/config.toml content (user owns it)", () => {
|
||||
const existing = `[features]\ncodex_hooks = false\nother = true\n`;
|
||||
const fs = mockCodexFs({ "/home/test/.codex/config.toml": existing });
|
||||
const adapter = new CodexRuntimeAdapter({ tmux: mockTmux(), fsOps: fs });
|
||||
|
||||
adapter.ensureCodexFeatureFlag(false);
|
||||
|
||||
expect(fs._store["/home/test/.codex/config.toml"]).toBe(existing);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user