import { describe, it, expect, vi, beforeEach } from "vitest";
// Stub neo4j session: hybrid() is mocked outright so the session is only
// used by the trash census; return an empty record set there.
vi.mock("../../lib/neo4j.js", () => ({
    getSession: () => ({
        run: vi.fn(async () => ({ records: [] })),
        close: vi.fn(async () => { }),
    }),
}));
vi.mock("../../lib/embeddings.js", () => ({
    embed: vi.fn(async () => [0, 0, 0]),
}));
const hybridMock = vi.fn();
vi.mock("../../../../../../lib/graph-search/dist/index.js", () => ({
    hybrid: (...args) => hybridMock(...args),
    isPublicOnlyScope: (allowedScopes) => Array.isArray(allowedScopes) && allowedScopes.length === 1 && allowedScopes[0] === "public",
    // Task 635: memory-search now imports the shared default floor; the mock
    // must export it or vitest rejects the named import.
    DEFAULT_VECTOR_THRESHOLD: 0.82,
}));
import { memorySearch } from "../memory-search.js";
function makeResult() {
    return {
        nodeId: "n1",
        labels: ["Person"],
        score: 0.9,
        vectorScore: 0.8,
        bm25Score: 0.5,
        related: [
            {
                nodeId: "n2",
                relationship: "KNOWS",
                direction: "outgoing",
                labels: ["Person"],
                properties: { name: "Bob", bio: "long bio" },
            },
        ],
        properties: {
            name: "Alice",
            slug: "alice",
            bio: "long bio string",
            notes: "operator notes",
            sourceUrl: "https://example.com",
        },
    };
}
const baseParams = {
    query: "alice",
    accountId: "acc-1",
};
describe("memorySearch — fields projection (Task 138)", () => {
    beforeEach(() => {
        hybridMock.mockReset();
        hybridMock.mockResolvedValue({
            mode: "hybrid",
            results: [makeResult()],
            expandMs: 0,
            rawMerged: 1,
            suppressed: 0,
            bm25Bypass: 0,
            threshold: null,
            stageCounts: {
                expansions: 1,
                vector: 1,
                bm25: 0,
                fused: 1,
                boosted: 1,
                deduped: 1,
                final: 1,
            },
        });
    });
    it("fields undefined → response identical to today (all properties present, related untouched)", async () => {
        const res = await memorySearch({ ...baseParams });
        expect(res).toHaveLength(1);
        expect(res[0].properties).toEqual({
            name: "Alice",
            slug: "alice",
            bio: "long bio string",
            notes: "operator notes",
            sourceUrl: "https://example.com",
        });
        expect(res[0].related[0].properties).toEqual({ name: "Bob", bio: "long bio" });
    });
    it("fields: [] → empty properties object, distinct from 'all'", async () => {
        const res = await memorySearch({ ...baseParams, fields: [] });
        expect(res[0].properties).toEqual({});
        expect(res[0].nodeId).toBe("n1");
        expect(res[0].labels).toEqual(["Person"]);
        expect(res[0].score).toBe(0.9);
    });
    it("fields: ['name', 'slug'] → only those keys retained", async () => {
        const res = await memorySearch({ ...baseParams, fields: ["name", "slug"] });
        expect(res[0].properties).toEqual({ name: "Alice", slug: "alice" });
    });
    it("fields: ['name', 'missingKey'] → unknown keys silently skipped", async () => {
        const res = await memorySearch({ ...baseParams, fields: ["name", "missingKey"] });
        expect(res[0].properties).toEqual({ name: "Alice" });
    });
    it("related[*].properties is NOT projected (separate concern)", async () => {
        const res = await memorySearch({ ...baseParams, fields: ["name"] });
        expect(res[0].related[0].properties).toEqual({ name: "Bob", bio: "long bio" });
    });
    it("emits [memory-search] observability line only when fields is set", async () => {
        const writes = [];
        const orig = process.stderr.write;
        process.stderr.write = ((chunk) => {
            writes.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString());
            return true;
        });
        try {
            await memorySearch({ ...baseParams });
            expect(writes.filter((w) => w.startsWith("[memory-search]"))).toHaveLength(0);
            writes.length = 0;
            await memorySearch({ ...baseParams, fields: ["name"] });
            const line = writes.find((w) => w.startsWith("[memory-search]"));
            expect(line).toBeDefined();
            expect(line).toContain("accountId=acc-1");
            expect(line).toContain("fields=name");
            expect(line).toContain("returnedProps=1");
            expect(line).toContain("droppedProps=4");
        }
        finally {
            process.stderr.write = orig;
        }
    });
});
//# sourceMappingURL=memory-search-fields.test.js.map