feat(blog): add Article entity with rich-text content + domain errors
This commit is contained in:
85
packages/blog/src/entities/article.test.ts
Normal file
85
packages/blog/src/entities/article.test.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { articleSchema, articleStatusSchema, type Article } from "./article";
|
||||
|
||||
describe("articleSchema", () => {
|
||||
it("accepts a minimal valid article with default status", () => {
|
||||
const result = articleSchema.parse({
|
||||
id: "abc",
|
||||
title: "Hello",
|
||||
slug: "hello",
|
||||
content: { type: "doc", children: [] },
|
||||
authorId: "u1",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
expect(result.status).toBe("draft");
|
||||
});
|
||||
|
||||
it("accepts unknown rich-text content", () => {
|
||||
const result = articleSchema.parse({
|
||||
id: "abc",
|
||||
title: "Hello",
|
||||
slug: "hello",
|
||||
content: "any string is also fine",
|
||||
authorId: "u1",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
expect(result.content).toBe("any string is also fine");
|
||||
});
|
||||
|
||||
it("rejects empty title", () => {
|
||||
expect(() =>
|
||||
articleSchema.parse({
|
||||
id: "a",
|
||||
title: "",
|
||||
slug: "s",
|
||||
content: null,
|
||||
authorId: "u",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
}),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it("rejects title over 255 chars", () => {
|
||||
expect(() =>
|
||||
articleSchema.parse({
|
||||
id: "a",
|
||||
title: "x".repeat(256),
|
||||
slug: "s",
|
||||
content: null,
|
||||
authorId: "u",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
}),
|
||||
).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("articleStatusSchema", () => {
|
||||
it("accepts 'draft' and 'published'", () => {
|
||||
expect(articleStatusSchema.parse("draft")).toBe("draft");
|
||||
expect(articleStatusSchema.parse("published")).toBe("published");
|
||||
});
|
||||
|
||||
it("rejects unknown status", () => {
|
||||
expect(() => articleStatusSchema.parse("archived")).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Article type", () => {
|
||||
it("widens content to unknown", () => {
|
||||
const _example: Article = {
|
||||
id: "x",
|
||||
title: "t",
|
||||
slug: "s",
|
||||
content: { whatever: true },
|
||||
status: "draft",
|
||||
authorId: "u",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
expect(_example).toBeDefined();
|
||||
});
|
||||
});
|
||||
17
packages/blog/src/entities/article.ts
Normal file
17
packages/blog/src/entities/article.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const articleStatusSchema = z.enum(["draft", "published"]);
|
||||
|
||||
export const articleSchema = z.object({
|
||||
id: z.string(),
|
||||
title: z.string().min(1).max(255),
|
||||
slug: z.string().min(1).max(255),
|
||||
content: z.unknown(),
|
||||
status: articleStatusSchema.default("draft"),
|
||||
authorId: z.string(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
});
|
||||
|
||||
export type Article = z.infer<typeof articleSchema>;
|
||||
export type ArticleStatus = z.infer<typeof articleStatusSchema>;
|
||||
11
packages/blog/src/entities/errors.ts
Normal file
11
packages/blog/src/entities/errors.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export class ArticleNotFoundError extends Error {
|
||||
constructor(message = "Article not found", options?: ErrorOptions) {
|
||||
super(message, options);
|
||||
}
|
||||
}
|
||||
|
||||
export class InputParseError extends Error {
|
||||
constructor(message: string, options?: ErrorOptions) {
|
||||
super(message, options);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user