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,38 @@
import { describe, it, expect } from "vitest";
import { deleteMediaController } from "@/interface-adapters/controllers/delete-media.controller";
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 { InputParseError } from "@/entities/errors/common";
import { mediaFactory } from "@/__factories__/media.factory";
describe("deleteMediaController", () => {
it("deletes media successfully", async () => {
const repo = new MockMediaRepository();
const seed = mediaFactory.build({ id: "m-1" });
await repo._store(seed);
const useCase = deleteMediaUseCase(repo);
const controller = deleteMediaController(useCase);
await expect(controller({ id: "m-1" })).resolves.toBeUndefined();
const result = await repo.getMedia("m-1");
expect(result).toBeUndefined();
});
it("throws InputParseError when id is missing", async () => {
const repo = new MockMediaRepository();
const useCase = deleteMediaUseCase(repo);
const controller = deleteMediaController(useCase);
await expect(controller({})).rejects.toThrow(InputParseError);
});
it("throws MediaNotFoundError when media does not exist", async () => {
const repo = new MockMediaRepository();
const useCase = deleteMediaUseCase(repo);
const controller = deleteMediaController(useCase);
await expect(controller({ id: "missing" })).rejects.toThrow(MediaNotFoundError);
});
});

View File

@@ -0,0 +1,17 @@
import { InputParseError } from "../../entities/errors/common";
import {
deleteMediaInputSchema,
type IDeleteMediaUseCase,
} from "../../application/use-cases/delete-media.use-case";
export type IDeleteMediaController = ReturnType<typeof deleteMediaController>;
export const deleteMediaController =
(deleteMediaUseCase: IDeleteMediaUseCase) =>
async (input: unknown): Promise<void> => {
const parsed = deleteMediaInputSchema.safeParse(input);
if (!parsed.success) {
throw new InputParseError("Invalid delete-media input", { cause: parsed.error });
}
await deleteMediaUseCase(parsed.data);
};

View File

@@ -0,0 +1,45 @@
import { describe, it, expect } from "vitest";
import { getMediaController } from "@/interface-adapters/controllers/get-media.controller";
import { getMediaUseCase } from "@/application/use-cases/get-media.use-case";
import { MockMediaRepository } from "@/infrastructure/repositories/media.repository.mock";
import { MediaNotFoundError } from "@/entities/errors/media";
import { InputParseError } from "@/entities/errors/common";
import { mediaFactory } from "@/__factories__/media.factory";
describe("getMediaController", () => {
it("returns media on valid input", async () => {
const repo = new MockMediaRepository();
const seed = mediaFactory.build({ id: "m-1" });
await repo._store(seed);
const useCase = getMediaUseCase(repo);
const controller = getMediaController(useCase);
const result = await controller({ id: "m-1" });
expect(result.id).toBe("m-1");
});
it("throws InputParseError when id is missing", async () => {
const repo = new MockMediaRepository();
const useCase = getMediaUseCase(repo);
const controller = getMediaController(useCase);
await expect(controller({})).rejects.toThrow(InputParseError);
});
it("throws InputParseError when id is empty string", async () => {
const repo = new MockMediaRepository();
const useCase = getMediaUseCase(repo);
const controller = getMediaController(useCase);
await expect(controller({ id: "" })).rejects.toThrow(InputParseError);
});
it("throws MediaNotFoundError when media does not exist", async () => {
const repo = new MockMediaRepository();
const useCase = getMediaUseCase(repo);
const controller = getMediaController(useCase);
await expect(controller({ id: "missing" })).rejects.toThrow(MediaNotFoundError);
});
});

View File

@@ -0,0 +1,23 @@
import { InputParseError } from "../../entities/errors/common";
import {
getMediaInputSchema,
type GetMediaOutput,
type IGetMediaUseCase,
} from "../../application/use-cases/get-media.use-case";
function presenter(value: GetMediaOutput) {
return value;
}
export type IGetMediaController = ReturnType<typeof getMediaController>;
export const getMediaController =
(getMediaUseCase: IGetMediaUseCase) =>
async (input: unknown): Promise<ReturnType<typeof presenter>> => {
const parsed = getMediaInputSchema.safeParse(input);
if (!parsed.success) {
throw new InputParseError("Invalid get-media input", { cause: parsed.error });
}
const result = await getMediaUseCase(parsed.data);
return presenter(result);
};

View File

@@ -0,0 +1,60 @@
import { describe, it, expect } from "vitest";
import { listMediaController } from "@/interface-adapters/controllers/list-media.controller";
import { listMediaUseCase } from "@/application/use-cases/list-media.use-case";
import { MockMediaRepository } from "@/infrastructure/repositories/media.repository.mock";
import { InputParseError } from "@/entities/errors/common";
import { mediaFactory } from "@/__factories__/media.factory";
describe("listMediaController", () => {
it("returns empty array when no media exists", async () => {
const repo = new MockMediaRepository();
const useCase = listMediaUseCase(repo);
const controller = listMediaController(useCase);
const result = await controller({});
expect(result).toHaveLength(0);
});
it("returns all media", async () => {
const repo = new MockMediaRepository();
await repo._store(mediaFactory.build());
await repo._store(mediaFactory.build());
const useCase = listMediaUseCase(repo);
const controller = listMediaController(useCase);
const result = await controller({});
expect(result).toHaveLength(2);
});
it("passes limit and offset to use case", async () => {
const repo = new MockMediaRepository();
for (let i = 0; i < 5; i++) {
await repo._store(mediaFactory.build());
}
const useCase = listMediaUseCase(repo);
const controller = listMediaController(useCase);
const result = await controller({ limit: 2, offset: 1 });
expect(result).toHaveLength(2);
});
it("throws InputParseError on unknown fields (strict schema)", async () => {
const repo = new MockMediaRepository();
const useCase = listMediaUseCase(repo);
const controller = listMediaController(useCase);
await expect(controller({ unknownField: 1 })).rejects.toThrow(
InputParseError,
);
});
it("throws InputParseError when limit is not a positive integer", async () => {
const repo = new MockMediaRepository();
const useCase = listMediaUseCase(repo);
const controller = listMediaController(useCase);
await expect(controller({ limit: -1 })).rejects.toThrow(InputParseError);
});
});

View File

@@ -0,0 +1,23 @@
import { InputParseError } from "../../entities/errors/common";
import {
listMediaInputSchema,
type ListMediaOutput,
type IListMediaUseCase,
} from "../../application/use-cases/list-media.use-case";
function presenter(value: ListMediaOutput) {
return value;
}
export type IListMediaController = ReturnType<typeof listMediaController>;
export const listMediaController =
(listMediaUseCase: IListMediaUseCase) =>
async (input: unknown): Promise<ReturnType<typeof presenter>> => {
const parsed = listMediaInputSchema.safeParse(input);
if (!parsed.success) {
throw new InputParseError("Invalid list-media input", { cause: parsed.error });
}
const result = await listMediaUseCase(parsed.data);
return presenter(result);
};