Skip to content

Commit e22ad83

Browse files
committed
test: add tests for getProjectAsset authorization
1 parent e4290a8 commit e22ad83

1 file changed

Lines changed: 213 additions & 0 deletions

File tree

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
/**
2+
* @jest-environment node
3+
*/
4+
import { Request, Response } from 'jest-express';
5+
import axios from 'axios';
6+
import mime from 'mime';
7+
import Project from '../../../models/project';
8+
import { getProjectAsset } from '../../project.controller';
9+
import { resolvePathToFile } from '../../../utils/filePath';
10+
11+
jest.mock('../../../models/project');
12+
jest.mock('../../../utils/filePath');
13+
jest.mock('mime');
14+
jest.mock('axios');
15+
16+
describe('project.controller', () => {
17+
describe('getProjectAsset()', () => {
18+
let request;
19+
let response;
20+
21+
beforeEach(() => {
22+
request = new Request();
23+
response = new Response();
24+
response.send = jest.fn();
25+
response.status = jest.fn().mockReturnThis();
26+
response.set = jest.fn().mockReturnThis();
27+
request.setParams({ project_id: 'project-123', 0: 'image.png' });
28+
Project.findOne = jest.fn().mockReturnValue({
29+
populate: jest.fn().mockReturnThis(),
30+
exec: jest.fn()
31+
});
32+
jest.clearAllMocks();
33+
});
34+
35+
afterEach(() => {
36+
request.resetMocked();
37+
response.resetMocked();
38+
});
39+
40+
it('returns 404 if project does not exist', async () => {
41+
Project.findOne().populate().exec.mockResolvedValue(null);
42+
43+
await getProjectAsset(request, response);
44+
45+
expect(response.status).toHaveBeenCalledWith(404);
46+
expect(response.send).toHaveBeenCalledWith({
47+
message: 'Project with that id does not exist'
48+
});
49+
});
50+
51+
it('returns 403 if private project is accessed by unauthenticated user', async () => {
52+
const project = {
53+
_id: 'project-123',
54+
visibility: 'Private',
55+
user: { _id: { equals: jest.fn() } },
56+
files: []
57+
};
58+
request.user = undefined;
59+
60+
Project.findOne().populate().exec.mockResolvedValue(project);
61+
62+
await getProjectAsset(request, response);
63+
64+
expect(response.status).toHaveBeenCalledWith(403);
65+
expect(response.send).toHaveBeenCalledWith({
66+
message: 'Project is private'
67+
});
68+
});
69+
70+
it('returns 403 if private project is accessed by non-owner', async () => {
71+
const ownerId = { equals: jest.fn().mockReturnValue(false) };
72+
const project = {
73+
_id: 'project-123',
74+
visibility: 'Private',
75+
user: { _id: ownerId },
76+
files: []
77+
};
78+
request.user = { _id: 'other-user-id' };
79+
80+
Project.findOne().populate().exec.mockResolvedValue(project);
81+
82+
await getProjectAsset(request, response);
83+
84+
expect(response.status).toHaveBeenCalledWith(403);
85+
expect(response.send).toHaveBeenCalledWith({
86+
message: 'Project is private'
87+
});
88+
expect(ownerId.equals).toHaveBeenCalledWith('other-user-id');
89+
});
90+
91+
it('allows owner to access private project assets', async () => {
92+
const ownerId = 'owner-123';
93+
const ownerIdObj = { equals: jest.fn().mockReturnValue(true) };
94+
const project = {
95+
_id: 'project-123',
96+
visibility: 'Private',
97+
user: { _id: ownerIdObj },
98+
files: []
99+
};
100+
const resolvedFile = {
101+
name: 'image.png',
102+
content: Buffer.from('image content')
103+
};
104+
105+
request.user = { _id: ownerId };
106+
Project.findOne().populate().exec.mockResolvedValue(project);
107+
resolvePathToFile.mockReturnValue(resolvedFile);
108+
mime.getType.mockReturnValue('image/png');
109+
110+
await getProjectAsset(request, response);
111+
112+
expect(ownerIdObj.equals).toHaveBeenCalledWith(ownerId);
113+
expect(response.status).not.toHaveBeenCalledWith(403);
114+
expect(response.set).toHaveBeenCalledWith('Content-Type', 'image/png');
115+
expect(response.send).toHaveBeenCalledWith(resolvedFile.content);
116+
});
117+
118+
it('allows anyone to access public project assets', async () => {
119+
const project = {
120+
_id: 'project-123',
121+
visibility: 'Public',
122+
user: { _id: { equals: jest.fn() } },
123+
files: []
124+
};
125+
const resolvedFile = {
126+
name: 'image.png',
127+
content: Buffer.from('image content')
128+
};
129+
130+
request.user = undefined; // unauthenticated
131+
Project.findOne().populate().exec.mockResolvedValue(project);
132+
resolvePathToFile.mockReturnValue(resolvedFile);
133+
mime.getType.mockReturnValue('image/png');
134+
135+
await getProjectAsset(request, response);
136+
137+
expect(response.status).not.toHaveBeenCalledWith(403);
138+
expect(response.set).toHaveBeenCalledWith('Content-Type', 'image/png');
139+
expect(response.send).toHaveBeenCalledWith(resolvedFile.content);
140+
});
141+
142+
it('returns 404 if asset does not exist', async () => {
143+
const project = {
144+
_id: 'project-123',
145+
visibility: 'Public',
146+
user: { _id: { equals: jest.fn() } },
147+
files: []
148+
};
149+
150+
Project.findOne().populate().exec.mockResolvedValue(project);
151+
resolvePathToFile.mockReturnValue(null);
152+
153+
await getProjectAsset(request, response);
154+
155+
expect(response.status).toHaveBeenCalledWith(404);
156+
expect(response.send).toHaveBeenCalledWith({
157+
message: 'Asset does not exist'
158+
});
159+
});
160+
161+
it('fetches and serves asset from URL when resolvedFile has url', async () => {
162+
const project = {
163+
_id: 'project-123',
164+
visibility: 'Public',
165+
user: { _id: { equals: jest.fn() } },
166+
files: []
167+
};
168+
const resolvedFile = {
169+
name: 'image.png',
170+
url: 'https://example.com/image.png'
171+
};
172+
const imageData = Buffer.from('image data');
173+
174+
Project.findOne().populate().exec.mockResolvedValue(project);
175+
resolvePathToFile.mockReturnValue(resolvedFile);
176+
mime.getType.mockReturnValue('image/png');
177+
axios.get.mockResolvedValue({ data: imageData });
178+
179+
await getProjectAsset(request, response);
180+
181+
expect(axios.get).toHaveBeenCalledWith(resolvedFile.url, {
182+
responseType: 'arraybuffer'
183+
});
184+
expect(response.set).toHaveBeenCalledWith('Content-Type', 'image/png');
185+
expect(response.send).toHaveBeenCalledWith(imageData);
186+
});
187+
188+
it('returns 404 if fetching asset URL fails', async () => {
189+
const project = {
190+
_id: 'project-123',
191+
visibility: 'Public',
192+
user: { _id: { equals: jest.fn() } },
193+
files: []
194+
};
195+
const resolvedFile = {
196+
name: 'image.png',
197+
url: 'https://example.com/image.png'
198+
};
199+
200+
Project.findOne().populate().exec.mockResolvedValue(project);
201+
resolvePathToFile.mockReturnValue(resolvedFile);
202+
mime.getType.mockReturnValue('image/png');
203+
axios.get.mockRejectedValue(new Error('Network error'));
204+
205+
await getProjectAsset(request, response);
206+
207+
expect(response.status).toHaveBeenCalledWith(404);
208+
expect(response.send).toHaveBeenCalledWith({
209+
message: 'Asset does not exist'
210+
});
211+
});
212+
});
213+
});

0 commit comments

Comments
 (0)