aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoramankumarsingh77 <amankumarsingh7702@gmail.com>2026-07-27 00:13:06 +0530
committerAman Kumar <145339995+amankumarsingh77@users.noreply.github.com>2026-08-02 19:12:46 +0530
commit3607b5523f9d23ec6b9facf8474c7bdf047537e1 (patch)
tree87c433788599b1f414cb248759f2eed6500071e2
parent84ebb018f59ab5efe8bc97e8e4b87435d6665b0b (diff)
chore(build): split tsconfig, typecheck tests, add node:test runner
test/ was never type-checked and had no runner. Adds tsconfig.base.json shared by a src project and a test project, an npm test script scoped to non-manual suites, and a CI step. Ports the profile store suite as proof. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
-rw-r--r--.github/workflows/ci.yml3
-rw-r--r--package.json3
-rw-r--r--test/manual/bridge-test.ts6
-rw-r--r--test/manual/daemon-transport-test.ts2
-rw-r--r--test/manual/ipc-server-test.ts9
-rw-r--r--test/profile/store-test.ts145
-rw-r--r--tsconfig.base.json22
-rw-r--r--tsconfig.json17
-rw-r--r--tsconfig.test.json9
9 files changed, 118 insertions, 98 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c93ad8d..f2de08f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -32,6 +32,9 @@ jobs:
- name: Typecheck
run: npm run typecheck
+ - name: Test
+ run: npm test
+
- name: Verify npm package
run: npm pack --dry-run > /dev/null
diff --git a/package.json b/package.json
index f3abf89..8c306d0 100644
--- a/package.json
+++ b/package.json
@@ -57,7 +57,8 @@
"typescript": "^5.0.0"
},
"scripts": {
- "typecheck": "tsc --noEmit",
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.test.json",
+ "test": "node --import tsx --test \"test/{profile,deep-research,domains,cdp,util}/**/*-test.ts\"",
"prepublishOnly": "tsc --noEmit"
},
"publishConfig": {
diff --git a/test/manual/bridge-test.ts b/test/manual/bridge-test.ts
index ecb4100..5402aae 100644
--- a/test/manual/bridge-test.ts
+++ b/test/manual/bridge-test.ts
@@ -3,7 +3,7 @@
* Requires: Chrome with remote debugging enabled (chrome://inspect/#remote-debugging).
* Run: npx tsx test/manual/bridge-test.ts
*/
-import { createCdpBridge, type SendToClient, type CdpBridge } from "../../src/daemon/bridge";
+import { createCdpBridge, type SendToClient } from "../../src/daemon/bridge";
import type { WireRequest, WireResponse, WireEvent } from "../../src/daemon/protocol";
let passed = 0;
@@ -35,12 +35,12 @@ async function main() {
const events: WireEvent[] = [];
let eventCount = 0;
- const send: SendToClient = (clientId, msg) => {
+ const send: SendToClient = (_clientId, msg) => {
if (msg.type === "response") responses.push(msg);
else events.push(msg);
};
- bridge.onEvent((evt, _targets) => { eventCount++; });
+ bridge.onEvent((_evt, _targets) => { eventCount++; });
const req1: WireRequest = { type: "request", id: 1, method: "Target.getTargets" };
bridge.handleRequest(req1, "pi-test", send);
diff --git a/test/manual/daemon-transport-test.ts b/test/manual/daemon-transport-test.ts
index 4ba2f5b..e9660c8 100644
--- a/test/manual/daemon-transport-test.ts
+++ b/test/manual/daemon-transport-test.ts
@@ -7,7 +7,7 @@
*/
import { createIpcServer } from "../../src/daemon/server";
import { createDaemonTransport } from "../../src/cdp/daemon-transport";
-import { DAEMON_SOCKET_PATH, serialize } from "../../src/daemon/protocol";
+import { DAEMON_SOCKET_PATH } from "../../src/daemon/protocol";
import type { WireMessage } from "../../src/daemon/protocol";
import { unlinkSync } from "node:fs";
diff --git a/test/manual/ipc-server-test.ts b/test/manual/ipc-server-test.ts
index a267a45..b4da3df 100644
--- a/test/manual/ipc-server-test.ts
+++ b/test/manual/ipc-server-test.ts
@@ -7,7 +7,7 @@ import { createConnection } from "node:net";
import { createInterface } from "node:readline";
import { createIpcServer } from "../../src/daemon/server";
import { DAEMON_SOCKET_PATH } from "../../src/daemon/protocol";
-import type { WireMessage } from "../../src/daemon/protocol";
+import type { WireEvent, WireMessage } from "../../src/daemon/protocol";
const TEST_CLIENT_ID = "pi-test-001";
@@ -76,7 +76,11 @@ async function main() {
// ── Test 4: Broadcast ─────────────────────────────────────────────────
receivedByClient.length = 0;
- const broadcastMsg = { type: "event", method: "Target.targetCreated", params: { targetInfo: { type: "page" } } };
+ const broadcastMsg: WireEvent = {
+ type: "event",
+ method: "Target.targetCreated",
+ params: { targetInfo: { type: "page" } },
+ };
server.broadcast(broadcastMsg);
await sleep(200);
@@ -93,7 +97,6 @@ async function main() {
// ── Test 6: Re-registration (same clientId after disconnect) ──────────
const socket2 = createConnection(DAEMON_SOCKET_PATH);
- const rl2 = createInterface({ input: socket2, crlfDelay: Infinity });
await new Promise<void>((resolve) => socket2.on("connect", resolve));
socket2.write(JSON.stringify({ type: "control", action: "register", clientId: TEST_CLIENT_ID }) + "\n");
diff --git a/test/profile/store-test.ts b/test/profile/store-test.ts
index c881372..7d38d34 100644
--- a/test/profile/store-test.ts
+++ b/test/profile/store-test.ts
@@ -4,24 +4,14 @@
* Redirects pi's agent dir via $PI_CODING_AGENT_DIR so the real
* ~/.pi/agent/browser-harness.json is never touched.
*
- * Run: npx tsx test/profile/store-test.ts
+ * Run: npm test
*/
+import { test, before, after, describe } from "node:test";
+import assert from "node:assert/strict";
import { mkdtempSync, mkdirSync, readdirSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
-let passed = 0;
-let failed = 0;
-const check = (cond: boolean, label: string): void => {
- if (cond) {
- passed++;
- console.log(` ✓ ${label}`);
- } else {
- failed++;
- console.error(` ✗ ${label}`);
- }
-};
-
const pin = {
userDataDir: "/home/u/.config/google-chrome",
profileDir: "Profile 1",
@@ -29,70 +19,87 @@ const pin = {
savedAt: new Date(0).toISOString(),
};
-async function main(): Promise<void> {
- const sandbox = mkdtempSync(join(tmpdir(), "pi-pin-test-"));
- process.env["PI_CODING_AGENT_DIR"] = sandbox;
+describe("profile pin store", () => {
+ let sandbox: string;
+
+ before(() => {
+ sandbox = mkdtempSync(join(tmpdir(), "pi-pin-test-"));
+ process.env["PI_CODING_AGENT_DIR"] = sandbox;
+ });
- // Imported after the env override so paths.ts resolves into the sandbox.
- const { pinFilePath } = await import("../../src/profile/paths");
- const { readPin, writePin, clearPin } = await import("../../src/profile/store");
+ after(() => {
+ rmSync(sandbox, { recursive: true, force: true });
+ });
- // P1: agent dir honours the env override
- check(pinFilePath() === join(sandbox, "browser-harness.json"), "P1: pin path follows $PI_CODING_AGENT_DIR");
+ test("P1: pin path follows $PI_CODING_AGENT_DIR", async () => {
+ const { pinFilePath } = await import("../../src/profile/paths");
+ assert.equal(pinFilePath(), join(sandbox, "browser-harness.json"));
+ });
- // P2: nothing written yet
- check((await readPin()) === null, "P2: absent file reads as no pin");
+ test("P2: absent file reads as no pin", async () => {
+ const { readPin } = await import("../../src/profile/store");
+ assert.equal(await readPin(), null);
+ });
- // P3: round trip
- {
+ test("P3: write succeeds and round-trips", async () => {
+ const { readPin, writePin } = await import("../../src/profile/store");
const written = await writePin(pin);
- check(written.success, "P3: write succeeds");
+ assert.equal(written.success, true);
const read = await readPin();
- check(read?.profileDir === "Profile 1", "P3: profileDir round-trips");
- check(read?.userDataDir === pin.userDataDir, "P3: userDataDir round-trips");
- check(read?.label === pin.label, "P3: label round-trips");
- }
+ assert.equal(read?.profileDir, "Profile 1");
+ assert.equal(read?.userDataDir, pin.userDataDir);
+ assert.equal(read?.label, pin.label);
+ });
- // P4: no temp files survive an atomic write
- check(readdirSync(sandbox).filter((f) => f.endsWith(".tmp")).length === 0, "P4: write leaves no .tmp files");
+ test("P4: write leaves no .tmp files", () => {
+ assert.equal(readdirSync(sandbox).filter((f) => f.endsWith(".tmp")).length, 0);
+ });
- // P5: overwrite replaces rather than appends
- {
+ test("P5: second write replaces the first", async () => {
+ const { readPin, writePin } = await import("../../src/profile/store");
await writePin({ ...pin, profileDir: "Default", label: "Work (work@example.com)" });
const read = await readPin();
- check(read?.profileDir === "Default", "P5: second write replaces the first");
- check(readdirSync(sandbox).filter((f) => f === "browser-harness.json").length === 1, "P5: one pin file on disk");
- }
+ assert.equal(read?.profileDir, "Default");
+ assert.equal(readdirSync(sandbox).filter((f) => f === "browser-harness.json").length, 1);
+ });
- // P6: clear
- {
+ test("P6: clear succeeds and cleared pin reads as none", async () => {
+ const { readPin, clearPin } = await import("../../src/profile/store");
const cleared = await clearPin();
- check(cleared.success, "P6: clear succeeds");
- check((await readPin()) === null, "P6: cleared pin reads as none");
- }
-
- // P7: corrupt file degrades to no pin
- writeFileSync(pinFilePath(), "{ not json at all", "utf8");
- check((await readPin()) === null, "P7: malformed JSON reads as no pin");
-
- // P8: unknown schema version is not guessed at
- writeFileSync(pinFilePath(), JSON.stringify({ version: 99, profile: pin }), "utf8");
- check((await readPin()) === null, "P8: future version reads as no pin");
-
- // P9: structurally invalid pin is rejected
- writeFileSync(pinFilePath(), JSON.stringify({ version: 1, profile: { profileDir: 42 } }), "utf8");
- check((await readPin()) === null, "P9: wrong field types read as no pin");
-
- // P10: a missing agent dir is created on write
- {
+ assert.equal(cleared.success, true);
+ assert.equal(await readPin(), null);
+ });
+
+ test("P7: malformed JSON reads as no pin", async () => {
+ const { readPin } = await import("../../src/profile/store");
+ const { pinFilePath } = await import("../../src/profile/paths");
+ writeFileSync(pinFilePath(), "{ not json at all", "utf8");
+ assert.equal(await readPin(), null);
+ });
+
+ test("P8: future version reads as no pin", async () => {
+ const { readPin } = await import("../../src/profile/store");
+ const { pinFilePath } = await import("../../src/profile/paths");
+ writeFileSync(pinFilePath(), JSON.stringify({ version: 99, profile: pin }), "utf8");
+ assert.equal(await readPin(), null);
+ });
+
+ test("P9: wrong field types read as no pin", async () => {
+ const { readPin } = await import("../../src/profile/store");
+ const { pinFilePath } = await import("../../src/profile/paths");
+ writeFileSync(pinFilePath(), JSON.stringify({ version: 1, profile: { profileDir: 42 } }), "utf8");
+ assert.equal(await readPin(), null);
+ });
+
+ test("P10: write recreates a missing agent dir", async () => {
+ const { readPin, writePin } = await import("../../src/profile/store");
rmSync(sandbox, { recursive: true, force: true });
const written = await writePin(pin);
- check(written.success, "P10: write recreates a missing agent dir");
- check((await readPin())?.profileDir === "Profile 1", "P10: pin readable after dir recreation");
- }
+ assert.equal(written.success, true);
+ assert.equal((await readPin())?.profileDir, "Profile 1");
+ });
- // P11: an unwritable location surfaces as an error result, not a throw
- {
+ test("P11: write into an invalid dir returns an error result", async () => {
const blocked = join(sandbox, "blocked");
mkdirSync(blocked, { recursive: true });
writeFileSync(join(blocked, "browser-harness.json"), "{}", "utf8");
@@ -100,17 +107,7 @@ async function main(): Promise<void> {
process.env["PI_CODING_AGENT_DIR"] = join(blocked, "browser-harness.json");
const { writePin: writeAgain } = await import(`../../src/profile/store?nocache=${Date.now()}`);
const written = await writeAgain(pin);
- check(!written.success, "P11: write into an invalid dir returns an error result");
+ assert.equal(written.success, false);
process.env["PI_CODING_AGENT_DIR"] = sandbox;
- }
-
- rmSync(sandbox, { recursive: true, force: true });
-
- console.log(`\n${passed} passed, ${failed} failed`);
- process.exit(failed === 0 ? 0 : 1);
-}
-
-main().catch((e) => {
- console.error("profile store test failed:", e);
- process.exit(1);
+ });
});
diff --git a/tsconfig.base.json b/tsconfig.base.json
new file mode 100644
index 0000000..b0dba19
--- /dev/null
+++ b/tsconfig.base.json
@@ -0,0 +1,22 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "strict": true,
+ "noImplicitAny": true,
+ "strictNullChecks": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noImplicitReturns": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedIndexedAccess": true,
+ "exactOptionalPropertyTypes": true,
+ "noPropertyAccessFromIndexSignature": true,
+ "noImplicitOverride": true,
+ "forceConsistentCasingInFileNames": true,
+ "verbatimModuleSyntax": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true
+ }
+}
diff --git a/tsconfig.json b/tsconfig.json
index 1e5fe42..853a264 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,21 +1,6 @@
{
+ "extends": "./tsconfig.base.json",
"compilerOptions": {
- "target": "ES2022",
- "module": "ESNext",
- "moduleResolution": "bundler",
- "strict": true,
- "noImplicitAny": true,
- "strictNullChecks": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
- "noImplicitReturns": true,
- "noFallthroughCasesInSwitch": true,
- "noUncheckedIndexedAccess": true,
- "exactOptionalPropertyTypes": true,
- "noPropertyAccessFromIndexSignature": true,
- "forceConsistentCasingInFileNames": true,
- "esModuleInterop": true,
- "skipLibCheck": true,
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
diff --git a/tsconfig.test.json b/tsconfig.test.json
new file mode 100644
index 0000000..b9d97a4
--- /dev/null
+++ b/tsconfig.test.json
@@ -0,0 +1,9 @@
+{
+ "extends": "./tsconfig.base.json",
+ "compilerOptions": {
+ "noEmit": true,
+ "types": ["node"]
+ },
+ "include": ["src/**/*.ts", "test/**/*.ts"],
+ "exclude": ["node_modules", "dist"]
+}