Skip to content

Commit 29265dc

Browse files
committed
test(doc): add test for hocuspocus hooks
- add test for document reset - add mock for s3Extension.configuration (fetch and store)
1 parent bec762a commit 29265dc

File tree

8 files changed

+352
-33
lines changed

8 files changed

+352
-33
lines changed

app/routes/collaboration.test.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, test, expect, vi } from 'vitest';
1+
import { describe, test, expect, vi, afterEach } from 'vitest';
22
import request from 'supertest';
33

44
import { initWsApp } from '../core/express.ts';
@@ -17,15 +17,26 @@ const wsApp = initWsApp(
1717
);
1818

1919
describe('collaboration', () => {
20+
const verifyJwtSpy = vi.spyOn(jwt, 'verifyJwt');
21+
const s3FetchSpy = vi.spyOn(s3Extension.configuration, 'fetch');
22+
const s3StoreSpy = vi.spyOn(s3Extension.configuration, 'store');
23+
24+
afterEach(() => {
25+
verifyJwtSpy.mockClear();
26+
s3FetchSpy.mockReset();
27+
s3StoreSpy.mockReset();
28+
});
29+
2030
test('GET /collaboration/status with valid token', async () => {
21-
const verifyJwtSpy = vi.spyOn(jwt, 'verifyJwt')
22-
.mockResolvedValueOnce(mockTokenPayload);
31+
verifyJwtSpy.mockResolvedValueOnce(mockTokenPayload);
2332

2433
const res = await request(wsApp)
2534
.get('/collaboration/status')
2635
.set('Authorization', 'Bearer my-valid-token');
2736

2837
expect(verifyJwtSpy).toHaveBeenCalledOnce();
38+
expect(s3FetchSpy).toHaveBeenCalledTimes(0);
39+
expect(s3StoreSpy).toHaveBeenCalledTimes(0);
2940

3041
expect(res.status).toBe(200);
3142
expect(res.body).toEqual({ openDocs: 0, openConnections: 0 });

app/routes/doc.test.ts

Lines changed: 128 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import request from 'supertest';
44

55
import { mockTokenPayload } from '../../assets/jwt.ts';
66
import { initWsApp } from '../core/express.ts';
7-
import { s3Extension } from '../core/hocuspocus.ts';
87
import * as jwt from '../../app/utils/jwt.ts';
98
import { s3Extension, registerHocuspocus } from '../core/hocuspocus.ts';
109
import { slateReportToDoc } from '../../app/utils/report.ts';
1110
import { report } from '../../assets/report.ts';
11+
import { getS3Implementation } from '../../assets/s3.ts';
1212

1313
describe('REST doc', () => {
1414
const wsApp = initWsApp(
@@ -22,18 +22,22 @@ describe('REST doc', () => {
2222
);
2323

2424
const verifyJwtSpy = vi.spyOn(jwt, 'verifyJwt');
25+
const verifyServiceTokenSpy = vi.spyOn(jwt, 'verifyServiceToken');
2526
const s3FetchSpy = vi.spyOn(s3Extension.configuration, 'fetch');
27+
const s3StoreSpy = vi.spyOn(s3Extension.configuration, 'store');
2628

2729
afterEach(() => {
2830
verifyJwtSpy.mockClear();
29-
s3FetchSpy.mockClear();
31+
verifyServiceTokenSpy.mockClear();
32+
s3FetchSpy.mockReset();
33+
s3StoreSpy.mockReset();
3034
});
3135

32-
test('GET /documents/ with incorrect document format', async () => {
36+
test('GET /documents/{name} with incorrect document format', async () => {
3337
verifyJwtSpy.mockResolvedValueOnce(mockTokenPayload);
3438

3539
const res = await request(wsApp)
36-
.get('/documents/doc_v1_9999')
40+
.get('/documents/doc_v1_7777')
3741
.set('Authorization', 'Bearer my-valid-token');
3842

3943
expect(verifyJwtSpy).toHaveBeenCalledOnce();
@@ -44,22 +48,25 @@ describe('REST doc', () => {
4448
details: {
4549
name: {
4650
message: 'Document name should start with "document"',
47-
value: 'doc_v1_9999',
51+
value: 'doc_v1_7777',
4852
},
4953
},
5054
});
5155
});
5256

53-
test('GET /documents/ with non existing document', async () => {
57+
test('GET /documents/{name} with non existing document', async () => {
5458
verifyJwtSpy.mockResolvedValueOnce(mockTokenPayload);
55-
s3FetchSpy.mockResolvedValueOnce(null);
59+
const { fetch, store } = getS3Implementation();
60+
s3FetchSpy.mockImplementationOnce(fetch);
61+
s3StoreSpy.mockImplementationOnce(store);
5662

5763
const res = await request(wsApp)
58-
.get('/documents/document_v1_8888')
64+
.get('/documents/document_v1_11')
5965
.set('Authorization', 'Bearer my-valid-token');
6066

6167
expect(verifyJwtSpy).toHaveBeenCalledOnce();
6268
expect(s3FetchSpy).toHaveBeenCalledOnce();
69+
expect(s3StoreSpy).toHaveBeenCalledTimes(0);
6370

6471
expect(res.status).toBe(404);
6572
expect(res.body).toStrictEqual({
@@ -68,20 +75,26 @@ describe('REST doc', () => {
6875
});
6976
});
7077

71-
test('GET /doc/ with existing document', async () => {
78+
test('GET /documents/{name} with existing document', async () => {
7279
verifyJwtSpy.mockResolvedValueOnce(mockTokenPayload);
7380

7481
const doc = new Y.Doc();
7582
slateReportToDoc(report, doc);
7683
const binaryData = Y.encodeStateAsUpdate(doc);
77-
s3FetchSpy.mockResolvedValueOnce(binaryData);
84+
85+
const { fetch, store } = getS3Implementation({
86+
document_v1_22: binaryData,
87+
});
88+
s3FetchSpy.mockImplementationOnce(fetch);
89+
s3StoreSpy.mockImplementationOnce(store);
7890

7991
const res = await request(wsApp)
80-
.get('/documents/document_v1_9999')
92+
.get('/documents/document_v1_22')
8193
.set('Authorization', 'Bearer my-valid-token');
8294

8395
expect(verifyJwtSpy).toHaveBeenCalledOnce();
8496
expect(s3FetchSpy).toHaveBeenCalledOnce();
97+
expect(s3StoreSpy).toHaveBeenCalledTimes(0);
8598

8699
expect(res.status).toBe(200);
87100
expect(res.body).toStrictEqual({
@@ -107,4 +120,108 @@ describe('REST doc', () => {
107120
},
108121
});
109122
});
123+
124+
test('GET /documents/{name}/reset/ with invalid data', async () => {
125+
verifyServiceTokenSpy.mockResolvedValueOnce(true);
126+
const { fetch, store } = getS3Implementation();
127+
s3FetchSpy.mockImplementationOnce(fetch);
128+
s3StoreSpy.mockImplementationOnce(store);
129+
130+
const res = await request(wsApp)
131+
.put('/documents/document_v1_33/reset')
132+
.send({})
133+
.set('Authorization', 'Bearer my-valid-service-token');
134+
135+
expect(verifyServiceTokenSpy).toHaveBeenCalledOnce();
136+
expect(s3FetchSpy).toHaveBeenCalledTimes(0);
137+
expect(s3StoreSpy).toHaveBeenCalledTimes(0);
138+
139+
expect(res.status).toBe(422);
140+
expect(res.body).toStrictEqual({
141+
details: {
142+
'body.document': {
143+
message: '\'document\' is required',
144+
},
145+
'body.last_updated_at': {
146+
message: '\'last_updated_at\' is required',
147+
},
148+
'body.sections_completed': {
149+
message: '\'sections_completed\' is required',
150+
},
151+
},
152+
message: 'Validation failed',
153+
});
154+
});
155+
156+
test('GET /documents/{name}/reset/ with no data in s3', async () => {
157+
verifyServiceTokenSpy.mockResolvedValueOnce(true);
158+
const { fetch, store } = getS3Implementation();
159+
s3FetchSpy.mockImplementationOnce(fetch);
160+
s3FetchSpy.mockImplementationOnce(fetch);
161+
s3StoreSpy.mockImplementation(store);
162+
163+
const res = await request(wsApp)
164+
.put('/documents/document_v1_44/reset')
165+
.send(report)
166+
.set('Authorization', 'Bearer my-valid-service-token');
167+
168+
expect(verifyServiceTokenSpy).toHaveBeenCalledOnce();
169+
expect(s3FetchSpy).toHaveBeenCalledTimes(2);
170+
expect(s3StoreSpy).toHaveBeenCalledTimes(0);
171+
172+
expect(res.status).toBe(200);
173+
expect(res.body).toStrictEqual({
174+
docName: 'document_v1_44',
175+
});
176+
});
177+
178+
test('GET /documents/{name}/reset/ with data in s3', async () => {
179+
verifyServiceTokenSpy.mockResolvedValueOnce(true);
180+
181+
const doc = new Y.Doc();
182+
slateReportToDoc(report, doc);
183+
const binaryData = Y.encodeStateAsUpdate(doc);
184+
const { fetch, store } = getS3Implementation({
185+
document_v1_55: binaryData,
186+
});
187+
s3FetchSpy.mockImplementationOnce(fetch);
188+
s3FetchSpy.mockImplementationOnce(fetch);
189+
s3StoreSpy.mockImplementationOnce(store);
190+
191+
const updatedReport = {
192+
...report,
193+
sections_completed: {
194+
...report.sections_completed,
195+
department: 'complete',
196+
},
197+
};
198+
199+
const res = await request(wsApp)
200+
.put('/documents/document_v1_55/reset')
201+
.send(updatedReport)
202+
.set('Authorization', 'Bearer my-valid-service-token');
203+
204+
expect(verifyServiceTokenSpy).toHaveBeenCalledOnce();
205+
expect(s3FetchSpy).toHaveBeenCalledTimes(2);
206+
expect(s3StoreSpy).toHaveBeenCalledTimes(1);
207+
208+
expect(res.status).toBe(200);
209+
expect(res.body).toStrictEqual({
210+
docName: 'document_v1_55',
211+
});
212+
213+
verifyJwtSpy.mockResolvedValueOnce(mockTokenPayload);
214+
s3FetchSpy.mockImplementationOnce(fetch);
215+
s3StoreSpy.mockImplementationOnce(store);
216+
217+
const res2 = await request(wsApp)
218+
.get('/documents/document_v1_55')
219+
.set('Authorization', 'Bearer my-valid-token');
220+
221+
// FIXME: confirm if direct connection does not load document into memory
222+
expect(verifyJwtSpy).toHaveBeenCalledOnce();
223+
expect(s3FetchSpy).toHaveBeenCalledTimes(3);
224+
expect(s3StoreSpy).toHaveBeenCalledTimes(1);
225+
expect(res2.status).toBe(200);
226+
});
110227
});

app/routes/expressHttpCodes.test.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
1-
import { describe, test, expect, vi } from 'vitest';
1+
import { describe, test, expect, vi, afterEach } from 'vitest';
22
import request from 'supertest';
33
import fs from 'fs';
44

55
import { initWsApp } from '../core/express.ts';
6+
import { registerSwaggerUi } from '../core/swagger.ts';
67

78
const wsApp = initWsApp(
89
{ allowedOrigins: [] },
9-
// eslint-disable-next-line @typescript-eslint/no-empty-function
10-
() => {},
10+
(app) => {
11+
registerSwaggerUi(app);
12+
},
1113
);
1214

1315
describe('base', () => {
16+
const readFileSyncSpy = vi.spyOn(fs, 'readFileSync');
17+
18+
afterEach(() => {
19+
readFileSyncSpy.mockClear();
20+
});
21+
1422
test('check unimplemented route', async () => {
1523
const res = await request(wsApp).get('/docx');
1624
expect(res.status).toBe(404);
@@ -20,11 +28,14 @@ describe('base', () => {
2028
});
2129

2230
test('check route with internal server error', async () => {
23-
vi.spyOn(fs, 'readFileSync').mockImplementationOnce(() => {
31+
readFileSyncSpy.mockImplementationOnce(() => {
2432
throw new Error('ENOENT: no such file or directory');
2533
});
2634

2735
const res = await request(wsApp).get('/docs/');
36+
37+
expect(readFileSyncSpy).toHaveBeenCalledOnce();
38+
2839
expect(res.status).toBe(500);
2940
expect(res.body).toStrictEqual({
3041
message: 'Internal server error',
@@ -34,11 +45,14 @@ describe('base', () => {
3445
});
3546

3647
test('check route with uncaught error', async () => {
37-
vi.spyOn(fs, 'readFileSync').mockImplementationOnce(() => {
48+
readFileSyncSpy.mockImplementationOnce(() => {
3849
throw 'oops';
3950
});
4051

4152
const res = await request(wsApp).get('/docs/');
53+
54+
expect(readFileSyncSpy).toHaveBeenCalledOnce();
55+
4256
expect(res.status).toBe(500);
4357
expect(res.body).toStrictEqual({
4458
message: 'Internal server error',

app/routes/expressUserAuth.test.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, test, expect, vi } from 'vitest';
1+
import { describe, test, expect, vi, afterEach } from 'vitest';
22
import request from 'supertest';
33

44
import { initWsApp } from '../core/express.ts';
@@ -11,6 +11,12 @@ const wsApp = initWsApp(
1111
);
1212

1313
describe('user authentication', () => {
14+
const verifyJwtSpy = vi.spyOn(jwt, 'verifyJwt');
15+
16+
afterEach(() => {
17+
verifyJwtSpy.mockClear();
18+
});
19+
1420
test('handles missing authentication token', async () => {
1521
const res = await request(wsApp).get('/collaboration/status');
1622
expect(res.status).toBe(401);
@@ -21,11 +27,14 @@ describe('user authentication', () => {
2127
});
2228

2329
test('handles incorrect authentication token', async () => {
24-
vi.spyOn(jwt, 'verifyJwt').mockResolvedValueOnce(new Error('JWT string does not consist of exactly 3 parts (header, payload, signature)'));
30+
verifyJwtSpy.mockResolvedValueOnce(new Error('JWT string does not consist of exactly 3 parts (header, payload, signature)'));
2531

2632
const res = await request(wsApp)
2733
.get('/collaboration/status')
2834
.set('Authorization', 'Bearer my-invalid-token');
35+
36+
expect(verifyJwtSpy).toHaveBeenCalledOnce();
37+
2938
expect(res.status).toBe(401);
3039
expect(res.body).toStrictEqual({
3140
message: 'Unauthorized',
@@ -37,7 +46,7 @@ describe('user authentication', () => {
3746
describe('service authentication', () => {
3847
test('handles missing authentication token', async () => {
3948
const res = await request(wsApp)
40-
.put('/documents/document_v1_123/reset')
49+
.put('/documents/document_v1_111/reset')
4150
.send({ document: null });
4251
expect(res.status).toBe(401);
4352
expect(res.body).toStrictEqual({
@@ -48,7 +57,7 @@ describe('service authentication', () => {
4857

4958
test('handles invalid authentication token', async () => {
5059
const res = await request(wsApp)
51-
.put('/documents/document_v1_123/reset')
60+
.put('/documents/document_v1_222/reset')
5261
.set('Authorization', 'Bearer abcabcabcabc')
5362
.send({ document: null });
5463
expect(res.status).toBe(401);

0 commit comments

Comments
 (0)