Initial commit

This commit is contained in:
fraqtal
2026-07-12 08:15:46 +00:00
commit ee0fec0691
1397 changed files with 127242 additions and 0 deletions

View File

@@ -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);
});
});

View File

@@ -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);
};

View File

@@ -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);
});
});

View File

@@ -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);
};

View File

@@ -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);
});
});

View File

@@ -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);
};