import { beforeEach, describe, expect, test, vi } from "vitest"; const getAllKnowledgeBaseArticlesMock = vi.fn(); const createKnowledgeBaseArticleMock = vi.fn(); const getAllKnowledgeBaseCategoriesMock = vi.fn(); const getFixedTMock = vi.fn(); const knowledgeBaseArticleCountMock = vi.fn(); const knowledgeBaseCategoryCountMock = vi.fn(); const knowledgeBaseDeleteMock = vi.fn(); const cachifiedMock = vi.fn(); vi.mock("../db/kbArticles.db.server", () => ({ createKnowledgeBaseArticle: (...args: unknown[]) => createKnowledgeBaseArticleMock(...args), getAllKnowledgeBaseArticles: (...args: unknown[]) => getAllKnowledgeBaseArticlesMock(...args), getFeaturedKnowledgeBaseArticles: vi.fn(), getKbArticleById: vi.fn(), getKbArticleByIdInKnowledgeBase: vi.fn(), getKbArticleBySlug: vi.fn(), })); vi.mock("../db/kbCategories.db.server", () => ({ createKnowledgeBaseCategory: vi.fn(), getAllKnowledgeBaseCategories: (...args: unknown[]) => getAllKnowledgeBaseCategoriesMock(...args), getKbCategoryById: vi.fn(), getKbCategoryBySlug: vi.fn(), })); vi.mock("../db/knowledgeBase.db.server", () => ({ getAllKnowledgeBases: vi.fn(), getKnowledgeBaseById: vi.fn(), getKnowledgeBaseBySlug: vi.fn(), })); vi.mock("../services/kbArticleSearch.server", () => ({ searchKnowledgeBaseArticles: vi.fn(), })); vi.mock("~/locale/i18next.server", () => ({ remixI18Next: { getFixedT: (...args: unknown[]) => getFixedTMock(...args), }, })); vi.mock("~/utils/cache.server", () => ({ cachified: (...args: unknown[]) => cachifiedMock(...args), })); vi.mock("~/utils/db.server", () => ({ db: { knowledgeBaseArticle: { count: (...args: unknown[]) => knowledgeBaseArticleCountMock(...args) }, knowledgeBaseCategory: { count: (...args: unknown[]) => knowledgeBaseCategoryCountMock(...args) }, knowledgeBase: { delete: (...args: unknown[]) => knowledgeBaseDeleteMock(...args) }, }, })); describe("KnowledgeBaseServiceServer", () => { beforeEach(() => { vi.clearAllMocks(); getAllKnowledgeBaseArticlesMock.mockResolvedValue([ { id: "article-1", slug: "untitled", order: 1, }, ]); getAllKnowledgeBaseCategoriesMock.mockResolvedValue([]); createKnowledgeBaseArticleMock.mockImplementation(async (input) => ({ id: "created", ...input })); getFixedTMock.mockImplementation(async (locale: string) => (key: string, vars?: Record) => `${locale}:${key}:${vars?.number ?? ""}`); knowledgeBaseArticleCountMock.mockResolvedValue(0); knowledgeBaseCategoryCountMock.mockResolvedValue(0); knowledgeBaseDeleteMock.mockResolvedValue({ id: "kb-1" }); cachifiedMock.mockImplementation(async ({ getFreshValue }: { getFreshValue: () => unknown }) => getFreshValue()); }); test("newArticle localizes generated untitled titles and persists the resolved language", async () => { const KnowledgeBaseServiceServer = (await import("./KnowledgeBaseService.server")).default; const created = await KnowledgeBaseServiceServer.newArticle({ kb: { id: "kb-1", slug: "docs", defaultLanguage: "de", } as never, params: {}, userId: "user-1", position: "last", }); expect(getFixedTMock).toHaveBeenCalledWith("de", "translations"); expect(createKnowledgeBaseArticleMock).toHaveBeenCalledWith(expect.objectContaining({ title: "de:knowledgeBase.adminCategories.untitledNumbered:1", language: "de" })); expect(created).toMatchObject({ title: "de:knowledgeBase.adminCategories.untitledNumbered:1", language: "de" }); }); test("del exposes coded delete guard errors for localized route handling", async () => { const { default: KnowledgeBaseServiceServer, getKnowledgeBaseActionErrorKey } = await import("./KnowledgeBaseService.server"); knowledgeBaseArticleCountMock.mockResolvedValueOnce(1); try { await KnowledgeBaseServiceServer.del({ id: "kb-1" } as never); throw new Error("Expected delete to reject"); } catch (error) { expect(error).toMatchObject({ code: "cannot-delete-with-articles" }); expect(getKnowledgeBaseActionErrorKey(error)).toBe("knowledgeBase.admin.errors.cannotDeleteWithArticles"); } }); test("duplicate and new article validation errors map to semantic i18n keys", async () => { const { default: KnowledgeBaseServiceServer, getKnowledgeBaseActionErrorKey } = await import("./KnowledgeBaseService.server"); await expect( KnowledgeBaseServiceServer.duplicateArticle({ kb: { id: "kb-1", slug: "docs", defaultLanguage: "en" } as never, language: "en", articleId: "missing" }) ).rejects.toMatchObject({ code: "invalid-article" }); try { await KnowledgeBaseServiceServer.newArticle({ kb: { id: "kb-1", slug: "docs", defaultLanguage: "en" } as never, params: {}, userId: "user-1", position: "last", categoryId: "missing", }); } catch (error) { expect(getKnowledgeBaseActionErrorKey(error)).toBe("knowledgeBase.adminCategories.errors.categoryNotFound"); } }); test("public lookup errors map to public i18n keys", async () => { const { default: KnowledgeBaseServiceServer, getKnowledgeBasePublicErrorKey } = await import("./KnowledgeBaseService.server"); cachifiedMock.mockResolvedValueOnce(null); try { await KnowledgeBaseServiceServer.get({ slug: "missing", enabled: true, request: new Request("https://example.com/help") }); throw new Error("Expected lookup to reject"); } catch (error) { expect(error).toMatchObject({ code: "not-found" }); expect(getKnowledgeBasePublicErrorKey(error)).toBe("knowledgeBase.public.errors.notFound"); } cachifiedMock.mockResolvedValueOnce({ title: "Draft docs", enabled: false }); try { await KnowledgeBaseServiceServer.get({ slug: "draft", enabled: true, request: new Request("https://example.com/help") }); throw new Error("Expected disabled knowledge base to reject"); } catch (error) { expect(error).toMatchObject({ code: "disabled" }); expect(getKnowledgeBasePublicErrorKey(error)).toBe("knowledgeBase.public.errors.disabled"); } }); });