149 lines
6.1 KiB
TypeScript
149 lines
6.1 KiB
TypeScript
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<string, unknown>) => `${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");
|
|
}
|
|
});
|
|
});
|