Skip to content

Commit

Permalink
feat: increase test coverage to api (#111)
Browse files Browse the repository at this point in the history
* set up the unit tests

* Route unit test

* buildPriceRepo test

* gasBillRepo test

* gdhiRepo test

* hpiRepo test

* itlRepo test

* pricesPaidRepo test

* rentRepo test

* socialRentAdjustmentsRepo test

* socialRentEarningsRepo test

* buildOriceService test

* config file threshold to 95%

* calculation service test

* gas bill service test

* gdhi service test

* hpi service test

* itl service test

* prices paid service test

* rent service test

* social rent adjustments service test

* social rent earnings service test

* set up the unit tests

* Route unit test

* buildPriceRepo test

* gasBillRepo test

* gdhiRepo test

* hpiRepo test

* itlRepo test

* pricesPaidRepo test

* rentRepo test

* socialRentAdjustmentsRepo test

* socialRentEarningsRepo test

* buildOriceService test

* config file threshold to 95%

* calculation service test

* gas bill service test

* gdhi service test

* hpi service test

* itl service test

* prices paid service test

* rent service test

* social rent adjustments service test

* social rent earnings service test

* fixed prisma scheme

* fixed naming issues due to 2000 not being 2020

* solved lint issues

---------

Co-authored-by: Gabriele Granello <[email protected]>
  • Loading branch information
gabrielegranello and Gabriele Granello authored Oct 18, 2024
1 parent 1292bba commit 1bfcad8
Show file tree
Hide file tree
Showing 23 changed files with 1,319 additions and 3 deletions.
107 changes: 107 additions & 0 deletions app/api/route.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { POST } from "../api/route";
import * as calculationService from "../services/calculationService";
import calculateFairhold from "../models/testClasses";
import { NextResponse } from "next/server";
import { calculationSchema, Calculation } from "../schemas/calculationSchema";

// Mock dependencies
jest.mock("../services/calculationService");
jest.mock("../models/testClasses", () => jest.fn()); // Mock calculateFairhold
jest.mock("next/server", () => ({
NextResponse: {
json: jest.fn((data) => ({ data })),
},
}));

const callResponse = (res: unknown) => {
return res;
};

describe("POST API Route", () => {
const mockRequest = (data: Calculation | string) => ({
json: jest.fn().mockResolvedValueOnce(data),
});

afterEach(() => {
jest.clearAllMocks(); // Reset mocks after each test
});

it("should return processed data for valid apiSchema input", async () => {
const validApiInput = calculationSchema.parse({
housePostcode: "SE17 1PE",
houseSize: 100,
houseAge: 3,
houseBedrooms: 2,
houseType: "D",
});

const householdData = {
/* mock household data */
};

const processedData = {
/* mock processed data */
};

// Set mock return values
(calculationService.getHouseholdData as jest.Mock).mockResolvedValueOnce(
householdData
);
(calculateFairhold as jest.Mock).mockReturnValueOnce(processedData);

const req = mockRequest(validApiInput);
const res = await POST(req as unknown as Request);

// Assertions
expect(calculationService.getHouseholdData).toHaveBeenCalledWith(
validApiInput
);
expect(calculateFairhold).toHaveBeenCalledWith(householdData);
expect(res).toEqual(NextResponse.json(processedData));
});

it("should return an error for invalid input", async () => {
const invalidInput = "invalid input";

const req = mockRequest(invalidInput);

const res = await POST(req as unknown as Request);
callResponse(res);

// Assertions for the expected error response
expect(NextResponse.json).toHaveBeenCalledWith(
expect.objectContaining({ error: expect.any(String) }),
{ status: 500 }
);
});

it("should handle service errors", async () => {
const validApiInput = calculationSchema.parse({
housePostcode: "SE17 1PE",
houseSize: 100,
houseAge: 3,
houseBedrooms: 2,
houseType: "D",
});

const errorMessage = "Service error";

// Mock service throwing an error
(calculationService.getHouseholdData as jest.Mock).mockRejectedValueOnce(
new Error(errorMessage)
);

const req = mockRequest(validApiInput);
const res = await POST(req as unknown as Request);
callResponse(res);

// Assertions
expect(calculationService.getHouseholdData).toHaveBeenCalledWith(
validApiInput
);
expect(NextResponse.json).toHaveBeenCalledWith(
{ error: errorMessage },
{ status: 500 }
);
});
});
2 changes: 1 addition & 1 deletion app/api/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { NextResponse } from "next/server";
import { api, apiSchema } from "../schemas/apiSchema";
import { calculationSchema } from "../schemas/calculationSchema";
import * as calculationService from "../services/calculationService";
import calculateFairhold from "@/app/models/testClasses";
import calculateFairhold from "../models/testClasses";

export async function POST(req: Request) {
try {
Expand Down
58 changes: 58 additions & 0 deletions app/data/buildPriceRepo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { buildPriceRepo } from "./buildPriceRepo";
import prisma from "./db";

jest.mock("./db", () => ({
buildPrices: {
findFirstOrThrow: jest.fn(),
},
}));

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

it("should return the build price for a valid house type", async () => {
const houseType = "single-family";
const mockBuildPrice = 300000;

(prisma.buildPrices.findFirstOrThrow as jest.Mock).mockResolvedValueOnce({
priceMid: mockBuildPrice,
});

const result = await buildPriceRepo.getBuildPriceByHouseType(houseType);
expect(result).toBe(mockBuildPrice);
expect(prisma.buildPrices.findFirstOrThrow).toHaveBeenCalledWith({
where: { houseType: { equals: houseType } },
select: { priceMid: true },
});
});

it("should throw an error when no price is found for the house type", async () => {
const houseType = "non-existent-house-type";

(prisma.buildPrices.findFirstOrThrow as jest.Mock).mockRejectedValueOnce(
new Error("No records found")
);

await expect(
buildPriceRepo.getBuildPriceByHouseType(houseType)
).rejects.toThrow(
`Data error: Unable to get buildPrice for houseType ${houseType}`
);
});

it("should throw an error when there is a database error", async () => {
const houseType = "single-family";

(prisma.buildPrices.findFirstOrThrow as jest.Mock).mockRejectedValueOnce(
new Error("Database error")
);

await expect(
buildPriceRepo.getBuildPriceByHouseType(houseType)
).rejects.toThrow(
`Data error: Unable to get buildPrice for houseType ${houseType}`
);
});
});
55 changes: 55 additions & 0 deletions app/data/gasBillRepo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// __tests__/gasBillRepo.test.ts
import { gasBillRepo } from "./gasBillRepo"; // Adjust the import according to your file structure
import prisma from "./db"; // Your Prisma setup file

jest.mock("./db", () => ({
gasBills: {
findFirstOrThrow: jest.fn(),
},
}));

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

it("should return the gas bill for a valid ITL", async () => {
const itl = "12345XYZ"; // Example ITL
const mockGasBill = 150; // Example gas bill amount

(prisma.gasBills.findFirstOrThrow as jest.Mock).mockResolvedValueOnce({
bill: mockGasBill,
});

const result = await gasBillRepo.getGasBillByITL3(itl);
expect(result).toBe(mockGasBill);
expect(prisma.gasBills.findFirstOrThrow).toHaveBeenCalledWith({
where: { itl: { startsWith: itl.substring(0, 3) } },
select: { bill: true },
});
});

it("should throw an error when no bill is found for the ITL", async () => {
const itl = "non-existent-ITL";

(prisma.gasBills.findFirstOrThrow as jest.Mock).mockRejectedValueOnce(
new Error("No records found")
);

await expect(gasBillRepo.getGasBillByITL3(itl)).rejects.toThrow(
`Data error: Unable to find gas_bills_2020 for itl3 ${itl}`
);
});

it("should throw an error when there is a database error", async () => {
const itl = "12345XYZ";

(prisma.gasBills.findFirstOrThrow as jest.Mock).mockRejectedValueOnce(
new Error("Database error")
);

await expect(gasBillRepo.getGasBillByITL3(itl)).rejects.toThrow(
`Data error: Unable to find gas_bills_2020 for itl3 ${itl}`
);
});
});
68 changes: 68 additions & 0 deletions app/data/gdhiRepo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// __tests__/gdhiRepo.test.ts
import { gdhiRepo } from "./gdhiRepo"; // Adjust the import according to your file structure
import prisma from "./db"; // Your Prisma setup file

