/* Copyright 2026 Marimo. All rights reserved. */ import { afterEach, beforeEach, describe, expect, it, type Mock, vi, } from "vitest"; import { variableName } from "@/__tests__/branded"; import * as aiContext from "@/core/ai/context/context"; import { getCodes } from "@/core/codemirror/copilot/getCodes"; import { dataSourceConnectionsAtom } from "@/core/datasets/data-source-connections"; import { DUCKDB_ENGINE } from "@/core/datasets/engines"; import { datasetsAtom } from "@/core/datasets/state"; import type { DatasetsState } from "@/core/datasets/types"; import { store } from "@/core/state/jotai"; import { variablesAtom } from "@/core/variables/state"; import type { FileUIPart, UIMessage } from "ai"; import { getAICompletionBody, getAICompletionBodyWithAttachments, isContextAttachment, MARIMO_CONTEXT_PART_TYPE, resolveChatContext, } from "../completion-utils"; // Mock getCodes function vi.mock("@/core/codemirror/copilot/getCodes", () => ({ getCodes: vi.fn(), })); describe("getAICompletionBody", () => { beforeEach(() => { // Reset the Jotai store before each test store.set(datasetsAtom, { tables: [], } as unknown as DatasetsState); store.set(dataSourceConnectionsAtom, { latestEngineSelected: DUCKDB_ENGINE, connectionsMap: new Map(), }); store.set(variablesAtom, {}); (getCodes as Mock).mockReturnValue("// Some other code"); }); it("should return the correct completion body with mentioned datasets", () => { // Set up test data in the Jotai store const testDatasets = [ { name: "dataset1", columns: [ { name: "col1", type: "number" }, { name: "col2", type: "string" }, ], }, { name: "dataset2", columns: [ { name: "col3", type: "boolean" }, { name: "col4", type: "date" }, ], }, ]; store.set(datasetsAtom, { tables: testDatasets } as DatasetsState); const input = "Use @data://dataset1 and @data://dataset2 for analysis"; const result = getAICompletionBody({ input }); expect(result).toMatchInlineSnapshot(` { "context": { "plainText": "Columns: col1 (number) col2 (string) Columns: col3 (boolean) col4 (date)", "schema": [], "variables": [], }, "includeOtherCode": "// Some other code", } `); }); it("should handle input with no mentioned datasets", () => { const input = "Perform some analysis without mentioning @data://datasets"; const result = getAICompletionBody({ input }); expect(result).toMatchInlineSnapshot(` { "context": { "plainText": "", "schema": [], "variables": [], }, "includeOtherCode": "// Some other code", } `); }); it("should handle input with non-existent datasets", () => { // Set up test data in the Jotai store const testDatasets = [ { name: "existingDataset", columns: [ { name: "col1", type: "number" }, { name: "col2", type: "string" }, ], }, ]; store.set(datasetsAtom, { tables: testDatasets } as DatasetsState); const input = "Use @data://existingDataset and @data://nonExistentDataset for analysis"; const result = getAICompletionBody({ input }); expect(result).toMatchInlineSnapshot(` { "context": { "plainText": "Columns: col1 (number) col2 (string)", "schema": [], "variables": [], }, "includeOtherCode": "// Some other code", } `); }); it("should handle dataset names with dots", () => { // Set up test data in the Jotai store const testDatasets = [ { name: "dataset.with.dots", columns: [ { name: "col1", type: "number" }, { name: "col2", type: "string" }, ], }, { name: "regular_dataset", columns: [{ name: "col3", type: "boolean" }], }, ]; store.set(datasetsAtom, { tables: testDatasets } as DatasetsState); const input = "Use @data://dataset.with.dots and @data://regular_dataset for analysis"; const result = getAICompletionBody({ input }); expect(result).toMatchInlineSnapshot(` { "context": { "plainText": "Columns: col1 (number) col2 (string) Columns: col3 (boolean)", "schema": [], "variables": [], }, "includeOtherCode": "// Some other code", } `); }); it("should handle connections", () => { // Set up test data in the Jotai store const testConnection = { name: DUCKDB_ENGINE, dialect: "duckdb", source: "duckdb", display_name: "DuckDB In-Memory", default_schema: "default_schema", databases: [ { name: "db1", schemas: [ { name: "default_schema", tables: [ { name: "table1", columns: [{ name: "col1", type: "number" }] }, { name: "table2", columns: [] }, ], }, { name: "other_schema", tables: [{ name: "table3", columns: [] }], }, ], }, ], }; store.set(dataSourceConnectionsAtom, { latestEngineSelected: DUCKDB_ENGINE, connectionsMap: new Map().set(DUCKDB_ENGINE, testConnection), }); const input = "Use @data://table1 for analysis"; const result = getAICompletionBody({ input }); expect(result).toMatchInlineSnapshot(` { "context": { "plainText": "Columns: col1 (number)", "schema": [], "variables": [], }, "includeOtherCode": "// Some other code", } `); }); it("should return the correct completion body with mentioned variables", () => { // Set up test data in the Jotai store const testVariables = { [variableName("var1")]: { name: variableName("var1"), value: "string value", dataType: "string", declaredBy: [], usedBy: [], }, [variableName("var2")]: { name: variableName("var2"), value: "42", dataType: "number", declaredBy: [], usedBy: [], }, }; store.set(variablesAtom, testVariables); const input = "Use @variable://var1 and @variable://var2 for analysis"; const result = getAICompletionBody({ input }); expect(result).toMatchInlineSnapshot(` { "context": { "plainText": ""string value" "42"", "schema": [], "variables": [], }, "includeOtherCode": "// Some other code", } `); }); it("should handle input with both datasets and variables", () => { // Set up test data in the Jotai store const testDatasets = [ { name: "dataset1", columns: [ { name: "col1", type: "number" }, { name: "col2", type: "string" }, ], }, ]; store.set(datasetsAtom, { tables: testDatasets } as DatasetsState); const testVariables = { [variableName("var1")]: { name: variableName("var1"), value: "string value", dataType: "string", declaredBy: [], usedBy: [], }, }; store.set(variablesAtom, testVariables); const input = "Use @data://dataset1 and @variable://var1 for analysis"; const result = getAICompletionBody({ input }); expect(result).toMatchInlineSnapshot(` { "context": { "plainText": "Columns: col1 (number) col2 (string) "string value"", "schema": [], "variables": [], }, "includeOtherCode": "// Some other code", } `); }); it("should handle non-existent variables", () => { // Set up test data in the Jotai store const testVariables = { [variableName("existingVar")]: { name: variableName("existingVar"), value: "string value", dataType: "string", declaredBy: [], usedBy: [], }, }; store.set(variablesAtom, testVariables); const input = "Use @variable://existingVar and @variable://nonExistentVar for analysis"; const result = getAICompletionBody({ input }); expect(result).toMatchInlineSnapshot(` { "context": { "plainText": ""string value"", "schema": [], "variables": [], }, "includeOtherCode": "// Some other code", } `); }); it("should prioritize datasets over variables when there's a name conflict", () => { // Set up test data in the Jotai store with a name conflict const testDatasets = [ { name: "conflict", columns: [{ name: "col1", type: "number" }], }, ]; store.set(datasetsAtom, { tables: testDatasets } as DatasetsState); const testVariables = { [variableName("conflict")]: { name: variableName("conflict"), value: "string value", dataType: "string", declaredBy: [], usedBy: [], }, }; store.set(variablesAtom, testVariables); const input = "Use @data://conflict for analysis"; const result = getAICompletionBody({ input }); expect(result).toMatchInlineSnapshot(` { "context": { "plainText": "Columns: col1 (number)", "schema": [], "variables": [], }, "includeOtherCode": "// Some other code", } `); }); }); describe("resolveChatContext", () => { beforeEach(() => { store.set(datasetsAtom, { tables: [], } as unknown as DatasetsState); store.set(dataSourceConnectionsAtom, { latestEngineSelected: DUCKDB_ENGINE, connectionsMap: new Map(), }); store.set(variablesAtom, {}); }); it("returns no context when the input has no @-mentions", async () => { const result = await resolveChatContext("just a plain question"); expect(result).toEqual({ contextPart: null, attachments: [] }); }); it("returns no context part when @-mentions resolve to nothing", async () => { const result = await resolveChatContext("look at @variable://ghost"); expect(result.contextPart).toBeNull(); expect(result.attachments).toEqual([]); }); it("captures resolved @-context into a data part", async () => { store.set(variablesAtom, { [variableName("var1")]: { name: variableName("var1"), value: "string value", dataType: "string", declaredBy: [], usedBy: [], }, }); const result = await resolveChatContext("inspect @variable://var1"); expect(result.contextPart?.type).toBe(MARIMO_CONTEXT_PART_TYPE); expect(result.contextPart?.data.contextIds).toEqual(["variable://var1"]); expect(result.contextPart?.data.plainText).toMatchInlineSnapshot( `""string value""`, ); }); }); describe("isContextAttachment", () => { type Part = UIMessage["parts"][number]; it("is true for a file part tagged as context", () => { const part = { type: "file", mediaType: "image/png", url: "data:image/png;base64,abc", providerMetadata: { marimo: { source: "context" } }, } as Part; expect(isContextAttachment(part)).toBe(true); }); it("is false for a user-uploaded file part (no marker)", () => { const part = { type: "file", mediaType: "image/png", url: "data:image/png;base64,abc", } as Part; expect(isContextAttachment(part)).toBe(false); }); it("is false for a file part with unrelated provider metadata", () => { const part = { type: "file", mediaType: "image/png", url: "data:image/png;base64,abc", providerMetadata: { openai: { foo: "bar" } }, } as Part; expect(isContextAttachment(part)).toBe(false); }); it("is false for non-file parts", () => { expect(isContextAttachment({ type: "text", text: "hi" } as Part)).toBe( false, ); }); }); describe("context attachment stamping", () => { const rawAttachment: FileUIPart = { type: "file", mediaType: "image/png", url: "data:image/png;base64,abc", }; beforeEach(() => { vi.spyOn(aiContext, "getAIContextRegistry").mockReturnValue({ parseAllContextIds: () => ["data://t1"], formatContextForAI: () => '', getAttachmentsForContext: async () => [rawAttachment], } as unknown as ReturnType); }); afterEach(() => { vi.restoreAllMocks(); }); it("stamps chat attachments as context-derived", async () => { const { attachments } = await resolveChatContext("see @data://t1"); expect(attachments).toHaveLength(1); expect(isContextAttachment(attachments[0])).toBe(true); // The original attachment is left untouched (we return a stamped copy). expect(rawAttachment.providerMetadata).toBeUndefined(); }); it("stamps completion attachments the same way as chat", async () => { const { attachments } = await getAICompletionBodyWithAttachments({ input: "see @data://t1", }); expect(attachments).toHaveLength(1); expect(isContextAttachment(attachments[0])).toBe(true); }); });