Initial commit
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { deleteMediaUseCase } from "@/application/use-cases/delete-media.use-case";
|
||||
import { MockMediaRepository } from "@/infrastructure/repositories/media.repository.mock";
|
||||
import { MediaNotFoundError } from "@/entities/errors/media";
|
||||
import { mediaFactory } from "@/__factories__/media.factory";
|
||||
|
||||
describe("deleteMediaUseCase", () => {
|
||||
it("deletes media when found", async () => {
|
||||
const repo = new MockMediaRepository();
|
||||
const seed = mediaFactory.build({ id: "m-1" });
|
||||
await repo._store(seed);
|
||||
|
||||
const useCase = deleteMediaUseCase(repo);
|
||||
await useCase({ id: "m-1" });
|
||||
|
||||
const result = await repo.getMedia("m-1");
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it("throws MediaNotFoundError when id does not exist", async () => {
|
||||
const repo = new MockMediaRepository();
|
||||
const useCase = deleteMediaUseCase(repo);
|
||||
|
||||
await expect(useCase({ id: "missing" })).rejects.toThrow(MediaNotFoundError);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { MediaNotFoundError } from "../../entities/errors/media";
|
||||
import type { IMediaRepository } from "../repositories/media.repository.interface";
|
||||
|
||||
// ── Input ────────────────────────────────────────────────────────────────
|
||||
export const deleteMediaInputSchema = z.object({ id: z.string().min(1) }).strict();
|
||||
export type DeleteMediaInput = z.infer<typeof deleteMediaInputSchema>;
|
||||
|
||||
// No output schema — use case returns void.
|
||||
|
||||
// ── Use case ─────────────────────────────────────────────────────────────
|
||||
export type IDeleteMediaUseCase = ReturnType<typeof deleteMediaUseCase>;
|
||||
|
||||
export const deleteMediaUseCase =
|
||||
(mediaRepository: IMediaRepository) =>
|
||||
async (input: DeleteMediaInput): Promise<void> => {
|
||||
const existing = await mediaRepository.getMedia(input.id);
|
||||
if (!existing) {
|
||||
throw new MediaNotFoundError(`Media with id "${input.id}" not found`);
|
||||
}
|
||||
await mediaRepository.deleteMedia(input.id);
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { ZodError } from "zod";
|
||||
import {
|
||||
getMediaUseCase,
|
||||
getMediaOutputSchema,
|
||||
} from "@/application/use-cases/get-media.use-case";
|
||||
import { MockMediaRepository } from "@/infrastructure/repositories/media.repository.mock";
|
||||
import { MediaNotFoundError } from "@/entities/errors/media";
|
||||
import { mediaFactory } from "@/__factories__/media.factory";
|
||||
|
||||
describe("getMediaUseCase", () => {
|
||||
it("returns media when found", async () => {
|
||||
const repo = new MockMediaRepository();
|
||||
const seed = mediaFactory.build({ id: "m-1" });
|
||||
await repo._store(seed);
|
||||
|
||||
const useCase = getMediaUseCase(repo);
|
||||
const result = await useCase({ id: "m-1" });
|
||||
|
||||
expect(result.id).toBe("m-1");
|
||||
expect(result.alt).toBe(seed.alt);
|
||||
});
|
||||
|
||||
it("throws MediaNotFoundError when id does not exist", async () => {
|
||||
const repo = new MockMediaRepository();
|
||||
const useCase = getMediaUseCase(repo);
|
||||
|
||||
await expect(useCase({ id: "missing" })).rejects.toThrow(
|
||||
MediaNotFoundError,
|
||||
);
|
||||
});
|
||||
|
||||
it("parses valid output without error", async () => {
|
||||
const validMedia = mediaFactory.build({ id: "m-r25" });
|
||||
expect(() => getMediaOutputSchema.parse(validMedia)).not.toThrow();
|
||||
});
|
||||
|
||||
it("throws ZodError when repository returns malformed media", async () => {
|
||||
const repo = new MockMediaRepository();
|
||||
// Store a malformed object (missing required fields) via type cast
|
||||
await repo._store({ id: "bad", alt: "alt", url: "u" } as never);
|
||||
|
||||
const useCase = getMediaUseCase(repo);
|
||||
await expect(useCase({ id: "bad" })).rejects.toBeInstanceOf(ZodError);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { MediaNotFoundError } from "../../entities/errors/media";
|
||||
import { mediaSchema } from "../../entities/models/media";
|
||||
import type { IMediaRepository } from "../repositories/media.repository.interface";
|
||||
|
||||
// ── Input ────────────────────────────────────────────────────────────────
|
||||
export const getMediaInputSchema = z.object({ id: z.string().min(1) }).strict();
|
||||
export type GetMediaInput = z.infer<typeof getMediaInputSchema>;
|
||||
|
||||
// ── Output ───────────────────────────────────────────────────────────────
|
||||
export const getMediaOutputSchema = mediaSchema;
|
||||
export type GetMediaOutput = z.infer<typeof getMediaOutputSchema>;
|
||||
|
||||
// ── Use case ─────────────────────────────────────────────────────────────
|
||||
export type IGetMediaUseCase = ReturnType<typeof getMediaUseCase>;
|
||||
|
||||
export const getMediaUseCase =
|
||||
(mediaRepository: IMediaRepository) =>
|
||||
async (input: GetMediaInput): Promise<GetMediaOutput> => {
|
||||
const media = await mediaRepository.getMedia(input.id);
|
||||
if (!media) {
|
||||
throw new MediaNotFoundError(`Media with id "${input.id}" not found`);
|
||||
}
|
||||
return getMediaOutputSchema.parse(media);
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { ZodError } from "zod";
|
||||
import {
|
||||
listMediaUseCase,
|
||||
listMediaOutputSchema,
|
||||
} from "@/application/use-cases/list-media.use-case";
|
||||
import { MockMediaRepository } from "@/infrastructure/repositories/media.repository.mock";
|
||||
import { mediaFactory } from "@/__factories__/media.factory";
|
||||
|
||||
describe("listMediaUseCase", () => {
|
||||
it("returns empty array when no media exists", async () => {
|
||||
const repo = new MockMediaRepository();
|
||||
const useCase = listMediaUseCase(repo);
|
||||
const result = await useCase({});
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("returns all media when store has items", async () => {
|
||||
const repo = new MockMediaRepository();
|
||||
await repo._store(mediaFactory.build());
|
||||
await repo._store(mediaFactory.build());
|
||||
|
||||
const useCase = listMediaUseCase(repo);
|
||||
const result = await useCase({});
|
||||
expect(result).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("respects limit and offset options", async () => {
|
||||
const repo = new MockMediaRepository();
|
||||
for (let i = 0; i < 5; i++) {
|
||||
await repo._store(mediaFactory.build());
|
||||
}
|
||||
|
||||
const useCase = listMediaUseCase(repo);
|
||||
const result = await useCase({ limit: 2, offset: 1 });
|
||||
expect(result).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("parses valid output without error", async () => {
|
||||
const items = [mediaFactory.build(), mediaFactory.build()];
|
||||
expect(() => listMediaOutputSchema.parse(items)).not.toThrow();
|
||||
});
|
||||
|
||||
it("throws ZodError when repository returns malformed items", async () => {
|
||||
const repo = new MockMediaRepository();
|
||||
// Store a malformed object (missing required fields)
|
||||
await repo._store({ id: "bad", alt: "alt", url: "u" } as never);
|
||||
|
||||
const useCase = listMediaUseCase(repo);
|
||||
await expect(useCase({})).rejects.toBeInstanceOf(ZodError);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { mediaSchema } from "../../entities/models/media";
|
||||
import type { IMediaRepository } from "../repositories/media.repository.interface";
|
||||
|
||||
// ── Input ────────────────────────────────────────────────────────────────
|
||||
export const listMediaInputSchema = z
|
||||
.object({
|
||||
limit: z.number().int().positive().optional(),
|
||||
offset: z.number().int().nonnegative().optional(),
|
||||
})
|
||||
.strict();
|
||||
export type ListMediaInput = z.infer<typeof listMediaInputSchema>;
|
||||
|
||||
// ── Output ───────────────────────────────────────────────────────────────
|
||||
export const listMediaOutputSchema = z.array(mediaSchema);
|
||||
export type ListMediaOutput = z.infer<typeof listMediaOutputSchema>;
|
||||
|
||||
// ── Use case ─────────────────────────────────────────────────────────────
|
||||
export type IListMediaUseCase = ReturnType<typeof listMediaUseCase>;
|
||||
|
||||
export const listMediaUseCase =
|
||||
(mediaRepository: IMediaRepository) =>
|
||||
async (input: ListMediaInput): Promise<ListMediaOutput> => {
|
||||
const result = await mediaRepository.listMedia(input);
|
||||
return listMediaOutputSchema.parse(result);
|
||||
};
|
||||
Reference in New Issue
Block a user