import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; const mocks = vi.hoisted(() => ({ createSearchClient: vi.fn(), searchWithFallback: vi.fn(), findKnowledgeBaseArticles: vi.fn(), findKnowledgeBaseArticlesByIds: vi.fn(), })); vi.mock("@toolctrl/search", () => ({ createSearchClient: mocks.createSearchClient, kbArticlesIndex: { name: "kb_articles", primaryKey: "id" }, searchWithFallback: mocks.searchWithFallback, })); vi.mock("../db/kbArticles.db.server", () => ({ findKnowledgeBaseArticles: mocks.findKnowledgeBaseArticles, findKnowledgeBaseArticlesByIds: mocks.findKnowledgeBaseArticlesByIds, })); import { searchKnowledgeBaseArticles } from "./kbArticleSearch.server"; const originalEnv = { ...process.env }; describe("searchKnowledgeBaseArticles", () => { beforeEach(() => { vi.clearAllMocks(); process.env.MEILI_URL = "http://meili.test"; delete process.env.SEARCH_BACKEND; mocks.createSearchClient.mockReturnValue({ index: vi.fn() }); }); afterEach(() => { process.env = { ...originalEnv }; }); it("hydrates Meilisearch hits by ID instead of scanning the full SQL search set", async () => { const articleA = { id: "article_a", title: "A" }; const articleB = { id: "article_b", title: "B" }; mocks.searchWithFallback.mockResolvedValue({ hits: [{ document: { id: "article_b" } }, { document: { id: "article_a" } }], estimatedTotalHits: 2, processingTimeMs: 3, }); mocks.findKnowledgeBaseArticlesByIds.mockResolvedValue([articleA, articleB]); await expect( searchKnowledgeBaseArticles({ knowledgeBaseSlug: "docs", language: "de", query: "billing", tenantId: "tenant_1", }) ).resolves.toEqual([articleB, articleA]); expect(mocks.findKnowledgeBaseArticlesByIds).toHaveBeenCalledWith({ knowledgeBaseSlug: "docs", language: "de", ids: ["article_b", "article_a"], }); expect(mocks.findKnowledgeBaseArticles).not.toHaveBeenCalled(); }); it("escapes the language value in the Meilisearch filter to prevent filter injection", async () => { mocks.searchWithFallback.mockResolvedValue({ hits: [], estimatedTotalHits: 0, processingTimeMs: 1 }); await searchKnowledgeBaseArticles({ knowledgeBaseSlug: "docs", // A crafted language attempting to break out of the quoted filter string. language: 'de" OR isPublished = "true', query: "billing", tenantId: "tenant_1", }); const passedOptions = mocks.searchWithFallback.mock.calls[0]?.[0]?.options; expect(passedOptions.extraFilters).toBe('language = "de\\" OR isPublished = \\"true" AND isPublished = true'); }); });