Skip to content

Commit e9801be

Browse files
committed
test: add unit tests for history service and controller with mocks
1 parent bc8df79 commit e9801be

File tree

3 files changed

+263
-0
lines changed

3 files changed

+263
-0
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { Test } from "@nestjs/testing";
2+
import { HistoryController } from "./history.controller";
3+
import { HistoryService } from "./history.service";
4+
import { historyServiceMock, mockHistoryItem } from "../mocks/HistoryMock";
5+
6+
describe("HistoryController (Unit)", () => {
7+
let controller: HistoryController;
8+
let historyService: typeof historyServiceMock;
9+
10+
beforeEach(async () => {
11+
const testingModule = await Test.createTestingModule({
12+
controllers: [HistoryController],
13+
providers: [{ provide: HistoryService, useValue: historyServiceMock }],
14+
}).compile();
15+
16+
controller = testingModule.get(HistoryController);
17+
historyService = testingModule.get(HistoryService);
18+
});
19+
20+
beforeEach(() => {
21+
jest.clearAllMocks();
22+
});
23+
24+
describe("getHistory", () => {
25+
it("should return history correctly (happy path)", async () => {
26+
historyService.getHistoryForTarget.mockResolvedValue({
27+
history: [mockHistoryItem],
28+
totalChanges: 1,
29+
totalPages: 1,
30+
page: 1,
31+
pageSize: 10,
32+
});
33+
34+
const response = await controller.getHistory(
35+
{ targetId: "id", targetModel: "Claim" },
36+
{}
37+
);
38+
expect(response.history.length).toBeGreaterThan(0);
39+
expect(response.totalChanges).toBeGreaterThanOrEqual(1);
40+
});
41+
42+
it("should throw error if targetId is empty", async () => {
43+
await expect(
44+
controller.getHistory({ targetId: "", targetModel: "Claim" }, {})
45+
).rejects.toThrow();
46+
});
47+
48+
it("should throw error if service fails", async () => {
49+
historyService.getHistoryForTarget.mockRejectedValue(new Error("fail"));
50+
await expect(
51+
controller.getHistory({ targetId: "id", targetModel: "Claim" }, {})
52+
).rejects.toThrow("fail");
53+
});
54+
});
55+
});
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import { getModelToken } from "@nestjs/mongoose";
2+
import { HistoryService } from "./history.service";
3+
import { Test, TestingModule } from "@nestjs/testing";
4+
import { Model } from "mongoose";
5+
import {
6+
mockHistoryResponse,
7+
mockAggregateMongoResult,
8+
mockHistoryItem,
9+
mockHistoryModel,
10+
} from "../mocks/HistoryMock";
11+
import { TargetModel, HistoryType } from "./schema/history.schema";
12+
import { HistoryDocument } from "./schema/history.schema";
13+
14+
describe("HistoryService (Unit)", () => {
15+
let service: HistoryService;
16+
let testingModule: TestingModule;
17+
let model: Model<HistoryDocument>;
18+
19+
beforeAll(async () => {
20+
testingModule = await Test.createTestingModule({
21+
providers: [
22+
HistoryService,
23+
{ provide: getModelToken("History"), useValue: mockHistoryModel },
24+
],
25+
}).compile();
26+
27+
model = testingModule.get(getModelToken("History"));
28+
service = testingModule.get<HistoryService>(HistoryService);
29+
});
30+
31+
beforeEach(() => {
32+
jest.clearAllMocks();
33+
jest.restoreAllMocks();
34+
});
35+
36+
describe("HistoryService.createHistory", () => {
37+
it("should create a new history document and call save", async () => {
38+
const result = await service.createHistory(mockHistoryItem);
39+
40+
const instance = (mockHistoryModel as jest.Mock).mock.instances[0];
41+
42+
expect(instance.save).toHaveBeenCalled();
43+
expect(result._id).toBe("abc");
44+
});
45+
46+
it("should throw if save fails", async () => {
47+
const error = new Error("Database error");
48+
49+
(mockHistoryModel as jest.Mock).mockImplementationOnce(function () {
50+
return {
51+
save: jest.fn().mockRejectedValue(error),
52+
};
53+
});
54+
55+
await expect(service.createHistory(mockHistoryItem)).rejects.toThrow(
56+
"Database error"
57+
);
58+
});
59+
});
60+
61+
describe("HistoryService.getHistoryParams", () => {
62+
it("should throw for invalid dataId", () => {
63+
const invalidId = "not-a-valid-id";
64+
expect(() =>
65+
service.getHistoryParams(
66+
invalidId,
67+
TargetModel.Claim,
68+
null,
69+
HistoryType.Create,
70+
{ some: "value" }
71+
)
72+
).toThrow(`Invalid dataId received: ${invalidId}`);
73+
});
74+
75+
it("should return correct params for valid dataId", () => {
76+
const validId = mockHistoryItem.targetId.toString();
77+
const params = service.getHistoryParams(
78+
validId,
79+
TargetModel.Claim,
80+
null,
81+
HistoryType.Update,
82+
{ some: "latest" },
83+
{ some: "previous" }
84+
);
85+
86+
expect(params).toHaveProperty("targetId");
87+
expect(params.targetModel).toBe(TargetModel.Claim);
88+
expect(params.details.after).toEqual({ some: "latest" });
89+
expect(params.details.before).toEqual({ some: "previous" });
90+
});
91+
});
92+
93+
describe("HistoryService.getDescriptionForHide", () => {
94+
it("should return empty string when content missing or not hidden", async () => {
95+
const responseWithoutContent = await service.getDescriptionForHide(
96+
{},
97+
TargetModel.Claim
98+
);
99+
expect(responseWithoutContent).toBe("");
100+
101+
const responseHidden = await service.getDescriptionForHide(
102+
{ content: mockHistoryItem._id, isHidden: false },
103+
TargetModel.Claim
104+
);
105+
expect(responseHidden).toBe("");
106+
});
107+
108+
it("should return description from history when hidden", async () => {
109+
jest
110+
.spyOn(service, "getHistoryForTarget")
111+
.mockResolvedValue(mockHistoryResponse);
112+
113+
const response = await service.getDescriptionForHide(
114+
{ _id: mockHistoryItem._id, isHidden: true },
115+
TargetModel.Claim
116+
);
117+
expect(response).toBe(
118+
mockHistoryResponse.history[0].details.after.description
119+
);
120+
});
121+
});
122+
123+
describe("HistoryService.getHistoryForTarget", () => {
124+
it("should return paginated history and counts", async () => {
125+
mockHistoryModel.aggregate.mockResolvedValueOnce(
126+
mockAggregateMongoResult
127+
);
128+
129+
const res = await service.getHistoryForTarget(
130+
mockHistoryItem.targetId.toString(),
131+
TargetModel.Claim,
132+
{ page: 0, pageSize: 10, order: "asc" }
133+
);
134+
135+
expect(res.history).toEqual([mockHistoryItem]);
136+
expect(res.totalChanges).toBe(1);
137+
expect(res.totalPages).toBe(1);
138+
expect(res.page).toBe(0);
139+
expect(res.pageSize).toBe(10);
140+
});
141+
142+
it("should handle empty aggregate result", async () => {
143+
(mockHistoryModel.aggregate as jest.Mock).mockResolvedValue(
144+
[{ data: [], totalCount: [] }]
145+
);
146+
147+
const res = await service.getHistoryForTarget(
148+
mockHistoryItem.targetId.toString(),
149+
TargetModel.Claim,
150+
{ page: 0, pageSize: 10, order: "asc" }
151+
);
152+
153+
expect(res.history).toEqual([]);
154+
expect(res.totalChanges).toBe(0);
155+
expect(res.totalPages).toBe(0);
156+
});
157+
});
158+
});

