← back to Kickbacks Ai Upstream
test/fleetSignals.test.ts
341 lines
// Fleet-chattiness fix (2026-06-12): the backend piggybacks the killswitch
// verdict (`kill`) and status-bar earnings (`balances` incl. `cap`) on the
// portfolio + metrics responses, and the extension stands its standalone
// /v1/killswitch + /v1/earnings pollers down while that data is fresh.
// Load-bearing rules pinned here:
// - only a FRESH 2xx parse produces a signal (never the warm-cache path);
// - a piggybacked verdict can never produce the OFFLINE posture;
// - cap-KEY-presence (even null) is the new-backend marker; without it the
// earnings poll keeps running (old backend ⇒ today's behavior);
// - registration replays the boot-time buffered verdict.
import { describe, it, expect, vi, afterEach } from "vitest";
import {
FleetSignals, parseKillField, parseBalancesField,
KILL_STALE_MS,
} from "../src/fleetSignals";
import { PortfolioClient } from "../src/portfolio/client";
import { MetricsClient } from "../src/metrics/client";
import { setupEarningsRefresh } from "../src/activation/earningsRefresh";
import { EarningsClient } from "../src/earnings/client";
import { SessionState } from "../src/sessionState";
import type { AuthClient } from "../src/auth/client";
import { makeContext } from "./mocks/vscode";
afterEach(() => {
vi.useRealTimers();
});
const ok = (body: unknown) =>
({ ok: true, status: 200, json: async () => body }) as Response;
// ─── parsers ────────────────────────────────────────────────────────────────
describe("parseKillField", () => {
it("maps a clear verdict; fresh 200 semantics (confirmed=killed)", () => {
expect(parseKillField({ killed: false, scope: null, reason: "" }))
.toEqual({ killed: false, confirmed: false, scope: undefined,
reason: "", offline: false });
});
it("maps a kill verdict with confirmed=true", () => {
const ks = parseKillField({ killed: true, reason: "fail-safe" });
expect(ks).toMatchObject({ killed: true, confirmed: true,
reason: "fail-safe" });
});
it("NEVER produces the offline posture", () => {
for (const raw of [{ killed: true }, { killed: false },
{ killed: true, offline: true }]) {
expect(parseKillField(raw)?.offline).toBe(false);
}
});
it("returns undefined (no signal) for missing/malformed input", () => {
for (const raw of [undefined, null, "x", 7, {}, { killed: "yes" }]) {
expect(parseKillField(raw)).toBeUndefined();
}
});
});
describe("parseBalancesField", () => {
it("cap KEY present (null) ⇒ capCapable, cap undefined", () => {
const s = parseBalancesField(
{ lifetime_usd: "1.20", today_usd: "0.04", cap: null });
expect(s).toMatchObject({ lifetimeUsd: "1.20", todayUsd: "0.04",
capCapable: true });
expect(s?.cap).toBeUndefined();
});
it("cap KEY absent ⇒ NOT capCapable (old backend marker)", () => {
const s = parseBalancesField({ lifetime_usd: "1.20", today_usd: "0.04" });
expect(s?.capCapable).toBe(false);
});
it("parses a real cap block via parseCap", () => {
const s = parseBalancesField({
lifetime_usd: "1.20", today_usd: "0.04",
cap: { scope: "daily", cap_usd: "50.00", reset_seconds: 3600 } });
expect(s?.cap).toEqual({ scope: "daily", capUsd: "50.00",
resetSeconds: 3600 });
});
it("returns undefined for malformed input", () => {
for (const raw of [undefined, null, {}, { lifetime_usd: 5 },
{ lifetime_usd: "1", today_usd: 2 }]) {
expect(parseBalancesField(raw)).toBeUndefined();
}
});
});
// ─── the store ──────────────────────────────────────────────────────────────
describe("FleetSignals store", () => {
it("noteKill stamps freshness and fires the sink", () => {
const fs = new FleetSignals();
const sink = vi.fn();
fs.onKillVerdict(sink);
expect(fs.killFreshWithin(KILL_STALE_MS)).toBe(false);
fs.noteKill({ killed: false });
expect(sink).toHaveBeenCalledTimes(1);
expect(fs.killFreshWithin(KILL_STALE_MS)).toBe(true);
});
it("malformed kill input produces NO signal and NO freshness", () => {
const fs = new FleetSignals();
const sink = vi.fn();
fs.onKillVerdict(sink);
fs.noteKill({ killed: "nope" });
fs.noteKill(undefined);
expect(sink).not.toHaveBeenCalled();
expect(fs.killFreshWithin(KILL_STALE_MS)).toBe(false);
});
it("freshness expires after KILL_STALE_MS", () => {
vi.useFakeTimers();
const fs = new FleetSignals();
fs.noteKill({ killed: false });
vi.advanceTimersByTime(KILL_STALE_MS - 1);
expect(fs.killFreshWithin(KILL_STALE_MS)).toBe(true);
vi.advanceTimersByTime(2);
expect(fs.killFreshWithin(KILL_STALE_MS)).toBe(false);
});
it("onKillVerdict REPLAYS a fresh buffered verdict at registration "
+ "(boot portfolio fetch precedes the kill machinery)", () => {
const fs = new FleetSignals();
fs.noteKill({ killed: true, reason: "global" }); // before any sink
const sink = vi.fn();
fs.onKillVerdict(sink);
expect(sink).toHaveBeenCalledTimes(1);
expect(sink.mock.calls[0][0]).toMatchObject({ killed: true,
confirmed: true });
});
it("noteBalances stores a snapshot and fires the earnings sink", () => {
const fs = new FleetSignals();
const sink = vi.fn();
fs.onEarningsUpdated(sink);
fs.noteBalances({ lifetime_usd: "2.00", today_usd: "0.10", cap: null });
expect(sink).toHaveBeenCalledTimes(1);
expect(fs.earningsSnapshot()).toMatchObject({ lifetimeUsd: "2.00" });
fs.clearEarnings();
expect(fs.earningsSnapshot()).toBeNull();
});
});
// ─── wire-client integration ────────────────────────────────────────────────
const AD = { ad_id: "a1", campaign_id: "c1", title_text: "x".repeat(35),
icon_ref: "i", click_url: "https://t/x", session_token: "tok" };
describe("PortfolioClient fleet-signal sink", () => {
it("fires kill + balances from a fresh AUTHED 2xx", async () => {
const signals = { noteKill: vi.fn(), noteBalances: vi.fn() };
const f = vi.fn(async () => ok({
ttl_seconds: 60, ads: [AD],
kill: { killed: false, scope: null, reason: "" },
balances: { lifetime_usd: "1.20", today_usd: "0.04", cap: null },
}));
const c = new PortfolioClient("http://b", () => "tok", f as never, signals);
await c.fetchPortfolio("2.1.143");
expect(signals.noteKill).toHaveBeenCalledWith(
{ killed: false, scope: null, reason: "" });
expect(signals.noteBalances).toHaveBeenCalledWith(
{ lifetime_usd: "1.20", today_usd: "0.04", cap: null });
});
it("fires kill even when a global kill EMPTIES the inventory", async () => {
const signals = { noteKill: vi.fn(), noteBalances: vi.fn() };
const f = vi.fn(async () => ok({
ttl_seconds: 60, ads: [], kill: { killed: true, reason: "global" } }));
const c = new PortfolioClient("http://b", () => "tok", f as never, signals);
await c.fetchPortfolio("2.1.143");
expect(signals.noteKill).toHaveBeenCalledWith(
{ killed: true, reason: "global" });
});
it("does NOT fire from the warm-cache fallback path", async () => {
const signals = { noteKill: vi.fn(), noteBalances: vi.fn() };
let n = 0;
const f = vi.fn(async () => {
n++;
if (n === 1) return ok({ ttl_seconds: 60, ads: [AD],
kill: { killed: false } });
throw new Error("network down");
});
const c = new PortfolioClient("http://b", () => "tok", f as never, signals);
await c.fetchPortfolio("2.1.143"); // primes cache, 1 signal
const r = await c.fetchPortfolio("2.1.143"); // error → warm cache serve
expect(r).not.toBeNull();
expect(signals.noteKill).toHaveBeenCalledTimes(1); // no second signal
});
it("the DEMO route fires kill but never balances", async () => {
const signals = { noteKill: vi.fn(), noteBalances: vi.fn() };
const f = vi.fn(async () => ok({
ttl_seconds: 60, ads: [AD], kill: { killed: false } }));
const c = new PortfolioClient("http://b", () => null, f as never, signals);
await c.fetchDemoPortfolio("2.1.143", "dev-1");
expect(signals.noteKill).toHaveBeenCalledTimes(1);
expect(signals.noteBalances).not.toHaveBeenCalled();
});
it("threads the campaign param onto the portfolio URL", async () => {
const urls: string[] = [];
const f = vi.fn(async (url: string) => {
urls.push(url);
return ok({ ttl_seconds: 60, ads: [] });
});
const c = new PortfolioClient("http://b", () => "tok", f as never);
await c.fetchPortfolio("2.1.143", "camp-9");
expect(urls[0]).toContain("&campaign=camp-9");
});
});
describe("MetricsClient fleet-signal sink", () => {
const send = async (signals: { noteKill: ReturnType<typeof vi.fn>;
noteBalances: ReturnType<typeof vi.fn> },
token: string | null, body: unknown,
okResp = true) => {
const f = vi.fn(async () => okResp
? ok(body)
: ({ ok: false, status: 500 }) as Response);
const m = new MetricsClient("http://b", () => token, () => "cid",
"0.0.1", f as never, undefined, signals);
await m.send("click", { adId: "a1", campaignId: "c1",
ccVersion: "2.1.143" });
};
it("authed 2xx: kill + balances both reach the store", async () => {
const signals = { noteKill: vi.fn(), noteBalances: vi.fn() };
await send(signals, "tok", {
measurement: "measured", billed: true,
kill: { killed: false },
balances: { lifetime_usd: "2.00", today_usd: "0.10", cap: null } });
expect(signals.noteKill).toHaveBeenCalledWith({ killed: false });
expect(signals.noteBalances).toHaveBeenCalledWith(
{ lifetime_usd: "2.00", today_usd: "0.10", cap: null });
});
it("signed-out (demo route): kill only, never balances", async () => {
const signals = { noteKill: vi.fn(), noteBalances: vi.fn() };
await send(signals, null, {
measurement: "measured", billed: true, kill: { killed: false },
balances: { lifetime_usd: "9.99", today_usd: "9.99" } });
expect(signals.noteKill).toHaveBeenCalledTimes(1);
expect(signals.noteBalances).not.toHaveBeenCalled();
});
it("a non-2xx response produces no signals", async () => {
const signals = { noteKill: vi.fn(), noteBalances: vi.fn() };
await send(signals, "tok", {}, false);
expect(signals.noteKill).not.toHaveBeenCalled();
expect(signals.noteBalances).not.toHaveBeenCalled();
});
it("an old-backend empty body produces no signals and no throw", async () => {
const signals = { noteKill: vi.fn(), noteBalances: vi.fn() };
const f = vi.fn(async () => ({ ok: true, status: 200,
json: async () => { throw new Error("no body"); } }) as never);
const m = new MetricsClient("http://b", () => "tok", () => "cid",
"0.0.1", f as never, undefined, signals);
await m.send("click", { adId: "a1", campaignId: "c1",
ccVersion: "2.1.143" });
expect(signals.noteKill).not.toHaveBeenCalled();
});
});
// ─── earningsRefresh fast path ──────────────────────────────────────────────
function wire(f: typeof fetch, signals: FleetSignals | null) {
let tok: string | null = null;
const session = new SessionState();
const statusBar = { set: vi.fn() };
const capWarning = { show: vi.fn(), hide: vi.fn() };
const auth = { accessToken: () => tok } as unknown as AuthClient;
const client = new EarningsClient("http://x", () => tok, f);
const { showActive } = setupEarningsRefresh(
auth, client, session, statusBar, "2.1.143", makeContext() as never,
undefined, capWarning, signals);
return { session, statusBar, capWarning, showActive,
setToken: (t: string | null) => { tok = t; } };
}
describe("setupEarningsRefresh fleet-signal fast path", () => {
it("paints from a fresh capCapable snapshot with ZERO network fetches", async () => {
const fetchSpy = vi.fn(async () => { throw new Error("must not fetch"); });
const signals = new FleetSignals();
signals.noteBalances({ lifetime_usd: "3.00", today_usd: "0.30",
cap: null });
const { statusBar, showActive, setToken } =
wire(fetchSpy as never, signals);
setToken("tok");
await showActive();
expect(fetchSpy).not.toHaveBeenCalled();
const last = statusBar.set.mock.calls.at(-1)?.[0] as { kind: string };
expect(last).toEqual({ kind: "active", version: "2.1.143",
usd: "3.00", usdToday: "0.30" });
});
it("drives the cap pill from the snapshot's cap", async () => {
const signals = new FleetSignals();
signals.noteBalances({ lifetime_usd: "3.00", today_usd: "50.00",
cap: { scope: "daily", cap_usd: "50.00", reset_seconds: 60 } });
const { capWarning, showActive, setToken } =
wire((async () => { throw new Error("no"); }) as never, signals);
setToken("tok");
await showActive();
expect(capWarning.show).toHaveBeenCalledWith(
{ scope: "daily", capUsd: "50.00", resetSeconds: 60 });
});
it("falls back to GET /v1/earnings when the carrier was NOT capCapable "
+ "(old backend keeps today's behavior)", async () => {
const fetchSpy = vi.fn(async () => ({ ok: true,
json: async () => ({ lifetime_usd: "1.20", today_usd: "0.04" }) }));
const signals = new FleetSignals();
signals.noteBalances({ lifetime_usd: "9.99", today_usd: "9.99" }); // no cap key
const { statusBar, showActive, setToken } =
wire(fetchSpy as never, signals);
setToken("tok");
await showActive();
expect(fetchSpy).toHaveBeenCalled();
const last = statusBar.set.mock.calls.at(-1)?.[0] as { usd?: string };
expect(last.usd).toBe("1.20"); // network figures, not the snapshot
});
it("falls back to the network once the snapshot goes stale", async () => {
vi.useFakeTimers();
const fetchSpy = vi.fn(async () => ({ ok: true,
json: async () => ({ lifetime_usd: "1.20", today_usd: "0.04" }) }));
const signals = new FleetSignals();
signals.noteBalances({ lifetime_usd: "3.00", today_usd: "0.30",
cap: null });
const { showActive, setToken } = wire(fetchSpy as never, signals);
setToken("tok");
vi.advanceTimersByTime(91_000);
await showActive();
expect(fetchSpy).toHaveBeenCalled();
});
});