Skip to content

Commit

Permalink
Merge pull request #919 from COS301-SE-2024/test/backend/increasing-c…
Browse files Browse the repository at this point in the history
…overagede

Test/backend/increasing coveragede
  • Loading branch information
Dominique-Da-Silva authored Oct 24, 2024
2 parents 8070931 + 685a7b1 commit b84ed1a
Show file tree
Hide file tree
Showing 3 changed files with 333 additions and 0 deletions.
83 changes: 83 additions & 0 deletions nodejs_api/__tests__/unit/giveaway.controller.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { Request, Response, NextFunction } from "express";
import * as giveawayService from "../../src/services/giveaway.service";
import { getParticipantCount, addParticipant } from "../../src/controllers/giveaway.controller";

// Mock dependencies
jest.mock("../../src/services/giveaway.service");
jest.mock("../../src/config/redis.config", () => ({
cacheResponse: jest.fn(),
DEFAULT_CACHE_DURATION: 600
}));

describe("Giveaway Controller", () => {
let mockRequest: Partial<Request>;
let mockResponse: Partial<Response>;
let nextFunction: NextFunction = jest.fn();

beforeEach(() => {
mockRequest = {};
mockResponse = {
status: jest.fn().mockReturnThis(),
json: jest.fn()
};
});

describe("getParticipantCount", () => {
it("should return participant count and cache the response", async () => {
const mockCount = { count: 100 };
(giveawayService.getParticipantCount as jest.Mock).mockResolvedValue(mockCount);

await getParticipantCount(mockRequest as Request, mockResponse as Response, nextFunction);

expect(giveawayService.getParticipantCount).toHaveBeenCalled();
expect(mockResponse.status).toHaveBeenCalledWith(200);
expect(mockResponse.json).toHaveBeenCalledWith(mockCount);
});

it("should handle errors by calling next function", async () => {
const error = new Error("Test Error");
(giveawayService.getParticipantCount as jest.Mock).mockRejectedValue(error);

await getParticipantCount(mockRequest as Request, mockResponse as Response, nextFunction);

expect(nextFunction).toHaveBeenCalledWith(error);
});
});

describe("addParticipant", () => {
it("should return 400 if any required field is missing", async () => {
mockRequest = { body: { ticketNumber: "12345", name: "John Doe" } }; // Missing email and phoneNumber

await addParticipant(mockRequest as Request, mockResponse as Response, nextFunction);

expect(mockResponse.status).toHaveBeenCalledWith(400);
expect(mockResponse.json).toHaveBeenCalledWith({
Error: "Missing parameter(s): email, phoneNumber"
});
});

it("should add a participant and return the response", async () => {
const mockData = { ticketNumber: "12345", name: "John Doe", email: "[email protected]", phoneNumber: "123456789" };
const mockResponseData = { success: true };
mockRequest = { body: mockData };
(giveawayService.addParticipant as jest.Mock).mockResolvedValue(mockResponseData);

await addParticipant(mockRequest as Request, mockResponse as Response, nextFunction);

expect(giveawayService.addParticipant).toHaveBeenCalledWith(mockData);
expect(mockResponse.status).toHaveBeenCalledWith(200);
expect(mockResponse.json).toHaveBeenCalledWith(mockResponseData);
});

it("should handle errors by calling next function", async () => {
const error = new Error("Test Error");
const mockData = { ticketNumber: "12345", name: "John Doe", email: "[email protected]", phoneNumber: "123456789" };
mockRequest = { body: mockData };
(giveawayService.addParticipant as jest.Mock).mockRejectedValue(error);

await addParticipant(mockRequest as Request, mockResponse as Response, nextFunction);

expect(nextFunction).toHaveBeenCalledWith(error);
});
});
});
107 changes: 107 additions & 0 deletions nodejs_api/__tests__/unit/giveaway.service.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { GIVEAWAY_TABLE, TICKETS_TABLE } from "../../src/config/dynamodb.config";
import { addJobToReadQueue, addJobToWriteQueue } from "../../src/services/jobs.service";
import * as giveawayService from "../../src/services/giveaway.service";
import { CustomError } from "../../src/errors/CustomError";
import { deleteCacheKey } from "../../src/config/redis.config";

jest.mock("../../src/services/jobs.service");
jest.mock("../../src/config/redis.config");

