Skip to content

Commit 9659531

Browse files
feat: add delete specie use case
1 parent 8987a1a commit 9659531

9 files changed

Lines changed: 226 additions & 0 deletions

File tree

src/modules/pet/routes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { updatePet } from "./pets/controllers/update";
1313
import { deletePetImage } from "./pets/controllers/delete-image";
1414
import { deletePet } from "./pets/controllers/delete";
1515
import { deleteBreed } from "./breeds/controllers/delete";
16+
import { deleteSpecie } from "./species/controllers/delete";
1617

1718
const petRoutes = new Elysia();
1819

@@ -31,6 +32,7 @@ petRoutes.group("pet", (app) =>
3132
.use(findAllBreedsBySpecie)
3233
.use(createSpecie)
3334
.use(updateSpecie)
35+
.use(deleteSpecie)
3436
.use(findAllSpecies),
3537
);
3638

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { describe, expect, it } from "bun:test";
2+
import { app } from "@/app";
3+
import db from "@/db";
4+
import { speciesTable } from "../specie";
5+
import { bearerToken } from "@/test";
6+
7+
describe("Delete specie e2e", () => {
8+
it("should delete a specie successfully", async () => {
9+
const specie = (
10+
await db
11+
.insert(speciesTable)
12+
.values({
13+
name: "Dog",
14+
})
15+
.returning()
16+
)[0];
17+
18+
const request = new Request(`http://localhost/pet/species/${specie.id}`, {
19+
method: "DELETE",
20+
headers: {
21+
Authorization: bearerToken,
22+
},
23+
});
24+
25+
const response = await app.handle(request);
26+
27+
expect(response.status).toBe(204);
28+
});
29+
30+
it("should return 404 when trying to delete a specie that doesn't exist", async () => {
31+
const request = new Request(
32+
"http://localhost/pet/species/08cb2572-77d4-4de3-b6f7-97e4b5cd6512",
33+
{
34+
method: "DELETE",
35+
headers: {
36+
Authorization: bearerToken,
37+
},
38+
},
39+
);
40+
41+
const response = await app.handle(request);
42+
43+
const body = await response.json();
44+
45+
expect(body.name).toBe("SpecieNotFoundError");
46+
47+
expect(response.status).toBe(404);
48+
});
49+
50+
it("should return 422 when delete a specie with invalid data", async () => {
51+
const request = new Request("http://localhost/pet/species/invalid", {
52+
method: "DELETE",
53+
headers: {
54+
Authorization: bearerToken,
55+
},
56+
});
57+
58+
const response = await app.handle(request);
59+
60+
const body = await response.json();
61+
62+
expect(body).toBeTruthy();
63+
64+
expect(response.status).toBe(422);
65+
});
66+
67+
it("should return 401 trying being Unauthorized", async () => {
68+
const request = new Request(
69+
"http://localhost/pet/species/b56647a9-9f40-4a31-9a58-42634bfff669",
70+
{
71+
method: "DELETE",
72+
},
73+
);
74+
75+
const response = await app.handle(request);
76+
77+
const body = await response.json();
78+
79+
expect(body.name).toBe("Unauthorized");
80+
81+
expect(response.status).toBe(401);
82+
});
83+
});
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import Elysia, { t } from "elysia";
2+
import { swaggerSpecieSchema } from "../schema";
3+
import { makeDeleteSpecieUseCase } from "../factories/make-delete";
4+
import { auth } from "@/utils/auth/plugin";
5+
import { swaggerUnauthorizedSchema } from "@/modules/auth/users/schema";
6+
import { swaggerSpecieNotFoundErrorSchema } from "../../shared/schema";
7+
import { SpecieNotFoundError } from "../../shared/errors/specie-not-found";
8+
9+
export const deleteSpecie = new Elysia()
10+
.use(auth())
11+
.error({
12+
SpecieNotFoundError,
13+
})
14+
.onError(({ code, error, set }) => {
15+
switch (code) {
16+
case "SpecieNotFoundError":
17+
set.status = "Not Found";
18+
return error;
19+
}
20+
})
21+
.delete(
22+
"/species/:id",
23+
async ({ params, set }) => {
24+
const deleteSpecieUseCase = makeDeleteSpecieUseCase();
25+
26+
await deleteSpecieUseCase.execute(params.id);
27+
28+
set.status = "No Content";
29+
},
30+
{
31+
auth: true,
32+
params: t.Object({
33+
id: t.String({
34+
format: "uuid",
35+
}),
36+
}),
37+
detail: {
38+
summary: "Delete specie",
39+
description: "Delete a specie",
40+
tags: ["Pet"],
41+
responses: {
42+
200: {
43+
description: "Success",
44+
content: {
45+
"application/json": {
46+
schema: swaggerSpecieSchema,
47+
},
48+
},
49+
},
50+
401: {
51+
description: "Unauthorized",
52+
content: {
53+
"application/json": {
54+
schema: swaggerUnauthorizedSchema,
55+
},
56+
},
57+
},
58+
404: {
59+
description: "Specie not found",
60+
content: {
61+
"application/json": {
62+
schema: swaggerSpecieNotFoundErrorSchema,
63+
},
64+
},
65+
},
66+
422: {
67+
description: "Validation Error",
68+
},
69+
},
70+
},
71+
},
72+
);
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { DrizzleSpeciesRepository } from "../repositories/drizzle-repository";
2+
import { DeleteSpecieUseCase } from "../use-cases/delete";
3+
4+
export const makeDeleteSpecieUseCase = () => {
5+
const drizzleSpeciesRepository = new DrizzleSpeciesRepository();
6+
const useCase = new DeleteSpecieUseCase(drizzleSpeciesRepository);
7+
8+
return useCase;
9+
};

