| 1 | import * as NodeChildProcess from "node:child_process"; |
| 2 | import * as NodeFS from "node:fs"; |
| 3 | import * as NodeOS from "node:os"; |
| 4 | import * as NodePath from "node:path"; |
| 5 | import * as NodeURL from "node:url"; |
| 6 | import { 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. |
| 9 | const hostArch = process.arch; |
| 10 | // oxlint-disable-next-line t3code/no-global-process-runtime -- Native compilation only runs on the actual Linux host. |
| 11 | const hostPlatform = process.platform; |
| 12 | |
| 13 | describe.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 | }); |