// Task 443 — memory-delete refuses agent-origin deletes targeting
// the per-account root `:LocalBusiness`. The MCP tool handler never
// forwards `origin`, so every agent call is non-system.
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { setupEmitCapture } from "./_helpers/emit-capture.js";
const mockSession = {
    run: vi.fn(),
    close: vi.fn().mockResolvedValue(undefined),
};
vi.mock("../../lib/neo4j.js", () => ({
    getSession: () => mockSession,
}));
vi.mock("../../../../../../lib/graph-trash/dist/index.js", () => ({
    trashNode: vi.fn(),
}));
vi.mock("../../lib/filter-token.js", () => ({
    verifyFilterToken: vi.fn(),
    filterTokenFingerprint: () => "fp",
}));
vi.mock("../memory-find-candidates.js", () => ({
    reverifyCandidate: vi.fn(),
}));
import { memoryDelete } from "../memory-delete.js";
import { trashNode } from "../../../../../../lib/graph-trash/dist/index.js";
const trashNodeMock = vi.mocked(trashNode);
const ACCOUNT_ID = "acct-1";
const LOCAL_BUSINESS_EID = "4:eid:lb";
beforeEach(() => {
    trashNodeMock.mockReset();
    mockSession.run.mockReset();
});
afterEach(() => {
    vi.restoreAllMocks();
});
function stageLabelQuery(labels) {
    mockSession.run.mockImplementation(async (cypher) => {
        if (cypher.includes("RETURN labels(n) AS labels")) {
            return {
                records: [
                    { get: (k) => (k === "labels" ? labels : undefined) },
                ],
            };
        }
        throw new Error(`unexpected cypher: ${cypher.slice(0, 80)}`);
    });
}
describe("memory-delete reserved-label gate (Task 443)", () => {
    it("(a) system origin deletes :LocalBusiness → trashNode runs (installer reset path)", async () => {
        stageLabelQuery(["LocalBusiness"]);
        trashNodeMock.mockResolvedValue({
            trashed: true,
            alreadyTrashed: false,
            nodeId: LOCAL_BUSINESS_EID,
            labels: ["LocalBusiness"],
            trashedAt: "2026-05-26T00:00:00Z",
            originalKeys: {},
        });
        const emit = setupEmitCapture();
        try {
            const result = await memoryDelete({
                accountId: ACCOUNT_ID,
                elementId: LOCAL_BUSINESS_EID,
                origin: "system",
            });
            expect(trashNodeMock).toHaveBeenCalledTimes(1);
            expect(result.labels).toContain("LocalBusiness");
        }
        finally {
            emit.restore();
        }
    });
    it("(b) non-system origin deletes :LocalBusiness directly → rejected with literal message, no mutation", async () => {
        stageLabelQuery(["LocalBusiness"]);
        const emit = setupEmitCapture();
        try {
            await expect(memoryDelete({ accountId: ACCOUNT_ID, elementId: LOCAL_BUSINESS_EID })).rejects.toThrow(/Delete blocked \(reserved-label\): LocalBusiness is the per-account root/);
            expect(trashNodeMock).not.toHaveBeenCalled();
        }
        finally {
            emit.restore();
        }
    });
    it("(c) cascade descendant carrying :LocalBusiness is skipped; adjacent KnowledgeDocument delete proceeds", async () => {
        const PARENT_EID = "4:eid:doc";
        const NORMAL_SECTION = "4:eid:section-normal";
        const POISONED_SECTION = "4:eid:section-localbusiness";
        let runIdx = 0;
        mockSession.run.mockImplementation(async (cypher) => {
            runIdx++;
            // 1st query: pre-mutation label lookup for the parent
            if (cypher.includes("RETURN labels(n) AS labels")) {
                return {
                    records: [
                        { get: (k) => (k === "labels" ? ["KnowledgeDocument"] : undefined) },
                    ],
                };
            }
            // 2nd query: cascade descendant lookup
            if (cypher.includes("HAS_SECTION")) {
                const sections = [
                    { eid: NORMAL_SECTION, labels: ["Section"] },
                    { eid: POISONED_SECTION, labels: ["Section", "LocalBusiness"] },
                ];
                return {
                    records: [
                        {
                            get: (k) => k === "sections" ? sections : k === "chunks" ? [] : undefined,
                        },
                    ],
                };
            }
            throw new Error(`unexpected cypher: ${cypher.slice(0, 80)}`);
        });
        trashNodeMock.mockImplementation(async ({ elementId }) => ({
            trashed: true,
            alreadyTrashed: false,
            nodeId: elementId,
            labels: elementId === PARENT_EID ? ["KnowledgeDocument"] : ["Section"],
            trashedAt: "2026-05-26T00:00:00Z",
            originalKeys: {},
        }));
        const emit = setupEmitCapture();
        try {
            const result = await memoryDelete({
                accountId: ACCOUNT_ID,
                elementId: PARENT_EID,
            });
            // Parent + normal section trashed; poisoned section skipped.
            expect(trashNodeMock).toHaveBeenCalledTimes(2);
            const trashedIds = trashNodeMock.mock.calls.map((c) => c[0].elementId);
            expect(trashedIds).toContain(PARENT_EID);
            expect(trashedIds).toContain(NORMAL_SECTION);
            expect(trashedIds).not.toContain(POISONED_SECTION);
            expect(result.cascade.sections).toBe(1);
        }
        finally {
            emit.restore();
        }
    });
});
//# sourceMappingURL=memory-delete-reserved-label.test.js.map