← back to Kickbacks Ai Upstream
test/webviewInjection-dualtarget.test.ts
189 lines
/** S9 dual-target webview injection: the codex-only serving path and the
* either-target honest-status contract.
*
* Pins (against the claude-only-primary regression a public-mirror PR
* exposed — kickbacks.ai#1):
* • claudeCompatible:false skips the doomed Claude write entirely, applies
* Codex IMMEDIATELY (no 10s "unconfirmed" window), paints active — and
* NEVER arms desyncState.lastApplyAt (the claude-webview-cache watchdog
* has no claude apply to heal; lastApplyAt===0 keeps it passive).
* • A transient Claude apply miss while a Codex block is live must not
* relabel the bar "incompatible" (any-target-patched union).
* • The claude-compatible path is byte-identical to before (lastApplyAt
* armed by the claude apply only).
* • cycleReassert on a codex-only boot never restores/re-writes Claude.
*/
import { describe, it, expect, vi, beforeEach } from "vitest";
vi.mock("../src/modes", () => ({
webviewMode: () => "on",
cliMode: () => "on",
bannerOverride: () => "server",
setWebviewMode: () => {},
setCliMode: () => {},
setBannerOverride: () => {},
}));
import { resetServingGate } from "../src/servingGate";
import { setupWebviewInjection } from "../src/activation/webviewInjection";
import { createActivationContext, type ActivationContext }
from "../src/activation/context";
import { SessionState } from "../src/sessionState";
import { makeContext } from "./mocks/vscode";
import type { PatchAd } from "../src/portfolio/client";
beforeEach(() => { resetServingGate(); });
const AD: PatchAd = {
adId: "ad-1", campaignId: "c-1", adText: "Linear -- plan, build, ship",
iconRef: "icon.1", iconUrl: "", clickUrl: "https://a.test",
bannerEnabled: false, sessionToken: "tok-1",
};
const mkCc = (o: { compatible?: boolean; applyOk?: boolean;
isPatched?: boolean } = {}) => ({
name: "claude-code" as const,
preflight: () => ({ ok: true, compatible: o.compatible ?? true,
version: "2.1.143" }),
version: () => "2.1.143",
isPatched: vi.fn(() => o.isPatched ?? false),
applyPatch: vi.fn(() => (o.applyOk ?? true)
? { ok: true } : { ok: false, reason: "apply miss" }),
restore: vi.fn(() => ({ ok: true, restored: true })),
});
const mkCodex = (o: { isPatched?: boolean } = {}) => ({
name: "codex" as const,
preflight: () => ({ ok: true, compatible: true, version: "26.513.21555" }),
version: () => "26.513.21555",
isPatched: vi.fn(() => o.isPatched ?? false),
applyPatch: vi.fn(() => ({ ok: true })),
restore: vi.fn(() => ({ ok: true, restored: true })),
});
async function mkWebview(opts: {
adapter: ReturnType<typeof mkCc>;
codexAdapter?: ReturnType<typeof mkCodex> | null;
claudeCompatible?: boolean;
}) {
const actx = createActivationContext();
actx.codexAdapter = (opts.codexAdapter ?? null) as never;
const desyncState = { lastApplyAt: 0, lastBlockStartAt: 0 };
const statusBarSet = vi.fn();
const showActive = vi.fn(async () => {});
const deps = {
ctx: makeContext(), actx, adapter: opts.adapter,
auth: { accessToken: () => "tok", clientId: () => "cid" },
debugCtl: { setPortfolioAd: () => {} },
session: new SessionState(),
portfolio: { fetchPortfolio: async () => null,
fetchDemoPortfolio: async () => null },
metrics: { send: vi.fn() },
logTail: { current: () => ({}), activityAgeMs: () => null },
testHooks: { handleTestRoute: async () => ({ status: 404, body: {} }) },
statusBar: { set: statusBarSet },
ccVersion: opts.claudeCompatible === false
? "codex/26.513.21555" : "2.1.143",
killed: false, killedRef: { current: false },
adRef: { current: AD as PatchAd | null },
portfolioResp: { ad: AD, ads: [AD], queueId: "q", ttlMs: 60_000,
rotationIntervalMs: 120_000, viewThresholdMs: 3_000, balances: null },
viewThresholdMs: 3_000,
statusBarShowActive: showActive,
scheduleEarningsRefresh: () => {},
desyncState,
...(opts.claudeCompatible === undefined
? {} : { claudeCompatible: opts.claudeCompatible }),
};
const r = await setupWebviewInjection(deps as never);
return { r, actx, desyncState, statusBarSet, showActive };
}
async function teardown(actx: ActivationContext): Promise<void> {
for (const t of actx.timers) clearInterval(t as NodeJS.Timeout);
actx.timers.length = 0;
if (actx.loopback) { await actx.loopback.stop(); actx.loopback = null; }
}
describe("codex-only serving (claudeCompatible: false)", () => {
it("skips the Claude write, applies Codex immediately, paints active,"
+ " never arms the desync watchdog", async () => {
const adapter = mkCc({ compatible: false });
const codex = mkCodex();
const { r, actx, desyncState, statusBarSet, showActive } =
await mkWebview({ adapter, codexAdapter: codex, claudeCompatible: false });
try {
expect(r.lbInfo).not.toBeNull();
// No doomed Claude write — not even one attempt.
expect(adapter.applyPatch).not.toHaveBeenCalled();
// Codex applied SYNCHRONOUSLY at setup (not deferred to the 10s pass).
expect(codex.applyPatch).toHaveBeenCalled();
// Honest label: active, never "incompatible".
expect(showActive).toHaveBeenCalled();
expect(statusBarSet).not.toHaveBeenCalledWith(
expect.objectContaining({ kind: "incompatible" }));
// The claude-webview desync watchdog stays passive: lastApplyAt===0
// reads as "no-apply" in reassert.ts::desyncDecision. A codex apply
// must never arm it (the mirror PR's exact mistake).
expect(desyncState.lastApplyAt).toBe(0);
} finally { await teardown(actx); }
});
it("cycleReassert never restores/re-writes Claude on a codex-only boot", async () => {
const adapter = mkCc({ compatible: false });
const codex = mkCodex();
const { r, actx, desyncState } =
await mkWebview({ adapter, codexAdapter: codex, claudeCompatible: false });
try {
adapter.applyPatch.mockClear();
adapter.restore.mockClear();
codex.applyPatch.mockClear();
r.cycleReassert!();
expect(adapter.restore).not.toHaveBeenCalled();
expect(adapter.applyPatch).not.toHaveBeenCalled();
expect(codex.applyPatch).toHaveBeenCalled();
expect(desyncState.lastApplyAt).toBe(0);
} finally { await teardown(actx); }
});
});
describe("either-target honest status", () => {
it("a transient Claude apply miss with a LIVE codex block does not relabel"
+ " the bar incompatible", async () => {
const adapter = mkCc({ applyOk: false, isPatched: false });
const codex = mkCodex({ isPatched: true });
const { actx, statusBarSet, showActive } =
await mkWebview({ adapter, codexAdapter: codex, claudeCompatible: true });
try {
expect(adapter.applyPatch).toHaveBeenCalled(); // tried (compatible)
expect(statusBarSet).not.toHaveBeenCalledWith(
expect.objectContaining({ kind: "incompatible" }));
expect(showActive).toHaveBeenCalled(); // deferred-to
} finally { await teardown(actx); }
});
it("a Claude apply miss with NO patched target still labels incompatible"
+ " (no false-positive active)", async () => {
const adapter = mkCc({ applyOk: false, isPatched: false });
const { actx, statusBarSet } =
await mkWebview({ adapter, codexAdapter: null, claudeCompatible: true });
try {
expect(statusBarSet).toHaveBeenCalledWith(
expect.objectContaining({ kind: "incompatible" }));
} finally { await teardown(actx); }
});
it("claude-compatible path unchanged: the claude apply arms lastApplyAt"
+ " (codex never does)", async () => {
const adapter = mkCc();
const codex = mkCodex();
const before = Date.now();
const { actx, desyncState } =
await mkWebview({ adapter, codexAdapter: codex }); // flag omitted
try {
expect(adapter.applyPatch).toHaveBeenCalled();
expect(desyncState.lastApplyAt).toBeGreaterThanOrEqual(before);
} finally { await teardown(actx); }
});
});