diff --git a/packages/blog/src/application/use-cases/create-article.use-case.test.ts b/packages/blog/src/application/use-cases/create-article.use-case.test.ts new file mode 100644 index 0000000..2cc7289 --- /dev/null +++ b/packages/blog/src/application/use-cases/create-article.use-case.test.ts @@ -0,0 +1,45 @@ +import { beforeEach, describe, expect, it } from "vitest"; +import { blogContainer } from "@/di/container"; +import { BLOG_SYMBOLS } from "@/di/symbols"; +import type { IArticlesRepository } from "@/application/repositories/articles-repository.interface"; +import { MockArticlesRepository } from "@/infrastructure/repositories/mock-articles.repository"; +import { createArticleUseCase } from "./create-article.use-case"; + +describe("createArticleUseCase", () => { + let repo: MockArticlesRepository; + + beforeEach(() => { + if (blogContainer.isBound(BLOG_SYMBOLS.IArticlesRepository)) { + blogContainer.unbind(BLOG_SYMBOLS.IArticlesRepository); + } + repo = new MockArticlesRepository(); + blogContainer + .bind(BLOG_SYMBOLS.IArticlesRepository) + .toConstantValue(repo); + }); + + it("creates an article in draft status with auto-generated slug", async () => { + const result = await createArticleUseCase({ + title: "Hello World", + content: "body", + authorId: "u1", + }); + expect(result.title).toBe("Hello World"); + expect(result.slug).toBe("hello-world"); + expect(result.status).toBe("draft"); + expect(result.id).toBeTruthy(); + + const stored = await repo.getArticle(result.id); + expect(stored).toBeDefined(); + }); + + it("uses provided slug when supplied", async () => { + const result = await createArticleUseCase({ + title: "Whatever", + content: "body", + authorId: "u1", + slug: "custom-slug", + }); + expect(result.slug).toBe("custom-slug"); + }); +}); diff --git a/packages/blog/src/application/use-cases/create-article.use-case.ts b/packages/blog/src/application/use-cases/create-article.use-case.ts new file mode 100644 index 0000000..1b77fc5 --- /dev/null +++ b/packages/blog/src/application/use-cases/create-article.use-case.ts @@ -0,0 +1,36 @@ +import type { Article } from "@/entities/article"; +import { blogContainer } from "@/di/container"; +import { BLOG_SYMBOLS } from "@/di/symbols"; +import type { IArticlesRepository } from "@/application/repositories/articles-repository.interface"; + +function generateSlug(title: string): string { + return title + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+|-+$/g, ""); +} + +export async function createArticleUseCase(input: { + title: string; + content: unknown; + authorId: string; + slug?: string; +}): Promise
{ + const repo = blogContainer.get( + BLOG_SYMBOLS.IArticlesRepository, + ); + + const now = new Date(); + const article: Article = { + id: crypto.randomUUID(), + title: input.title, + slug: input.slug ?? generateSlug(input.title), + content: input.content, + status: "draft", + authorId: input.authorId, + createdAt: now, + updatedAt: now, + }; + + return repo.createArticle(article); +}