diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 5622495..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3b58833..26e25fd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,9 +2,9 @@ name: Test CI on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] jobs: test: @@ -30,18 +30,18 @@ jobs: uses: actions/checkout@v3 - name: Set up Node.js - uses: actions/setup-node@v + uses: actions/setup-node@v4 with: - node-version: '18' - cache: 'npm' + node-version: "18" + cache: "npm" - name: Install dependencies run: npm ci - name: Build Docker image run: | - docker build -t volunchain-backend . - docker run -d --name volunchain_test --link postgres:db -e DATABASE_URL=postgresql://volunchain:volunchain123@db:5432/volunchain_test volunchain-backend + docker build -t volunchain-backend . + docker run -d --name volunchain_test --link postgres:db -e DATABASE_URL=postgresql://volunchain:volunchain123@db:5432/volunchain_test volunchain-backend - name: Generate Prisma Client run: npx prisma generate @@ -72,4 +72,4 @@ jobs: if: always() run: | docker stop volunchain_test - docker rm volunchain_test + docker rm volunchain_test diff --git a/src/.DS_Store b/src/.DS_Store deleted file mode 100644 index 86d74fa..0000000 Binary files a/src/.DS_Store and /dev/null differ diff --git a/src/modules/.DS_Store b/src/modules/.DS_Store deleted file mode 100644 index 490f1ba..0000000 Binary files a/src/modules/.DS_Store and /dev/null differ diff --git a/src/modules/auth/__tests__/controllers/AuthController.int.test.ts b/src/modules/auth/__tests__/controllers/AuthController.int.test.ts new file mode 100644 index 0000000..39b48e8 --- /dev/null +++ b/src/modules/auth/__tests__/controllers/AuthController.int.test.ts @@ -0,0 +1,22 @@ +import { Request, Response } from "express"; +// Integration test for AuthController +import AuthController from "../../presentation/controllers/Auth.controller"; + +describe("AuthController Integration", () => { + it("should have a register method", () => { + expect(typeof AuthController.register).toBe("function"); + }); + + test("should return error if required fields are missing on register", async () => { + const req = { body: {} } as Partial; + const res = { + status: jest.fn().mockReturnThis(), + json: jest.fn(), + } as Partial; + await AuthController.register(req as Request, res as Response); + expect(res.status).toHaveBeenCalledWith(400); + expect(res.json).toHaveBeenCalledWith( + expect.objectContaining({ message: expect.any(String) }) + ); + }); +}); diff --git a/src/controllers/Auth.controller.ts b/src/modules/auth/presentation/controllers/Auth.controller.ts similarity index 79% rename from src/controllers/Auth.controller.ts rename to src/modules/auth/presentation/controllers/Auth.controller.ts index 26c5c21..797fba6 100644 --- a/src/controllers/Auth.controller.ts +++ b/src/modules/auth/presentation/controllers/Auth.controller.ts @@ -1,6 +1,6 @@ import { Request, Response } from "express"; -import AuthService from "../services/AuthService"; -import { AuthenticatedRequest } from "../types/auth.types"; +import AuthService from "../../../../services/AuthService"; +import { AuthenticatedRequest } from "../../../../types/auth.types"; class AuthController { private authService: AuthService; @@ -22,12 +22,9 @@ class AuthController { ); res.status(201).json(response); } catch (error) { - res - .status(400) - .json({ - message: - error instanceof Error ? error.message : "Registration failed", - }); + res.status(400).json({ + message: error instanceof Error ? error.message : "Registration failed", + }); } }; @@ -48,12 +45,9 @@ class AuthController { const response = await this.authService.verifyEmail(token); res.json(response); } catch (error) { - res - .status(400) - .json({ - message: - error instanceof Error ? error.message : "Verification failed", - }); + res.status(400).json({ + message: error instanceof Error ? error.message : "Verification failed", + }); } }; @@ -72,12 +66,10 @@ class AuthController { const response = await this.authService.resendVerificationEmail(email); res.json(response); } catch (error) { - res - .status(400) - .json({ - message: - error instanceof Error ? error.message : "Could not resend email", - }); + res.status(400).json({ + message: + error instanceof Error ? error.message : "Could not resend email", + }); } }; @@ -88,11 +80,9 @@ class AuthController { const token = await this.authService.authenticate(walletAddress); res.json({ token }); } catch (error) { - res - .status(401) - .json({ - message: error instanceof Error ? error.message : "Unknown error", - }); + res.status(401).json({ + message: error instanceof Error ? error.message : "Unknown error", + }); } }; @@ -111,14 +101,12 @@ class AuthController { ); res.json(status); } catch (error) { - res - .status(400) - .json({ - message: - error instanceof Error - ? error.message - : "Could not check verification status", - }); + res.status(400).json({ + message: + error instanceof Error + ? error.message + : "Could not check verification status", + }); } }; diff --git a/src/modules/certificate/__tests__/controllers/certificate.controller.int.test.ts b/src/modules/certificate/__tests__/controllers/certificate.controller.int.test.ts new file mode 100644 index 0000000..9405e2d --- /dev/null +++ b/src/modules/certificate/__tests__/controllers/certificate.controller.int.test.ts @@ -0,0 +1,34 @@ +// Integration test for certificate.controller +import { Request, Response } from "express"; +import * as CertificateController from "../../presentation/controllers/certificate.controller"; + +describe("CertificateController Integration", () => { + it("should have a downloadCertificate function", () => { + expect(typeof CertificateController.downloadCertificate).toBe("function"); + }); + + test("should return error if volunteer does not exist on downloadCertificate", async () => { + const req = { + params: { id: "nonexistent" }, + user: { + id: "user1", + email: "test@example.com", + role: "volunteer", + isVerified: true, + }, + query: {}, + } as Partial; + const res = { + status: jest.fn().mockReturnThis(), + json: jest.fn(), + } as Partial; + await CertificateController.downloadCertificate( + req as Request, + res as Response + ); + expect(res.status).toHaveBeenCalledWith(403); + expect(res.json).toHaveBeenCalledWith( + expect.objectContaining({ error: expect.any(String) }) + ); + }); +}); diff --git a/src/controllers/certificate.controller.ts b/src/modules/certificate/presentation/controllers/certificate.controller.ts similarity index 89% rename from src/controllers/certificate.controller.ts rename to src/modules/certificate/presentation/controllers/certificate.controller.ts index c4441a1..0823db1 100644 --- a/src/controllers/certificate.controller.ts +++ b/src/modules/certificate/presentation/controllers/certificate.controller.ts @@ -1,8 +1,8 @@ -import { Request, Response } from "express"; -import { container } from "../shared/infrastructure/container"; -import { prisma } from "../config/prisma"; -import { ICertificateService } from "../shared/domain/interfaces/ICertificateService"; -import { AuthenticatedRequest } from "../types/auth.types"; +import { container } from "../../../../shared/infrastructure/container"; +import { prisma } from "../../../../config/prisma"; +import { ICertificateService } from "../../../../shared/domain/interfaces/ICertificateService"; +import { AuthenticatedRequest } from "../../../../types/auth.types"; +import { Response } from "express"; const certificateService: ICertificateService = container.certificateService; diff --git a/src/modules/nft/__tests__/controllers/NFTController.int.test.ts b/src/modules/nft/__tests__/controllers/NFTController.int.test.ts new file mode 100644 index 0000000..f44a11e --- /dev/null +++ b/src/modules/nft/__tests__/controllers/NFTController.int.test.ts @@ -0,0 +1,21 @@ +import { Request, Response } from "express"; +import NFTController from "../../presentation/controllers/NFTController"; + +describe("NFTController Integration", () => { + it("should have a createNFT method", () => { + expect(typeof NFTController.createNFT).toBe("function"); + }); + + test("should return error if createNFT is called with empty body", async () => { + const req = { body: {} } as Partial; + const res = { + status: jest.fn().mockReturnThis(), + json: jest.fn(), + } as unknown as Response; + await NFTController.createNFT(req as Request, res as Response); + expect(res.status).toHaveBeenCalledWith(400); + expect(res.json).toHaveBeenCalledWith( + expect.objectContaining({ error: expect.any(String) }) + ); + }); +}); diff --git a/src/controllers/NFTController.ts b/src/modules/nft/presentation/controllers/NFTController.ts similarity index 96% rename from src/controllers/NFTController.ts rename to src/modules/nft/presentation/controllers/NFTController.ts index 31af35d..9fda7e4 100644 --- a/src/controllers/NFTController.ts +++ b/src/modules/nft/presentation/controllers/NFTController.ts @@ -1,5 +1,5 @@ import { Request, Response } from "express"; -import NFTService from "../services/NFTService"; +import NFTService from "../../../../services/NFTService"; class NFTController { // Creates_a_new_NFT_and_returns_the_created_NFT_data_OKK!! diff --git a/src/modules/organization/__tests__/controllers/OrganizationController.int.test.ts b/src/modules/organization/__tests__/controllers/OrganizationController.int.test.ts new file mode 100644 index 0000000..b4e07a3 --- /dev/null +++ b/src/modules/organization/__tests__/controllers/OrganizationController.int.test.ts @@ -0,0 +1,132 @@ +// Integration test for OrganizationController +import OrganizationController from "../../presentation/controllers/OrganizationController"; +import request from "supertest"; +import express, { Express } from "express"; + +// Mock the OrganizationService +jest.mock("../../../../services/OrganizationService"); +import { OrganizationService } from "../../../../services/OrganizationService"; + +// Mock asyncHandler to just return the function (for test simplicity) +jest.mock("../../../../utils/asyncHandler", () => ({ + asyncHandler: unknown>(fn: T) => fn, +})); + +function setupApp(): Express { + const app = express(); + app.use(express.json()); + // OrganizationController is a singleton instance + app.post("/organizations", OrganizationController.createOrganization); + app.get("/organizations/:id", OrganizationController.getOrganizationById); + app.get( + "/organizations/email/:email", + OrganizationController.getOrganizationByEmail + ); + app.put("/organizations/:id", OrganizationController.updateOrganization); + app.delete("/organizations/:id", OrganizationController.deleteOrganization); + app.get("/organizations", OrganizationController.getAllOrganizations); + return app; +} + +describe("OrganizationController", () => { + let app: Express; + + beforeEach(() => { + app = setupApp(); + jest.clearAllMocks(); + }); + + describe("POST /organizations", () => { + it("should create an organization and return 201", async () => { + const mockOrg = { id: "1", name: "Org" }; + (OrganizationService as jest.Mock).mockImplementation(() => ({ + createOrganization: jest.fn().mockResolvedValue(mockOrg), + })); + const res = await request(app).post("/organizations").send({ + name: "Org", + email: "org@mail.com", + password: "pass", + category: "cat", + wallet: "wallet", + }); + expect(res.status).toBe(201); + expect(res.body).toEqual(mockOrg); + }); + }); + + describe("GET /organizations/:id", () => { + it("should return an organization by id", async () => { + const mockOrg = { id: "1", name: "Org" }; + (OrganizationService as jest.Mock).mockImplementation(() => ({ + getOrganizationById: jest.fn().mockResolvedValue(mockOrg), + })); + const res = await request(app).get("/organizations/1"); + expect(res.status).toBe(200); + expect(res.body).toEqual(mockOrg); + }); + it("should return 404 if not found", async () => { + (OrganizationService as jest.Mock).mockImplementation(() => ({ + getOrganizationById: jest.fn().mockResolvedValue(null), + })); + const res = await request(app).get("/organizations/1"); + expect(res.status).toBe(404); + expect(res.body.error).toBe("Organization not found"); + }); + }); + + describe("GET /organizations/email/:email", () => { + it("should return an organization by email", async () => { + const mockOrg = { id: "1", name: "Org" }; + (OrganizationService as jest.Mock).mockImplementation(() => ({ + getOrganizationByEmail: jest.fn().mockResolvedValue(mockOrg), + })); + const res = await request(app).get("/organizations/email/test@mail.com"); + expect(res.status).toBe(200); + expect(res.body).toEqual(mockOrg); + }); + it("should return 404 if not found", async () => { + (OrganizationService as jest.Mock).mockImplementation(() => ({ + getOrganizationByEmail: jest.fn().mockResolvedValue(null), + })); + const res = await request(app).get("/organizations/email/test@mail.com"); + expect(res.status).toBe(404); + expect(res.body.error).toBe("Organization not found"); + }); + }); + + describe("PUT /organizations/:id", () => { + it("should update an organization and return 200", async () => { + const mockOrg = { id: "1", name: "Updated Org" }; + (OrganizationService as jest.Mock).mockImplementation(() => ({ + updateOrganization: jest.fn().mockResolvedValue(mockOrg), + })); + const res = await request(app) + .put("/organizations/1") + .send({ name: "Updated Org" }); + expect(res.status).toBe(200); + expect(res.body).toEqual(mockOrg); + }); + }); + + describe("DELETE /organizations/:id", () => { + it("should delete an organization and return 204", async () => { + (OrganizationService as jest.Mock).mockImplementation(() => ({ + deleteOrganization: jest.fn().mockResolvedValue(undefined), + })); + const res = await request(app).delete("/organizations/1"); + expect(res.status).toBe(204); + }); + }); + + describe("GET /organizations", () => { + it("should return all organizations", async () => { + const mockOrgs = [{ id: "1" }, { id: "2" }]; + (OrganizationService as jest.Mock).mockImplementation(() => ({ + getAllOrganizations: jest.fn().mockResolvedValue(mockOrgs), + })); + const res = await request(app).get("/organizations"); + expect(res.status).toBe(200); + expect(res.body).toEqual(mockOrgs); + }); + }); +}); diff --git a/src/controllers/OrganizationController.ts b/src/modules/organization/presentation/controllers/OrganizationController.ts similarity index 94% rename from src/controllers/OrganizationController.ts rename to src/modules/organization/presentation/controllers/OrganizationController.ts index ead962a..2c8029d 100644 --- a/src/controllers/OrganizationController.ts +++ b/src/modules/organization/presentation/controllers/OrganizationController.ts @@ -1,6 +1,6 @@ import { Request, Response } from "express"; -import { OrganizationService } from "../services/OrganizationService"; -import { asyncHandler } from "../utils/asyncHandler"; +import { OrganizationService } from "../../../../services/OrganizationService"; +import { asyncHandler } from "../../../../utils/asyncHandler"; class OrganizationController { private organizationService: OrganizationService; diff --git a/src/modules/project/__tests__/controllers/ProjectController.int.test.ts b/src/modules/project/__tests__/controllers/ProjectController.int.test.ts new file mode 100644 index 0000000..72a5249 --- /dev/null +++ b/src/modules/project/__tests__/controllers/ProjectController.int.test.ts @@ -0,0 +1,24 @@ +// Integration test for ProjectController +import { Request, Response } from "express"; +import ProjectController from "../../presentation/controllers/Project.controller"; + +describe("ProjectController Integration", () => { + const controller = new ProjectController(); + + it("should have a createProject method", () => { + expect(typeof ProjectController.prototype.createProject).toBe("function"); + }); + + test("should return error if required fields are missing on createProject", async () => { + const req = { body: {} } as Partial; + const res = { + status: jest.fn().mockReturnThis(), + json: jest.fn(), + } as Partial; + await controller.createProject(req as Request, res as Response); + expect(res.status).toHaveBeenCalledWith(400); + expect(res.json).toHaveBeenCalledWith( + expect.objectContaining({ error: expect.any(String) }) + ); + }); +}); diff --git a/src/controllers/Project.controller.ts b/src/modules/project/presentation/controllers/Project.controller.ts similarity index 96% rename from src/controllers/Project.controller.ts rename to src/modules/project/presentation/controllers/Project.controller.ts index 8e1984f..1d15d56 100644 --- a/src/controllers/Project.controller.ts +++ b/src/modules/project/presentation/controllers/Project.controller.ts @@ -1,5 +1,5 @@ import { Request, Response } from "express"; -import ProjectService from "../services/ProjectService"; +import ProjectService from "../../../../services/ProjectService"; class ProjectController { private projectService = new ProjectService(); diff --git a/src/modules/user/__tests__/controllers/UserController.int.test.ts b/src/modules/user/__tests__/controllers/UserController.int.test.ts new file mode 100644 index 0000000..cd6162c --- /dev/null +++ b/src/modules/user/__tests__/controllers/UserController.int.test.ts @@ -0,0 +1,175 @@ +// Integration test for UserController +import request from "supertest"; +import express, { Express } from "express"; +import UserController from "../../presentation/controllers/UserController"; + +// Mock the UserService +jest.mock("../../../../services/UserService"); +import { UserService } from "../../../../services/UserService"; + +// Mock DTOs +jest.mock("../../dto/CreateUserDto", () => { + return { + CreateUserDto: function (data: Record) { + // Mock validation logic + if (!data.email) throw new Error("Email is required"); + return { email: data.email, ...data }; + }, + }; +}); +jest.mock("../../dto/UpdateUserDto", () => { + return { + UpdateUserDto: function (data: Record) { + // Mock basic validation similar to CreateUserDto + return { ...data }; + }, + }; +}); + +function setupApp(): Express { + const app = express(); + app.use(express.json()); + const controller = new UserController(); + app.post("/users", controller.createUser.bind(controller)); + app.get("/users/:id", controller.getUserById.bind(controller)); + app.get("/users", controller.getUserByEmail.bind(controller)); + app.put("/users/:id", controller.updateUser.bind(controller)); + return app; +} + +const setupMockUserService = (methods: Partial>) => { + (UserService as jest.Mock).mockImplementation(() => methods); +}; + +describe("UserController", () => { + let app: Express; + + beforeEach(() => { + app = setupApp(); + jest.clearAllMocks(); + }); + + describe("POST /users", () => { + it("should create a user and return 201", async () => { + const mockUser = { id: "1", email: "test@example.com" }; + setupMockUserService({ + createUser: jest.fn().mockResolvedValue(mockUser), + }); + const res = await request(app) + .post("/users") + .send({ email: "test@example.com" }); + expect(res.status).toBe(201); + expect(res.body).toEqual(mockUser); + }); + + it("should handle errors and return 400", async () => { + (UserService as jest.Mock).mockImplementation(() => ({ + createUser: jest.fn().mockRejectedValue(new Error("fail")), + })); + const res = await request(app) + .post("/users") + .send({ email: "test@example.com" }); + expect(res.status).toBe(400); + expect(res.body.error).toBe("fail"); + }); + + it("should handle validation errors with specific status codes", async () => { + (UserService as jest.Mock).mockImplementation(() => ({ + createUser: jest + .fn() + .mockRejectedValue(new Error("Invalid email format")), + })); + const res = await request(app) + .post("/users") + .send({ email: "invalid-email" }); + expect(res.status).toBe(422); + expect(res.body.error).toContain("Ivalid email format"); + }); + }); + + describe("GET /users/:id", () => { + it("should return a user by id", async () => { + const mockUser = { id: "1", email: "test@example.com" }; + (UserService as jest.Mock).mockImplementation(() => ({ + getUserById: jest.fn().mockResolvedValue(mockUser), + })); + const res = await request(app).get("/users/1"); + expect(res.status).toBe(200); + expect(res.body).toEqual(mockUser); + }); + + it("should return 404 if user not found", async () => { + (UserService as jest.Mock).mockImplementation(() => ({ + getUserById: jest.fn().mockResolvedValue(null), + })); + const res = await request(app).get("/users/1"); + expect(res.status).toBe(404); + expect(res.body.error).toBe("User not found"); + }); + + it("should handle errors and return 400", async () => { + (UserService as jest.Mock).mockImplementation(() => ({ + getUserById: jest.fn().mockRejectedValue(new Error("fail")), + })); + const res = await request(app).get("/users/1"); + expect(res.status).toBe(400); + expect(res.body.error).toBe("fail"); + }); + }); + + describe("GET /users?email=", () => { + it("should return a user by email", async () => { + const mockUser = { id: "1", email: "test@example.com" }; + (UserService as jest.Mock).mockImplementation(() => ({ + getUserByEmail: jest.fn().mockResolvedValue(mockUser), + })); + const res = await request(app).get("/users?email=test@example.com"); + expect(res.status).toBe(200); + expect(res.body).toEqual(mockUser); + }); + + it("should return 400 if email is missing", async () => { + const res = await request(app).get("/users"); + expect(res.status).toBe(400); + expect(res.body.error).toBe("Email is required"); + }); + + it("should return 404 if user not found", async () => { + (UserService as jest.Mock).mockImplementation(() => ({ + getUserByEmail: jest.fn().mockResolvedValue(null), + })); + const res = await request(app).get("/users?email=test@example.com"); + expect(res.status).toBe(404); + expect(res.body.error).toBe("User not found"); + }); + + it("should handle errors and return 400", async () => { + (UserService as jest.Mock).mockImplementation(() => ({ + getUserByEmail: jest.fn().mockRejectedValue(new Error("fail")), + })); + const res = await request(app).get("/users?email=test@example.com"); + expect(res.status).toBe(400); + expect(res.body.error).toBe("fail"); + }); + }); + + describe("PUT /users/:id", () => { + it("should update a user and return 200", async () => { + (UserService as jest.Mock).mockImplementation(() => ({ + updateUser: jest.fn().mockResolvedValue(undefined), + })); + const res = await request(app).put("/users/1").send({ name: "Updated" }); + expect(res.status).toBe(200); + expect(res.body.message).toBe("User updated successfully"); + }); + + it("should handle errors and return 400", async () => { + (UserService as jest.Mock).mockImplementation(() => ({ + updateUser: jest.fn().mockRejectedValue(new Error("fail")), + })); + const res = await request(app).put("/users/1").send({ name: "Updated" }); + expect(res.status).toBe(400); + expect(res.body.error).toBe("fail"); + }); + }); +}); diff --git a/src/controllers/UserController.ts b/src/modules/user/presentation/controllers/UserController.ts similarity index 88% rename from src/controllers/UserController.ts rename to src/modules/user/presentation/controllers/UserController.ts index 5007064..1ef2aa5 100644 --- a/src/controllers/UserController.ts +++ b/src/modules/user/presentation/controllers/UserController.ts @@ -1,8 +1,8 @@ -import { CreateUserDto } from "../modules/user/dto/CreateUserDto"; -import { UserService } from "../services/UserService"; +import { CreateUserDto } from "../../../../modules/user/dto/CreateUserDto"; +import { UserService } from "../../../../services/UserService"; import { Response } from "express"; -import { UpdateUserDto } from "../modules/user/dto/UpdateUserDto"; -import { AuthenticatedRequest } from "../types/auth.types"; +import { UpdateUserDto } from "../../../../modules/user/dto/UpdateUserDto"; +import { AuthenticatedRequest } from "../../../../types/auth.types"; class UserController { private userService = new UserService(); diff --git a/src/controllers/userVolunteer.controller.ts b/src/modules/user/presentation/controllers/userVolunteer.controller.ts similarity index 90% rename from src/controllers/userVolunteer.controller.ts rename to src/modules/user/presentation/controllers/userVolunteer.controller.ts index 046612e..8254504 100644 --- a/src/controllers/userVolunteer.controller.ts +++ b/src/modules/user/presentation/controllers/userVolunteer.controller.ts @@ -1,6 +1,6 @@ import { Request, Response } from "express"; -import { UserVolunteerService } from "../services/userVolunteer.service"; -import { prisma } from "../config/prisma"; +import { UserVolunteerService } from "../../../../services/userVolunteer.service"; +import { prisma } from "../../../../config/prisma"; class UserVolunteerController { private userVolunteerService = new UserVolunteerService(prisma); diff --git a/src/modules/volunteer/.DS_Store b/src/modules/volunteer/.DS_Store deleted file mode 100644 index 5008ddf..0000000 Binary files a/src/modules/volunteer/.DS_Store and /dev/null differ diff --git a/src/modules/volunteer/__tests__/controllers/VolunteerController.int.test.ts b/src/modules/volunteer/__tests__/controllers/VolunteerController.int.test.ts new file mode 100644 index 0000000..b724e8c --- /dev/null +++ b/src/modules/volunteer/__tests__/controllers/VolunteerController.int.test.ts @@ -0,0 +1,103 @@ +import request from "supertest"; +import express, { Express } from "express"; +import VolunteerController from "../../../../../src/modules/volunteer/presentation/controllers/VolunteerController"; + +// Mock the VolunteerService +jest.mock("../../../../src/services/VolunteerService"); +import VolunteerService from "../../../../../src/services/VolunteerService"; + +function setupApp(): Express { + const app = express(); + app.use(express.json()); + const controller = new VolunteerController(); + app.post("/volunteers", controller.createVolunteer.bind(controller)); + app.get("/volunteers/:id", controller.getVolunteerById.bind(controller)); + app.get( + "/projects/:projectId/volunteers", + controller.getVolunteersByProjectId.bind(controller) + ); + return app; +} + +describe("VolunteerController", () => { + let app: Express; + + beforeEach(() => { + app = setupApp(); + jest.clearAllMocks(); + }); + + describe("POST /volunteers", () => { + it("should create a volunteer and return 201", async () => { + const mockVolunteer = { id: "1", name: "Test" }; + (VolunteerService as jest.Mock).mockImplementation(() => ({ + createVolunteer: jest.fn().mockResolvedValue(mockVolunteer), + })); + const res = await request(app).post("/volunteers").send({ name: "Test" }); + expect(res.status).toBe(201); + expect(res.body).toEqual(mockVolunteer); + }); + + it("should handle errors and return 400", async () => { + (VolunteerService as jest.Mock).mockImplementation(() => ({ + createVolunteer: jest.fn().mockRejectedValue(new Error("fail")), + })); + const res = await request(app).post("/volunteers").send({ name: "Test" }); + expect(res.status).toBe(400); + expect(res.body.error).toBe("fail"); + }); + }); + + describe("GET /volunteers/:id", () => { + it("should return a volunteer by id", async () => { + const mockVolunteer = { id: "1", name: "Test" }; + (VolunteerService as jest.Mock).mockImplementation(() => ({ + getVolunteerById: jest.fn().mockResolvedValue(mockVolunteer), + })); + const res = await request(app).get("/volunteers/1"); + expect(res.status).toBe(200); + expect(res.body).toEqual(mockVolunteer); + }); + + it("should return 404 if volunteer not found", async () => { + (VolunteerService as jest.Mock).mockImplementation(() => ({ + getVolunteerById: jest.fn().mockResolvedValue(null), + })); + const res = await request(app).get("/volunteers/1"); + expect(res.status).toBe(404); + expect(res.body.error).toBe("Volunteer not found"); + }); + + it("should handle errors and return 400", async () => { + (VolunteerService as jest.Mock).mockImplementation(() => ({ + getVolunteerById: jest.fn().mockRejectedValue(new Error("fail")), + })); + const res = await request(app).get("/volunteers/1"); + expect(res.status).toBe(400); + expect(res.body.error).toBe("fail"); + }); + }); + + describe("GET /projects/:projectId/volunteers", () => { + it("should return volunteers for a project", async () => { + const mockVolunteers = [{ id: "1" }, { id: "2" }]; + (VolunteerService as jest.Mock).mockImplementation(() => ({ + getVolunteersByProjectId: jest.fn().mockResolvedValue(mockVolunteers), + })); + const res = await request(app).get("/projects/123/volunteers"); + expect(res.status).toBe(200); + expect(res.body).toEqual(mockVolunteers); + }); + + it("should handle errors and return 400", async () => { + (VolunteerService as jest.Mock).mockImplementation(() => ({ + getVolunteersByProjectId: jest + .fn() + .mockRejectedValue(new Error("fail")), + })); + const res = await request(app).get("/projects/123/volunteers"); + expect(res.status).toBe(400); + expect(res.body.error).toBe("fail"); + }); + }); +}); diff --git a/src/controllers/VolunteerController.ts b/src/modules/volunteer/presentation/controllers/VolunteerController.ts similarity index 90% rename from src/controllers/VolunteerController.ts rename to src/modules/volunteer/presentation/controllers/VolunteerController.ts index b8fcb08..1cdd09b 100644 --- a/src/controllers/VolunteerController.ts +++ b/src/modules/volunteer/presentation/controllers/VolunteerController.ts @@ -1,6 +1,6 @@ import { Request, Response } from "express"; -import VolunteerService from "../services/VolunteerService"; -import { CreateVolunteerDTO } from "../modules/volunteer/dto/volunteer.dto"; +import VolunteerService from "../../../../services/VolunteerService"; +import { CreateVolunteerDTO } from "../../../../modules/volunteer/dto/volunteer.dto"; export default class VolunteerController { private volunteerService = new VolunteerService();