server/mocks/HistoryMock.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { HistoryType, TargetModel } from "../history/schema/history.schema";
2+
import { Types } from "mongoose";
3+
4+
export const historyServiceMock = {
5+
getHistoryForTarget: jest.fn(),
6+
};
7+
8+
export const mockHistoryItem = {
9+
_id: "23432",
10+
targetId: new Types.ObjectId(),
11+
targetModel: TargetModel.Claim,
12+
type: HistoryType.Update,
13+
details: { after: { description: "new" }, before: { description: "old" } },
14+
date: new Date(),
15+
};
16+
17+
export const mockHistoryResponse = {
18+
history: [mockHistoryItem],
19+
totalChanges: 1,
20+
totalPages: 1,
21+
page: 0,
22+
pageSize: 10,
23+
};
24+
25+
export const mockAggregateMongoResult = [
26+
{
27+
data: [mockHistoryItem],
28+
totalCount: [{ total: 1 }],
29+
},
30+
];
31+
32+
type MockedModel = jest.Mock & {
33+
aggregate: jest.Mock;
34+
};
35+
36+
export const mockHistoryModel = jest.fn() as MockedModel;
37+
38+
mockHistoryModel.mockImplementation(function (this: any, data) {
39+
this.save = jest.fn().mockResolvedValue({
40+
...mockHistoryItem,
41+
_id: "abc",
42+
});
43+
});
44+
45+
mockHistoryModel.aggregate = jest.fn();
46+
47+
48+
49+
50+

0 commit comments

Comments
 (0)