From 2529fbb78e107df3e61915cd759a3c57543f820f Mon Sep 17 00:00:00 2001 From: GideonBature Date: Sat, 28 Jun 2025 21:50:22 +0100 Subject: [PATCH 1/3] Move controllers to module structure and add controller-level tests Move controllers to module structure and add controller-level tests --- src/.DS_Store | Bin 6148 -> 6148 bytes .../controllers/AuthController.int.test.ts | 22 +++ .../controllers/Auth.controller.ts | 54 +++--- .../certificate.controller.int.test.ts | 34 ++++ .../controllers/certificate.controller.ts | 10 +- .../controllers/NFTController.int.test.ts | 21 +++ .../controllers/NFTController.ts | 2 +- .../OrganizationController.int.test.ts | 132 +++++++++++++++ .../controllers/OrganizationController.ts | 4 +- .../controllers/ProjectController.int.test.ts | 24 +++ .../controllers/Project.controller.ts | 2 +- .../controllers/UserController.int.test.ts | 155 ++++++++++++++++++ .../controllers/UserController.ts | 8 +- .../controllers/userVolunteer.controller.ts | 4 +- .../VolunteerController.int.test.ts | 103 ++++++++++++ .../controllers/VolunteerController.ts | 4 +- 16 files changed, 529 insertions(+), 50 deletions(-) create mode 100644 src/modules/auth/__tests__/controllers/AuthController.int.test.ts rename src/{ => modules/auth/presentation}/controllers/Auth.controller.ts (79%) create mode 100644 src/modules/certificate/__tests__/controllers/certificate.controller.int.test.ts rename src/{ => modules/certificate/presentation}/controllers/certificate.controller.ts (89%) create mode 100644 src/modules/nft/__tests__/controllers/NFTController.int.test.ts rename src/{ => modules/nft/presentation}/controllers/NFTController.ts (96%) create mode 100644 src/modules/organization/__tests__/controllers/OrganizationController.int.test.ts rename src/{ => modules/organization/presentation}/controllers/OrganizationController.ts (94%) create mode 100644 src/modules/project/__tests__/controllers/ProjectController.int.test.ts rename src/{ => modules/project/presentation}/controllers/Project.controller.ts (96%) create mode 100644 src/modules/user/__tests__/controllers/UserController.int.test.ts rename src/{ => modules/user/presentation}/controllers/UserController.ts (88%) rename src/{ => modules/user/presentation}/controllers/userVolunteer.controller.ts (90%) create mode 100644 src/modules/volunteer/__tests__/controllers/VolunteerController.int.test.ts rename src/{ => modules/volunteer/presentation}/controllers/VolunteerController.ts (90%) diff --git a/src/.DS_Store b/src/.DS_Store index 86d74fa49b080ef10d687e8cad61083b93c65b61..3d7bb5a7571fdc70c088f11f473e7d9401681c45 100644 GIT binary patch delta 31 ncmZoMXfc@J&&azmU^g=(?_?ep{mq3e)0ihVm~Cd~_{$Ffnyd-S delta 68 zcmZoMXfc@J&&aniU^g=(-((&ZeRXbzWQKf(Jcbg6A|RaugsDJQv1d+xa#Buy5(5K+ S01&U=ti!UDc{4l5Uw!~c+z}oC 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..46b3196 --- /dev/null +++ b/src/modules/user/__tests__/controllers/UserController.int.test.ts @@ -0,0 +1,155 @@ +// 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("../../../../modules/user/dto/CreateUserDto", () => { + return { + CreateUserDto: function () { + return {}; + }, + }; +}); +jest.mock("../../../../modules/user/dto/UpdateUserDto", () => { + return { + UpdateUserDto: function () { + return {}; + }, + }; +}); + +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; +} + +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" }; + (UserService as jest.Mock).mockImplementation(() => ({ + 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"); + }); + }); + + 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/__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(); From 96433dcd6eccb8907724e7ba7d7c28ae96e2346c Mon Sep 17 00:00:00 2001 From: GideonBature Date: Sun, 29 Jun 2025 04:49:12 +0100 Subject: [PATCH 2/3] Fix: add version number to ci workflow --- .github/workflows/test.yml | 16 ++++----- .../controllers/UserController.int.test.ts | 36 ++++++++++++++----- 2 files changed, 36 insertions(+), 16 deletions(-) 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/modules/user/__tests__/controllers/UserController.int.test.ts b/src/modules/user/__tests__/controllers/UserController.int.test.ts index 46b3196..cd6162c 100644 --- a/src/modules/user/__tests__/controllers/UserController.int.test.ts +++ b/src/modules/user/__tests__/controllers/UserController.int.test.ts @@ -8,17 +8,20 @@ jest.mock("../../../../services/UserService"); import { UserService } from "../../../../services/UserService"; // Mock DTOs -jest.mock("../../../../modules/user/dto/CreateUserDto", () => { +jest.mock("../../dto/CreateUserDto", () => { return { - CreateUserDto: function () { - return {}; + CreateUserDto: function (data: Record) { + // Mock validation logic + if (!data.email) throw new Error("Email is required"); + return { email: data.email, ...data }; }, }; }); -jest.mock("../../../../modules/user/dto/UpdateUserDto", () => { +jest.mock("../../dto/UpdateUserDto", () => { return { - UpdateUserDto: function () { - return {}; + UpdateUserDto: function (data: Record) { + // Mock basic validation similar to CreateUserDto + return { ...data }; }, }; }); @@ -34,6 +37,10 @@ function setupApp(): Express { return app; } +const setupMockUserService = (methods: Partial>) => { + (UserService as jest.Mock).mockImplementation(() => methods); +}; + describe("UserController", () => { let app: Express; @@ -45,9 +52,9 @@ describe("UserController", () => { describe("POST /users", () => { it("should create a user and return 201", async () => { const mockUser = { id: "1", email: "test@example.com" }; - (UserService as jest.Mock).mockImplementation(() => ({ + setupMockUserService({ createUser: jest.fn().mockResolvedValue(mockUser), - })); + }); const res = await request(app) .post("/users") .send({ email: "test@example.com" }); @@ -65,6 +72,19 @@ describe("UserController", () => { 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", () => { From 7f5a22e2426f0220cdbec995baefd33f7f579a2d Mon Sep 17 00:00:00 2001 From: villarley Date: Tue, 8 Jul 2025 12:18:04 -0600 Subject: [PATCH 3/3] Remove unnecessary .DS_Store files from the project directory --- .DS_Store | Bin 6148 -> 0 bytes src/.DS_Store | Bin 6148 -> 0 bytes src/modules/.DS_Store | Bin 6148 -> 0 bytes src/modules/volunteer/.DS_Store | Bin 6148 -> 0 bytes 4 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store delete mode 100644 src/.DS_Store delete mode 100644 src/modules/.DS_Store delete mode 100644 src/modules/volunteer/.DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 5622495aa116f6cdcbf5814fdbbee45954253464..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%Wl&^6upzAtSr$QZ|TNF91jCqQjl0j9Z_WeDgm(-+7at{4)VEdsBPyS3i zM``|%h8EE+awwz$U64x=-A5|~Ylu2VPm{jj$uGqCQHZfh-As&A^2jGQ&k=VEoNYvp z;pb663FhH>17hx(%=?_;LWFOH2%B^+W0cuRiTk_2UB_%gumiGyo?NGO#Y$PN?-{kW z;cZE^Ead4zFJG;^iELtCLq2O_t+El;3lSFZq+VIxGI$X(jgh65pF^-RS>HN5UF7M& zmw&_3E)c9omQh9Ahz9w%m9wk8>~TC)JL;!B^u0J~wSI|8wYIdpVyqZz#)sagaNJ9* zWE6L-=p8?ugrRTG+m`*tbI1M0?Sn8#EH7{)N$?yOF7MxZffJ59;V5uo$?c^BM%}3Q z8#|N9-jmj@`FOuI-8CmqA8~tce>$xjckVrScHF=4FN5$aGKUGAYtl9p&VdzEYyRAe z0zV9Rt?a#o-N-w$w1@c35u2I4u)#iseYpxg^m4YDM|_|bsWnEAH_Q;(lBOn1K3tLOA$RV{YOB`pewDwKULr_EGq>) diff --git a/src/.DS_Store b/src/.DS_Store deleted file mode 100644 index 3d7bb5a7571fdc70c088f11f473e7d9401681c45..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKL2uJA6n^fy)?J0v14z3dMdCV*!q5cb61s8VN)Q|Xm8JUHywNnm?<5!y)C-=}fraJt(# zZcDn?pKHBtgQd_;rCV~|i@u5xRBMA80s<&_thl1-t_53hbEM9`FB; ze}Dd8C;2~;V^w6kyvpb7vMI$^=aVUE!}`HU4zriduiF%n3jhrj-D zJX6hyzo-B`I~#@|A%+Y-JwJI9#4^BHdNmHvH{O(CG?x*K29JjE5PJbhuLx1t!;;JMC<*1L zEwdyXD6Xd`EZefX)!KO6+-=mIomOK~cgB0$6gOLwiEXW|Z|H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0