describe("Giveaway Service", () => {
afterEach(() => {
jest.clearAllMocks();
});

describe("getParticipantCount", () => {
it("should return the count of participants", async () => {
const mockResponse = {
Count: 5,
};

(addJobToReadQueue as jest.Mock).mockResolvedValue({ finished: jest.fn().mockResolvedValue(mockResponse) });

const result = await giveawayService.getParticipantCount();

expect(result).toEqual({ count: 5 });
expect(addJobToReadQueue).toHaveBeenCalledWith(expect.objectContaining({
params: {
TableName: GIVEAWAY_TABLE,
Select: "COUNT"
}
}));
expect(addJobToReadQueue).toHaveBeenCalledTimes(1);
});

it("should return a count of 0 if no participants are found", async () => {
const mockResponse = {
Count: null,
};

(addJobToReadQueue as jest.Mock).mockResolvedValue({ finished: jest.fn().mockResolvedValue(mockResponse) });

const result = await giveawayService.getParticipantCount();

expect(result).toEqual({ count: 0 });
expect(addJobToReadQueue).toHaveBeenCalledWith(expect.objectContaining({
params: {
TableName: GIVEAWAY_TABLE,
Select: "COUNT"
}
}));
expect(addJobToReadQueue).toHaveBeenCalledTimes(1);
});
});

describe("addParticipant", () => {

it("should throw an error if the ticket number is invalid", async () => {
const mockFormData = {
ticketNumber: "INVALID_TICKET",
};

const mockTicketResponse = {
Items: [],
};

(addJobToReadQueue as jest.Mock).mockResolvedValueOnce({ finished: jest.fn().mockResolvedValue(mockTicketResponse) });

await expect(giveawayService.addParticipant(mockFormData)).rejects.toThrow(CustomError);
expect(addJobToReadQueue).toHaveBeenCalledWith(expect.objectContaining({
params: {
TableName: TICKETS_TABLE,
IndexName: "ticketnumber-index",
KeyConditionExpression: "ticketnumber = :ticketnumber",
ExpressionAttributeValues: { ":ticketnumber": mockFormData.ticketNumber }
}
}));
expect(addJobToReadQueue).toHaveBeenCalledTimes(1);
});

it("should throw an error if no entry id is generated", async () => {
const mockFormData = {
email: "[email protected]",
name: "Test User",
phoneNumber: "123456789",
ticketNumber: "TICKET123",
};

const mockTicketResponse = {
Items: [{ ticketnumber: "TICKET123" }],
};

const mockParticipantCountResponse = {
Count: 1,
};

(addJobToReadQueue as jest.Mock)
.mockResolvedValueOnce({ finished: jest.fn().mockResolvedValue(mockTicketResponse) })
.mockResolvedValueOnce({ finished: jest.fn().mockResolvedValue(mockParticipantCountResponse) });

jest.spyOn(Math, 'random').mockReturnValue(0);

await expect(giveawayService.addParticipant(mockFormData)).rejects.toThrow(CustomError);

expect(addJobToReadQueue).toHaveBeenCalledTimes(21);
});
});
});
143 changes: 143 additions & 0 deletions nodejs_api/__tests__/unit/jobs.controller.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { Request, Response, NextFunction } from "express";
import { getJobStatus, removeCacheKey, removeAllCache } from "../../src/controllers/jobs.controller";
import * as jobsService from "../../src/services/jobs.service";
import { deleteAllCache, deleteCacheKey } from "../../src/config/redis.config";
import { CustomError } from "../../src/errors/CustomError";

// Mock dependencies
jest.mock('../../src/services/jobs.service', () => ({
getJob: jest.fn(),
}));
jest.mock("../../src/config/redis.config");

