|
6 | 6 | import { describe, it } from "node:test"; |
7 | 7 | import assert from "node:assert/strict"; |
8 | 8 | import { mkdtemp } from "node:fs/promises"; |
| 9 | +import { mkdtempSync } from "node:fs"; |
9 | 10 | import { tmpdir } from "node:os"; |
10 | 11 | import path from "node:path"; |
11 | 12 | import { LiveLLMWikiProvider } from "../live/provider.ts"; |
12 | 13 |
|
| 14 | +// A real, private directory. These constructor tests previously hardcoded |
| 15 | +// TMP_ROOT, which is a shared path any process on the host can occupy. When it |
| 16 | +// exists as a FILE, createWiki() fails with "root exists but is not a |
| 17 | +// directory" and the positive cases fail, while the negative cases still pass |
| 18 | +// because scope validation throws before touching the filesystem. That made the |
| 19 | +// suite dependent on ambient host state rather than hermetic. |
| 20 | +const TMP_ROOT = mkdtempSync(path.join(tmpdir(), "llmwiki-provider-")); |
| 21 | + |
13 | 22 | const scope = { user: "u1" }; |
14 | 23 | const mk = (root: string) => new LiveLLMWikiProvider({ root, scope, projectId: "proj-1" }); |
15 | 24 |
|
16 | 25 | describe("LiveLLMWikiProvider", () => { |
17 | 26 | it("requires projectId", () => { |
18 | 27 | assert.throws( |
19 | | - () => new (LiveLLMWikiProvider as any)({ root: "/tmp/x", scope }), |
| 28 | + () => new (LiveLLMWikiProvider as any)({ root: TMP_ROOT, scope }), |
20 | 29 | (e: any) => e?.code === "E_LLMWIKI_PROJECT_ID_REQUIRED", |
21 | 30 | ); |
22 | 31 | }); |
@@ -64,7 +73,7 @@ describe("LiveLLMWikiProvider", () => { |
64 | 73 | }); |
65 | 74 |
|
66 | 75 | it("capabilities advertises text/messages/verbatim + package", () => { |
67 | | - const c = mk("/tmp/whatever").capabilities(); |
| 76 | + const c = mk(TMP_ROOT).capabilities(); |
68 | 77 | assert.deepEqual(c.ingestModes, ["text", "messages", "verbatim"]); |
69 | 78 | assert.equal(c.extensions.package, true); |
70 | 79 | }); |
@@ -260,28 +269,28 @@ describe("LiveLLMWikiProvider scope is copied at the boundary (no reference leak |
260 | 269 | describe("LiveLLMWikiProvider construction scope validation (FIX G)", () => { |
261 | 270 | it("throws E_LLMWIKI_PROVIDER_SCOPE_MISMATCH when scope is empty {}", () => { |
262 | 271 | assert.throws( |
263 | | - () => new LiveLLMWikiProvider({ root: "/tmp/x", scope: {}, projectId: "proj-1" }), |
| 272 | + () => new LiveLLMWikiProvider({ root: TMP_ROOT, scope: {}, projectId: "proj-1" }), |
264 | 273 | (e: any) => e?.code === "E_LLMWIKI_PROVIDER_SCOPE_MISMATCH", |
265 | 274 | ); |
266 | 275 | }); |
267 | 276 |
|
268 | 277 | it("throws E_LLMWIKI_PROVIDER_SCOPE_MISMATCH when required field 'user' is missing", () => { |
269 | 278 | assert.throws( |
270 | | - () => new LiveLLMWikiProvider({ root: "/tmp/x", scope: { namespace: "ns" }, projectId: "proj-1" }), |
| 279 | + () => new LiveLLMWikiProvider({ root: TMP_ROOT, scope: { namespace: "ns" }, projectId: "proj-1" }), |
271 | 280 | (e: any) => e?.code === "E_LLMWIKI_PROVIDER_SCOPE_MISMATCH", |
272 | 281 | ); |
273 | 282 | }); |
274 | 283 |
|
275 | 284 | it("throws E_LLMWIKI_PROVIDER_SCOPE_MISMATCH when 'user' is empty string", () => { |
276 | 285 | assert.throws( |
277 | | - () => new LiveLLMWikiProvider({ root: "/tmp/x", scope: { user: "" }, projectId: "proj-1" }), |
| 286 | + () => new LiveLLMWikiProvider({ root: TMP_ROOT, scope: { user: "" }, projectId: "proj-1" }), |
278 | 287 | (e: any) => e?.code === "E_LLMWIKI_PROVIDER_SCOPE_MISMATCH", |
279 | 288 | ); |
280 | 289 | }); |
281 | 290 |
|
282 | 291 | it("valid scope { user: 'u1' } constructs without throwing", () => { |
283 | 292 | assert.doesNotThrow( |
284 | | - () => new LiveLLMWikiProvider({ root: "/tmp/x", scope: { user: "u1" }, projectId: "proj-1" }), |
| 293 | + () => new LiveLLMWikiProvider({ root: TMP_ROOT, scope: { user: "u1" }, projectId: "proj-1" }), |
285 | 294 | ); |
286 | 295 | }); |
287 | 296 | }); |
|
0 commit comments