From acf6ed20bcfa2967604e83c59707f9f5df57fce3 Mon Sep 17 00:00:00 2001 From: Danijel Martinek Date: Mon, 6 Apr 2026 14:30:34 +0200 Subject: [PATCH] feat(core): add content controller with tests (articles CRUD) --- .../content/articles.controller.ts | 44 ++++++++++++++++ .../content/articles.controller.test.ts | 50 +++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 packages/core/src/interface-adapters/controllers/content/articles.controller.ts create mode 100644 packages/core/tests/unit/controllers/content/articles.controller.test.ts diff --git a/packages/core/src/interface-adapters/controllers/content/articles.controller.ts b/packages/core/src/interface-adapters/controllers/content/articles.controller.ts new file mode 100644 index 0000000..f35534b --- /dev/null +++ b/packages/core/src/interface-adapters/controllers/content/articles.controller.ts @@ -0,0 +1,44 @@ +import { z } from "zod"; + +import { InputParseError } from "@/entities/errors/common.js"; +import type { Article } from "@/entities/models/article.js"; +import { createArticleUseCase } from "@/application/use-cases/content/create-article.use-case.js"; +import { getArticlesUseCase } from "@/application/use-cases/content/get-articles.use-case.js"; + +const createInputSchema = z.object({ + title: z.string().min(1).max(255), + content: z.string(), + authorId: z.string(), + slug: z.string().optional(), +}); + +const getInputSchema = z.object({ + status: z.string().optional(), + authorId: z.string().optional(), + limit: z.number().optional(), + offset: z.number().optional(), +}); + +export async function createArticleController( + input: Partial> +): Promise
{ + const { data, error: inputParseError } = createInputSchema.safeParse(input); + + if (inputParseError) { + throw new InputParseError("Invalid data", { cause: inputParseError }); + } + + return await createArticleUseCase(data); +} + +export async function getArticlesController( + input: Partial> +): Promise { + const { data, error: inputParseError } = getInputSchema.safeParse(input); + + if (inputParseError) { + throw new InputParseError("Invalid data", { cause: inputParseError }); + } + + return await getArticlesUseCase(data); +} diff --git a/packages/core/tests/unit/controllers/content/articles.controller.test.ts b/packages/core/tests/unit/controllers/content/articles.controller.test.ts new file mode 100644 index 0000000..7031121 --- /dev/null +++ b/packages/core/tests/unit/controllers/content/articles.controller.test.ts @@ -0,0 +1,50 @@ +import "reflect-metadata"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; + +import { + destroyContainer, + initializeContainer, +} from "@/di/container.js"; +import { + createArticleController, + getArticlesController, +} from "@/interface-adapters/controllers/content/articles.controller.js"; +import { InputParseError } from "@/entities/errors/common.js"; + +beforeEach(() => { + initializeContainer(); +}); + +afterEach(() => { + destroyContainer(); +}); + +describe("createArticleController", () => { + it("creates an article with valid input", async () => { + const result = await createArticleController({ + title: "Test Article", + content: "Some content", + authorId: "1", + }); + expect(result.title).toBe("Test Article"); + expect(result.slug).toBe("test-article"); + }); + + it("throws InputParseError for missing title", async () => { + await expect( + createArticleController({ content: "content", authorId: "1" } as any) + ).rejects.toBeInstanceOf(InputParseError); + }); +}); + +describe("getArticlesController", () => { + it("returns articles", async () => { + await createArticleController({ + title: "Article", + content: "Content", + authorId: "1", + }); + const result = await getArticlesController({}); + expect(result).toHaveLength(1); + }); +});