Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/os-notifier/__serial__/package.test.ts

Raw
import {
	chmodSync,
	cpSync,
	mkdirSync,
	mkdtempSync,
	readdirSync,
	readFileSync,
	rmSync,
	statSync,
	writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { describe, expect, it } from "vitest";
import {
	createPiSession,
	type PiSession,
} from "../../../test/fixture/create-pi-session.ts";

const built = fileURLToPath(
	new URL("../../../dist/os-notifier", import.meta.url),
);

describe.skipIf(
	process.env["PI_EXT_VERIFY_OS_NOTIFIER_PACKAGE"] !== "1" ||
		process.platform === "win32",
)("os-notifier relocated focus resource", () => {
	it("loads the JAR only on demand and makes an extracted writable copy safe for Android", async () => {
		const root = mkdtempSync(join(tmpdir(), "os-notifier-package-"));
		const target = join(root, "package");
		let session: PiSession | undefined;
		try {
			cpSync(built, target, { recursive: true });
			const pkg = JSON.parse(
				readFileSync(join(target, "package.json"), "utf8"),
			) as {
				pi: { extensions: string[] };
			};
			const entry = pkg.pi.extensions[0];
			if (!entry) throw new Error("os-notifier Pi extension entry missing");
			const jar = join(target, "android/focus.jar");
			expect(readdirSync(join(target, "android"))).toEqual(["focus.jar"]);
			expect(readFileSync(jar)).toEqual(
				readFileSync(
					fileURLToPath(new URL("../android/focus.jar", import.meta.url)),
				),
			);
			chmodSync(jar, 0o644);
			const bin = join(root, "bin");
			mkdirSync(bin);
			const queryLog = join(root, "query.log");
			const notifyLog = join(root, "notification.log");
			writeFileSync(
				join(bin, "app_process"),
				`#!${process.execPath}\nrequire("node:fs").writeFileSync(process.env.TEST_FOCUS_LOG, process.env.CLASSPATH);process.exit(0);\n`,
			);
			writeFileSync(
				join(bin, "termux-notification"),
				`#!${process.execPath}\nrequire("node:fs").writeFileSync(process.env.TEST_NOTIFY_LOG, "posted");\n`,
			);
			writeFileSync(
				join(bin, "termux-vibrate"),
				`#!${process.execPath}\nprocess.exit(0);\n`,
			);
			for (const name of readdirSync(bin)) chmodSync(join(bin, name), 0o755);
			session = await createPiSession({
				cwd: root,
				extensionPaths: [join(target, entry)],
				env: {
					TERMUX_VERSION: "test",
					PATH: bin,
					TEST_FOCUS_LOG: queryLog,
					TEST_NOTIFY_LOG: notifyLog,
				},
			});
			expect(session.errors).toEqual([]);
			session.session.extensionRunner.setUIContext(
				session.session.extensionRunner.getUIContext(),
				"tui",
			);
			await session.session.extensionRunner.emit({
				type: "session_start",
				reason: "startup",
			});
			expect(statSync(jar).mode & 0o222).toBe(0o200);
			expect(() => readFileSync(queryLog)).toThrow();
			await session.session.extensionRunner.emit({
				type: "input",
				text: "test",
				source: "interactive",
			} as never);
			await session.session.extensionRunner.emitBeforeAgentStart(
				{ messageType: "user", prompt: "test" },
				{ cwd: root },
			);
			await session.session.extensionRunner.emit({
				type: "agent_start",
			} as never);
			await session.session.extensionRunner.emit({
				type: "agent_end",
				messages: [{ role: "assistant", stopReason: "stop" }],
			} as never);
			await session.session.extensionRunner.emit({
				type: "agent_settled",
			} as never);
			expect(readFileSync(queryLog, "utf8")).toBe(jar);
			expect(statSync(jar).mode & 0o222).toBe(0);
			await expect
				.poll(() => {
					try {
						return readFileSync(notifyLog, "utf8");
					} catch {
						return "";
					}
				})
				.toBe("posted");
			await session.dispose();
			session = undefined;
			if (process.platform === "android") {
				// Independent native check: the actual Android runtime loads relocated DEX.
				const chunk = readdirSync(target).find((name) =>
					/^termux-focus-.*\.js$/.test(name),
				);
				if (!chunk) throw new Error("packaged focus chunk missing");
				const helper = (await import(
					pathToFileURL(join(target, chunk)).href
				)) as { termuxFocused: () => Promise<boolean | undefined> };
				expect([true, false]).toContain(await helper.termuxFocused());
			}
		} finally {
			await session?.dispose();
			rmSync(root, { recursive: true, force: true });
		}
	});
});