import { describe, it, expect, beforeEach, vi } from "vitest"; import { Test, TestingModule } from "@nestjs/testing"; import { BadRequestException } from "@nestjs/common"; import { ImportExportService } from "./import-export.service"; import { KnowledgeService } from "../knowledge.service"; import { PrismaService } from "../../prisma/prisma.service"; import { ExportFormat } from "../dto"; import { EntryStatus, Visibility } from "@prisma/client"; describe("ImportExportService", () => { let service: ImportExportService; let knowledgeService: KnowledgeService; let prisma: PrismaService; const workspaceId = "workspace-123"; const userId = "user-123"; const mockEntry = { id: "entry-123", workspaceId, slug: "test-entry", title: "Test Entry", content: "Test content", summary: "Test summary", status: EntryStatus.PUBLISHED, visibility: Visibility.WORKSPACE, createdAt: new Date(), updatedAt: new Date(), tags: [ { tag: { id: "tag-1", name: "TypeScript", slug: "typescript", color: "#3178c6", }, }, ], }; const mockKnowledgeService = { create: vi.fn(), findAll: vi.fn(), }; const mockPrismaService = { knowledgeEntry: { findMany: vi.fn(), }, }; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ ImportExportService, { provide: KnowledgeService, useValue: mockKnowledgeService, }, { provide: PrismaService, useValue: mockPrismaService, }, ], }).compile(); service = module.get(ImportExportService); knowledgeService = module.get(KnowledgeService); prisma = module.get(PrismaService); vi.clearAllMocks(); }); describe("importEntries", () => { it("should import a single markdown file successfully", async () => { const markdown = `--- title: Test Entry status: PUBLISHED tags: - TypeScript - Testing --- This is the content of the entry.`; const file: Express.Multer.File = { fieldname: "file", originalname: "test.md", encoding: "utf-8", mimetype: "text/markdown", size: markdown.length, buffer: Buffer.from(markdown), stream: null as any, destination: "", filename: "", path: "", }; mockKnowledgeService.create.mockResolvedValue({ id: "entry-123", slug: "test-entry", title: "Test Entry", }); const result = await service.importEntries(workspaceId, userId, file); expect(result.totalFiles).toBe(1); expect(result.imported).toBe(1); expect(result.failed).toBe(0); expect(result.results[0].success).toBe(true); expect(result.results[0].title).toBe("Test Entry"); expect(mockKnowledgeService.create).toHaveBeenCalledWith( workspaceId, userId, expect.objectContaining({ title: "Test Entry", content: "This is the content of the entry.", status: EntryStatus.PUBLISHED, tags: ["TypeScript", "Testing"], }) ); }); it("should use filename as title if frontmatter title is missing", async () => { const markdown = `This is content without frontmatter.`; const file: Express.Multer.File = { fieldname: "file", originalname: "my-entry.md", encoding: "utf-8", mimetype: "text/markdown", size: markdown.length, buffer: Buffer.from(markdown), stream: null as any, destination: "", filename: "", path: "", }; mockKnowledgeService.create.mockResolvedValue({ id: "entry-123", slug: "my-entry", title: "my-entry", }); const result = await service.importEntries(workspaceId, userId, file); expect(result.imported).toBe(1); expect(mockKnowledgeService.create).toHaveBeenCalledWith( workspaceId, userId, expect.objectContaining({ title: "my-entry", content: "This is content without frontmatter.", }) ); }); it("should reject invalid file types", async () => { const file: Express.Multer.File = { fieldname: "file", originalname: "test.txt", encoding: "utf-8", mimetype: "text/plain", size: 100, buffer: Buffer.from("test"), stream: null as any, destination: "", filename: "", path: "", }; await expect(service.importEntries(workspaceId, userId, file)).rejects.toThrow( BadRequestException ); }); it("should handle import errors gracefully", async () => { const markdown = `--- title: Test Entry --- Content`; const file: Express.Multer.File = { fieldname: "file", originalname: "test.md", encoding: "utf-8", mimetype: "text/markdown", size: markdown.length, buffer: Buffer.from(markdown), stream: null as any, destination: "", filename: "", path: "", }; mockKnowledgeService.create.mockRejectedValue(new Error("Database error")); const result = await service.importEntries(workspaceId, userId, file); expect(result.totalFiles).toBe(1); expect(result.imported).toBe(0); expect(result.failed).toBe(1); expect(result.results[0].success).toBe(false); expect(result.results[0].error).toBe("Database error"); }); it("should reject empty markdown content", async () => { const markdown = `--- title: Empty Entry --- `; const file: Express.Multer.File = { fieldname: "file", originalname: "empty.md", encoding: "utf-8", mimetype: "text/markdown", size: markdown.length, buffer: Buffer.from(markdown), stream: null as any, destination: "", filename: "", path: "", }; const result = await service.importEntries(workspaceId, userId, file); expect(result.imported).toBe(0); expect(result.failed).toBe(1); expect(result.results[0].error).toBe("Empty content"); }); }); describe("exportEntries", () => { it("should export entries as markdown format", async () => { mockPrismaService.knowledgeEntry.findMany.mockResolvedValue([mockEntry]); const result = await service.exportEntries(workspaceId, ExportFormat.MARKDOWN); expect(result.filename).toMatch(/knowledge-export-\d{4}-\d{2}-\d{2}\.zip/); expect(result.stream).toBeDefined(); expect(mockPrismaService.knowledgeEntry.findMany).toHaveBeenCalledWith({ where: { workspaceId }, include: { tags: { include: { tag: true, }, }, }, orderBy: { title: "asc", }, }); }); it("should export only specified entries", async () => { const entryIds = ["entry-123", "entry-456"]; mockPrismaService.knowledgeEntry.findMany.mockResolvedValue([mockEntry]); await service.exportEntries(workspaceId, ExportFormat.JSON, entryIds); expect(mockPrismaService.knowledgeEntry.findMany).toHaveBeenCalledWith({ where: { workspaceId, id: { in: entryIds }, }, include: { tags: { include: { tag: true, }, }, }, orderBy: { title: "asc", }, }); }); it("should throw error when no entries found", async () => { mockPrismaService.knowledgeEntry.findMany.mockResolvedValue([]); await expect(service.exportEntries(workspaceId, ExportFormat.MARKDOWN)).rejects.toThrow( BadRequestException ); }); }); });