Skip to content

Commit bf2bd27

Browse files
author
Tehan
committed
fix(test): stop global embedding mock.module bleed across test files (use provider-factory seam)
Bun mock.module is process-global and mock.restore() cannot undo it cross-file in Bun 1.3.14. The skill-memory test files (reembed, recall, ctx-skill-note) and promotion.test.ts each globally mocked the embedding barrel, which bled into ctx-memory's provider-coordination tests (5s timeouts) and into each other under CI worker sharding. Converted all four files to the non-global seam that ctx-memory's own tests use: _setTestProviderFactoryForProject + registerProjectEmbedding with mandatory afterEach reset. Zero mock.module calls for any embedding barrel remain in the test suite.
1 parent 93ace54 commit bf2bd27

4 files changed

Lines changed: 271 additions & 105 deletions

File tree

packages/plugin/src/features/magic-context/memory/promotion.test.ts

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,10 @@ import { afterEach, beforeEach, describe, expect, it, mock, spyOn } from "bun:te
44
import { Database } from "../../../shared/sqlite";
55
import { closeQuietly } from "../../../shared/sqlite-helpers";
66
import { CATEGORY_DEFAULT_TTL } from "./constants";
7-
// The real module is imported (and evaluated) here BEFORE the mock registers,
8-
// so it captures every real export. Bun's mock.module is process-global and
9-
// persists across files in one test run, so a PARTIAL mock leaks into sibling
10-
// tests that import the omitted exports (e.g. embedding-backfill.test.ts /
11-
// embedding-cache.test.ts fail with "Export named 'embedBatchForProject' not
12-
// found" under a whole-dir run). Spreading the real module keeps the mock
13-
// complete — only the three functions this file needs stubbed are overridden.
14-
import * as realEmbedding from "./embedding";
157
import { computeNormalizedHash } from "./normalize-hash";
168

17-
const mockEmbedText = mock(async () => null);
189
const mockLog = mock(() => {});
1910

