diff options
| author | amankumarsingh77 <amankumarsingh7702@gmail.com> | 2026-07-27 00:13:06 +0530 |
|---|---|---|
| committer | Aman Kumar <145339995+amankumarsingh77@users.noreply.github.com> | 2026-08-02 19:12:46 +0530 |
| commit | 3607b5523f9d23ec6b9facf8474c7bdf047537e1 (patch) | |
| tree | 87c433788599b1f414cb248759f2eed6500071e2 | |
| parent | 84ebb018f59ab5efe8bc97e8e4b87435d6665b0b (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.yml | 3 | ||||
| -rw-r--r-- | package.json | 3 | ||||
| -rw-r--r-- | test/manual/bridge-test.ts | 6 | ||||
| -rw-r--r-- | test/manual/daemon-transport-test.ts | 2 | ||||
| -rw-r--r-- | test/manual/ipc-server-test.ts | 9 | ||||
| -rw-r--r-- | test/profile/store-test.ts | 145 | ||||
| -rw-r--r-- | tsconfig.base.json | 22 | ||||
| -rw-r--r-- | tsconfig.json | 17 | ||||
| -rw-r--r-- | tsconfig.test.json | 9 |
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"] +} |
