Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/bak/__tests__/debug.test.ts

Raw
import {
	mkdirSync,
	mkdtempSync,
	readdirSync,
	readFileSync,
	rmSync,
	writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import {
	createTestSession,
	sandboxEnv,
	type TestSession,
	withProcessEnv,
} from "../../../test/harness";

describe("bak debug", () => {
	let root: string | undefined;
	let session: TestSession | undefined;

	afterEach(() => {
		session?.dispose();
		if (root) rmSync(root, { recursive: true, force: true });
		session = undefined;
		root = undefined;
	});

	it("writes its lifecycle JSONL through the standalone factory", async () => {
		root = mkdtempSync(path.join(tmpdir(), "pi-ext-bak-debug-"));
		const env = sandboxEnv(root, { env: { PI_BAK_DEBUG: "1" } });
		vi.resetModules();
		const factory = await withProcessEnv(
			env,
			async () => (await import("../index.ts")).default,
		);
		session = await createTestSession({
			cwd: root,
			extensionFactories: [factory],
			env: { PI_BAK_DEBUG: "1" },
		});
		await session.session.extensionRunner.emit({
			type: "session_start",
			reason: "startup",
		});
		await session.session.extensionRunner.emit({
			type: "session_shutdown",
			reason: "quit",
		});

		if (!env.XDG_STATE_HOME) throw new Error("missing sandbox state home");
		const directory = path.join(env.XDG_STATE_HOME, "pi-ext", "debug", "bak");
		const [file] = readdirSync(directory);
		const events = readFileSync(path.join(directory, file), "utf8")
			.trim()
			.split("\n")
			.map((line) => JSON.parse(line).event);
		expect(events).toEqual(
			expect.arrayContaining(["session.start", "session.shutdown"]),
		);
	});

	it.each([
		["true", true],
		["false", false],
	])(
		"attempts a shutdown backup only when bak.autoBackup is %s",
		async (_name, autoBackup) => {
			root = mkdtempSync(path.join(tmpdir(), "pi-ext-bak-debug-"));
			const env = sandboxEnv(root, { env: { PI_BAK_DEBUG: "1" } });
			const agentDir = path.join(env.HOME ?? "", ".pi", "agent");
			mkdirSync(agentDir, { recursive: true });
			writeFileSync(
				path.join(agentDir, "settings.json"),
				JSON.stringify({ bak: { autoBackup } }),
			);
			vi.resetModules();
			const factory = await withProcessEnv(
				env,
				async () => (await import("../index.ts")).default,
			);
			session = await createTestSession({
				cwd: root,
				extensionFactories: [factory],
				env: { PI_BAK_DEBUG: "1" },
			});
			const runner = session.session.extensionRunner;
			await runner.emit({ type: "session_start", reason: "startup" });
			await runner.emit({ type: "session_shutdown", reason: "quit" });

			if (!env.XDG_STATE_HOME) throw new Error("missing sandbox state home");
			const directory = path.join(env.XDG_STATE_HOME, "pi-ext", "debug", "bak");
			const [file] = readdirSync(directory);
			// Every automatic attempt records an outcome, even when it has nothing to upload.
			const attempts = readFileSync(path.join(directory, file), "utf8")
				.trim()
				.split("\n")
				.map((line) => JSON.parse(line))
				.filter((entry) => entry.operation === "automatic-backup");
			expect(attempts.length > 0).toBe(autoBackup);
		},
	);

	it("records skipped, successful, and failed command outcomes", async () => {
		root = mkdtempSync(path.join(tmpdir(), "pi-ext-bak-debug-"));
		const env = sandboxEnv(root, { env: { PI_BAK_DEBUG: "1" } });
		vi.resetModules();
		session = await createTestSession({
			cwd: root,
			env: { PI_BAK_DEBUG: "1" },
		});
		const ctx = session.session.extensionRunner.createContext();
		const { createBakRuntime } = await withProcessEnv(
			env,
			() => import("../implementation.ts"),
		);
		const { closeDebug } = await withProcessEnv(
			env,
			() => import("../src/debug.ts"),
		);
		const disabled = createBakRuntime({
			missing: ["PI_BAK_S3_ENDPOINT"],
		});
		disabled.sessionStart(ctx);
		await disabled.command.handler("backup", ctx);
		const configured = createBakRuntime({
			missing: [],
			config: {
				endpoint: "https://example.invalid",
				bucket: "test",
				region: "auto",
				accessKeyId: "id",
				secretAccessKey: "secret",
			},
		});
		configured.sessionStart(ctx);
		await configured.command.handler("status", ctx);
		await configured.command.handler("init", ctx);
		closeDebug();

		if (!env.XDG_STATE_HOME) throw new Error("missing sandbox state home");
		const directory = path.join(env.XDG_STATE_HOME, "pi-ext", "debug", "bak");
		const [file] = readdirSync(directory);
		const outcomes = readFileSync(path.join(directory, file), "utf8")
			.trim()
			.split("\n")
			.map((line) => JSON.parse(line))
			.filter((entry) => entry.event === "operation.finish");
		expect(outcomes).toEqual(
			expect.arrayContaining([
				expect.objectContaining({
					operation: "command",
					subcommand: "backup",
					outcome: "skipped",
				}),
				expect.objectContaining({
					operation: "command",
					subcommand: "status",
					outcome: "success",
				}),
				expect.objectContaining({
					operation: "command",
					subcommand: "init",
					outcome: "failed",
				}),
			]),
		);
	});
});