refactor: share cron and ollama test helpers

This commit is contained in:
Peter Steinberger 2026-03-13 19:46:08 +00:00
parent 8473a29da7
commit fff514c7f2
6 changed files with 51 additions and 127 deletions

View File

@ -1,31 +1,11 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { jsonResponse, requestBodyText, requestUrl } from "../test-helpers/http.js";
import {
enrichOllamaModelsWithContext,
resolveOllamaApiBase,
type OllamaTagModel,
} from "./ollama-models.js";
function jsonResponse(body: unknown, status = 200): Response {
return new Response(JSON.stringify(body), {
status,
headers: { "Content-Type": "application/json" },
});
}
function requestUrl(input: string | URL | Request): string {
if (typeof input === "string") {
return input;
}
if (input instanceof URL) {
return input.toString();
}
return input.url;
}
function requestBody(body: BodyInit | null | undefined): string {
return typeof body === "string" ? body : "{}";
}
describe("ollama-models", () => {
afterEach(() => {
vi.unstubAllGlobals();
@ -43,7 +23,7 @@ describe("ollama-models", () => {
if (!url.endsWith("/api/show")) {
throw new Error(`Unexpected fetch: ${url}`);
}
const body = JSON.parse(requestBody(init?.body)) as { name?: string };
const body = JSON.parse(requestBodyText(init?.body)) as { name?: string };
if (body.name === "llama3:8b") {
return jsonResponse({ model_info: { "llama.context_length": 65536 } });
}

View File

@ -1,5 +1,6 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import type { RuntimeEnv } from "../runtime.js";
import { jsonResponse, requestBodyText, requestUrl } from "../test-helpers/http.js";
import type { WizardPrompter } from "../wizard/prompts.js";
import {
configureOllamaNonInteractive,
@ -23,27 +24,6 @@ vi.mock("./oauth-env.js", () => ({
isRemoteEnvironment: isRemoteEnvironmentMock,
}));
function jsonResponse(body: unknown, status = 200): Response {
return new Response(JSON.stringify(body), {
status,
headers: { "Content-Type": "application/json" },
});
}
function requestUrl(input: string | URL | Request): string {
if (typeof input === "string") {
return input;
}
if (input instanceof URL) {
return input.toString();
}
return input.url;
}
function requestBody(body: BodyInit | null | undefined): string {
return typeof body === "string" ? body : "{}";
}
function createOllamaFetchMock(params: {
tags?: string[];
show?: Record<string, number | undefined>;
@ -61,7 +41,7 @@ function createOllamaFetchMock(params: {
return jsonResponse({ models: (params.tags ?? []).map((name) => ({ name })) });
}
if (url.endsWith("/api/show")) {
const body = JSON.parse(requestBody(init?.body)) as { name?: string };
const body = JSON.parse(requestBodyText(init?.body)) as { name?: string };
const contextWindow = body.name ? params.show?.[body.name] : undefined;
return contextWindow
? jsonResponse({ model_info: { "llama.context_length": contextWindow } })
@ -359,7 +339,7 @@ describe("ollama setup", () => {
});
const pullRequest = fetchMock.mock.calls[1]?.[1];
expect(JSON.parse(requestBody(pullRequest?.body))).toEqual({ name: "llama3.2:latest" });
expect(JSON.parse(requestBodyText(pullRequest?.body))).toEqual({ name: "llama3.2:latest" });
expect(result.agents?.defaults?.model).toEqual(
expect.objectContaining({ primary: "ollama/llama3.2:latest" }),
);

View File

@ -1,6 +1,7 @@
import "./isolated-agent.mocks.js";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { runEmbeddedPiAgent } from "../agents/pi-embedded.js";
import { createCliDeps, mockAgentPayloads } from "./isolated-agent.delivery.test-helpers.js";
import { runCronIsolatedAgentTurn } from "./isolated-agent.js";
import {
makeCfg,
@ -9,27 +10,6 @@ import {
writeSessionStoreEntries,
} from "./isolated-agent.test-harness.js";
function makeDeps() {
return {
sendMessageSlack: vi.fn(),
sendMessageWhatsApp: vi.fn(),
sendMessageTelegram: vi.fn(),
sendMessageDiscord: vi.fn(),
sendMessageSignal: vi.fn(),
sendMessageIMessage: vi.fn(),
};
}
function mockEmbeddedOk() {
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
payloads: [{ text: "ok" }],
meta: {
durationMs: 5,
agentMeta: { sessionId: "s", provider: "p", model: "m" },
},
});
}
function lastEmbeddedLane(): string | undefined {
const calls = vi.mocked(runEmbeddedPiAgent).mock.calls;
expect(calls.length).toBeGreaterThan(0);
@ -45,11 +25,11 @@ async function runLaneCase(home: string, lane?: string) {
lastTo: "",
},
});
mockEmbeddedOk();
mockAgentPayloads([{ text: "ok" }]);
await runCronIsolatedAgentTurn({
cfg: makeCfg(home, storePath),
deps: makeDeps(),
deps: createCliDeps(),
job: makeJob({ kind: "agentTurn", message: "do it", deliver: false }),
message: "do it",
sessionKey: "cron:job-1",

View File

@ -2,6 +2,7 @@ import "./isolated-agent.mocks.js";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { loadModelCatalog } from "../agents/model-catalog.js";
import { runEmbeddedPiAgent } from "../agents/pi-embedded.js";
import { createCliDeps, mockAgentPayloads } from "./isolated-agent.delivery.test-helpers.js";
import { runCronIsolatedAgentTurn } from "./isolated-agent.js";
import {
makeCfg,
@ -13,27 +14,6 @@ import type { CronJob } from "./types.js";
const withTempHome = withTempCronHome;
function makeDeps() {
return {
sendMessageSlack: vi.fn(),
sendMessageWhatsApp: vi.fn(),
sendMessageTelegram: vi.fn(),
sendMessageDiscord: vi.fn(),
sendMessageSignal: vi.fn(),
sendMessageIMessage: vi.fn(),
};
}
function mockEmbeddedOk() {
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
payloads: [{ text: "ok" }],
meta: {
durationMs: 5,
agentMeta: { sessionId: "s", provider: "p", model: "m" },
},
});
}
/**
* Extract the provider and model from the last runEmbeddedPiAgent call.
*/
@ -62,7 +42,7 @@ async function runTurnCore(home: string, options: TurnOptions = {}) {
},
...options.storeEntries,
});
mockEmbeddedOk();
mockAgentPayloads([{ text: "ok" }]);
const jobPayload = options.jobPayload ?? {
kind: "agentTurn" as const,
@ -72,7 +52,7 @@ async function runTurnCore(home: string, options: TurnOptions = {}) {
const res = await runCronIsolatedAgentTurn({
cfg: makeCfg(home, storePath, options.cfgOverrides),
deps: makeDeps(),
deps: createCliDeps(),
job: makeJob(jobPayload),
message: DEFAULT_MESSAGE,
sessionKey: options.sessionKey ?? "cron:job-1",
@ -310,7 +290,7 @@ describe("cron model formatting and precedence edge cases", () => {
// Step 2: No job model, session store says openai
vi.mocked(runEmbeddedPiAgent).mockClear();
mockEmbeddedOk();
mockAgentPayloads([{ text: "ok" }]);
const step2 = await runTurn(home, {
jobPayload: { kind: "agentTurn", message: DEFAULT_MESSAGE, deliver: false },
storeEntries: {
@ -327,7 +307,7 @@ describe("cron model formatting and precedence edge cases", () => {
// Step 3: Job payload says anthropic, session store still says openai
vi.mocked(runEmbeddedPiAgent).mockClear();
mockEmbeddedOk();
mockAgentPayloads([{ text: "ok" }]);
const step3 = await runTurn(home, {
jobPayload: {
kind: "agentTurn",
@ -365,7 +345,7 @@ describe("cron model formatting and precedence edge cases", () => {
// Run 2: no override — must revert to default anthropic
vi.mocked(runEmbeddedPiAgent).mockClear();
mockEmbeddedOk();
mockAgentPayloads([{ text: "ok" }]);
const r2 = await runTurn(home, {
jobPayload: { kind: "agentTurn", message: DEFAULT_MESSAGE, deliver: false },
});

View File

@ -133,6 +133,16 @@ async function runTelegramDeliveryResult(bestEffort: boolean) {
return outcome;
}
function expectSuccessfulTelegramTextDelivery(params: {
res: Awaited<ReturnType<typeof runCronIsolatedAgentTurn>>;
deps: CliDeps;
}): void {
expect(params.res.status).toBe("ok");
expect(params.res.delivered).toBe(true);
expect(params.res.deliveryAttempted).toBe(true);
expect(runSubagentAnnounceFlow).not.toHaveBeenCalled();
}
async function runSignalDeliveryResult(bestEffort: boolean) {
let outcome:
| {
@ -379,31 +389,11 @@ describe("runCronIsolatedAgentTurn", () => {
});
it("delivers text directly when best-effort is disabled", async () => {
await withTempHome(async (home) => {
const storePath = await writeSessionStore(home, { lastProvider: "webchat", lastTo: "" });
const deps = createCliDeps();
mockAgentPayloads([{ text: "hello from cron" }]);
const res = await runTelegramAnnounceTurn({
home,
storePath,
deps,
delivery: {
mode: "announce",
channel: "telegram",
to: "123",
bestEffort: false,
},
});
expect(res.status).toBe("ok");
expect(res.delivered).toBe(true);
expect(res.deliveryAttempted).toBe(true);
expect(runSubagentAnnounceFlow).not.toHaveBeenCalled();
expectDirectTelegramDelivery(deps, {
chatId: "123",
text: "hello from cron",
});
const { res, deps } = await runTelegramDeliveryResult(false);
expectSuccessfulTelegramTextDelivery({ res, deps });
expectDirectTelegramDelivery(deps, {
chatId: "123",
text: "hello from cron",
});
});
@ -459,10 +449,7 @@ describe("runCronIsolatedAgentTurn", () => {
},
});
expect(res.status).toBe("ok");
expect(res.delivered).toBe(true);
expect(res.deliveryAttempted).toBe(true);
expect(runSubagentAnnounceFlow).not.toHaveBeenCalled();
expectSuccessfulTelegramTextDelivery({ res, deps });
expect(deps.sendMessageTelegram).toHaveBeenCalledTimes(2);
expect(deps.sendMessageTelegram).toHaveBeenLastCalledWith(
"123",
@ -490,10 +477,7 @@ describe("runCronIsolatedAgentTurn", () => {
it("delivers text directly when best-effort is enabled", async () => {
const { res, deps } = await runTelegramDeliveryResult(true);
expect(res.status).toBe("ok");
expect(res.delivered).toBe(true);
expect(res.deliveryAttempted).toBe(true);
expect(runSubagentAnnounceFlow).not.toHaveBeenCalled();
expectSuccessfulTelegramTextDelivery({ res, deps });
expectDirectTelegramDelivery(deps, {
chatId: "123",
text: "hello from cron",

20
src/test-helpers/http.ts Normal file
View File

@ -0,0 +1,20 @@
export function jsonResponse(body: unknown, status = 200): Response {
return new Response(JSON.stringify(body), {
status,
headers: { "Content-Type": "application/json" },
});
}
export function requestUrl(input: string | URL | Request): string {
if (typeof input === "string") {
return input;
}
if (input instanceof URL) {
return input.toString();
}
return input.url;
}
export function requestBodyText(body: BodyInit | null | undefined): string {
return typeof body === "string" ? body : "{}";
}