jest.mock("./db", () => ({
gDHI: {
findFirstOrThrow: jest.fn(),
},
}));

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

it("should return the GDHI value for a valid ITL3", async () => {
const itl3 = "XYZ123456"; // Example ITL3
const mockGDHI = 1000; // Example GDHI value

// Mock the Prisma client response
(prisma.gDHI.findFirstOrThrow as jest.Mock).mockResolvedValueOnce({
gdhi2020: mockGDHI,
});

// Call the function
const result = await gdhiRepo.getGDHI2020ByITL3(itl3);

// Assertions
expect(result).toBe(mockGDHI);
expect(prisma.gDHI.findFirstOrThrow).toHaveBeenCalledWith({
where: {
AND: {
itl3: { equals: itl3 },
gdhi2020: { not: null },
},
},
select: { gdhi2020: true },
});
});

it("should throw an error when no GDHI value is found for the ITL3", async () => {
const itl3 = "non-existent-ITL3";

// Mock rejection of the Prisma client
(prisma.gDHI.findFirstOrThrow as jest.Mock).mockRejectedValueOnce(
new Error("No records found")
);

// Call the function and expect an error
await expect(gdhiRepo.getGDHI2020ByITL3(itl3)).rejects.toThrow(
`Data error: Unable to find gdhi2020 for itl3 ${itl3}`
);
});

