import { it, expect, beforeEach, describe } from "vitest"; import { defineContractSuite } from "@repo/core-testing/contract"; import type { IArticlesRepository } from "../application/repositories/articles.repository.interface.js"; import { articleFactory } from "../__factories__/article.factory.js"; export const articlesRepositoryContract = defineContractSuite( "IArticlesRepository", ({ buildSubject, getTracer }) => { let repo: IArticlesRepository; beforeEach(async () => { articleFactory.reset(); repo = await buildSubject(); }); // --- createArticle --- it("createArticle returns an article with an id and the correct fields", async () => { const seed = articleFactory.build({ title: "Hello World" }); const created = await repo.createArticle(seed); // Implementations may assign their own id (e.g. Payload), so we only // verify the id is a non-empty string and the other fields match. expect(typeof created.id).toBe("string"); expect(created.id.length).toBeGreaterThan(0); expect(created.title).toBe("Hello World"); expect(created.slug).toBe(seed.slug); expect(created.status).toBe(seed.status); expect(created.authorId).toBe(seed.authorId); }); // --- getArticle --- it("createArticle then getArticle returns it by the returned id", async () => { const seed = articleFactory.build(); const created = await repo.createArticle(seed); // Use the id returned by createArticle (Payload may differ from seed.id) const result = await repo.getArticle(created.id); expect(result).toBeDefined(); expect(result?.id).toBe(created.id); expect(result?.slug).toBe(seed.slug); }); it("getArticle returns undefined for missing id", async () => { expect(await repo.getArticle("does-not-exist")).toBeUndefined(); }); // --- getArticleBySlug --- it("createArticle then getArticleBySlug returns it by slug", async () => { const seed = articleFactory.build({ slug: "my-slug" }); const created = await repo.createArticle(seed); const result = await repo.getArticleBySlug("my-slug"); expect(result).toBeDefined(); expect(result?.id).toBe(created.id); expect(result?.slug).toBe("my-slug"); }); it("getArticleBySlug returns undefined for missing slug", async () => { expect(await repo.getArticleBySlug("does-not-exist")).toBeUndefined(); }); // --- getArticles --- it("getArticles returns empty array when no articles", async () => { const list = await repo.getArticles(); expect(list).toHaveLength(0); }); it("getArticles returns all articles when no filter", async () => { await repo.createArticle(articleFactory.build()); await repo.createArticle(articleFactory.build()); const list = await repo.getArticles(); expect(list).toHaveLength(2); }); it("getArticles filters by status", async () => { await repo.createArticle(articleFactory.build({ status: "draft" })); await repo.createArticle( articleFactory.build({ status: "published" }), ); const drafts = await repo.getArticles({ status: "draft" }); expect(drafts).toHaveLength(1); expect(drafts[0]?.status).toBe("draft"); }); it("getArticles filters by authorId", async () => { await repo.createArticle( articleFactory.build({ authorId: "author-a" }), ); await repo.createArticle( articleFactory.build({ authorId: "author-b" }), ); const result = await repo.getArticles({ authorId: "author-a" }); expect(result).toHaveLength(1); expect(result[0]?.authorId).toBe("author-a"); }); // --- updateArticle --- it("updateArticle changes fields and returns updated article", async () => { const seed = articleFactory.build({ status: "draft" }); const created = await repo.createArticle(seed); // Use the returned id for the update lookup const updated = await repo.updateArticle(created.id, { status: "published", }); expect(updated).toBeDefined(); expect(updated?.id).toBe(created.id); expect(updated?.status).toBe("published"); }); it("updateArticle returns undefined for missing id", async () => { const result = await repo.updateArticle("no-such-id", { title: "new", }); expect(result).toBeUndefined(); }); describe("span emission (R50)", () => { it("getArticles emits articles.getArticles span with op=repository", async () => { if (!getTracer) return; const tracer = getTracer(); tracer.reset(); await repo.getArticles({ limit: 5 }); const span = tracer.findSpan("articles.getArticles"); expect(span).toBeDefined(); expect(span!.op).toBe("repository"); expect(span!.attributes.limit).toBe(5); }); it("getArticle emits articles.getArticle span with id attribute", async () => { if (!getTracer) return; const tracer = getTracer(); tracer.reset(); await repo.getArticle("nonexistent"); const span = tracer.findSpan("articles.getArticle"); expect(span).toBeDefined(); expect(span!.op).toBe("repository"); expect(span!.attributes.id).toBe("nonexistent"); }); it("getArticleBySlug emits articles.getArticleBySlug span with slug attribute", async () => { if (!getTracer) return; const tracer = getTracer(); tracer.reset(); await repo.getArticleBySlug("nonexistent"); const span = tracer.findSpan("articles.getArticleBySlug"); expect(span).toBeDefined(); expect(span!.op).toBe("repository"); expect(span!.attributes.slug).toBe("nonexistent"); }); it("createArticle emits articles.createArticle span", async () => { if (!getTracer) return; const tracer = getTracer(); tracer.reset(); const seed = articleFactory.build(); await repo.createArticle(seed); const span = tracer.findSpan("articles.createArticle"); expect(span).toBeDefined(); expect(span!.op).toBe("repository"); expect(span!.attributes.slug).toBe(seed.slug); }); it("updateArticle emits articles.updateArticle span", async () => { if (!getTracer) return; const tracer = getTracer(); tracer.reset(); const seed = articleFactory.build(); const created = await repo.createArticle(seed); await repo.updateArticle(created.id, { title: "Updated" }); const span = tracer.findSpan("articles.updateArticle"); expect(span).toBeDefined(); expect(span!.op).toBe("repository"); expect(span!.attributes.id).toBe(created.id); }); }); }, );