Skip to content

Commit 656bb32

Browse files
authored
Merge pull request #121 from hackaburg/copilot/update-rating-service-tests
Rewrite rating-service.spec.ts to use TestDatabaseService
2 parents e00f3ef + 33eb7e7 commit 656bb32

1 file changed

Lines changed: 171 additions & 123 deletions

File tree

backend/test/services/rating-service.spec.ts

Lines changed: 171 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,95 @@
1-
import { BadRequestError, ForbiddenError, NotFoundError } from "routing-controllers";
1+
import { ForbiddenError, NotFoundError } from "routing-controllers";
2+
import { Repository } from "typeorm";
23
import { Criterion } from "../../src/entities/criterion";
34
import { Project } from "../../src/entities/project";
45
import { Rating } from "../../src/entities/rating";
56
import { Team } from "../../src/entities/team";
67
import { User } from "../../src/entities/user";
7-
import { IDatabaseService } from "../../src/services/database-service";
8+
import { UserRole } from "../../src/entities/user-role";
89
import { IRatingService, RatingService } from "../../src/services/rating-service";
910
import { ISettingsService } from "../../src/services/settings-service";
1011
import { MockedService } from "./mock";
1112
import { MockSettingsService } from "./mock/mock-settings-service";
13+
import { TestDatabaseService } from "./mock/mock-database-service";
1214