20-
mock.module("./embedding", () => ({
21-
...realEmbedding,
22-
embedText: mockEmbedText,
23-
embedTextForProject: mockEmbedText,
24-
getEmbeddingModelId: () => "mock:model",
25-
}));
26-
2711
mock.module("../../../shared/logger", () => ({
2812
log: mockLog,
2913
sessionLog: mockLog,
@@ -101,8 +85,6 @@ function makeMemoryDatabase(): Database {
10185
}
10286

10387
beforeEach(() => {
104-
mockEmbedText.mockReset();
105-
mockEmbedText.mockImplementation(async () => null);
10688
mockLog.mockReset();
10789
mockLog.mockImplementation(() => {});
10890
});

packages/plugin/src/features/magic-context/skill-memory/recall.test.ts

Lines changed: 98 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1-
import { describe, expect, mock, test } from "bun:test";
1+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
22
import { Database } from "../../../shared/sqlite";
33
import { closeQuietly } from "../../../shared/sqlite-helpers";
4+
import {
5+
_resetProjectEmbeddingRegistryForTests,
6+
_setTestProviderFactoryForProject,
7+
type ProjectEmbeddingRegistrationSnapshot,
8+
registerProjectEmbedding,
9+
} from "../memory/embedding";
10+
import type { EmbeddingProvider, EmbeddingPurpose } from "../memory/embedding-provider";
411
import { float32ArrayToBlob } from "../memory/storage-memory-embeddings";
512
import { runMigrations } from "../migrations";
613
import { initializeDatabase } from "../storage-db";
@@ -21,6 +28,45 @@ function makeDb(): Database {
2128
return db;
2229
}
2330

31+
let EMBED_UP = true;
32+
33+
function installRecallTestProvider(): void {
34+
_setTestProviderFactoryForProject(
35+
(): EmbeddingProvider => ({
36+
modelId: "test-provider-model",
37+
initialize: async () => true,
38+
embed: async (text: string, _signal?: AbortSignal, _purpose?: EmbeddingPurpose) => {
39+
if (!EMBED_UP) return null;
40+
return text.includes("auth") ? new Float32Array([1, 0]) : new Float32Array([0, 1]);
41+
},
42+
embedBatch: async (
43+
texts: string[],
44+
_signal?: AbortSignal,
45+
_purpose?: EmbeddingPurpose,
46+
) =>
47+
texts.map((t) => {
48+
if (!EMBED_UP) return new Float32Array();
49+
return t.includes("auth") ? new Float32Array([1, 0]) : new Float32Array([0, 1]);
50+
}),
51+
dispose: async () => {},
52+
isLoaded: () => true,
53+
}),
54+
);
55+
}
56+
57+
function registerRecallTestProject(
58+
db: Database,
59+
identity: string,
60+
): ProjectEmbeddingRegistrationSnapshot {
61+
return registerProjectEmbedding(
62+
db,
63+
identity,
64+
{ provider: "local", model: "mock-model" },
65+
{ memoryEnabled: true, gitCommitEnabled: false },
66+
identity,
67+
);
68+
}
69+
2470
describe("flatRecall", () => {
2571
test("returns empty array when no notes exist (cold-start rung 5)", () => {
2672
const db = makeDb();
@@ -119,20 +165,6 @@ describe("rankRung1", () => {
119165
});
120166
});
121167

122-
let EMBED_UP = true;
123-
mock.module("../memory/embedding", () => ({
124-
embedTextForProject: async (_p: string, text: string) =>
125-
EMBED_UP
126-
? {
127-
vector: text.includes("auth")
128-
? new Float32Array([1, 0])
129-
: new Float32Array([0, 1]),
130-
modelId: "m1",
131-
generation: 1,
132-
}
133-
: null,
134-
}));
135-
136168
function modeOf(block: string): string | null {
137169
return block.match(/<skill-memory[^>]*\bmode="([^"]+)"/)?.[1] ?? null;
138170
}
@@ -144,9 +176,21 @@ const cfg = {
144176
};
145177

146178
describe("recallSkillMemoryBlock (intent-scoped rungs)", () => {
179+
beforeEach(() => {
180+
_resetProjectEmbeddingRegistryForTests();
181+
_setTestProviderFactoryForProject(null);
182+
installRecallTestProvider();
183+
});
184+
185+
afterEach(() => {
186+
_resetProjectEmbeddingRegistryForTests();
187+
_setTestProviderFactoryForProject(null);
188+
});
189+
147190
test("rung 1 full: provider up + intent + a model-matched embedded note", async () => {
148191
const db = makeDb();
149192
EMBED_UP = true;
193+
const snap = registerRecallTestProject(db, "git:x");
150194
insertSkillMemoryNote(db, {
151195
skillId: "s",
152196
resolvedPath: "/p",
@@ -160,7 +204,7 @@ describe("recallSkillMemoryBlock (intent-scoped rungs)", () => {
160204
createdAt: 1,
161205
intentEmbedding: float32ArrayToBlob(new Float32Array([1, 0])),
162206
deltaEmbedding: float32ArrayToBlob(new Float32Array([1, 0])),
163-
embeddingModelVersion: "m1",
207+
embeddingModelVersion: snap.modelId,
164208
});
165209
const block = await recallSkillMemoryBlock(db, {
166210
skill: "s",
@@ -175,6 +219,7 @@ describe("recallSkillMemoryBlock (intent-scoped rungs)", () => {
175219
test("rung 2 no-intent: provider up, no intent → flat", async () => {
176220
const db = makeDb();
177221
EMBED_UP = true;
222+
registerRecallTestProject(db, "git:x");
178223
insertSkillMemoryNote(db, {
179224
skillId: "s",
180225
resolvedPath: "/p",
@@ -199,6 +244,7 @@ describe("recallSkillMemoryBlock (intent-scoped rungs)", () => {
199244
test("rung 3 fts5-fallback: provider down + intent → FTS", async () => {
200245
const db = makeDb();
201246
EMBED_UP = false;
247+
registerRecallTestProject(db, "git:x");
202248
insertSkillMemoryNote(db, {
203249
skillId: "s",
204250
resolvedPath: "/p",
@@ -224,6 +270,7 @@ describe("recallSkillMemoryBlock (intent-scoped rungs)", () => {
224270
test("rung 4 flat-fts: provider down + UNINDEXABLE intent (sanitize→empty) → flat", async () => {
225271
const db = makeDb();
226272
EMBED_UP = false;
273+
registerRecallTestProject(db, "git:x");
227274
insertSkillMemoryNote(db, {
228275
skillId: "s",
229276
resolvedPath: "/p",
@@ -249,6 +296,7 @@ describe("recallSkillMemoryBlock (intent-scoped rungs)", () => {
249296
test("rung 5 cold: no notes → empty block", async () => {
250297
const db = makeDb();
251298
EMBED_UP = true;
299+
registerRecallTestProject(db, "git:x");
252300
const block = await recallSkillMemoryBlock(db, {
253301
skill: "s",
254302
intent: "x",
@@ -262,6 +310,7 @@ describe("recallSkillMemoryBlock (intent-scoped rungs)", () => {
262310
test("zero model-matched → falls to rung 3, never empty full block", async () => {
263311
const db = makeDb();
264312
EMBED_UP = true;
313+
registerRecallTestProject(db, "git:x");
265314
insertSkillMemoryNote(db, {
266315
skillId: "s",
267316
resolvedPath: "/p",
@@ -289,6 +338,7 @@ describe("recallSkillMemoryBlock (intent-scoped rungs)", () => {
289338
test("intent-scoped recall matches a note sharing SOME (not all) intent tokens (OR semantics)", async () => {
290339
const db = makeDb();
291340
EMBED_UP = false; // force rung 3 FTS path
341+
registerRecallTestProject(db, "git:abc");
292342
try {
293343
insertSkillMemoryNote(db, {
294344
skillId: "tdd",
@@ -335,6 +385,7 @@ describe("recallSkillMemoryBlock (intent-scoped rungs)", () => {
335385
test("pinned notes appear even when intent doesn't match them (M2)", async () => {
336386
const db = makeDb();
337387
EMBED_UP = true;
388+
const snap = registerRecallTestProject(db, "git:x");
338389
db.prepare(
339390
`INSERT INTO skill_memory (skill_id,resolved_path,tier,project_identity,intent,kind,delta,normalized_hash,hit_count,pinned,created_at)
340391
VALUES ('s','/p','global','*','old auth fix','fix','rotate token','h1',0,1,1)`,
@@ -353,7 +404,7 @@ describe("recallSkillMemoryBlock (intent-scoped rungs)", () => {
353404
createdAt: 1000 + i,
354405
intentEmbedding: float32ArrayToBlob(new Float32Array([0, 1])),
355406
deltaEmbedding: float32ArrayToBlob(new Float32Array([0, 1])),
356-
embeddingModelVersion: "m1",
407+
embeddingModelVersion: snap.modelId,
357408
});
358409
}
359410
const block = await recallSkillMemoryBlock(db, {
@@ -407,9 +458,23 @@ describe("buildSkillMemoryBlock", () => {
407458
});
408459

409460
describe("cross-project global recall", () => {
461+
beforeEach(() => {
462+
_resetProjectEmbeddingRegistryForTests();
463+
_setTestProviderFactoryForProject(null);
464+
installRecallTestProvider();
465+
EMBED_UP = true;
466+
});
467+
468+
afterEach(() => {
469+
_resetProjectEmbeddingRegistryForTests();
470+
_setTestProviderFactoryForProject(null);
471+
});
472+
410473
test("a global note learned in repo A surfaces when recalled from repo B", async () => {
411474
const db = makeDb();
412475
try {
476+
registerRecallTestProject(db, "git:repoA");
477+
registerRecallTestProject(db, "git:repoB");
413478
promoteSkillObservations(db, "git:repoA", [
414479
{
415480
skillId: "council",
@@ -436,6 +501,7 @@ describe("cross-project global recall", () => {
436501
// '*' rows, so historian notes were orphaned for project-local skills.
437502
const db = makeDb();
438503
try {
504+
registerRecallTestProject(db, "git:repoA");
439505
promoteSkillObservations(db, "git:repoA", [
440506
{
441507
skillId: "tdd",
@@ -459,6 +525,7 @@ describe("cross-project global recall", () => {
459525
test("a project-local AGENT note and a global HISTORIAN note both surface for the same skill", async () => {
460526
const db = makeDb();
461527
try {
528+
registerRecallTestProject(db, "git:repoLocal");
462529
// Agent-written, project-tier note.
463530
insertSkillMemoryNote(db, {
464531
skillId: "tdd",
@@ -491,9 +558,22 @@ describe("cross-project global recall", () => {
491558
});
492559

493560
describe("recallSkillMemoryBlock bumps recall_count for surfaced notes", () => {
561+
beforeEach(() => {
562+
_resetProjectEmbeddingRegistryForTests();
563+
_setTestProviderFactoryForProject(null);
564+
installRecallTestProvider();
565+
EMBED_UP = true;
566+
});
567+
568+
afterEach(() => {
569+
_resetProjectEmbeddingRegistryForTests();
570+
_setTestProviderFactoryForProject(null);
571+
});
572+
494573
test("a surfaced note's recall_count increments per recall (no-intent rung)", async () => {
495574
const db = makeDb();
496575
try {
576+
registerRecallTestProject(db, "git:abc");
497577
insertSkillMemoryNote(db, {
498578
skillId: "tdd",
499579
resolvedPath: "/p/SKILL.md",
@@ -537,6 +617,7 @@ describe("recallSkillMemoryBlock bumps recall_count for surfaced notes", () => {
537617
test("a cold-start recall (no notes) bumps nothing and returns empty", async () => {
538618
const db = makeDb();
539619
try {
620+
registerRecallTestProject(db, "git:abc");
540621
const block = await recallSkillMemoryBlock(db, {
541622
skill: "ghost",
542623
scope: "global",

0 commit comments

Comments
 (0)