test(features): R50 — repo contract suites assert span shape per method

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-07 19:09:50 +02:00
parent a4efceb104
commit d4bc045a28
18 changed files with 293 additions and 54 deletions

View File

@@ -1,4 +1,4 @@
import { it, expect, beforeEach } from "vitest"; import { it, expect, beforeEach, describe } from "vitest";
import { defineContractSuite } from "@repo/core-testing/contract"; import { defineContractSuite } from "@repo/core-testing/contract";
import type { IUsersRepository } from "../application/repositories/users.repository.interface.js"; import type { IUsersRepository } from "../application/repositories/users.repository.interface.js";
import { userFactory } from "../__factories__/user.factory.js"; import { userFactory } from "../__factories__/user.factory.js";
@@ -6,7 +6,7 @@ import { userFactory } from "../__factories__/user.factory.js";
export const usersRepositoryContract = export const usersRepositoryContract =
defineContractSuite<IUsersRepository>( defineContractSuite<IUsersRepository>(
"IUsersRepository", "IUsersRepository",
({ buildSubject }) => { ({ buildSubject, getTracer }) => {
let repo: IUsersRepository; let repo: IUsersRepository;
beforeEach(async () => { beforeEach(async () => {
@@ -52,5 +52,40 @@ export const usersRepositoryContract =
expect(created.id).toBe(seed.id); expect(created.id).toBe(seed.id);
expect(created.username).toBe("carol"); expect(created.username).toBe("carol");
}); });
describe("span emission (R50)", () => {
it("getUser emits users.getUser span with id attribute", async () => {
if (!getTracer) return;
const tracer = getTracer();
tracer.reset();
await repo.getUser("nonexistent");
const span = tracer.findSpan("users.getUser");
expect(span).toBeDefined();
expect(span!.op).toBe("repository");
expect(span!.attributes.id).toBe("nonexistent");
});
it("getUserByUsername emits users.getUserByUsername span", async () => {
if (!getTracer) return;
const tracer = getTracer();
tracer.reset();
await repo.getUserByUsername("alice");
const span = tracer.findSpan("users.getUserByUsername");
expect(span).toBeDefined();
expect(span!.op).toBe("repository");
});
it("createUser emits users.createUser span with id attribute", async () => {
if (!getTracer) return;
const tracer = getTracer();
tracer.reset();
const seed = userFactory.build({ username: "span-test-user" });
await repo.createUser(seed);
const span = tracer.findSpan("users.createUser");
expect(span).toBeDefined();
expect(span!.op).toBe("repository");
expect(span!.attributes.id).toBe(seed.id);
});
});
}, },
); );

View File

@@ -1,8 +1,13 @@
import { describe } from "vitest"; import { describe } from "vitest";
import { RecordingTracer } from "@repo/core-testing/instrumentation";
import { MockUsersRepository } from "@/infrastructure/repositories/users.repository.mock"; import { MockUsersRepository } from "@/infrastructure/repositories/users.repository.mock";
import { usersRepositoryContract } from "@/__contracts__/users-repository.contract"; import { usersRepositoryContract } from "@/__contracts__/users-repository.contract";
describe("MockUsersRepository", () => { describe("MockUsersRepository", () => {
const tracer = new RecordingTracer();
// Start with empty store so contract tests run from a clean slate. // Start with empty store so contract tests run from a clean slate.
usersRepositoryContract.run(() => new MockUsersRepository([])); usersRepositoryContract.run(
() => new MockUsersRepository([], tracer),
{ tracer: () => tracer },
);
}); });

View File