describe("Jobs Controller", () => {
let mockRequest: Partial<Request>;
let mockResponse: Partial<Response>;
let nextFunction: NextFunction = jest.fn();

beforeEach(() => {
mockRequest = {};
mockResponse = {
status: jest.fn().mockReturnThis(),
json: jest.fn()
};
jest.clearAllMocks();
});

describe("getJobStatus", () => {
it("should return completed status if job is completed", async () => {
const mockJob = {
isCompleted: jest.fn().mockResolvedValue(true),
isFailed: jest.fn(),
returnvalue: { data: "job result" }
};
mockRequest = { params: { id: "123", type: "type1" } };
(jobsService.getJob as jest.Mock).mockResolvedValue(mockJob);

await getJobStatus(mockRequest as Request, mockResponse as Response, nextFunction);

expect(jobsService.getJob).toHaveBeenCalledWith("123", "type1");
expect(mockJob.isCompleted).toHaveBeenCalled();
expect(mockResponse.status).toHaveBeenCalledWith(200);
expect(mockResponse.json).toHaveBeenCalledWith({ status: "completed", result: mockJob.returnvalue });
});

it("should return job in progress if job is not completed", async () => {
const mockJob = {
isCompleted: jest.fn().mockResolvedValue(false),
isFailed: jest.fn().mockResolvedValue(false)
};
mockRequest = { params: { id: "123", type: "type1" } };
(jobsService.getJob as jest.Mock).mockResolvedValue(mockJob);

await getJobStatus(mockRequest as Request, mockResponse as Response, nextFunction);

expect(jobsService.getJob).toHaveBeenCalledWith("123", "type1");
expect(mockJob.isCompleted).toHaveBeenCalled();
expect(mockResponse.status).toHaveBeenCalledWith(200);
expect(mockResponse.json).toHaveBeenCalledWith({ status: "job in progress" });
});

it("should return 500 if job has failed", async () => {
const mockJob = {
isCompleted: jest.fn().mockResolvedValue(false),
isFailed: jest.fn().mockResolvedValue(true),
failedReason: "Job error"
};
mockRequest = { params: { id: "123", type: "type1" } };
(jobsService.getJob as jest.Mock).mockResolvedValue(mockJob);

await getJobStatus(mockRequest as Request, mockResponse as Response, nextFunction);

expect(nextFunction).toHaveBeenCalledWith(new CustomError("Job error", 500));
});

it("should return 404 if job is not found", async () => {
mockRequest = { params: { id: "123", type: "type1" } };
(jobsService.getJob as jest.Mock).mockResolvedValue(null);

await getJobStatus(mockRequest as Request, mockResponse as Response, nextFunction);

expect(nextFunction).toHaveBeenCalledWith(new CustomError("Job not found", 404));
});

it("should handle errors by calling next function", async () => {
const error = new Error("Test error");
mockRequest = { params: { id: "123", type: "type1" } };
(jobsService.getJob as jest.Mock).mockRejectedValue(error);

await getJobStatus(mockRequest as Request, mockResponse as Response, nextFunction);

expect(nextFunction).toHaveBeenCalledWith(error);
});
});

describe("removeCacheKey", () => {
it("should return 400 if cache key is missing", async () => {
mockRequest = { params: {} };

await removeCacheKey(mockRequest as Request, mockResponse as Response, nextFunction);

expect(nextFunction).toHaveBeenCalledWith(new CustomError("Missing parameter: key", 400));
});

it("should clear cache for the given key", async () => {
mockRequest = { params: { key: "cache-key" } };

await removeCacheKey(mockRequest as Request, mockResponse as Response, nextFunction);

expect(deleteCacheKey).toHaveBeenCalledWith("cache-key");
expect(mockResponse.status).toHaveBeenCalledWith(200);
expect(mockResponse.json).toHaveBeenCalledWith({ status: "cache cleared for key cache-key" });
});

it("should handle errors by calling next function", async () => {
const error = new Error("Test error");
mockRequest = { params: { key: "cache-key" } };
(deleteCacheKey as jest.Mock).mockRejectedValue(error);

await removeCacheKey(mockRequest as Request, mockResponse as Response, nextFunction);

expect(nextFunction).toHaveBeenCalledWith(error);
});
});

describe("removeAllCache", () => {
it("should clear all cache", async () => {
await removeAllCache(mockRequest as Request, mockResponse as Response, nextFunction);

expect(deleteAllCache).toHaveBeenCalled();
expect(mockResponse.status).toHaveBeenCalledWith(200);
expect(mockResponse.json).toHaveBeenCalledWith({ status: "all cache cleared" });
});

it("should handle errors by calling next function", async () => {
const error = new Error("Test error");
(deleteAllCache as jest.Mock).mockRejectedValue(error);

await removeAllCache(mockRequest as Request, mockResponse as Response, nextFunction);

expect(nextFunction).toHaveBeenCalledWith(error);
});
});
});

0 comments on commit b84ed1a

Please sign in to comment.