src/modules/pet/species/repositories/drizzle-repository.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,8 @@ export class DrizzleSpeciesRepository implements SpeciesRepository {
5454

5555
return rows[0];
5656
}
57+
58+
async deleteSpecie(id: Specie["id"]): Promise<void> {
59+
await db.delete(speciesTable).where(eq(speciesTable.id, id));
60+
}
5761
}

src/modules/pet/species/repositories/in-memory-repository.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,8 @@ export class InMemorySpeciesRepository implements SpeciesRepository {
3939
async findSpecieByName(name: string): Promise<Specie | null> {
4040
return this.species.find((specie) => specie.name === name) || null;
4141
}
42+
43+
async deleteSpecie(id: Specie["id"]): Promise<void> {
44+
this.species = this.species.filter((specie) => specie.id !== id);
45+
}
4246
}

src/modules/pet/species/repository.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ export interface SpeciesRepository {
66
findAll(): Promise<Specie[]>;
77
findSpecieById(id: string): Promise<Specie | null>;
88
findSpecieByName(name: string): Promise<Specie | null>;
9+
deleteSpecie(id: Specie["id"]): Promise<void>;
910
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { InMemorySpeciesRepository } from "../repositories/in-memory-repository";
2+
import { beforeEach, describe, expect, it } from "bun:test";
3+
import { UpdateSpecie } from "../types";
4+
import { SpecieNotFoundError } from "../../shared/errors/specie-not-found";
5+
import { DeleteSpecieUseCase } from "./delete";
6+
7+
describe("Delete specie use case", () => {
8+
let deleteSpecieUseCase: DeleteSpecieUseCase;
9+
let inMemorySpeciesRepository: InMemorySpeciesRepository;
10+
11+
beforeEach(() => {
12+
inMemorySpeciesRepository = new InMemorySpeciesRepository();
13+
deleteSpecieUseCase = new DeleteSpecieUseCase(inMemorySpeciesRepository);
14+
});
15+
16+
it("should delete a specie", async () => {
17+
const specie = await inMemorySpeciesRepository.createSpecie({
18+
name: "Dog",
19+
});
20+
21+
expect(deleteSpecieUseCase.execute(specie.id)).resolves.toBeUndefined();
22+
});
23+
24+
it("should throw SpecieNotFoundError when trying to delete a specie that does not exist", async () => {
25+
const specie: UpdateSpecie = {
26+
id: "123",
27+
name: "Dog",
28+
};
29+
30+
expect(deleteSpecieUseCase.execute(specie.id)).rejects.toThrowError(
31+
SpecieNotFoundError,
32+
);
33+
});
34+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { SpecieNotFoundError } from "../../shared/errors/specie-not-found";
2+
import { SpeciesRepository } from "../repository";
3+
import { Specie } from "../types";
4+
5+
export class DeleteSpecieUseCase {
6+
constructor(private speciesRepository: SpeciesRepository) {}
7+
8+
async execute(specieId: Specie["id"]): Promise<void> {
9+
const specie = await this.speciesRepository.findSpecieById(specieId);
10+
11+
if (!specie) {
12+
throw new SpecieNotFoundError();
13+
}
14+
15+
await this.speciesRepository.deleteSpecie(specieId);
16+
}
17+
}

0 commit comments

Comments
 (0)