@@ -1,4 +1,5 @@
import { describe, vi, beforeEach } from "vitest"; import { describe, vi, beforeEach } from "vitest";
import { RecordingTracer } from "@repo/core-testing/instrumentation";
import { UsersRepository } from "@/infrastructure/repositories/users.repository"; import { UsersRepository } from "@/infrastructure/repositories/users.repository";
import { usersRepositoryContract } from "@/__contracts__/users-repository.contract"; import { usersRepositoryContract } from "@/__contracts__/users-repository.contract";
import { stubPayloadConfig } from "@repo/core-testing/payload/stub-config"; import { stubPayloadConfig } from "@repo/core-testing/payload/stub-config";
@@ -48,15 +49,20 @@ function buildPayloadStub() {
describe("UsersRepository", () => { describe("UsersRepository", () => {
describe("contract", () => { describe("contract", () => {
const tracer = new RecordingTracer();
beforeEach(() => { beforeEach(() => {
vi.clearAllMocks(); vi.clearAllMocks();
}); });
usersRepositoryContract.run(async () => { usersRepositoryContract.run(
const stub = buildPayloadStub(); async () => {
const { getPayload } = await import("payload"); const stub = buildPayloadStub();
(getPayload as ReturnType<typeof vi.fn>).mockResolvedValue(stub); const { getPayload } = await import("payload");
return new UsersRepository(stubPayloadConfig); (getPayload as ReturnType<typeof vi.fn>).mockResolvedValue(stub);
}); return new UsersRepository(stubPayloadConfig, tracer);
},
{ tracer: () => tracer },
);
}); });
}); });

View File

@@ -1,4 +1,4 @@
import { it, expect, beforeEach } from "vitest"; import { it, expect, beforeEach, describe } from "vitest";
import { defineContractSuite } from "@repo/core-testing/contract"; import { defineContractSuite } from "@repo/core-testing/contract";
import type { IArticlesRepository } from "../application/repositories/articles.repository.interface.js"; import type { IArticlesRepository } from "../application/repositories/articles.repository.interface.js";
import { articleFactory } from "../__factories__/article.factory.js"; import { articleFactory } from "../__factories__/article.factory.js";
@@ -6,7 +6,7 @@ import { articleFactory } from "../__factories__/article.factory.js";
export const articlesRepositoryContract = export const articlesRepositoryContract =
defineContractSuite<IArticlesRepository>( defineContractSuite<IArticlesRepository>(
"IArticlesRepository", "IArticlesRepository",
({ buildSubject }) => { ({ buildSubject, getTracer }) => {
let repo: IArticlesRepository; let repo: IArticlesRepository;
beforeEach(async () => { beforeEach(async () => {
@@ -116,5 +116,65 @@ export const articlesRepositoryContract =
}); });
expect(result).toBeUndefined(); 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);
});
});
}, },
); );

View File

@@ -1,7 +1,12 @@
import { describe } from "vitest"; import { describe } from "vitest";
import { RecordingTracer } from "@repo/core-testing/instrumentation";
import { MockArticlesRepository } from "@/infrastructure/repositories/articles.repository.mock"; import { MockArticlesRepository } from "@/infrastructure/repositories/articles.repository.mock";
import { articlesRepositoryContract } from "@/__contracts__/articles-repository.contract"; import { articlesRepositoryContract } from "@/__contracts__/articles-repository.contract";
describe("MockArticlesRepository", () => { describe("MockArticlesRepository", () => {
articlesRepositoryContract.run(() => new MockArticlesRepository()); const tracer = new RecordingTracer();
articlesRepositoryContract.run(
() => new MockArticlesRepository(tracer),
{ tracer: () => tracer },
);
}); });

View File

