Skip to content

Commit f5efab5

Browse files
authored
Entity dummy response and assertions (getodk#820)
Wrote basic integration tests for the APIs
1 parent ca92c4c commit f5efab5

File tree

4 files changed

+704
-32
lines changed

4 files changed

+704
-32
lines changed

lib/http/service.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ module.exports = (container) => {
7373
require('../resources/comments')(service, endpoint);
7474
require('../resources/analytics')(service, endpoint);
7575
require('../resources/datasets')(service, endpoint);
76+
require('../resources/entities')(service, endpoint);
7677

7778
////////////////////////////////////////////////////////////////////////////////
7879
// POSTRESOURCE HANDLERS

lib/resources/entities.js

Lines changed: 369 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,369 @@
1+
// Copyright 2023 ODK Central Developers
2+
// See the NOTICE file at the top-level directory of this distribution and at
3+
// https://github.com/getodk/central-backend/blob/master/NOTICE.
4+
// This file is part of ODK Central. It is subject to the license terms in
5+
// the LICENSE file found in the top-level directory of this distribution and at
6+
// https://www.apache.org/licenses/LICENSE-2.0. No part of ODK Central,
7+
// including this file, may be copied, modified, propagated, or distributed
8+
// except according to the terms contained in the LICENSE file.
9+
10+
const { getOrNotFound } = require('../util/promise');
11+
const { mergeLeft } = require('ramda');
12+
const { success } = require('../util/http');
13+
14+
const getActor = () => ({
15+
id: 1,
16+
displayName: 'John Doe',
17+
type: 'user',
18+
createdAt: new Date(),
19+
updatedAt: null,
20+
deletedAt: null
21+
});
22+
const getEntityDef = () => ({
23+
versionNumber: 1,
24+
label: '',
25+
current: true,
26+
createdAt: '2023-01-01T09:00:00.000Z',
27+
creatorId: 1,
28+
userAgent: 'Postman'
29+
});
30+
31+
const getEntity = (i) => ({
32+
uuid: `00000000-0000-0000-0000-00000000000${i}`,
33+
createdAt: '2023-01-01T09:00:00.000Z',
34+
updatedAt: null,
35+
deletedAt: null,
36+
creatorId: 1
37+
});
38+
39+
const populateEntities = () => {
40+
const entities = [];
41+
for (let i = 1; i <= 5; i+=1) {
42+
entities.push({
43+
...getEntity(i),
44+
currentVersion: getEntityDef()
45+
});
46+
}
47+
return entities;
48+
};
49+
50+
module.exports = (service, endpoint) => {
51+
52+
service.get('/projects/:id/datasets/:name/entities', endpoint(async ({ Projects }, { params, queryOptions, query }) => {
53+
54+
await Projects.getById(params.id, queryOptions).then(getOrNotFound);
55+
const entities = populateEntities();
56+
57+
if (queryOptions.extended) {
58+
for (let i = 0; i < 5; i+=1) {
59+
entities[i].currentVersion.creator = getActor();
60+
entities[i].creator = getActor();
61+
}
62+
}
63+
64+
if (query.deleted) {
65+
entities[4].deletedAt = new Date();
66+
return [entities[4]];
67+
}
68+
69+
return entities;
70+
}));
71+
72+
service.get('/projects/:id/datasets/:name/entities/:uuid', endpoint(async ({ Projects }, { params, queryOptions }) => {
73+
74+
await Projects.getById(params.id, queryOptions).then(getOrNotFound);
75+
const entity = getEntity(1);
76+
entity.updatedAt = '2023-01-02T09:00:00.000Z';
77+
entity.currentVersion = {
78+
...getEntityDef(),
79+
source: {
80+
type: 'api',
81+
details: null
82+
},
83+
data: {
84+
firstName: 'Jane',
85+
lastName: 'Roe',
86+
city: 'Toronto'
87+
},
88+
versionNumber: 2,
89+
label: 'Jane Roe',
90+
createdAt: '2023-01-02T09:00:00.000Z'
91+
};
92+
return entity;
93+
}));
94+
95+
service.get('/projects/:id/datasets/:name/entities/:uuid/versions', endpoint(async ({ Projects }, { params, queryOptions }) => {
96+
97+
await Projects.getById(params.id, queryOptions).then(getOrNotFound);
98+
99+
const versions = ['John Doe', 'Jane Doe', 'Richard Roe', 'Janie Doe', 'Baby Roe'].map((name, i) => ({
100+
...getEntityDef(),
101+
current: i === 4,
102+
source: {
103+
type: 'api',
104+
details: null
105+
},
106+
data: {
107+
firstName: name.split(' ')[0],
108+
lastName: name.split(' ')[1],
109+
city: 'Toronto'
110+
},
111+
versionNumber: i+1,
112+
label: name,
113+
createdAt: new Date()
114+
}));
115+
116+
return versions;
117+
}));
118+
119+
// May not be needed for now
120+
service.get('/projects/:id/datasets/:name/entities/:uuid/versions/:versionNumber', endpoint(async ({ Projects }, { params, queryOptions }) => {
121+
122+
await Projects.getById(params.id, queryOptions).then(getOrNotFound);
123+
124+
return {
125+
...getEntityDef(),
126+
current: false,
127+
source: {
128+
type: 'api',
129+
details: null
130+
},
131+
data: {
132+
firstName: 'Janie',
133+
lastName: 'Doe',
134+
city: 'Toronto'
135+
},
136+
versionNumber: params.versionNumber,
137+
label: 'Janie Doe',
138+
createdAt: new Date()
139+
};
140+
141+
}));
142+
143+
service.get('/projects/:id/datasets/:name/entities/:uuid/diffs', endpoint(async ({ Projects }, { params, queryOptions }) => {
144+
145+
await Projects.getById(params.id, queryOptions).then(getOrNotFound);
146+
147+
return {
148+
2: [ // 2 is version number
149+
{ old: 'John', new: 'Jane', propertyName: 'firstName' },
150+
{ old: 'Doe', new: 'Roe', propertyName: 'lastName' },
151+
{ old: 'John Doe', new: 'Jane Roe', propertyName: 'label' }
152+
]
153+
};
154+
}));
155+
156+
service.get('/projects/:id/datasets/:name/entities/:uuid/audits', endpoint(async ({ Projects }, { params, queryOptions }) => {
157+
158+
await Projects.getById(params.id, queryOptions).then(getOrNotFound);
159+
160+
return [
161+
{
162+
actorId: 1,
163+
action: 'entity.update.version',
164+
acteeId: '11111111-1111-1111-11111111111111111',
165+
details: {
166+
entityId: '00000000-0000-0000-0000-000000000001',
167+
label: 'Jane Roe',
168+
versionNumber: 2,
169+
source: {
170+
type: 'api',
171+
details: null
172+
}
173+
},
174+
loggedAt: '2023-01-02T09:00:00.000Z',
175+
notes: null,
176+
actor: getActor()
177+
},
178+
{
179+
actorId: 1,
180+
action: 'entity.create',
181+
acteeId: '11111111-1111-1111-11111111111111111',
182+
details: {
183+
entityId: '00000000-0000-0000-0000-000000000001',
184+
label: 'John Doe',
185+
versionNumber: 1,
186+
source: {
187+
type: 'submission',
188+
details: {
189+
instanceId: '11111111-1111-1111-11111111111111111',
190+
instanceName: 'first instance',
191+
xmlFormId: 'submission'
192+
}
193+
},
194+
// This nesting needs to be discussed
195+
relatedEvents: [
196+
{
197+
actorId: 1,
198+
action: 'submission.update',
199+
acteeId: '11111111-1111-1111-511c9466b12c',
200+
details: {
201+
instanceId: 'uuid:f9636668-814c-4450-859e-e651f3d81fd5',
202+
submissionId: 359983,
203+
reviewState: 'approved'
204+
},
205+
loggedAt: '2023-01-01T09:00:00.000Z',
206+
notes: null,
207+
actor: getActor()
208+
},
209+
{
210+
actorId: 1,
211+
action: 'submission.create',
212+
acteeId: '11111111-1111-1111-511c9466b12c',
213+
details: {
214+
instanceId: 'uuid:f9636668-814c-4450-859e-e651f3d81fd5',
215+
submissionId: 359983,
216+
instanceName: 'Lorem ipsum'
217+
},
218+
loggedAt: '2023-01-01T09:00:00.000Z',
219+
notes: null,
220+
actor: getActor()
221+
}
222+
]
223+
},
224+
loggedAt: '2023-01-01T09:00:00.000Z',
225+
notes: null,
226+
actor: getActor()
227+
}
228+
];
229+
}));
230+
231+
service.post('/projects/:id/datasets/:name/entities', endpoint(async ({ Projects }, { params, queryOptions, body, headers }) => {
232+
233+
await Projects.getById(params.id, queryOptions).then(getOrNotFound);
234+
235+
const { uuid, label, ...data } = body;
236+
237+
const date = new Date();
238+
239+
const userAgent = headers['user-agent'];
240+
241+
const entity = {
242+
...getEntity(1),
243+
uuid,
244+
createdAt: date,
245+
currentVersion: {
246+
...getEntityDef(),
247+
sourceId: userAgent,
248+
source: {
249+
type: 'api',
250+
details: null
251+
},
252+
label,
253+
createdAt: date,
254+
data
255+
}
256+
};
257+
258+
return entity;
259+
260+
}));
261+
262+
service.put('/projects/:id/datasets/:name/entities/:uuid', endpoint(async ({ Projects }, { params, queryOptions, body, headers }) => {
263+
264+
await Projects.getById(params.id, queryOptions).then(getOrNotFound);
265+
266+
const { uuid, label, ...data } = body;
267+
268+
const updatedAt = new Date();
269+
270+
const userAgent = headers['user-agent'];
271+
272+
const entity = {
273+
...getEntity(1),
274+
uuid,
275+
createdAt: '2023-01-01T09:00:00.000Z',
276+
updatedAt,
277+
currentVersion: {
278+
...getEntityDef(),
279+
userAgent,
280+
versionNumber: 2,
281+
source: {
282+
type: 'api',
283+
details: null
284+
},
285+
label,
286+
createdAt: updatedAt,
287+
data
288+
}
289+
};
290+
291+
return entity;
292+
293+
}));
294+
295+
service.patch('/projects/:id/datasets/:name/entities/:uuid', endpoint(async ({ Projects }, { params, queryOptions, body, headers }) => {
296+
297+
await Projects.getById(params.id, queryOptions).then(getOrNotFound);
298+
299+
const originalData = {
300+
uuid: '10000000-0000-0000-0000-000000000001',
301+
label: 'Johnny Doe',
302+
firstName: 'Johnny',
303+
lastName: 'Doe',
304+
city: 'Toronto'
305+
};
306+
307+
const { uuid, label, ...data } = mergeLeft(body, originalData);
308+
309+
const updatedAt = new Date();
310+
311+
const userAgent = headers['user-agent'];
312+
313+
const entity = {
314+
...getEntity(1),
315+
uuid,
316+
createdAt: '2023-01-01T09:00:00.000Z',
317+
updatedAt,
318+
currentVersion: {
319+
...getEntityDef(),
320+
userAgent,
321+
versionNumber: 2,
322+
source: {
323+
type: 'api',
324+
details: null
325+
},
326+
label,
327+
createdAt: updatedAt,
328+
data
329+
}
330+
};
331+
332+
return entity;
333+
334+
}));
335+
336+
service.delete('/projects/:id/datasets/:name/entities/:uuid', endpoint(async ({ Projects }, { params, queryOptions }) => {
337+
338+
await Projects.getById(params.id, queryOptions).then(getOrNotFound);
339+
340+
return success();
341+
342+
}));
343+
344+
// Lowest priority
345+
service.post('/projects/:id/datasets/:name/entities/:uuid/restore', endpoint(async ({ Projects }, { params, queryOptions }) => {
346+
347+
await Projects.getById(params.id, queryOptions).then(getOrNotFound);
348+
349+
const entity = getEntity(1);
350+
entity.updatedAt = '2023-01-02T09:00:00.000Z';
351+
entity.currentVersion = {
352+
...getEntityDef(),
353+
source: {
354+
type: 'api',
355+
details: null
356+
},
357+
data: {
358+
firstName: 'Jane',
359+
lastName: 'Roe',
360+
city: 'Toronto'
361+
},
362+
versionNumber: 1,
363+
label: 'Jane Roe',
364+
createdAt: '2023-01-03T09:00:00.000Z'
365+
};
366+
return entity;
367+
368+
}));
369+
};

0 commit comments

Comments
 (0)