Skip to content

Commit 0120296

Browse files
committed
test: updateProject whitelist and auth
1 parent 3eec5dc commit 0120296

1 file changed

Lines changed: 143 additions & 0 deletions

File tree

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/**
2+
* @jest-environment node
3+
*/
4+
import { Request, Response } from 'jest-express';
5+
6+
import Project from '../../../models/project';
7+
import { updateProject } from '../../project.controller';
8+
9+
jest.mock('../../../models/project');
10+
11+
describe('project.controller', () => {
12+
describe('updateProject()', () => {
13+
let request;
14+
let response;
15+
16+
beforeEach(() => {
17+
request = new Request();
18+
response = new Response();
19+
response.send = jest.fn();
20+
response.json = jest.fn();
21+
response.status = jest.fn().mockReturnThis();
22+
Project.findById = jest.fn().mockReturnValue({ exec: jest.fn() });
23+
Project.findByIdAndUpdate = jest.fn();
24+
jest.clearAllMocks();
25+
});
26+
27+
afterEach(() => {
28+
request.resetMocked();
29+
response.resetMocked();
30+
});
31+
32+
it('returns 404 if project does not exist', async () => {
33+
request.setParams({ project_id: 'some-id' });
34+
request.user = { _id: 'owner-id' };
35+
request.body = { name: 'Test' };
36+
37+
Project.findById().exec.mockResolvedValue(null);
38+
39+
await updateProject(request, response);
40+
41+
expect(response.status).toHaveBeenCalledWith(404);
42+
expect(response.send).toHaveBeenCalledWith({
43+
success: false,
44+
message: 'Project with that id does not exist.'
45+
});
46+
expect(Project.findByIdAndUpdate).not.toHaveBeenCalled();
47+
});
48+
49+
it('returns 403 if project is not owned by authenticated user', async () => {
50+
const project = {
51+
user: { equals: jest.fn().mockReturnValue(false) }
52+
};
53+
request.setParams({ project_id: 'some-id' });
54+
request.user = { _id: 'other-user-id' };
55+
request.body = { name: 'Test' };
56+
57+
Project.findById().exec.mockResolvedValue(project);
58+
59+
await updateProject(request, response);
60+
61+
expect(response.status).toHaveBeenCalledWith(403);
62+
expect(response.send).toHaveBeenCalledWith({
63+
success: false,
64+
message: 'Session does not match owner of project.'
65+
});
66+
expect(Project.findByIdAndUpdate).not.toHaveBeenCalled();
67+
});
68+
69+
it('only updates whitelisted fields (user and slug in body are ignored)', async () => {
70+
const ownerId = 'owner-123';
71+
const project = {
72+
_id: 'proj-1',
73+
user: { equals: (id) => id === ownerId },
74+
updatedAt: new Date('2024-01-01'),
75+
files: []
76+
};
77+
const updatedDoc = {
78+
_id: 'proj-1',
79+
user: ownerId,
80+
name: 'Updated name',
81+
slug: 'original-slug',
82+
files: []
83+
};
84+
85+
request.setParams({ project_id: 'proj-1' });
86+
request.user = { _id: ownerId };
87+
request.body = {
88+
name: 'Updated name',
89+
user: 'another-user-id',
90+
slug: 'hacked-slug'
91+
};
92+
93+
Project.findById().exec.mockResolvedValue(project);
94+
Project.findByIdAndUpdate.mockReturnValue({
95+
populate: jest.fn().mockReturnThis(),
96+
exec: jest.fn().mockResolvedValue(updatedDoc)
97+
});
98+
99+
await updateProject(request, response);
100+
101+
expect(Project.findByIdAndUpdate).toHaveBeenCalled();
102+
const updateArg = Project.findByIdAndUpdate.mock.calls[0][1];
103+
const setPayload = updateArg.$set;
104+
105+
expect(setPayload).toHaveProperty('name', 'Updated name');
106+
expect(setPayload).not.toHaveProperty('user');
107+
expect(setPayload).not.toHaveProperty('slug');
108+
});
109+
110+
it('allows updating whitelisted fields (name, visibility, updatedAt)', async () => {
111+
const ownerId = 'owner-123';
112+
const project = {
113+
_id: 'proj-1',
114+
user: { equals: (id) => id === ownerId },
115+
updatedAt: new Date('2024-01-01'),
116+
files: []
117+
};
118+
119+
request.setParams({ project_id: 'proj-1' });
120+
request.user = { _id: ownerId };
121+
request.body = {
122+
name: 'New name',
123+
visibility: 'Private',
124+
updatedAt: new Date('2024-01-02')
125+
};
126+
127+
Project.findById().exec.mockResolvedValue(project);
128+
Project.findByIdAndUpdate.mockReturnValue({
129+
populate: jest.fn().mockReturnThis(),
130+
exec: jest.fn().mockResolvedValue({})
131+
});
132+
133+
await updateProject(request, response);
134+
135+
const updateArg = Project.findByIdAndUpdate.mock.calls[0][1];
136+
const setPayload = updateArg.$set;
137+
138+
expect(setPayload).toHaveProperty('name', 'New name');
139+
expect(setPayload).toHaveProperty('visibility', 'Private');
140+
expect(setPayload).toHaveProperty('updatedAt');
141+
});
142+
});
143+
});

0 commit comments

Comments
 (0)