@@ -1,4 +1,5 @@
import { describe, expect, it, vi, beforeEach } from "vitest"; import { describe, expect, it, vi, beforeEach } from "vitest";
import { RecordingTracer } from "@repo/core-testing/instrumentation";
import { ArticlesRepository } from "@/infrastructure/repositories/articles.repository"; import { ArticlesRepository } from "@/infrastructure/repositories/articles.repository";
import { articlesRepositoryContract } from "@/__contracts__/articles-repository.contract"; import { articlesRepositoryContract } from "@/__contracts__/articles-repository.contract";
import { stubPayloadConfig } from "@repo/core-testing/payload/stub-config"; import { stubPayloadConfig } from "@repo/core-testing/payload/stub-config";
@@ -102,12 +103,16 @@ vi.mock("payload", () => ({
describe("ArticlesRepository", () => { describe("ArticlesRepository", () => {
describe("contract", () => { describe("contract", () => {
articlesRepositoryContract.run(async () => { const tracer = new RecordingTracer();
const stub = buildPayloadStub(); articlesRepositoryContract.run(
const { getPayload } = await import("payload"); async () => {
(getPayload as ReturnType<typeof vi.fn>).mockResolvedValue(stub); const stub = buildPayloadStub();
return new ArticlesRepository(stubPayloadConfig); const { getPayload } = await import("payload");
}); (getPayload as ReturnType<typeof vi.fn>).mockResolvedValue(stub);
return new ArticlesRepository(stubPayloadConfig, tracer);
},
{ tracer: () => tracer },
);
}); });
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------

View File

@@ -1,4 +1,4 @@
import { it, expect, beforeEach } from "vitest"; import { it, expect, beforeEach, describe } from "vitest";
import { defineContractSuite } from "@repo/core-testing/contract"; import { defineContractSuite } from "@repo/core-testing/contract";
import type { IPagesRepository } from "../application/repositories/pages.repository.interface.js"; import type { IPagesRepository } from "../application/repositories/pages.repository.interface.js";
import type { Page } from "../entities/models/page.js"; import type { Page } from "../entities/models/page.js";
@@ -47,7 +47,7 @@ export const CONTRACT_PAGES_SEED: Page[] = [
export const pagesRepositoryContract = export const pagesRepositoryContract =
defineContractSuite<IPagesRepository>( defineContractSuite<IPagesRepository>(
"IPagesRepository", "IPagesRepository",
({ buildSubject }) => { ({ buildSubject, getTracer }) => {
let repo: IPagesRepository; let repo: IPagesRepository;
beforeEach(async () => { beforeEach(async () => {
@@ -95,5 +95,29 @@ export const pagesRepositoryContract =
expect(page.status).toBe("draft"); expect(page.status).toBe("draft");
} }
}); });
describe("span emission (R50)", () => {
it("getPageBySlug emits pages.getPageBySlug span with slug attribute", async () => {
if (!getTracer) return;
const tracer = getTracer();
tracer.reset();
await repo.getPageBySlug("about");
const span = tracer.findSpan("pages.getPageBySlug");
expect(span).toBeDefined();
expect(span!.op).toBe("repository");
expect(span!.attributes.slug).toBe("about");
});
it("getPages emits pages.getPages span with op=repository", async () => {
if (!getTracer) return;
const tracer = getTracer();
tracer.reset();
await repo.getPages({ status: "published" });
const span = tracer.findSpan("pages.getPages");
expect(span).toBeDefined();
expect(span!.op).toBe("repository");
expect(span!.attributes.status).toBe("published");
});
});
}, },
); );

View File

@@ -1,4 +1,4 @@
import { it, expect, beforeEach } from "vitest"; import { it, expect, beforeEach, describe } from "vitest";
import { defineContractSuite } from "@repo/core-testing/contract"; import { defineContractSuite } from "@repo/core-testing/contract";
import type { ISiteSettingsRepository } from "../application/repositories/site-settings.repository.interface.js"; import type { ISiteSettingsRepository } from "../application/repositories/site-settings.repository.interface.js";
@@ -11,7 +11,7 @@ import type { ISiteSettingsRepository } from "../application/repositories/site-s
export const siteSettingsRepositoryContract = export const siteSettingsRepositoryContract =
defineContractSuite<ISiteSettingsRepository>( defineContractSuite<ISiteSettingsRepository>(
"ISiteSettingsRepository", "ISiteSettingsRepository",
({ buildSubject }) => { ({ buildSubject, getTracer }) => {
let repo: ISiteSettingsRepository; let repo: ISiteSettingsRepository;
beforeEach(async () => { beforeEach(async () => {
@@ -34,5 +34,17 @@ export const siteSettingsRepositoryContract =
typeof settings.siteDescription === "string", typeof settings.siteDescription === "string",
).toBe(true); ).toBe(true);
}); });
describe("span emission (R50)", () => {
it("getSiteSettings emits site-settings.getSiteSettings span with op=repository", async () => {
if (!getTracer) return;
const tracer = getTracer();
tracer.reset();
await repo.getSiteSettings();
const span = tracer.findSpan("site-settings.getSiteSettings");
expect(span).toBeDefined();
expect(span!.op).toBe("repository");
});
});
}, },
); );

View File

@@ -1,4 +1,5 @@
import { describe } from "vitest"; import { describe } from "vitest";
import { RecordingTracer } from "@repo/core-testing/instrumentation";
import { MockPagesRepository } from "@/infrastructure/repositories/pages.repository.mock"; import { MockPagesRepository } from "@/infrastructure/repositories/pages.repository.mock";
import { import {
pagesRepositoryContract, pagesRepositoryContract,
@@ -6,7 +7,11 @@ import {
} from "@/__contracts__/pages-repository.contract"; } from "@/__contracts__/pages-repository.contract";
describe("MockPagesRepository", () => { describe("MockPagesRepository", () => {
const tracer = new RecordingTracer();
// Pre-seed with the contract fixtures so the contract assertions find what // Pre-seed with the contract fixtures so the contract assertions find what
// they expect (an "about" published page and a "draft-page" draft page). // they expect (an "about" published page and a "draft-page" draft page).
pagesRepositoryContract.run(() => new MockPagesRepository(CONTRACT_PAGES_SEED)); pagesRepositoryContract.run(
() => new MockPagesRepository(CONTRACT_PAGES_SEED, tracer),
{ tracer: () => tracer },
);
}); });

View File

@@ -1,4 +1,5 @@
import { describe, vi } from "vitest"; import { describe, vi } from "vitest";
import { RecordingTracer } from "@repo/core-testing/instrumentation";
import { PagesRepository } from "@/infrastructure/repositories/pages.repository"; import { PagesRepository } from "@/infrastructure/repositories/pages.repository";
import { import {
pagesRepositoryContract, pagesRepositoryContract,
@@ -73,11 +74,15 @@ vi.mock("payload", () => ({
describe("PagesRepository", () => { describe("PagesRepository", () => {
describe("contract", () => { describe("contract", () => {
pagesRepositoryContract.run(async () => { const tracer = new RecordingTracer();
const stub = buildPayloadPagesStub(CONTRACT_PAGES_SEED); pagesRepositoryContract.run(
const { getPayload } = await import("payload"); async () => {
(getPayload as ReturnType<typeof vi.fn>).mockResolvedValue(stub); const stub = buildPayloadPagesStub(CONTRACT_PAGES_SEED);
return new PagesRepository(stubPayloadConfig); const { getPayload } = await import("payload");
}); (getPayload as ReturnType<typeof vi.fn>).mockResolvedValue(stub);
return new PagesRepository(stubPayloadConfig, tracer);
},
{ tracer: () => tracer },
);
}); });
}); });

View File

@@ -1,7 +1,12 @@
import { describe } from "vitest"; import { describe } from "vitest";
import { RecordingTracer } from "@repo/core-testing/instrumentation";
import { MockSiteSettingsRepository } from "@/infrastructure/repositories/site-settings.repository.mock"; import { MockSiteSettingsRepository } from "@/infrastructure/repositories/site-settings.repository.mock";
import { siteSettingsRepositoryContract } from "@/__contracts__/site-settings-repository.contract"; import { siteSettingsRepositoryContract } from "@/__contracts__/site-settings-repository.contract";
describe("MockSiteSettingsRepository", () => { describe("MockSiteSettingsRepository", () => {
siteSettingsRepositoryContract.run(() => new MockSiteSettingsRepository()); const tracer = new RecordingTracer();
siteSettingsRepositoryContract.run(
() => new MockSiteSettingsRepository(tracer),
{ tracer: () => tracer },
);
}); });

View File

@@ -1,4 +1,5 @@
import { describe, vi } from "vitest"; import { describe, vi } from "vitest";
import { RecordingTracer } from "@repo/core-testing/instrumentation";
import { SiteSettingsRepository } from "@/infrastructure/repositories/site-settings.repository"; import { SiteSettingsRepository } from "@/infrastructure/repositories/site-settings.repository";
import { siteSettingsRepositoryContract } from "@/__contracts__/site-settings-repository.contract"; import { siteSettingsRepositoryContract } from "@/__contracts__/site-settings-repository.contract";
import { stubPayloadConfig } from "@repo/core-testing/payload/stub-config"; import { stubPayloadConfig } from "@repo/core-testing/payload/stub-config";
@@ -30,11 +31,15 @@ vi.mock("payload", () => ({
describe("SiteSettingsRepository", () => { describe("SiteSettingsRepository", () => {
describe("contract", () => { describe("contract", () => {
siteSettingsRepositoryContract.run(async () => { const tracer = new RecordingTracer();
const stub = buildSiteSettingsStub(); siteSettingsRepositoryContract.run(
const { getPayload } = await import("payload"); async () => {
(getPayload as ReturnType<typeof vi.fn>).mockResolvedValue(stub); const stub = buildSiteSettingsStub();
return new SiteSettingsRepository(stubPayloadConfig); const { getPayload } = await import("payload");
}); (getPayload as ReturnType<typeof vi.fn>).mockResolvedValue(stub);
return new SiteSettingsRepository(stubPayloadConfig, tracer);
},
{ tracer: () => tracer },
);
}); });
}); });

View File

@@ -1,4 +1,4 @@
import { it, expect, beforeEach } from "vitest"; import { it, expect, beforeEach, describe } from "vitest";
import { defineContractSuite } from "@repo/core-testing/contract"; import { defineContractSuite } from "@repo/core-testing/contract";
import type { IMediaRepository } from "../application/repositories/media.repository.interface.js"; import type { IMediaRepository } from "../application/repositories/media.repository.interface.js";
import { mediaFactory } from "../__factories__/media.factory.js"; import { mediaFactory } from "../__factories__/media.factory.js";
@@ -6,7 +6,7 @@ import { mediaFactory } from "../__factories__/media.factory.js";
export const mediaRepositoryContract = export const mediaRepositoryContract =
defineContractSuite<IMediaRepository>( defineContractSuite<IMediaRepository>(
"IMediaRepository", "IMediaRepository",
({ buildSubject }) => { ({ buildSubject, getTracer }) => {
let repo: IMediaRepository; let repo: IMediaRepository;
beforeEach(async () => { beforeEach(async () => {
@@ -57,5 +57,40 @@ export const mediaRepositoryContract =
const result = await repo.getMedia(""); const result = await repo.getMedia("");
expect(result).toBeUndefined(); expect(result).toBeUndefined();
}); });
describe("span emission (R50)", () => {
it("getMedia emits media.getMedia span with id attribute", async () => {
if (!getTracer) return;
const tracer = getTracer();
tracer.reset();
await repo.getMedia("nonexistent");
const span = tracer.findSpan("media.getMedia");
expect(span).toBeDefined();
expect(span!.op).toBe("repository");
expect(span!.attributes.id).toBe("nonexistent");
});
it("listMedia emits media.listMedia span with op=repository", async () => {
if (!getTracer) return;
const tracer = getTracer();
tracer.reset();
await repo.listMedia({ limit: 10 });
const span = tracer.findSpan("media.listMedia");
expect(span).toBeDefined();
expect(span!.op).toBe("repository");
expect(span!.attributes.limit).toBe(10);
});
it("deleteMedia emits media.deleteMedia span with id attribute", async () => {
if (!getTracer) return;
const tracer = getTracer();
tracer.reset();
await repo.deleteMedia("nonexistent");
const span = tracer.findSpan("media.deleteMedia");
expect(span).toBeDefined();
expect(span!.op).toBe("repository");
expect(span!.attributes.id).toBe("nonexistent");
});
});
}, },
); );

View File

@@ -1,7 +1,12 @@
import { describe } from "vitest"; import { describe } from "vitest";
import { RecordingTracer } from "@repo/core-testing/instrumentation";
import { MockMediaRepository } from "@/infrastructure/repositories/media.repository.mock"; import { MockMediaRepository } from "@/infrastructure/repositories/media.repository.mock";
import { mediaRepositoryContract } from "@/__contracts__/media-repository.contract"; import { mediaRepositoryContract } from "@/__contracts__/media-repository.contract";
describe("MockMediaRepository", () => { describe("MockMediaRepository", () => {
mediaRepositoryContract.run(() => new MockMediaRepository()); const tracer = new RecordingTracer();
mediaRepositoryContract.run(
() => new MockMediaRepository(tracer),
{ tracer: () => tracer },
);
}); });

View File

@@ -1,4 +1,5 @@
import { describe, expect, it, vi } from "vitest"; import { describe, expect, it, vi } from "vitest";
import { RecordingTracer } from "@repo/core-testing/instrumentation";
import { MediaRepository } from "@/infrastructure/repositories/media.repository"; import { MediaRepository } from "@/infrastructure/repositories/media.repository";
import { mediaRepositoryContract } from "@/__contracts__/media-repository.contract"; import { mediaRepositoryContract } from "@/__contracts__/media-repository.contract";
import { stubPayloadConfig } from "@repo/core-testing/payload/stub-config"; import { stubPayloadConfig } from "@repo/core-testing/payload/stub-config";
@@ -58,12 +59,16 @@ vi.mock("payload", () => ({
describe("MediaRepository", () => { describe("MediaRepository", () => {
describe("contract", () => { describe("contract", () => {
mediaRepositoryContract.run(async () => { const tracer = new RecordingTracer();
const stub = buildPayloadStub(); mediaRepositoryContract.run(
const { getPayload } = await import("payload"); async () => {
(getPayload as ReturnType<typeof vi.fn>).mockResolvedValue(stub); const stub = buildPayloadStub();
return new MediaRepository(stubPayloadConfig); const { getPayload } = await import("payload");
}); (getPayload as ReturnType<typeof vi.fn>).mockResolvedValue(stub);
return new MediaRepository(stubPayloadConfig, tracer);
},
{ tracer: () => tracer },
);
}); });
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------

View File

@@ -1,4 +1,4 @@
import { it, expect, beforeEach } from "vitest"; import { it, expect, beforeEach, describe } from "vitest";
import { defineContractSuite } from "@repo/core-testing/contract"; import { defineContractSuite } from "@repo/core-testing/contract";
import type { IHeaderRepository } from "../application/repositories/header.repository.interface.js"; import type { IHeaderRepository } from "../application/repositories/header.repository.interface.js";
import type { Header } from "../entities/models/header.js"; import type { Header } from "../entities/models/header.js";
@@ -25,7 +25,7 @@ export const CONTRACT_HEADER_SEED: Header = {
export const headerRepositoryContract = export const headerRepositoryContract =
defineContractSuite<IHeaderRepository>( defineContractSuite<IHeaderRepository>(
"IHeaderRepository", "IHeaderRepository",
({ buildSubject }) => { ({ buildSubject, getTracer }) => {
let repo: IHeaderRepository; let repo: IHeaderRepository;
beforeEach(async () => { beforeEach(async () => {
@@ -71,5 +71,17 @@ export const headerRepositoryContract =
header.logoId === undefined || typeof header.logoId === "string", header.logoId === undefined || typeof header.logoId === "string",
).toBe(true); ).toBe(true);
}); });
describe("span emission (R50)", () => {
it("getHeader emits header.getHeader span with op=repository", async () => {
if (!getTracer) return;
const tracer = getTracer();
tracer.reset();
await repo.getHeader();
const span = tracer.findSpan("header.getHeader");
expect(span).toBeDefined();
expect(span!.op).toBe("repository");
});
});
}, },
); );

View File

@@ -1,7 +1,12 @@
import { describe } from "vitest"; import { describe } from "vitest";
import { RecordingTracer } from "@repo/core-testing/instrumentation";
import { MockHeaderRepository } from "@/infrastructure/repositories/header.repository.mock"; import { MockHeaderRepository } from "@/infrastructure/repositories/header.repository.mock";
import { headerRepositoryContract, CONTRACT_HEADER_SEED } from "@/__contracts__/header-repository.contract"; import { headerRepositoryContract, CONTRACT_HEADER_SEED } from "@/__contracts__/header-repository.contract";
describe("MockHeaderRepository", () => { describe("MockHeaderRepository", () => {
headerRepositoryContract.run(() => new MockHeaderRepository(CONTRACT_HEADER_SEED)); const tracer = new RecordingTracer();
headerRepositoryContract.run(
() => new MockHeaderRepository(CONTRACT_HEADER_SEED, tracer),
{ tracer: () => tracer },
);
}); });

View File

@@ -1,4 +1,5 @@
import { describe, it, expect, vi } from "vitest"; import { describe, it, expect, vi } from "vitest";
import { RecordingTracer } from "@repo/core-testing/instrumentation";
import { HeaderRepository } from "@/infrastructure/repositories/header.repository"; import { HeaderRepository } from "@/infrastructure/repositories/header.repository";
import { headerRepositoryContract, CONTRACT_HEADER_SEED } from "@/__contracts__/header-repository.contract"; import { headerRepositoryContract, CONTRACT_HEADER_SEED } from "@/__contracts__/header-repository.contract";
import { stubPayloadConfig } from "@repo/core-testing/payload/stub-config"; import { stubPayloadConfig } from "@repo/core-testing/payload/stub-config";
@@ -25,12 +26,16 @@ vi.mock("payload", () => ({
describe("HeaderRepository", () => { describe("HeaderRepository", () => {
describe("contract", () => { describe("contract", () => {
headerRepositoryContract.run(async () => { const tracer = new RecordingTracer();
const stub = buildHeaderStub(); headerRepositoryContract.run(
const { getPayload } = await import("payload"); async () => {
(getPayload as ReturnType<typeof vi.fn>).mockResolvedValue(stub); const stub = buildHeaderStub();
return new HeaderRepository(stubPayloadConfig); const { getPayload } = await import("payload");
}); (getPayload as ReturnType<typeof vi.fn>).mockResolvedValue(stub);
return new HeaderRepository(stubPayloadConfig, tracer);
},
{ tracer: () => tracer },
);
}); });
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------