it("should throw an error when there is a database error", async () => {
const itl3 = "XYZ123456";

// Mock rejection of the Prisma client
(prisma.gDHI.findFirstOrThrow as jest.Mock).mockRejectedValueOnce(
new Error("Database error")
);

// Call the function and expect an error
await expect(gdhiRepo.getGDHI2020ByITL3(itl3)).rejects.toThrow(
`Data error: Unable to find gdhi2020 for itl3 ${itl3}`
);
});
});
69 changes: 69 additions & 0 deletions app/data/hpiRepo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// __tests__/hpiRepo.test.ts
import { hpi2000Repo } from "./hpiRepo"; // Adjust the import according to your file structure
import prisma from "./db"; // Your Prisma setup file

jest.mock("./db", () => ({
hPI: {
aggregate: jest.fn(),
},
}));

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

it("should return the average HPI value for a valid ITL3", async () => {
const itl3 = "XYZ123456"; // Example ITL3
const mockAverageHpi = 150.5; // Example average HPI value

// Mock the Prisma client response
(prisma.hPI.aggregate as jest.Mock).mockResolvedValueOnce({
_avg: { hpi2000: mockAverageHpi },
});

// Call the function
const result = await hpi2000Repo.getHPIByITL3(itl3);

// Assertions
expect(result).toBe(mockAverageHpi);
expect(prisma.hPI.aggregate).toHaveBeenCalledWith({
where: {
itl3: {
endsWith: itl3,
},
},
_avg: {
hpi2000: true,
},
});
});

it("should throw an error when no HPI value is found for the ITL3", async () => {
const itl3 = "non-existent-ITL3";

// Mock rejection of the Prisma client
(prisma.hPI.aggregate as jest.Mock).mockResolvedValueOnce({
_avg: { hpi2000: null },
});

// Call the function and expect an error
await expect(hpi2000Repo.getHPIByITL3(itl3)).rejects.toThrow(
`Data error: Unable to find hpi2000 for itl3 ${itl3}`
);
});

it("should throw an error when there is a database error", async () => {
const itl3 = "XYZ123456";

// Mock rejection of the Prisma client
(prisma.hPI.aggregate as jest.Mock).mockRejectedValueOnce(
new Error("Database error")
);

// Call the function and expect an error
await expect(hpi2000Repo.getHPIByITL3(itl3)).rejects.toThrow(
`Data error: Unable to find hpi2000 for itl3 ${itl3}`
);
});
});
7 changes: 7 additions & 0 deletions app/data/hpiRepo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ const getHPIByITL3 = async (itl3: string): Promise<number> => {
hpi2000: true,
},
});

// Check if the average HPI is null and throw an error
if (averageHpi === null) {
throw new Error(`Data error: Unable to find hpi2000 for itl3 ${itl3}`);
}


return averageHpi as number;
} catch (error) {
throw Error(`Data error: Unable to find hpi2000 for itl3 ${itl3}`);
Expand Down
Loading

0 comments on commit 1bfcad8

Please sign in to comment.