1import * as NodeChildProcess from "node:child_process";
2import * as NodeFS from "node:fs";
3import * as NodeOS from "node:os";
4import * as NodePath from "node:path";
5import * as NodeURL from "node:url";
6import { afterAll, beforeAll, describe, expect, it } from "vite-plus/test";
7
8// oxlint-disable-next-line t3code/no-global-process-runtime -- The native compiler targets the actual host; this script has no Effect runtime.
9const hostArch = process.arch;
10// oxlint-disable-next-line t3code/no-global-process-runtime -- Native compilation only runs on the actual Linux host.
11const hostPlatform = process.platform;
12
13describe.skipIf(hostPlatform !== "linux")("bundled libsecret helper", () => {
14 let directory;
15 let executable;
16 beforeAll(() => {
17 directory = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-browser-secret-test-"));
18 executable = NodePath.join(directory, "t3-browser-secret");
19 const root = NodeURL.fileURLToPath(new URL("../../../native/browser-secret/", import.meta.url));
20 const flags = NodeChildProcess.execFileSync(
21 "pkg-config",
22 ["--cflags", "--libs", "libsecret-1"],
23 {
24 encoding: "utf8",
25 },
26 )
27 .trim()
28 .split(/\s+/);
29 NodeChildProcess.execFileSync(
30 process.env.CC || "cc",
31 [
32 "-std=c11",
33 "-Wall",
34 "-Wextra",
35 "-Werror",
36 NodePath.join(root, "main.c"),
37 NodePath.join(root, "test.c"),
38 "-Wl,--wrap=secret_service_search_sync",
39 "-Wl,--wrap=secret_item_get_locked",
40 "-Wl,--wrap=secret_item_get_secret",
41 "-o",
42 executable,
43 ...flags,
44 ],
45 { stdio: "pipe" },
46 );
47 });
48 afterAll(() => {
49 if (directory) NodeFS.rmSync(directory, { recursive: true, force: true });
50 });
51
52 const run = (args) =>
53 NodeChildProcess.spawnSync(executable, args, {
54 env: { ...process.env, DBUS_SESSION_BUS_ADDRESS: "unix:path=/unused-test-bus" },
55 });
56
57 it("builds an executable for the requested architecture into a staged resource directory", () => {
58 const output = NodePath.join(directory, "resources", "browser-secret", "t3-browser-secret");
59 NodeChildProcess.execFileSync(process.execPath, [
60 NodeURL.fileURLToPath(new URL("./build-browser-secret.mjs", import.meta.url)),
61 "--arch",
62 hostArch,
63 "--output",
64 output,
65 ]);
66 const header = NodeFS.readFileSync(output).subarray(0, 20);
67 expect(header.toString("hex", 0, 6)).toBe("7f454c460201");
68 expect(header.readUInt16LE(18)).toBe({ x64: 62, arm64: 183 }[hostArch]);
69 expect(NodeFS.statSync(output).mode & 0o111).not.toBe(0);
70 // Invalid arguments exit before the real executable could contact a keyring.
71 expect(NodeChildProcess.spawnSync(output, []).status).toBe(64);
72 });
73
74 it("preserves the exact secret bytes with no added or removed delimiter", () => {
75 const result = run(["success"]);
76 expect(result.status).toBe(0);
77 expect(result.stdout).toEqual(Buffer.from("secret\0with whitespace \t\r\n"));
78 expect(result.stderr.length).toBe(0);
79 });
80
81 it.each([
82 ["missing", 2],
83 ["empty", 2],
84 ["locked", 3],
85 ["cancelled", 3],
86 ["denied", 3],
87 ["unavailable", 4],
88 ["unloaded", 4],
89 ])("reports %s without emitting a secret", (scenario, code) => {
90 const result = run([scenario]);
91 expect(result.status).toBe(code);
92 expect(result.stdout.length).toBe(0);
93 });
94 it("rejects invalid arguments before accessing the keyring", () => {
95 for (const args of [[], [""], ["chrome", "extra"]]) {
96 const result = run(args);
97 expect(result.status).toBe(64);
98 expect(result.stdout.length).toBe(0);
99 }
100 });
101});