import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, it } from "@effect/vitest";
import { Effect } from "effect";
import { makeSelfHostAppsRuntime } from "./self-host-runtime";
import { makeInMemoryAppsStore, makeTestResolver, dailyBriefFileSet } from "../testing";
import type { Bindings } from "./bindings";
const run = (effect: Effect.Effect): Promise => Effect.runPromise(effect);
// A GitHub resolver returning two open issues, one stale.
const githubHandlers = {
github: {
"repos.listForAuthenticatedUser": () => [{ full_name: "acme/app" }],
"issues.listForRepo": () => [
{
number: 1,
title: "Fresh bug",
labels: [{ name: "bug" }],
assignee: { login: "rhys" },
updated_at: new Date().toISOString(),
html_url: "https://github.com/acme/app/issues/1",
},
{
number: 2,
title: "Old bug",
labels: [],
assignee: null,
updated_at: "2020-01-01T00:00:00Z",
html_url: "https://github.com/acme/app/issues/2",
},
],
},
};
const githubBindings: Bindings = { github: { kind: "single", connection: "rhys-github" } };
describe("AppsRuntime end-to-end (publish -> invoke -> workflow)", () => {
it("publishes daily-brief, invokes the tool into the scope db, runs the workflow", async () => {
const store = makeInMemoryAppsStore();
const resolver = makeTestResolver(githubHandlers);
const host = makeSelfHostAppsRuntime({
dataDir: mkdtempSync(join(tmpdir(), "apps-rt-")),
store,
resolver,
inMemory: true,
});
const { runtime } = host;
// --- publish ----------------------------------------------------------
const published = await run(runtime.publish({ scope: "rhys", files: dailyBriefFileSet() }));
expect(published.descriptor.tools.map((t) => t.name).sort()).toEqual([
"issues-sync",
"search-all-mail",
]);
// --- invoke the tool: writes the scope `issues` table -----------------
const syncResult = (await run(
runtime.invokeTool({
scope: "rhys",
tool: "issues-sync",
args: {},
bindings: githubBindings,
}),
)) as { synced: number; repos: number };
expect(syncResult).toEqual({ synced: 2, repos: 1 });
// The scope db now has the two issues.
const db = await run(host.scopeDb.forScope("rhys"));
const rows = await run(db.exec<{ n: number }>("SELECT COUNT(*) AS n FROM issues"));
expect(Number(rows[0].n)).toBe(2);
// --- run the workflow: syncs again + flags the stale issue ------------
const runView = await run(
runtime.startWorkflow({
scope: "rhys",
workflow: "morning-sync",
input: {},
bindings: githubBindings,
runId: "morning-1",
}),
);
expect(runView.status).toBe("completed");
expect(runView.output).toEqual({ synced: 2, stale: 1 });
// Journal has the step.tool call and the find-stale step.
const steps = await run(runtime.listSteps("morning-1"));
const names = steps.map((s) => s.name);
expect(names).toContain("tool:issues-sync");
expect(names).toContain("find-stale");
expect(steps.every((s) => s.status === "completed")).toBe(true);
await host.close();
});
});