1315
describe("RatingService", () => {
16+
let database: TestDatabaseService;
1417
let settingsService: MockedService<ISettingsService>;
15-
let mockRatingsRepo: any;
16-
let mockProjectsRepo: any;
17-
let mockTeamsRepo: any;
18-
let mockUsersRepo: any;
19-
let mockDatabase: IDatabaseService;
2018
let ratingService: IRatingService;
2119

22-
const mockUser = Object.assign(new User(), { id: 1 });
23-
const mockTeam = Object.assign(new Team(), { id: 10, users: ["2", "3"] });
24-
const mockProject = Object.assign(new Project(), {
25-
id: 100,
26-
team: mockTeam,
27-
allowRating: true,
28-
});
29-
const mockCriterion = Object.assign(new Criterion(), { id: 5 });
30-
const mockRating = Object.assign(new Rating(), {
31-
project: mockProject,
32-
user: mockUser,
33-
criterion: mockCriterion,
20+
let userRepo: Repository<User>;
21+
let teamRepo: Repository<Team>;
22+
let projectRepo: Repository<Project>;
23+
let criterionRepo: Repository<Criterion>;
24+
let ratingRepo: Repository<Rating>;
25+
26+
let ratingUser: User;
27+
let teamMember: User;
28+
let mockTeam: Team;
29+
let mockProject: Project;
30+
let mockCriterion: Criterion;
31+
32+
beforeAll(async () => {
33+
database = new TestDatabaseService();
34+
await database.bootstrap();
3435
});
3536

3637
beforeEach(async () => {
38+
await database.nuke();
39+
3740
settingsService = new MockSettingsService();
3841

39-
mockRatingsRepo = {
40-
find: jest.fn(),
41-
findOneBy: jest.fn(),
42-
findOne: jest.fn(),
43-
save: jest.fn(),
44-
delete: jest.fn()
45-
};
46-
mockProjectsRepo = {
47-
find: jest.fn(),
48-
findOneBy: jest.fn()
49-
};
50-
mockTeamsRepo = { findOneBy: jest.fn() };
51-
mockUsersRepo = { findOneBy: jest.fn() };
52-
53-
mockDatabase = {
54-
bootstrap: jest.fn(),
55-
getRepository: jest.fn().mockImplementation((entity: any) => {
56-
if (entity === Rating) return mockRatingsRepo;
57-
if (entity === Project) return mockProjectsRepo;
58-
if (entity === Team) return mockTeamsRepo;
59-
if (entity === User) return mockUsersRepo;
60-
return {};
61-
}),
62-
} as any;
63-
64-
ratingService = new RatingService(mockDatabase, settingsService.instance);
42+
userRepo = database.getRepository(User);
43+
teamRepo = database.getRepository(Team);
44+
projectRepo = database.getRepository(Project);
45+
criterionRepo = database.getRepository(Criterion);
46+
ratingRepo = database.getRepository(Rating);
47+
48+
// A user who will submit ratings (not in the project's team)
49+
ratingUser = new User();
50+
ratingUser.firstName = "Rater";
51+
ratingUser.lastName = "User";
52+
ratingUser.email = "rater@test.com";
53+
ratingUser.password = "";
54+
ratingUser.role = UserRole.User;
55+
ratingUser.verifyToken = "";
56+
ratingUser.tokenSecret = "";
57+
ratingUser.forgotPasswordToken = "";
58+
59+
// A user who is a member of the project team
60+
teamMember = new User();
61+
teamMember.firstName = "Team";
62+
teamMember.lastName = "Member";
63+
teamMember.email = "member@test.com";
64+
teamMember.password = "";
65+
teamMember.role = UserRole.User;
66+
teamMember.verifyToken = "";
67+
teamMember.tokenSecret = "";
68+
teamMember.forgotPasswordToken = "";
69+
70+
[ratingUser, teamMember] = await userRepo.save([ratingUser, teamMember]);
71+
72+
mockTeam = new Team();
73+
mockTeam.title = "Test Team";
74+
mockTeam.users = [teamMember.id.toString()];
75+
mockTeam.teamImg = "";
76+
mockTeam.description = "";
77+
mockTeam.requests = [];
78+
mockTeam = await teamRepo.save(mockTeam);
79+
80+
mockProject = new Project();
81+
mockProject.team = mockTeam;
82+
mockProject.title = "Test Project";
83+
mockProject.description = "";
84+
mockProject.allowRating = true;
85+
mockProject = await projectRepo.save(mockProject);
86+
87+
mockCriterion = new Criterion();
88+
mockCriterion.title = "Test Criterion";
89+
mockCriterion.description = "";
90+
mockCriterion = await criterionRepo.save(mockCriterion);
91+
92+
ratingService = new RatingService(database, settingsService.instance);
6593
await ratingService.bootstrap();
6694
});
6795

@@ -71,10 +99,17 @@ describe("RatingService", () => {
7199
expect.assertions(1);
72100

73101
settingsService.mocks.getSettings.mockResolvedValue(
74-
{ application: { allowRatingProjects: false } } as any
102+
{ application: { allowRatingProjects: false } } as any,
75103
);
76104

77-
await expect(ratingService.upsertRating(mockRating, mockUser)).rejects.toThrow(
105+
const rating = Object.assign(new Rating(), {
106+
project: mockProject,
107+
user: ratingUser,
108+
criterion: mockCriterion,
109+
rating: 3,
110+
});
111+
112+
await expect(ratingService.upsertRating(rating, ratingUser)).rejects.toThrow(
78113
ForbiddenError,
79114
);
80115
});
@@ -83,12 +118,17 @@ describe("RatingService", () => {
83118
expect.assertions(1);
84119

85120
settingsService.mocks.getSettings.mockResolvedValue(
86-
{ application: { allowRatingProjects: true } } as any
121+
{ application: { allowRatingProjects: true } } as any,
87122
);
88123

89-
mockProjectsRepo.findOneBy.mockResolvedValue(null);
124+
const rating = Object.assign(new Rating(), {
125+
project: { id: 99999 },
126+
user: ratingUser,
127+
criterion: mockCriterion,
128+
rating: 3,
129+
});
90130

91-
await expect(ratingService.upsertRating(mockRating, mockUser)).rejects.toThrow(
131+
await expect(ratingService.upsertRating(rating, ratingUser)).rejects.toThrow(
92132
NotFoundError,
93133
);
94134
});
@@ -97,54 +137,39 @@ describe("RatingService", () => {
97137
expect.assertions(1);
98138

99139
settingsService.mocks.getSettings.mockResolvedValue(
100-
{ application: { allowRatingProjects: true } } as any
140+
{ application: { allowRatingProjects: true } } as any,
101141
);
102142

103-
mockProjectsRepo.findOneBy.mockResolvedValue(
104-
Object.assign(new Project(), { ...mockProject, allowRating: false }),
105-
);
143+
await projectRepo.update(mockProject.id, { allowRating: false });
106144

107145
// The backend should not be tricked by an allowRating: true in the payload
108-
const payload = {
109-
...mockRating,
110-
project: {
111-
...mockRating.project,
112-
allowRating: true
113-
}
114-
}
115-
await expect(ratingService.upsertRating(payload, mockUser)).rejects.toThrow(
146+
const rating = Object.assign(new Rating(), {
147+
project: { ...mockProject, allowRating: true },
148+
user: ratingUser,
149+
criterion: mockCriterion,
150+
rating: 3,
151+
});
152+
153+
await expect(ratingService.upsertRating(rating, ratingUser)).rejects.toThrow(
116154
ForbiddenError,
117155
);
118156
});
119157

120-
it("throws NotFoundError when team does not exist", async () => {
121-
expect.assertions(1);
122-
123-
settingsService.mocks.getSettings.mockResolvedValue(
124-
{ application: { allowRatingProjects: true } } as any
125-
);
126-
127-
mockProjectsRepo.findOneBy.mockResolvedValue(mockProject);
128-
mockTeamsRepo.findOneBy.mockResolvedValue(null);
129-
130-
await expect(ratingService.upsertRating(mockRating, mockUser)).rejects.toThrow(
131-
NotFoundError,
132-
);
133-
});
134-
135158
it("throws ForbiddenError when a user tries to rate their own project", async () => {
136159
expect.assertions(1);
137160

138161
settingsService.mocks.getSettings.mockResolvedValue(
139-
{ application: { allowRatingProjects: true } } as any
162+
{ application: { allowRatingProjects: true } } as any,
140163
);
141164

142-
mockProjectsRepo.findOneBy.mockResolvedValue(mockProject);
143-
mockTeamsRepo.findOneBy.mockResolvedValue(
144-
Object.assign(new Team(), { ...mockTeam, users: ["1", "2", "3"] }),
145-
);
165+
const rating = Object.assign(new Rating(), {
166+
project: mockProject,
167+
user: teamMember,
168+
criterion: mockCriterion,
169+
rating: 3,
170+
});
146171

147-
await expect(ratingService.upsertRating(mockRating, mockUser)).rejects.toThrow(
172+
await expect(ratingService.upsertRating(rating, teamMember)).rejects.toThrow(
148173
ForbiddenError,
149174
);
150175
});
@@ -153,69 +178,92 @@ describe("RatingService", () => {
153178
expect.assertions(2);
154179

155180
settingsService.mocks.getSettings.mockResolvedValue(
156-
{ application: { allowRatingProjects: true } } as any
181+
{ application: { allowRatingProjects: true } } as any,
157182
);
158183

159-
mockProjectsRepo.findOneBy.mockResolvedValue(mockProject);
160-
mockTeamsRepo.findOneBy.mockResolvedValue(mockTeam);
161-
mockRatingsRepo.findOne.mockResolvedValue(null);
162-
const savedRating = Object.assign(new Rating(), { ...mockRating, id: 42 });
163-
mockRatingsRepo.save.mockResolvedValue(savedRating);
184+
const rating = Object.assign(new Rating(), {
185+
project: mockProject,
186+
user: ratingUser,
187+
criterion: mockCriterion,
188+
rating: 4,
189+
});
164190

165-
const result = await ratingService.upsertRating(mockRating, mockUser);
191+
const result = await ratingService.upsertRating(rating, ratingUser);
166192

167-
expect(result).toBe(savedRating);
168-
expect(mockRatingsRepo.save).toHaveBeenCalledWith(mockRating);
193+
expect(result.id).toBeDefined();
194+
expect(result.rating).toBe(4);
169195
});
170196

171197
it("is forbidden to impersonate other users", async () => {
172198
expect.assertions(1);
173199

174200
settingsService.mocks.getSettings.mockResolvedValue(
175-
{ application: { allowRatingProjects: true } } as any
201+
{ application: { allowRatingProjects: true } } as any,
176202
);
177203

178-
mockProjectsRepo.findOneBy.mockResolvedValue(mockProject);
179-
mockTeamsRepo.findOneBy.mockResolvedValue(mockTeam);
180-
mockRatingsRepo.findOne.mockResolvedValue(null);
181-
182-
mockRating.user = {
183-
...mockUser,
184-
id: 1234
185-
};
204+
const rating = Object.assign(new Rating(), {
205+
project: mockProject,
206+
user: { ...ratingUser, id: teamMember.id },
207+
criterion: mockCriterion,
208+
rating: 3,
209+
});
186210

187-
await expect(ratingService.upsertRating(mockRating, mockUser)).rejects.toThrow(
211+
await expect(ratingService.upsertRating(rating, ratingUser)).rejects.toThrow(
188212
ForbiddenError,
189213
);
190214
});
191-
192215
});
193216
});
194217

195218
describe("getRatingResults", () => {
196219
it("aggregates ratings for two projects with two ratings each", async () => {
197220
expect.assertions(5);
198221

199-
const projectA = Object.assign(new Project(), { id: 1, team: mockTeam });
200-
const projectB = Object.assign(new Project(), { id: 2, team: mockTeam });
201-
202-
mockProjectsRepo.find.mockResolvedValue([ projectA, projectB ])
203-
204-
const criterionA = Object.assign(new Criterion(), { id: 1 });
205-
const criterionB = Object.assign(new Criterion(), { id: 2 });
206-
207-
const ratingsFixture = [
208-
// Project A
209-
Object.assign(new Rating(), { id: 1, project: projectA, criterion: criterionA, rating: 2 }),
210-
Object.assign(new Rating(), { id: 2, project: projectA, criterion: criterionA, rating: 3 }),
211-
Object.assign(new Rating(), { id: 3, project: projectA, criterion: criterionB, rating: 1 }),
212-
// Project B
213-
Object.assign(new Rating(), { id: 4, project: projectB, criterion: criterionB, rating: 2 }),
214-
Object.assign(new Rating(), { id: 5, project: projectB, criterion: criterionB, rating: 5 }),
215-
Object.assign(new Rating(), { id: 6, project: projectB, criterion: criterionB, rating: 5 }),
216-
];
217-
218-
mockRatingsRepo.find.mockResolvedValue(ratingsFixture);
222+
const projectA = await projectRepo.save(
223+
Object.assign(new Project(), {
224+
team: mockTeam,
225+
title: "Project A",
226+
description: "",
227+
allowRating: true,
228+
}),
229+
);
230+
const projectB = await projectRepo.save(
231+
Object.assign(new Project(), {
232+
team: mockTeam,
233+
title: "Project B",
234+
description: "",
235+
allowRating: true,
236+
}),
237+
);
238+
239+
const criterionA = await criterionRepo.save(
240+
Object.assign(new Criterion(), { title: "Criterion A", description: "" }),
241+
);
242+
const criterionB = await criterionRepo.save(
243+
Object.assign(new Criterion(), { title: "Criterion B", description: "" }),
244+
);
245+
246+
// Create extra users to submit ratings (not team members)
247+
const [raterA, raterB, raterC] = await userRepo.save([
248+
Object.assign(new User(), { firstName: "A", lastName: "R", email: "ra@test.com", password: "", role: UserRole.User, verifyToken: "", tokenSecret: "", forgotPasswordToken: "" }),
249+
Object.assign(new User(), { firstName: "B", lastName: "R", email: "rb@test.com", password: "", role: UserRole.User, verifyToken: "", tokenSecret: "", forgotPasswordToken: "" }),
250+
Object.assign(new User(), { firstName: "C", lastName: "R", email: "rc@test.com", password: "", role: UserRole.User, verifyToken: "", tokenSecret: "", forgotPasswordToken: "" }),
251+
]);
252+
253+
await ratingRepo.save([
254+
// Project A, criterionA: avg 2.5
255+
Object.assign(new Rating(), { project: projectA, criterion: criterionA, user: raterA, rating: 2 }),
256+
Object.assign(new Rating(), { project: projectA, criterion: criterionA, user: raterB, rating: 3 }),
257+
// Project A, criterionB: avg 1
258+
Object.assign(new Rating(), { project: projectA, criterion: criterionB, user: raterA, rating: 1 }),
259+
// Project B, criterionB: avg 4
260+
Object.assign(new Rating(), { project: projectB, criterion: criterionB, user: raterA, rating: 2 }),
261+
Object.assign(new Rating(), { project: projectB, criterion: criterionB, user: raterB, rating: 5 }),
262+
Object.assign(new Rating(), { project: projectB, criterion: criterionB, user: raterC, rating: 5 }),
263+
]);
264+
265+
// getRatingResults only counts projects that exist; exclude the default mockProject
266+
await projectRepo.delete(mockProject.id);
219267

220268
const results = await ratingService.getRatingResults();
221269

0 commit comments

Comments
 (0)