@stamhoofd/backend 2.78.3 → 2.79.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.ci.json +19 -8
- package/index.ts +8 -1
- package/package.json +13 -12
- package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +1 -1
- package/src/endpoints/auth/CreateAdminEndpoint.ts +1 -1
- package/src/endpoints/auth/CreateTokenEndpoint.ts +1 -1
- package/src/endpoints/auth/ForgotPasswordEndpoint.ts +1 -1
- package/src/endpoints/auth/PatchUserEndpoint.ts +1 -1
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +2686 -0
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +206 -20
- package/src/endpoints/global/members/shouldCheckIfMemberIsDuplicate.ts +9 -21
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +1 -1
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +2 -2
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +15 -14
- package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +3 -23
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +12 -0
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +2 -3
- package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.test.ts +164 -0
- package/src/helpers/AdminPermissionChecker.ts +41 -2
- package/src/helpers/AuthenticatedStructures.ts +77 -20
- package/src/helpers/ForwardHandler.test.ts +16 -5
- package/src/helpers/ForwardHandler.ts +21 -9
- package/src/helpers/MemberUserSyncer.test.ts +822 -0
- package/src/helpers/MemberUserSyncer.ts +137 -108
- package/src/helpers/TagHelper.ts +3 -3
- package/src/seeds/1734596144-fill-previous-period-id.ts +1 -1
- package/src/seeds/1741008870-fix-auditlog-description.ts +50 -0
- package/src/seeds/1741011468-fix-auditlog-description-uft16.ts +88 -0
- package/src/services/FileSignService.ts +1 -1
- package/src/services/PlatformMembershipService.ts +7 -2
- package/src/services/SSOService.ts +1 -1
- package/tests/e2e/register.test.ts +2 -2
|
@@ -0,0 +1,2686 @@
|
|
|
1
|
+
import { Database } from '@simonbackx/simple-database';
|
|
2
|
+
import { PatchableArray, PatchableArrayAutoEncoder, PatchMap } from '@simonbackx/simple-encoding';
|
|
3
|
+
import { Endpoint, Request } from '@simonbackx/simple-endpoints';
|
|
4
|
+
import { GroupFactory, MemberFactory, OrganizationFactory, OrganizationTagFactory, Platform, RegistrationFactory, Token, UserFactory } from '@stamhoofd/models';
|
|
5
|
+
import { Address, Country, EmergencyContact, MemberDetails, MemberWithRegistrationsBlob, OrganizationMetaData, OrganizationRecordsConfiguration, Parent, PatchAnswers, PermissionLevel, Permissions, PermissionsResourceType, RecordCategory, RecordSettings, RecordTextAnswer, ResourcePermissions, ReviewTime, ReviewTimes } from '@stamhoofd/structures';
|
|
6
|
+
import { TestUtils } from '@stamhoofd/test-utils';
|
|
7
|
+
import { testServer } from '../../../../tests/helpers/TestServer';
|
|
8
|
+
import { PatchOrganizationMembersEndpoint } from './PatchOrganizationMembersEndpoint';
|
|
9
|
+
|
|
10
|
+
const baseUrl = `/organization/members`;
|
|
11
|
+
const endpoint = new PatchOrganizationMembersEndpoint();
|
|
12
|
+
type EndpointType = typeof endpoint;
|
|
13
|
+
type Body = EndpointType extends Endpoint<any, any, infer B, any> ? B : never;
|
|
14
|
+
|
|
15
|
+
const firstName = 'John';
|
|
16
|
+
const lastName = 'Doe';
|
|
17
|
+
const birthDay = { year: 1993, month: 4, day: 5 };
|
|
18
|
+
|
|
19
|
+
const errorWithCode = (code: string) => expect.objectContaining({ code }) as jest.Constructable;
|
|
20
|
+
|
|
21
|
+
describe('Endpoint.PatchOrganizationMembersEndpoint', () => {
|
|
22
|
+
beforeEach(async () => {
|
|
23
|
+
TestUtils.setEnvironment('userMode', 'platform');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
afterEach(async () => {
|
|
27
|
+
// Delete all members (so the duplicate checks work as expected)
|
|
28
|
+
await Database.delete('DELETE FROM `members`');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('Duplicate members', () => {
|
|
32
|
+
test('The security code should be a requirement', async () => {
|
|
33
|
+
const organization = await new OrganizationFactory({ }).create();
|
|
34
|
+
const user = await new UserFactory({
|
|
35
|
+
permissions: Permissions.create({ level: PermissionLevel.Full }),
|
|
36
|
+
organization, // since we are in platform mode, this will only set the permissions for this organization
|
|
37
|
+
}).create();
|
|
38
|
+
|
|
39
|
+
const existingMember = await new MemberFactory({
|
|
40
|
+
firstName,
|
|
41
|
+
lastName,
|
|
42
|
+
birthDay,
|
|
43
|
+
generateData: true,
|
|
44
|
+
}).create();
|
|
45
|
+
|
|
46
|
+
const token = await Token.createToken(user);
|
|
47
|
+
|
|
48
|
+
const arr: Body = new PatchableArray();
|
|
49
|
+
const put = MemberWithRegistrationsBlob.create({
|
|
50
|
+
details: MemberDetails.create({
|
|
51
|
+
firstName,
|
|
52
|
+
lastName,
|
|
53
|
+
birthDay: new Date(existingMember.details.birthDay!.getTime() + 1),
|
|
54
|
+
}),
|
|
55
|
+
});
|
|
56
|
+
arr.addPut(put);
|
|
57
|
+
|
|
58
|
+
const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
|
|
59
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
60
|
+
await expect(testServer.test(endpoint, request))
|
|
61
|
+
.rejects
|
|
62
|
+
.toThrow(errorWithCode('known_member_missing_rights'));
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('The security code is not a requirement for members without additional data', async () => {
|
|
66
|
+
const organization = await new OrganizationFactory({ }).create();
|
|
67
|
+
const user = await new UserFactory({
|
|
68
|
+
permissions: Permissions.create({ level: PermissionLevel.Full }),
|
|
69
|
+
organization, // since we are in platform mode, this will only set the permissions for this organization
|
|
70
|
+
}).create();
|
|
71
|
+
|
|
72
|
+
const existingMember = await new MemberFactory({
|
|
73
|
+
firstName,
|
|
74
|
+
lastName,
|
|
75
|
+
birthDay,
|
|
76
|
+
generateData: false,
|
|
77
|
+
}).create();
|
|
78
|
+
|
|
79
|
+
const token = await Token.createToken(user);
|
|
80
|
+
|
|
81
|
+
const arr: Body = new PatchableArray();
|
|
82
|
+
const put = MemberWithRegistrationsBlob.create({
|
|
83
|
+
details: MemberDetails.create({
|
|
84
|
+
firstName,
|
|
85
|
+
lastName,
|
|
86
|
+
birthDay: new Date(existingMember.details.birthDay!.getTime() + 1),
|
|
87
|
+
email: 'anewemail@example.com',
|
|
88
|
+
}),
|
|
89
|
+
});
|
|
90
|
+
arr.addPut(put);
|
|
91
|
+
|
|
92
|
+
const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
|
|
93
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
94
|
+
const response = await testServer.test(endpoint, request);
|
|
95
|
+
expect(response.status).toBe(200);
|
|
96
|
+
|
|
97
|
+
// Check id of the returned memebr matches the existing member
|
|
98
|
+
expect(response.body.members.length).toBe(1);
|
|
99
|
+
expect(response.body.members[0].id).toBe(existingMember.id);
|
|
100
|
+
|
|
101
|
+
// Check data matches the original data + changes from the put
|
|
102
|
+
const member = response.body.members[0];
|
|
103
|
+
expect(member.details.firstName).toBe(firstName);
|
|
104
|
+
expect(member.details.lastName).toBe(lastName);
|
|
105
|
+
expect(member.details.birthDay).toEqual(existingMember.details.birthDay);
|
|
106
|
+
expect(member.details.email).toBe('anewemail@example.com'); // this has been merged
|
|
107
|
+
expect(member.details.alternativeEmails).toHaveLength(0);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('A duplicate member with existing registrations returns those registrations after a merge', async () => {
|
|
111
|
+
const organization = await new OrganizationFactory({ }).create();
|
|
112
|
+
const user = await new UserFactory({
|
|
113
|
+
permissions: Permissions.create({ level: PermissionLevel.Full }),
|
|
114
|
+
organization, // since we are in platform mode, this will only set the permissions for this organization
|
|
115
|
+
}).create();
|
|
116
|
+
|
|
117
|
+
const details = MemberDetails.create({
|
|
118
|
+
firstName,
|
|
119
|
+
lastName,
|
|
120
|
+
securityCode: 'ABC-123',
|
|
121
|
+
email: 'original@example.com',
|
|
122
|
+
parents: [
|
|
123
|
+
Parent.create({
|
|
124
|
+
firstName: 'Jane',
|
|
125
|
+
lastName: 'Doe',
|
|
126
|
+
email: 'jane.doe@example.com',
|
|
127
|
+
}),
|
|
128
|
+
],
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const existingMember = await new MemberFactory({
|
|
132
|
+
birthDay,
|
|
133
|
+
details,
|
|
134
|
+
}).create();
|
|
135
|
+
|
|
136
|
+
// Create a registration for this member
|
|
137
|
+
const group = await new GroupFactory({ organization }).create();
|
|
138
|
+
const registration = await new RegistrationFactory({
|
|
139
|
+
member: existingMember,
|
|
140
|
+
group,
|
|
141
|
+
}).create();
|
|
142
|
+
|
|
143
|
+
const token = await Token.createToken(user);
|
|
144
|
+
|
|
145
|
+
const arr: Body = new PatchableArray();
|
|
146
|
+
const put = MemberWithRegistrationsBlob.create({
|
|
147
|
+
details: MemberDetails.create({
|
|
148
|
+
firstName,
|
|
149
|
+
lastName,
|
|
150
|
+
birthDay: new Date(existingMember.details.birthDay!.getTime() + 1),
|
|
151
|
+
securityCode: existingMember.details.securityCode,
|
|
152
|
+
email: 'anewemail@example.com',
|
|
153
|
+
}),
|
|
154
|
+
});
|
|
155
|
+
arr.addPut(put);
|
|
156
|
+
|
|
157
|
+
const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
|
|
158
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
159
|
+
const response = await testServer.test(endpoint, request);
|
|
160
|
+
expect(response.status).toBe(200);
|
|
161
|
+
|
|
162
|
+
// Check id of the returned memebr matches the existing member
|
|
163
|
+
expect(response.body.members.length).toBe(1);
|
|
164
|
+
expect(response.body.members[0].id).toBe(existingMember.id);
|
|
165
|
+
|
|
166
|
+
// Check data matches the original data + changes from the put
|
|
167
|
+
const member = response.body.members[0];
|
|
168
|
+
expect(member.details.firstName).toBe(firstName);
|
|
169
|
+
expect(member.details.lastName).toBe(lastName);
|
|
170
|
+
expect(member.details.birthDay).toEqual(existingMember.details.birthDay);
|
|
171
|
+
expect(member.details.email).toBe('original@example.com'); // this has been merged
|
|
172
|
+
expect(member.details.alternativeEmails).toEqual(['anewemail@example.com']); // this has been merged
|
|
173
|
+
|
|
174
|
+
// Check the registration is still there
|
|
175
|
+
expect(member.registrations.length).toBe(1);
|
|
176
|
+
expect(member.registrations[0].id).toBe(registration.id);
|
|
177
|
+
|
|
178
|
+
// Check parent is still there
|
|
179
|
+
expect(member.details.parents.length).toBe(1);
|
|
180
|
+
expect(member.details.parents[0]).toEqual(existingMember.details.parents[0]);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe('Permission checking', () => {
|
|
185
|
+
test('An admin cannot edit members of a different organization', async () => {
|
|
186
|
+
const organization = await new OrganizationFactory({}).create();
|
|
187
|
+
const otherOrganization = await new OrganizationFactory({}).create();
|
|
188
|
+
|
|
189
|
+
const user = await new UserFactory({
|
|
190
|
+
permissions: Permissions.create({
|
|
191
|
+
level: PermissionLevel.Full,
|
|
192
|
+
}),
|
|
193
|
+
organization, // since we are in platform mode, this will only set the permissions for this organization
|
|
194
|
+
}).create();
|
|
195
|
+
|
|
196
|
+
const member = await new MemberFactory({
|
|
197
|
+
firstName,
|
|
198
|
+
lastName,
|
|
199
|
+
birthDay,
|
|
200
|
+
generateData: false,
|
|
201
|
+
}).create();
|
|
202
|
+
|
|
203
|
+
// Register this member
|
|
204
|
+
await new RegistrationFactory({
|
|
205
|
+
member,
|
|
206
|
+
organization: otherOrganization,
|
|
207
|
+
}).create();
|
|
208
|
+
|
|
209
|
+
const token = await Token.createToken(user);
|
|
210
|
+
|
|
211
|
+
const arr: Body = new PatchableArray();
|
|
212
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
213
|
+
id: member.id,
|
|
214
|
+
details: MemberDetails.patch({
|
|
215
|
+
firstName: 'Changed',
|
|
216
|
+
}),
|
|
217
|
+
});
|
|
218
|
+
arr.addPatch(patch);
|
|
219
|
+
|
|
220
|
+
const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
|
|
221
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
222
|
+
await expect(testServer.test(endpoint, request)).rejects.toThrow(errorWithCode('not_found'));
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test('An admin can edit members registered in its own organization', async () => {
|
|
226
|
+
const organization = await new OrganizationFactory({}).create();
|
|
227
|
+
|
|
228
|
+
const user = await new UserFactory({
|
|
229
|
+
permissions: Permissions.create({
|
|
230
|
+
level: PermissionLevel.Full,
|
|
231
|
+
}),
|
|
232
|
+
organization, // since we are in platform mode, this will only set the permissions for this organization
|
|
233
|
+
}).create();
|
|
234
|
+
|
|
235
|
+
const member = await new MemberFactory({
|
|
236
|
+
firstName,
|
|
237
|
+
lastName,
|
|
238
|
+
birthDay,
|
|
239
|
+
generateData: false,
|
|
240
|
+
}).create();
|
|
241
|
+
|
|
242
|
+
// Register this member
|
|
243
|
+
await new RegistrationFactory({
|
|
244
|
+
member,
|
|
245
|
+
organization,
|
|
246
|
+
}).create();
|
|
247
|
+
|
|
248
|
+
const token = await Token.createToken(user);
|
|
249
|
+
|
|
250
|
+
const arr: Body = new PatchableArray();
|
|
251
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
252
|
+
id: member.id,
|
|
253
|
+
details: MemberDetails.patch({
|
|
254
|
+
firstName: 'Changed',
|
|
255
|
+
}),
|
|
256
|
+
});
|
|
257
|
+
arr.addPatch(patch);
|
|
258
|
+
|
|
259
|
+
const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
|
|
260
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
261
|
+
const response = await testServer.test(endpoint, request);
|
|
262
|
+
|
|
263
|
+
// Check returned
|
|
264
|
+
expect(response.status).toBe(200);
|
|
265
|
+
expect(response.body.members.length).toBe(1);
|
|
266
|
+
const memberStruct = response.body.members[0];
|
|
267
|
+
expect(memberStruct.details).toMatchObject({
|
|
268
|
+
firstName: 'Changed',
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test('A full platform admin can edit members without registrations', async () => {
|
|
273
|
+
const organization = await new OrganizationFactory({}).create();
|
|
274
|
+
|
|
275
|
+
const user = await new UserFactory({
|
|
276
|
+
globalPermissions: Permissions.create({
|
|
277
|
+
level: PermissionLevel.Full,
|
|
278
|
+
}),
|
|
279
|
+
}).create();
|
|
280
|
+
|
|
281
|
+
const member = await new MemberFactory({
|
|
282
|
+
firstName,
|
|
283
|
+
lastName,
|
|
284
|
+
birthDay,
|
|
285
|
+
generateData: false,
|
|
286
|
+
}).create();
|
|
287
|
+
|
|
288
|
+
const token = await Token.createToken(user);
|
|
289
|
+
|
|
290
|
+
const arr: Body = new PatchableArray();
|
|
291
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
292
|
+
id: member.id,
|
|
293
|
+
details: MemberDetails.patch({
|
|
294
|
+
firstName: 'Changed',
|
|
295
|
+
}),
|
|
296
|
+
});
|
|
297
|
+
arr.addPatch(patch);
|
|
298
|
+
|
|
299
|
+
const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
|
|
300
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
301
|
+
const response = await testServer.test(endpoint, request);
|
|
302
|
+
|
|
303
|
+
// Check returned
|
|
304
|
+
expect(response.status).toBe(200);
|
|
305
|
+
expect(response.body.members.length).toBe(1);
|
|
306
|
+
const memberStruct = response.body.members[0];
|
|
307
|
+
expect(memberStruct.details).toMatchObject({
|
|
308
|
+
firstName: 'Changed',
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test('[Regression] A platform admin with all tag access can edit members without registrations', async () => {
|
|
313
|
+
const organization = await new OrganizationFactory({}).create();
|
|
314
|
+
|
|
315
|
+
const user = await new UserFactory({
|
|
316
|
+
globalPermissions: Permissions.create({
|
|
317
|
+
level: PermissionLevel.None,
|
|
318
|
+
resources: new Map([
|
|
319
|
+
// All Tags
|
|
320
|
+
[PermissionsResourceType.OrganizationTags, new Map(
|
|
321
|
+
[['', ResourcePermissions.create({ level: PermissionLevel.Full })]],
|
|
322
|
+
)],
|
|
323
|
+
]),
|
|
324
|
+
}),
|
|
325
|
+
}).create();
|
|
326
|
+
|
|
327
|
+
const member = await new MemberFactory({
|
|
328
|
+
firstName,
|
|
329
|
+
lastName,
|
|
330
|
+
birthDay,
|
|
331
|
+
generateData: false,
|
|
332
|
+
}).create();
|
|
333
|
+
|
|
334
|
+
const token = await Token.createToken(user);
|
|
335
|
+
|
|
336
|
+
const arr: Body = new PatchableArray();
|
|
337
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
338
|
+
id: member.id,
|
|
339
|
+
details: MemberDetails.patch({
|
|
340
|
+
firstName: 'Changed',
|
|
341
|
+
}),
|
|
342
|
+
});
|
|
343
|
+
arr.addPatch(patch);
|
|
344
|
+
|
|
345
|
+
const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
|
|
346
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
347
|
+
const response = await testServer.test(endpoint, request);
|
|
348
|
+
|
|
349
|
+
// Check returned
|
|
350
|
+
expect(response.status).toBe(200);
|
|
351
|
+
expect(response.body.members.length).toBe(1);
|
|
352
|
+
const memberStruct = response.body.members[0];
|
|
353
|
+
expect(memberStruct.details).toMatchObject({
|
|
354
|
+
firstName: 'Changed',
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
describe('Record answers', () => {
|
|
360
|
+
test('An admin can set records of its own organization', async () => {
|
|
361
|
+
const commentsRecord = RecordSettings.create({
|
|
362
|
+
name: 'Opmerkingen',
|
|
363
|
+
externalPermissionLevel: PermissionLevel.Read, // this should be ignored since we are an admin
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
const recordCategory = RecordCategory.create({
|
|
367
|
+
name: 'Medische fiche',
|
|
368
|
+
records: [
|
|
369
|
+
commentsRecord,
|
|
370
|
+
],
|
|
371
|
+
});
|
|
372
|
+
const organization = await new OrganizationFactory({
|
|
373
|
+
meta: OrganizationMetaData.create({
|
|
374
|
+
recordsConfiguration: OrganizationRecordsConfiguration.create({
|
|
375
|
+
recordCategories: [recordCategory],
|
|
376
|
+
}),
|
|
377
|
+
}),
|
|
378
|
+
}).create();
|
|
379
|
+
|
|
380
|
+
const user = await new UserFactory({
|
|
381
|
+
permissions: Permissions.create({ level: PermissionLevel.Full }),
|
|
382
|
+
organization, // since we are in platform mode, this will only set the permissions for this organization
|
|
383
|
+
}).create();
|
|
384
|
+
|
|
385
|
+
const member = await new MemberFactory({
|
|
386
|
+
firstName,
|
|
387
|
+
lastName,
|
|
388
|
+
birthDay,
|
|
389
|
+
generateData: false,
|
|
390
|
+
}).create();
|
|
391
|
+
|
|
392
|
+
// Register this member
|
|
393
|
+
await new RegistrationFactory({
|
|
394
|
+
member,
|
|
395
|
+
organization,
|
|
396
|
+
}).create();
|
|
397
|
+
|
|
398
|
+
const token = await Token.createToken(user);
|
|
399
|
+
|
|
400
|
+
const recordAnswers = new PatchMap() as PatchAnswers;
|
|
401
|
+
|
|
402
|
+
recordAnswers.set(commentsRecord.id, RecordTextAnswer.create({
|
|
403
|
+
settings: commentsRecord,
|
|
404
|
+
value: 'Some comments',
|
|
405
|
+
}));
|
|
406
|
+
|
|
407
|
+
const arr: Body = new PatchableArray();
|
|
408
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
409
|
+
id: member.id,
|
|
410
|
+
details: MemberDetails.patch({
|
|
411
|
+
recordAnswers,
|
|
412
|
+
}),
|
|
413
|
+
});
|
|
414
|
+
arr.addPatch(patch);
|
|
415
|
+
|
|
416
|
+
const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
|
|
417
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
418
|
+
const response = await testServer.test(endpoint, request);
|
|
419
|
+
|
|
420
|
+
// Check returned
|
|
421
|
+
expect(response.status).toBe(200);
|
|
422
|
+
expect(response.body.members.length).toBe(1);
|
|
423
|
+
const struct = response.body.members[0];
|
|
424
|
+
expect(struct.details.recordAnswers.get(commentsRecord.id)).toMatchObject({
|
|
425
|
+
value: 'Some comments',
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
test('An admin with read only record category permission cannot set the records in that category', async () => {
|
|
430
|
+
const commentsRecord = RecordSettings.create({
|
|
431
|
+
name: 'Opmerkingen',
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
const recordCategory = RecordCategory.create({
|
|
435
|
+
name: 'Medische fiche',
|
|
436
|
+
records: [
|
|
437
|
+
commentsRecord,
|
|
438
|
+
],
|
|
439
|
+
});
|
|
440
|
+
const organization = await new OrganizationFactory({
|
|
441
|
+
meta: OrganizationMetaData.create({
|
|
442
|
+
recordsConfiguration: OrganizationRecordsConfiguration.create({
|
|
443
|
+
recordCategories: [recordCategory],
|
|
444
|
+
}),
|
|
445
|
+
}),
|
|
446
|
+
}).create();
|
|
447
|
+
|
|
448
|
+
const group = await new GroupFactory({ organization }).create();
|
|
449
|
+
|
|
450
|
+
const user = await new UserFactory({
|
|
451
|
+
permissions: Permissions.create({
|
|
452
|
+
level: PermissionLevel.None,
|
|
453
|
+
resources: new Map([
|
|
454
|
+
[PermissionsResourceType.RecordCategories, new Map([
|
|
455
|
+
[recordCategory.id, ResourcePermissions.create({ level: PermissionLevel.Read })],
|
|
456
|
+
])],
|
|
457
|
+
[PermissionsResourceType.Groups, new Map([
|
|
458
|
+
[group.id, ResourcePermissions.create({ level: PermissionLevel.Full })],
|
|
459
|
+
])],
|
|
460
|
+
]),
|
|
461
|
+
}),
|
|
462
|
+
organization, // since we are in platform mode, this will only set the permissions for this organization
|
|
463
|
+
}).create();
|
|
464
|
+
|
|
465
|
+
const member = await new MemberFactory({
|
|
466
|
+
firstName,
|
|
467
|
+
lastName,
|
|
468
|
+
birthDay,
|
|
469
|
+
generateData: false,
|
|
470
|
+
}).create();
|
|
471
|
+
|
|
472
|
+
// Register this member
|
|
473
|
+
await new RegistrationFactory({
|
|
474
|
+
member,
|
|
475
|
+
group,
|
|
476
|
+
}).create();
|
|
477
|
+
|
|
478
|
+
const token = await Token.createToken(user);
|
|
479
|
+
|
|
480
|
+
const recordAnswers = new PatchMap() as PatchAnswers;
|
|
481
|
+
|
|
482
|
+
recordAnswers.set(commentsRecord.id, RecordTextAnswer.create({
|
|
483
|
+
settings: commentsRecord,
|
|
484
|
+
value: 'Some comments',
|
|
485
|
+
}));
|
|
486
|
+
|
|
487
|
+
const arr: Body = new PatchableArray();
|
|
488
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
489
|
+
id: member.id,
|
|
490
|
+
details: MemberDetails.patch({
|
|
491
|
+
recordAnswers,
|
|
492
|
+
}),
|
|
493
|
+
});
|
|
494
|
+
arr.addPatch(patch);
|
|
495
|
+
|
|
496
|
+
const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
|
|
497
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
498
|
+
await expect(testServer.test(endpoint, request)).rejects.toThrow(errorWithCode('permission_denied'));
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
test('An admin without record category permission cannot set the records in that category', async () => {
|
|
502
|
+
const commentsRecord = RecordSettings.create({
|
|
503
|
+
name: 'Opmerkingen',
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
const recordCategory = RecordCategory.create({
|
|
507
|
+
name: 'Medische fiche',
|
|
508
|
+
records: [
|
|
509
|
+
commentsRecord,
|
|
510
|
+
],
|
|
511
|
+
});
|
|
512
|
+
const organization = await new OrganizationFactory({
|
|
513
|
+
meta: OrganizationMetaData.create({
|
|
514
|
+
recordsConfiguration: OrganizationRecordsConfiguration.create({
|
|
515
|
+
recordCategories: [recordCategory],
|
|
516
|
+
}),
|
|
517
|
+
}),
|
|
518
|
+
}).create();
|
|
519
|
+
|
|
520
|
+
const group = await new GroupFactory({ organization }).create();
|
|
521
|
+
|
|
522
|
+
const user = await new UserFactory({
|
|
523
|
+
permissions: Permissions.create({
|
|
524
|
+
level: PermissionLevel.None,
|
|
525
|
+
resources: new Map([
|
|
526
|
+
[PermissionsResourceType.Groups, new Map([
|
|
527
|
+
[group.id, ResourcePermissions.create({ level: PermissionLevel.Full })],
|
|
528
|
+
])],
|
|
529
|
+
]),
|
|
530
|
+
}),
|
|
531
|
+
organization, // since we are in platform mode, this will only set the permissions for this organization
|
|
532
|
+
}).create();
|
|
533
|
+
|
|
534
|
+
const member = await new MemberFactory({
|
|
535
|
+
firstName,
|
|
536
|
+
lastName,
|
|
537
|
+
birthDay,
|
|
538
|
+
generateData: false,
|
|
539
|
+
}).create();
|
|
540
|
+
|
|
541
|
+
// Register this member
|
|
542
|
+
await new RegistrationFactory({
|
|
543
|
+
member,
|
|
544
|
+
group,
|
|
545
|
+
}).create();
|
|
546
|
+
|
|
547
|
+
const token = await Token.createToken(user);
|
|
548
|
+
|
|
549
|
+
const recordAnswers = new PatchMap() as PatchAnswers;
|
|
550
|
+
|
|
551
|
+
recordAnswers.set(commentsRecord.id, RecordTextAnswer.create({
|
|
552
|
+
settings: commentsRecord,
|
|
553
|
+
value: 'Some comments',
|
|
554
|
+
}));
|
|
555
|
+
|
|
556
|
+
const arr: Body = new PatchableArray();
|
|
557
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
558
|
+
id: member.id,
|
|
559
|
+
details: MemberDetails.patch({
|
|
560
|
+
recordAnswers,
|
|
561
|
+
}),
|
|
562
|
+
});
|
|
563
|
+
arr.addPatch(patch);
|
|
564
|
+
|
|
565
|
+
const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
|
|
566
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
567
|
+
await expect(testServer.test(endpoint, request)).rejects.toThrow(errorWithCode('permission_denied'));
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
test('An admin can set records of the platform', async () => {
|
|
571
|
+
const commentsRecord = RecordSettings.create({
|
|
572
|
+
name: 'Opmerkingen',
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
const recordCategory = RecordCategory.create({
|
|
576
|
+
name: 'Medische fiche',
|
|
577
|
+
records: [
|
|
578
|
+
commentsRecord,
|
|
579
|
+
],
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
const platform = await Platform.getForEditing();
|
|
583
|
+
platform.config.recordsConfiguration.recordCategories.push(recordCategory);
|
|
584
|
+
await platform.save();
|
|
585
|
+
|
|
586
|
+
const organization = await new OrganizationFactory({}).create();
|
|
587
|
+
const group = await new GroupFactory({ organization }).create();
|
|
588
|
+
|
|
589
|
+
const user = await new UserFactory({
|
|
590
|
+
permissions: Permissions.create({
|
|
591
|
+
level: PermissionLevel.None,
|
|
592
|
+
resources: new Map([
|
|
593
|
+
[PermissionsResourceType.RecordCategories, new Map([
|
|
594
|
+
[recordCategory.id, ResourcePermissions.create({ level: PermissionLevel.Write })],
|
|
595
|
+
])],
|
|
596
|
+
[PermissionsResourceType.Groups, new Map([
|
|
597
|
+
[group.id, ResourcePermissions.create({ level: PermissionLevel.Full })],
|
|
598
|
+
])],
|
|
599
|
+
]),
|
|
600
|
+
}),
|
|
601
|
+
organization, // since we are in platform mode, this will only set the permissions for this organization
|
|
602
|
+
}).create();
|
|
603
|
+
|
|
604
|
+
const member = await new MemberFactory({
|
|
605
|
+
firstName,
|
|
606
|
+
lastName,
|
|
607
|
+
birthDay,
|
|
608
|
+
generateData: false,
|
|
609
|
+
}).create();
|
|
610
|
+
|
|
611
|
+
// Register this member
|
|
612
|
+
await new RegistrationFactory({
|
|
613
|
+
member,
|
|
614
|
+
group,
|
|
615
|
+
}).create();
|
|
616
|
+
|
|
617
|
+
const token = await Token.createToken(user);
|
|
618
|
+
|
|
619
|
+
const recordAnswers = new PatchMap() as PatchAnswers;
|
|
620
|
+
|
|
621
|
+
recordAnswers.set(commentsRecord.id, RecordTextAnswer.create({
|
|
622
|
+
settings: commentsRecord,
|
|
623
|
+
value: 'Some comments',
|
|
624
|
+
}));
|
|
625
|
+
|
|
626
|
+
const arr: Body = new PatchableArray();
|
|
627
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
628
|
+
id: member.id,
|
|
629
|
+
details: MemberDetails.patch({
|
|
630
|
+
recordAnswers,
|
|
631
|
+
}),
|
|
632
|
+
});
|
|
633
|
+
arr.addPatch(patch);
|
|
634
|
+
|
|
635
|
+
const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
|
|
636
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
637
|
+
const response = await testServer.test(endpoint, request);
|
|
638
|
+
|
|
639
|
+
// Check returned
|
|
640
|
+
expect(response.status).toBe(200);
|
|
641
|
+
expect(response.body.members.length).toBe(1);
|
|
642
|
+
const struct = response.body.members[0];
|
|
643
|
+
expect(struct.details.recordAnswers.get(commentsRecord.id)).toMatchObject({
|
|
644
|
+
value: 'Some comments',
|
|
645
|
+
});
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
test('[Regression] A platform admin with tag-access to an organization can change platform records', async () => {
|
|
649
|
+
const commentsRecord = RecordSettings.create({
|
|
650
|
+
name: 'Opmerkingen',
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
const recordCategory = RecordCategory.create({
|
|
654
|
+
name: 'Medische fiche',
|
|
655
|
+
records: [
|
|
656
|
+
commentsRecord,
|
|
657
|
+
],
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
const platform = await Platform.getForEditing();
|
|
661
|
+
platform.config.recordsConfiguration.recordCategories.push(recordCategory);
|
|
662
|
+
await platform.save();
|
|
663
|
+
|
|
664
|
+
const tag = await new OrganizationTagFactory({}).create();
|
|
665
|
+
const organization = await new OrganizationFactory({
|
|
666
|
+
tags: [tag.id],
|
|
667
|
+
}).create();
|
|
668
|
+
const group = await new GroupFactory({ organization }).create();
|
|
669
|
+
|
|
670
|
+
const user = await new UserFactory({
|
|
671
|
+
globalPermissions: Permissions.create({
|
|
672
|
+
level: PermissionLevel.None,
|
|
673
|
+
resources: new Map([
|
|
674
|
+
[PermissionsResourceType.OrganizationTags, new Map([
|
|
675
|
+
[tag.id, ResourcePermissions.create({ level: PermissionLevel.Full })],
|
|
676
|
+
])],
|
|
677
|
+
]),
|
|
678
|
+
}),
|
|
679
|
+
organization, // since we are in platform mode, this will only set the permissions for this organization
|
|
680
|
+
}).create();
|
|
681
|
+
|
|
682
|
+
const member = await new MemberFactory({
|
|
683
|
+
firstName,
|
|
684
|
+
lastName,
|
|
685
|
+
birthDay,
|
|
686
|
+
generateData: false,
|
|
687
|
+
}).create();
|
|
688
|
+
|
|
689
|
+
// Register this member
|
|
690
|
+
await new RegistrationFactory({
|
|
691
|
+
member,
|
|
692
|
+
group,
|
|
693
|
+
}).create();
|
|
694
|
+
|
|
695
|
+
const token = await Token.createToken(user);
|
|
696
|
+
|
|
697
|
+
const recordAnswers = new PatchMap() as PatchAnswers;
|
|
698
|
+
|
|
699
|
+
recordAnswers.set(commentsRecord.id, RecordTextAnswer.create({
|
|
700
|
+
settings: commentsRecord,
|
|
701
|
+
value: 'Some comments',
|
|
702
|
+
}));
|
|
703
|
+
|
|
704
|
+
const arr: Body = new PatchableArray();
|
|
705
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
706
|
+
id: member.id,
|
|
707
|
+
details: MemberDetails.patch({
|
|
708
|
+
recordAnswers,
|
|
709
|
+
}),
|
|
710
|
+
});
|
|
711
|
+
arr.addPatch(patch);
|
|
712
|
+
|
|
713
|
+
const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
|
|
714
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
715
|
+
const response = await testServer.test(endpoint, request);
|
|
716
|
+
|
|
717
|
+
// Check returned
|
|
718
|
+
expect(response.status).toBe(200);
|
|
719
|
+
expect(response.body.members.length).toBe(1);
|
|
720
|
+
const struct = response.body.members[0];
|
|
721
|
+
expect(struct.details.recordAnswers.get(commentsRecord.id)).toMatchObject({
|
|
722
|
+
value: 'Some comments',
|
|
723
|
+
});
|
|
724
|
+
});
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
describe('Parents', () => {
|
|
728
|
+
test('Setting updatedAt for one parent, changes it for the whole family', async () => {
|
|
729
|
+
const user = await new UserFactory({}).create();
|
|
730
|
+
|
|
731
|
+
const parent1 = Parent.create({
|
|
732
|
+
firstName: 'Linda',
|
|
733
|
+
lastName: 'Doe',
|
|
734
|
+
email: 'linda@example.com',
|
|
735
|
+
alternativeEmails: ['linda@work.com'],
|
|
736
|
+
phone: '+32412345678',
|
|
737
|
+
address: Address.create({
|
|
738
|
+
street: 'Main street 1',
|
|
739
|
+
postalCode: '1000',
|
|
740
|
+
city: 'Brussels',
|
|
741
|
+
country: Country.Belgium,
|
|
742
|
+
}),
|
|
743
|
+
nationalRegisterNumber: '93042012345',
|
|
744
|
+
updatedAt: new Date(0),
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
const member1 = await new MemberFactory({
|
|
748
|
+
user,
|
|
749
|
+
details: MemberDetails.create({
|
|
750
|
+
firstName: 'John',
|
|
751
|
+
lastName: 'Doe',
|
|
752
|
+
parents: [parent1],
|
|
753
|
+
}),
|
|
754
|
+
}).create();
|
|
755
|
+
|
|
756
|
+
const member2 = await new MemberFactory({
|
|
757
|
+
user,
|
|
758
|
+
details: MemberDetails.create({
|
|
759
|
+
firstName: 'Jane',
|
|
760
|
+
lastName: 'Doe',
|
|
761
|
+
parents: [parent1],
|
|
762
|
+
}),
|
|
763
|
+
}).create();
|
|
764
|
+
|
|
765
|
+
// Parent3 was reviewed last, so has priority
|
|
766
|
+
const member3 = await new MemberFactory({
|
|
767
|
+
user,
|
|
768
|
+
details: MemberDetails.create({
|
|
769
|
+
firstName: 'Bob',
|
|
770
|
+
lastName: 'Doe',
|
|
771
|
+
parents: [parent1],
|
|
772
|
+
}),
|
|
773
|
+
}).create();
|
|
774
|
+
|
|
775
|
+
// Now simulate a change to member1's parents, and check if all parents are updated to the same id and details
|
|
776
|
+
const admin = await new UserFactory({
|
|
777
|
+
globalPermissions: Permissions.create({ level: PermissionLevel.Full }),
|
|
778
|
+
}).create();
|
|
779
|
+
const token = await Token.createToken(admin);
|
|
780
|
+
|
|
781
|
+
const arr: Body = new PatchableArray();
|
|
782
|
+
const d = new Date();
|
|
783
|
+
const parentsPatch = new PatchableArray() as PatchableArrayAutoEncoder<Parent>;
|
|
784
|
+
parentsPatch.addPatch(
|
|
785
|
+
Parent.patch({
|
|
786
|
+
id: parent1.id,
|
|
787
|
+
updatedAt: d,
|
|
788
|
+
}),
|
|
789
|
+
);
|
|
790
|
+
|
|
791
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
792
|
+
id: member1.id,
|
|
793
|
+
details: MemberDetails.patch({
|
|
794
|
+
parents: parentsPatch,
|
|
795
|
+
}),
|
|
796
|
+
});
|
|
797
|
+
arr.addPatch(patch);
|
|
798
|
+
|
|
799
|
+
const request = Request.buildJson('PATCH', baseUrl, undefined, arr);
|
|
800
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
801
|
+
const response = await testServer.test(endpoint, request);
|
|
802
|
+
|
|
803
|
+
// Check returned
|
|
804
|
+
expect(response.status).toBe(200);
|
|
805
|
+
expect(response.body.members.length).toBe(1);
|
|
806
|
+
|
|
807
|
+
// Load parents again
|
|
808
|
+
await member1.refresh();
|
|
809
|
+
await member2.refresh();
|
|
810
|
+
await member3.refresh();
|
|
811
|
+
|
|
812
|
+
// Check all parents equal
|
|
813
|
+
const expectedParent = Parent.create({
|
|
814
|
+
...parent1,
|
|
815
|
+
updatedAt: d,
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
expect(member1.details.parents).toEqual([expectedParent]);
|
|
819
|
+
expect(member2.details.parents).toEqual([expectedParent]);
|
|
820
|
+
expect(member3.details.parents).toEqual([expectedParent]);
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
test('Patches without updatedAt are handled correctly and applied to the most recent parent', async () => {
|
|
824
|
+
const user = await new UserFactory({}).create();
|
|
825
|
+
|
|
826
|
+
const mostRecentParent = Parent.create({
|
|
827
|
+
firstName: 'Linda',
|
|
828
|
+
lastName: 'Doe',
|
|
829
|
+
email: 'linda@example.com',
|
|
830
|
+
alternativeEmails: ['linda@work.com'],
|
|
831
|
+
phone: '+32412345678',
|
|
832
|
+
address: Address.create({
|
|
833
|
+
street: 'Main street 1',
|
|
834
|
+
postalCode: '1000',
|
|
835
|
+
city: 'Brussels',
|
|
836
|
+
country: Country.Belgium,
|
|
837
|
+
}),
|
|
838
|
+
nationalRegisterNumber: '93042012347',
|
|
839
|
+
updatedAt: new Date(10_000),
|
|
840
|
+
createdAt: new Date(0),
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
const outOfDateParent = Parent.create({
|
|
844
|
+
id: mostRecentParent.id,
|
|
845
|
+
firstName: 'Linda',
|
|
846
|
+
lastName: 'Doe',
|
|
847
|
+
email: 'linda@example.com',
|
|
848
|
+
alternativeEmails: ['linda@work.com'],
|
|
849
|
+
phone: '+32412345678',
|
|
850
|
+
address: Address.create({
|
|
851
|
+
street: 'Main street 1',
|
|
852
|
+
postalCode: '1000',
|
|
853
|
+
city: 'Brussels',
|
|
854
|
+
country: Country.Belgium,
|
|
855
|
+
}),
|
|
856
|
+
nationalRegisterNumber: '93042012345',
|
|
857
|
+
updatedAt: new Date(0),
|
|
858
|
+
createdAt: new Date(200),
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
const member1 = await new MemberFactory({
|
|
862
|
+
user,
|
|
863
|
+
details: MemberDetails.create({
|
|
864
|
+
firstName: 'John',
|
|
865
|
+
lastName: 'Doe',
|
|
866
|
+
parents: [outOfDateParent],
|
|
867
|
+
}),
|
|
868
|
+
}).create();
|
|
869
|
+
|
|
870
|
+
const member2 = await new MemberFactory({
|
|
871
|
+
user,
|
|
872
|
+
details: MemberDetails.create({
|
|
873
|
+
firstName: 'Jane',
|
|
874
|
+
lastName: 'Doe',
|
|
875
|
+
parents: [outOfDateParent],
|
|
876
|
+
}),
|
|
877
|
+
}).create();
|
|
878
|
+
|
|
879
|
+
// Parent3 was reviewed last, so has priority
|
|
880
|
+
const member3 = await new MemberFactory({
|
|
881
|
+
user,
|
|
882
|
+
details: MemberDetails.create({
|
|
883
|
+
firstName: 'Bob',
|
|
884
|
+
lastName: 'Doe',
|
|
885
|
+
parents: [mostRecentParent],
|
|
886
|
+
}),
|
|
887
|
+
}).create();
|
|
888
|
+
|
|
889
|
+
// Now simulate a change to member1's parents, and check if all parents are updated to the same id and details
|
|
890
|
+
const admin = await new UserFactory({
|
|
891
|
+
globalPermissions: Permissions.create({ level: PermissionLevel.Full }),
|
|
892
|
+
}).create();
|
|
893
|
+
const token = await Token.createToken(admin);
|
|
894
|
+
|
|
895
|
+
const arr: Body = new PatchableArray();
|
|
896
|
+
const parentsPatch = new PatchableArray() as PatchableArrayAutoEncoder<Parent>;
|
|
897
|
+
parentsPatch.addPatch(
|
|
898
|
+
Parent.patch({
|
|
899
|
+
id: outOfDateParent.id,
|
|
900
|
+
email: 'changed@example.com',
|
|
901
|
+
}),
|
|
902
|
+
);
|
|
903
|
+
|
|
904
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
905
|
+
id: member2.id,
|
|
906
|
+
details: MemberDetails.patch({
|
|
907
|
+
parents: parentsPatch,
|
|
908
|
+
}),
|
|
909
|
+
});
|
|
910
|
+
arr.addPatch(patch);
|
|
911
|
+
|
|
912
|
+
const request = Request.buildJson('PATCH', baseUrl, undefined, arr);
|
|
913
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
914
|
+
const response = await testServer.test(endpoint, request);
|
|
915
|
+
|
|
916
|
+
// Check returned
|
|
917
|
+
expect(response.status).toBe(200);
|
|
918
|
+
expect(response.body.members.length).toBe(1);
|
|
919
|
+
|
|
920
|
+
// Load parents again
|
|
921
|
+
await member1.refresh();
|
|
922
|
+
await member2.refresh();
|
|
923
|
+
await member3.refresh();
|
|
924
|
+
|
|
925
|
+
// Check all parents equal
|
|
926
|
+
const expectedParent = Parent.create({
|
|
927
|
+
...mostRecentParent,
|
|
928
|
+
email: 'changed@example.com',
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
expect(member1.details.parents).toEqual([expectedParent]);
|
|
932
|
+
expect(member2.details.parents).toEqual([expectedParent]);
|
|
933
|
+
expect(member3.details.parents).toEqual([expectedParent]);
|
|
934
|
+
});
|
|
935
|
+
|
|
936
|
+
test('Patches without updatedAt are handled correctly and applied to the most recent parent when merging', async () => {
|
|
937
|
+
const user = await new UserFactory({}).create();
|
|
938
|
+
|
|
939
|
+
const mostRecentParent = Parent.create({
|
|
940
|
+
firstName: 'Linda',
|
|
941
|
+
lastName: 'Doe',
|
|
942
|
+
email: 'linda@example.com',
|
|
943
|
+
alternativeEmails: ['linda@work.com'],
|
|
944
|
+
phone: '+32412345678',
|
|
945
|
+
address: Address.create({
|
|
946
|
+
street: 'Main street 1',
|
|
947
|
+
postalCode: '1000',
|
|
948
|
+
city: 'Brussels',
|
|
949
|
+
country: Country.Belgium,
|
|
950
|
+
}),
|
|
951
|
+
nationalRegisterNumber: '93042012347',
|
|
952
|
+
updatedAt: new Date(10_000),
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
const outOfDateParent = Parent.create({
|
|
956
|
+
firstName: 'Linda',
|
|
957
|
+
lastName: 'Doe',
|
|
958
|
+
email: 'linda@example.com',
|
|
959
|
+
alternativeEmails: ['linda+oldest@work.com'],
|
|
960
|
+
phone: '+32412345678',
|
|
961
|
+
address: Address.create({
|
|
962
|
+
street: 'Main street 1',
|
|
963
|
+
postalCode: '1000',
|
|
964
|
+
city: 'Brussels',
|
|
965
|
+
country: Country.Belgium,
|
|
966
|
+
}),
|
|
967
|
+
nationalRegisterNumber: '93042012345',
|
|
968
|
+
updatedAt: new Date(0),
|
|
969
|
+
createdAt: new Date(0),
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
const outOfDateParent2 = Parent.create({
|
|
973
|
+
firstName: 'Linda',
|
|
974
|
+
lastName: 'Doe',
|
|
975
|
+
email: 'linda@example.com',
|
|
976
|
+
alternativeEmails: ['linda@work.com'],
|
|
977
|
+
phone: '+32412345678',
|
|
978
|
+
address: Address.create({
|
|
979
|
+
street: 'Main street 1',
|
|
980
|
+
postalCode: '1000',
|
|
981
|
+
city: 'Brussels',
|
|
982
|
+
country: Country.Belgium,
|
|
983
|
+
}),
|
|
984
|
+
nationalRegisterNumber: '93042012345',
|
|
985
|
+
updatedAt: new Date(1000),
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
const member1 = await new MemberFactory({
|
|
989
|
+
user,
|
|
990
|
+
details: MemberDetails.create({
|
|
991
|
+
firstName: 'John',
|
|
992
|
+
lastName: 'Doe',
|
|
993
|
+
parents: [outOfDateParent],
|
|
994
|
+
}),
|
|
995
|
+
}).create();
|
|
996
|
+
|
|
997
|
+
const member2 = await new MemberFactory({
|
|
998
|
+
user,
|
|
999
|
+
details: MemberDetails.create({
|
|
1000
|
+
firstName: 'Jane',
|
|
1001
|
+
lastName: 'Doe',
|
|
1002
|
+
parents: [outOfDateParent2],
|
|
1003
|
+
}),
|
|
1004
|
+
}).create();
|
|
1005
|
+
|
|
1006
|
+
// Parent3 was reviewed last, so has priority
|
|
1007
|
+
const member3 = await new MemberFactory({
|
|
1008
|
+
user,
|
|
1009
|
+
details: MemberDetails.create({
|
|
1010
|
+
firstName: 'Bob',
|
|
1011
|
+
lastName: 'Doe',
|
|
1012
|
+
parents: [mostRecentParent],
|
|
1013
|
+
}),
|
|
1014
|
+
}).create();
|
|
1015
|
+
|
|
1016
|
+
// Now simulate a change to member1's parents, and check if all parents are updated to the same id and details
|
|
1017
|
+
const admin = await new UserFactory({
|
|
1018
|
+
globalPermissions: Permissions.create({ level: PermissionLevel.Full }),
|
|
1019
|
+
}).create();
|
|
1020
|
+
const token = await Token.createToken(admin);
|
|
1021
|
+
|
|
1022
|
+
const arr: Body = new PatchableArray();
|
|
1023
|
+
const parentsPatch = new PatchableArray() as PatchableArrayAutoEncoder<Parent>;
|
|
1024
|
+
parentsPatch.addPatch(
|
|
1025
|
+
Parent.patch({
|
|
1026
|
+
id: outOfDateParent.id,
|
|
1027
|
+
email: 'changed@example.com',
|
|
1028
|
+
}),
|
|
1029
|
+
);
|
|
1030
|
+
|
|
1031
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
1032
|
+
id: member2.id,
|
|
1033
|
+
details: MemberDetails.patch({
|
|
1034
|
+
parents: parentsPatch,
|
|
1035
|
+
}),
|
|
1036
|
+
});
|
|
1037
|
+
arr.addPatch(patch);
|
|
1038
|
+
|
|
1039
|
+
const request = Request.buildJson('PATCH', baseUrl, undefined, arr);
|
|
1040
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
1041
|
+
const response = await testServer.test(endpoint, request);
|
|
1042
|
+
|
|
1043
|
+
// Check returned
|
|
1044
|
+
expect(response.status).toBe(200);
|
|
1045
|
+
expect(response.body.members.length).toBe(1);
|
|
1046
|
+
|
|
1047
|
+
// Load parents again
|
|
1048
|
+
await member1.refresh();
|
|
1049
|
+
await member2.refresh();
|
|
1050
|
+
await member3.refresh();
|
|
1051
|
+
|
|
1052
|
+
// Check all parents equal
|
|
1053
|
+
const expectedParent = Parent.create({
|
|
1054
|
+
...mostRecentParent,
|
|
1055
|
+
id: outOfDateParent.id, // the oldest parent id is use
|
|
1056
|
+
createdAt: outOfDateParent.createdAt,
|
|
1057
|
+
email: 'changed@example.com',
|
|
1058
|
+
alternativeEmails: [
|
|
1059
|
+
'linda@work.com',
|
|
1060
|
+
'linda+oldest@work.com',
|
|
1061
|
+
],
|
|
1062
|
+
});
|
|
1063
|
+
|
|
1064
|
+
expect(member1.details.parents).toEqual([expectedParent]);
|
|
1065
|
+
expect(member2.details.parents).toEqual([expectedParent]);
|
|
1066
|
+
expect(member3.details.parents).toEqual([expectedParent]);
|
|
1067
|
+
});
|
|
1068
|
+
|
|
1069
|
+
test('Puts without updatedAt are handled correctly and applied to the most recent parent when merging', async () => {
|
|
1070
|
+
const user = await new UserFactory({}).create();
|
|
1071
|
+
|
|
1072
|
+
const mostRecentParent = Parent.create({
|
|
1073
|
+
firstName: 'Linda',
|
|
1074
|
+
lastName: 'Doe',
|
|
1075
|
+
email: 'linda@example.com',
|
|
1076
|
+
alternativeEmails: ['linda@work.com'],
|
|
1077
|
+
phone: '+32412345678',
|
|
1078
|
+
address: Address.create({
|
|
1079
|
+
street: 'Main street 1',
|
|
1080
|
+
postalCode: '1000',
|
|
1081
|
+
city: 'Brussels',
|
|
1082
|
+
country: Country.Belgium,
|
|
1083
|
+
}),
|
|
1084
|
+
nationalRegisterNumber: '93042012347',
|
|
1085
|
+
updatedAt: new Date(10_000),
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
const outOfDateParent = Parent.create({
|
|
1089
|
+
firstName: 'Linda',
|
|
1090
|
+
lastName: 'Doe',
|
|
1091
|
+
email: 'linda@example.com',
|
|
1092
|
+
alternativeEmails: ['linda+oldest@work.com'],
|
|
1093
|
+
phone: '+32412345678',
|
|
1094
|
+
address: Address.create({
|
|
1095
|
+
street: 'Main street 1',
|
|
1096
|
+
postalCode: '1000',
|
|
1097
|
+
city: 'Brussels',
|
|
1098
|
+
country: Country.Belgium,
|
|
1099
|
+
}),
|
|
1100
|
+
nationalRegisterNumber: '93042012345',
|
|
1101
|
+
updatedAt: new Date(1000),
|
|
1102
|
+
createdAt: new Date(0),
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
const member1 = await new MemberFactory({
|
|
1106
|
+
user,
|
|
1107
|
+
details: MemberDetails.create({
|
|
1108
|
+
firstName: 'John',
|
|
1109
|
+
lastName: 'Doe',
|
|
1110
|
+
parents: [outOfDateParent],
|
|
1111
|
+
}),
|
|
1112
|
+
}).create();
|
|
1113
|
+
|
|
1114
|
+
const member2 = await new MemberFactory({
|
|
1115
|
+
user,
|
|
1116
|
+
details: MemberDetails.create({
|
|
1117
|
+
firstName: 'Jane',
|
|
1118
|
+
lastName: 'Doe',
|
|
1119
|
+
parents: [],
|
|
1120
|
+
}),
|
|
1121
|
+
}).create();
|
|
1122
|
+
|
|
1123
|
+
// Parent3 was reviewed last, so has priority
|
|
1124
|
+
const member3 = await new MemberFactory({
|
|
1125
|
+
user,
|
|
1126
|
+
details: MemberDetails.create({
|
|
1127
|
+
firstName: 'Bob',
|
|
1128
|
+
lastName: 'Doe',
|
|
1129
|
+
parents: [mostRecentParent],
|
|
1130
|
+
}),
|
|
1131
|
+
}).create();
|
|
1132
|
+
|
|
1133
|
+
// Now simulate a change to member1's parents, and check if all parents are updated to the same id and details
|
|
1134
|
+
const admin = await new UserFactory({
|
|
1135
|
+
globalPermissions: Permissions.create({ level: PermissionLevel.Full }),
|
|
1136
|
+
}).create();
|
|
1137
|
+
const token = await Token.createToken(admin);
|
|
1138
|
+
|
|
1139
|
+
const arr: Body = new PatchableArray();
|
|
1140
|
+
const parentsPatch = new PatchableArray() as PatchableArrayAutoEncoder<Parent>;
|
|
1141
|
+
const createdParent = Parent.create({
|
|
1142
|
+
firstName: 'Linda',
|
|
1143
|
+
lastName: 'Doe',
|
|
1144
|
+
updatedAt: new Date(0),
|
|
1145
|
+
email: 'changed@example.com',
|
|
1146
|
+
phone: '+3241111111',
|
|
1147
|
+
alternativeEmails: ['another-alternative-email@example.com'],
|
|
1148
|
+
});
|
|
1149
|
+
parentsPatch.addPut(createdParent);
|
|
1150
|
+
|
|
1151
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
1152
|
+
id: member2.id,
|
|
1153
|
+
details: MemberDetails.patch({
|
|
1154
|
+
parents: parentsPatch,
|
|
1155
|
+
}),
|
|
1156
|
+
});
|
|
1157
|
+
arr.addPatch(patch);
|
|
1158
|
+
|
|
1159
|
+
const request = Request.buildJson('PATCH', baseUrl, undefined, arr);
|
|
1160
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
1161
|
+
const response = await testServer.test(endpoint, request);
|
|
1162
|
+
|
|
1163
|
+
// Check returned
|
|
1164
|
+
expect(response.status).toBe(200);
|
|
1165
|
+
expect(response.body.members.length).toBe(1);
|
|
1166
|
+
|
|
1167
|
+
// Load parents again
|
|
1168
|
+
await member1.refresh();
|
|
1169
|
+
await member2.refresh();
|
|
1170
|
+
await member3.refresh();
|
|
1171
|
+
|
|
1172
|
+
// Check all parents equal
|
|
1173
|
+
const expectedParent = Parent.create({
|
|
1174
|
+
...mostRecentParent,
|
|
1175
|
+
id: outOfDateParent.id, // the oldest parent id is use
|
|
1176
|
+
createdAt: outOfDateParent.createdAt,
|
|
1177
|
+
email: 'changed@example.com',
|
|
1178
|
+
phone: '+3241111111',
|
|
1179
|
+
alternativeEmails: [
|
|
1180
|
+
'linda@work.com',
|
|
1181
|
+
'linda+oldest@work.com',
|
|
1182
|
+
'another-alternative-email@example.com',
|
|
1183
|
+
],
|
|
1184
|
+
});
|
|
1185
|
+
|
|
1186
|
+
expect(member1.details.parents).toEqual([expectedParent]);
|
|
1187
|
+
expect(member2.details.parents).toEqual([expectedParent]);
|
|
1188
|
+
expect(member3.details.parents).toEqual([expectedParent]);
|
|
1189
|
+
});
|
|
1190
|
+
|
|
1191
|
+
test('Duplicate parents are merged in a family', async () => {
|
|
1192
|
+
const user = await new UserFactory({}).create();
|
|
1193
|
+
|
|
1194
|
+
const parent1 = Parent.create({
|
|
1195
|
+
firstName: 'Linda',
|
|
1196
|
+
lastName: 'Doe',
|
|
1197
|
+
email: 'linda@example.com',
|
|
1198
|
+
alternativeEmails: ['linda@work.com'],
|
|
1199
|
+
phone: '+32412345678',
|
|
1200
|
+
address: Address.create({
|
|
1201
|
+
street: 'Main street 1',
|
|
1202
|
+
postalCode: '1000',
|
|
1203
|
+
city: 'Brussels',
|
|
1204
|
+
country: Country.Belgium,
|
|
1205
|
+
}),
|
|
1206
|
+
nationalRegisterNumber: '93042012345',
|
|
1207
|
+
createdAt: new Date(0),
|
|
1208
|
+
});
|
|
1209
|
+
|
|
1210
|
+
// Create two clones of this parent with a different ID
|
|
1211
|
+
const parent2 = Parent.create({
|
|
1212
|
+
firstName: 'Linda',
|
|
1213
|
+
lastName: 'Doe',
|
|
1214
|
+
createdAt: new Date(1000),
|
|
1215
|
+
});
|
|
1216
|
+
|
|
1217
|
+
const parent3 = Parent.create({
|
|
1218
|
+
firstName: 'Linda',
|
|
1219
|
+
lastName: 'Doe',
|
|
1220
|
+
email: 'linda@example.com',
|
|
1221
|
+
alternativeEmails: ['linda@work2.com'],
|
|
1222
|
+
phone: '+32412345679',
|
|
1223
|
+
address: Address.create({
|
|
1224
|
+
street: 'Main street 2',
|
|
1225
|
+
postalCode: '1000',
|
|
1226
|
+
city: 'Brussels',
|
|
1227
|
+
country: Country.Belgium,
|
|
1228
|
+
}),
|
|
1229
|
+
nationalRegisterNumber: '93042012348',
|
|
1230
|
+
createdAt: new Date(2000),
|
|
1231
|
+
});
|
|
1232
|
+
|
|
1233
|
+
const member1 = await new MemberFactory({
|
|
1234
|
+
user,
|
|
1235
|
+
details: MemberDetails.create({
|
|
1236
|
+
firstName: 'John',
|
|
1237
|
+
lastName: 'Doe',
|
|
1238
|
+
parents: [parent1],
|
|
1239
|
+
reviewTimes: ReviewTimes.create({
|
|
1240
|
+
times: [
|
|
1241
|
+
ReviewTime.create({
|
|
1242
|
+
name: 'parents',
|
|
1243
|
+
reviewedAt: new Date(0),
|
|
1244
|
+
}),
|
|
1245
|
+
],
|
|
1246
|
+
}),
|
|
1247
|
+
}),
|
|
1248
|
+
}).create();
|
|
1249
|
+
|
|
1250
|
+
const member2 = await new MemberFactory({
|
|
1251
|
+
user,
|
|
1252
|
+
details: MemberDetails.create({
|
|
1253
|
+
firstName: 'Jane',
|
|
1254
|
+
lastName: 'Doe',
|
|
1255
|
+
parents: [parent2],
|
|
1256
|
+
reviewTimes: ReviewTimes.create({
|
|
1257
|
+
times: [
|
|
1258
|
+
ReviewTime.create({
|
|
1259
|
+
name: 'parents',
|
|
1260
|
+
reviewedAt: new Date(1000),
|
|
1261
|
+
}),
|
|
1262
|
+
],
|
|
1263
|
+
}),
|
|
1264
|
+
}),
|
|
1265
|
+
}).create();
|
|
1266
|
+
|
|
1267
|
+
// Parent3 was reviewed last, so has priority
|
|
1268
|
+
const member3 = await new MemberFactory({
|
|
1269
|
+
user,
|
|
1270
|
+
details: MemberDetails.create({
|
|
1271
|
+
firstName: 'Bob',
|
|
1272
|
+
lastName: 'Doe',
|
|
1273
|
+
parents: [parent3],
|
|
1274
|
+
reviewTimes: ReviewTimes.create({
|
|
1275
|
+
times: [
|
|
1276
|
+
ReviewTime.create({
|
|
1277
|
+
name: 'parents',
|
|
1278
|
+
reviewedAt: new Date(2000),
|
|
1279
|
+
}),
|
|
1280
|
+
],
|
|
1281
|
+
}),
|
|
1282
|
+
}),
|
|
1283
|
+
}).create();
|
|
1284
|
+
|
|
1285
|
+
// Now simulate a change to member1's parents, and check if all parents are updated to the same id and details
|
|
1286
|
+
const admin = await new UserFactory({
|
|
1287
|
+
globalPermissions: Permissions.create({ level: PermissionLevel.Full }),
|
|
1288
|
+
}).create();
|
|
1289
|
+
const token = await Token.createToken(admin);
|
|
1290
|
+
|
|
1291
|
+
const nonEmtpyArray = new PatchableArray() as PatchableArrayAutoEncoder<Parent>;
|
|
1292
|
+
nonEmtpyArray.addPatch(Parent.patch({
|
|
1293
|
+
id: parent1.id,
|
|
1294
|
+
// no changes
|
|
1295
|
+
}));
|
|
1296
|
+
|
|
1297
|
+
const arr: Body = new PatchableArray();
|
|
1298
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
1299
|
+
id: member1.id,
|
|
1300
|
+
details: MemberDetails.patch({
|
|
1301
|
+
parents: nonEmtpyArray,
|
|
1302
|
+
}),
|
|
1303
|
+
});
|
|
1304
|
+
arr.addPatch(patch);
|
|
1305
|
+
|
|
1306
|
+
const request = Request.buildJson('PATCH', baseUrl, undefined, arr);
|
|
1307
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
1308
|
+
const response = await testServer.test(endpoint, request);
|
|
1309
|
+
|
|
1310
|
+
// Check returned
|
|
1311
|
+
expect(response.status).toBe(200);
|
|
1312
|
+
expect(response.body.members.length).toBe(1);
|
|
1313
|
+
|
|
1314
|
+
// Load parents again
|
|
1315
|
+
await member1.refresh();
|
|
1316
|
+
await member2.refresh();
|
|
1317
|
+
await member3.refresh();
|
|
1318
|
+
|
|
1319
|
+
// Check all parents equal
|
|
1320
|
+
const expectedParent = Parent.create({
|
|
1321
|
+
...parent3,
|
|
1322
|
+
id: parent1.id, // the oldest parent id is use
|
|
1323
|
+
createdAt: parent1.createdAt,
|
|
1324
|
+
alternativeEmails: [
|
|
1325
|
+
'linda@work2.com',
|
|
1326
|
+
'linda@work.com',
|
|
1327
|
+
],
|
|
1328
|
+
});
|
|
1329
|
+
|
|
1330
|
+
expect(member1.details.parents).toEqual([expectedParent]);
|
|
1331
|
+
expect(member2.details.parents).toEqual([expectedParent]);
|
|
1332
|
+
expect(member3.details.parents).toEqual([expectedParent]);
|
|
1333
|
+
});
|
|
1334
|
+
|
|
1335
|
+
test('Deleting a parent alternative email address is possible in a family if all parents have the same ID', async () => {
|
|
1336
|
+
const user = await new UserFactory({}).create();
|
|
1337
|
+
|
|
1338
|
+
const parent1 = Parent.create({
|
|
1339
|
+
firstName: 'Linda',
|
|
1340
|
+
lastName: 'Doe',
|
|
1341
|
+
email: 'linda@example.com',
|
|
1342
|
+
alternativeEmails: ['linda@work.com'],
|
|
1343
|
+
phone: '+32412345678',
|
|
1344
|
+
address: Address.create({
|
|
1345
|
+
street: 'Main street 1',
|
|
1346
|
+
postalCode: '1000',
|
|
1347
|
+
city: 'Brussels',
|
|
1348
|
+
country: Country.Belgium,
|
|
1349
|
+
}),
|
|
1350
|
+
nationalRegisterNumber: '93042012345',
|
|
1351
|
+
});
|
|
1352
|
+
|
|
1353
|
+
// Create two clones of this parent with a different ID
|
|
1354
|
+
const parent2 = Parent.create({
|
|
1355
|
+
id: parent1.id,
|
|
1356
|
+
firstName: 'Linda',
|
|
1357
|
+
lastName: 'Doe',
|
|
1358
|
+
});
|
|
1359
|
+
|
|
1360
|
+
const parent3 = Parent.create({
|
|
1361
|
+
id: parent1.id,
|
|
1362
|
+
firstName: 'Linda',
|
|
1363
|
+
lastName: 'Doe',
|
|
1364
|
+
email: 'linda@example.com',
|
|
1365
|
+
alternativeEmails: ['linda@work2.com'],
|
|
1366
|
+
phone: '+32412345679',
|
|
1367
|
+
address: Address.create({
|
|
1368
|
+
street: 'Main street 2',
|
|
1369
|
+
postalCode: '1000',
|
|
1370
|
+
city: 'Brussels',
|
|
1371
|
+
country: Country.Belgium,
|
|
1372
|
+
}),
|
|
1373
|
+
nationalRegisterNumber: '93042012348',
|
|
1374
|
+
});
|
|
1375
|
+
|
|
1376
|
+
const member1 = await new MemberFactory({
|
|
1377
|
+
user,
|
|
1378
|
+
details: MemberDetails.create({
|
|
1379
|
+
firstName: 'John',
|
|
1380
|
+
lastName: 'Doe',
|
|
1381
|
+
parents: [parent1],
|
|
1382
|
+
reviewTimes: ReviewTimes.create({
|
|
1383
|
+
times: [
|
|
1384
|
+
ReviewTime.create({
|
|
1385
|
+
name: 'parents',
|
|
1386
|
+
reviewedAt: new Date(0),
|
|
1387
|
+
}),
|
|
1388
|
+
],
|
|
1389
|
+
}),
|
|
1390
|
+
}),
|
|
1391
|
+
}).create();
|
|
1392
|
+
|
|
1393
|
+
const member2 = await new MemberFactory({
|
|
1394
|
+
user,
|
|
1395
|
+
details: MemberDetails.create({
|
|
1396
|
+
firstName: 'Jane',
|
|
1397
|
+
lastName: 'Doe',
|
|
1398
|
+
parents: [parent2],
|
|
1399
|
+
reviewTimes: ReviewTimes.create({
|
|
1400
|
+
times: [
|
|
1401
|
+
ReviewTime.create({
|
|
1402
|
+
name: 'parents',
|
|
1403
|
+
reviewedAt: new Date(1000),
|
|
1404
|
+
}),
|
|
1405
|
+
],
|
|
1406
|
+
}),
|
|
1407
|
+
}),
|
|
1408
|
+
}).create();
|
|
1409
|
+
|
|
1410
|
+
// Parent3 was reviewed last, so has priority
|
|
1411
|
+
const member3 = await new MemberFactory({
|
|
1412
|
+
user,
|
|
1413
|
+
details: MemberDetails.create({
|
|
1414
|
+
firstName: 'Bob',
|
|
1415
|
+
lastName: 'Doe',
|
|
1416
|
+
parents: [parent3],
|
|
1417
|
+
reviewTimes: ReviewTimes.create({
|
|
1418
|
+
times: [
|
|
1419
|
+
ReviewTime.create({
|
|
1420
|
+
name: 'parents',
|
|
1421
|
+
reviewedAt: new Date(2000),
|
|
1422
|
+
}),
|
|
1423
|
+
],
|
|
1424
|
+
}),
|
|
1425
|
+
}),
|
|
1426
|
+
}).create();
|
|
1427
|
+
|
|
1428
|
+
// Now simulate a change to member1's parents, and check if all parents are updated to the same id and details
|
|
1429
|
+
const admin = await new UserFactory({
|
|
1430
|
+
globalPermissions: Permissions.create({ level: PermissionLevel.Full }),
|
|
1431
|
+
}).create();
|
|
1432
|
+
const token = await Token.createToken(admin);
|
|
1433
|
+
const nonEmtpyArray = new PatchableArray() as PatchableArrayAutoEncoder<Parent>;
|
|
1434
|
+
nonEmtpyArray.addPatch(Parent.patch({
|
|
1435
|
+
id: parent1.id,
|
|
1436
|
+
// no changes
|
|
1437
|
+
}));
|
|
1438
|
+
const arr: Body = new PatchableArray();
|
|
1439
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
1440
|
+
id: member1.id,
|
|
1441
|
+
details: MemberDetails.patch({
|
|
1442
|
+
parents: nonEmtpyArray,
|
|
1443
|
+
}),
|
|
1444
|
+
});
|
|
1445
|
+
arr.addPatch(patch);
|
|
1446
|
+
|
|
1447
|
+
const request = Request.buildJson('PATCH', baseUrl, undefined, arr);
|
|
1448
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
1449
|
+
const response = await testServer.test(endpoint, request);
|
|
1450
|
+
|
|
1451
|
+
// Check returned
|
|
1452
|
+
expect(response.status).toBe(200);
|
|
1453
|
+
expect(response.body.members.length).toBe(1);
|
|
1454
|
+
|
|
1455
|
+
// Load parents again
|
|
1456
|
+
await member1.refresh();
|
|
1457
|
+
await member2.refresh();
|
|
1458
|
+
await member3.refresh();
|
|
1459
|
+
|
|
1460
|
+
// Check all parents equal
|
|
1461
|
+
const expectedParent = parent3;
|
|
1462
|
+
|
|
1463
|
+
expect(member1.details.parents).toEqual([expectedParent]);
|
|
1464
|
+
expect(member2.details.parents).toEqual([expectedParent]);
|
|
1465
|
+
expect(member3.details.parents).toEqual([expectedParent]);
|
|
1466
|
+
});
|
|
1467
|
+
|
|
1468
|
+
test('Deleting a parent address, NRN, phone or email is possible in a family if all parents have the same ID', async () => {
|
|
1469
|
+
const user = await new UserFactory({}).create();
|
|
1470
|
+
|
|
1471
|
+
const parent1 = Parent.create({
|
|
1472
|
+
firstName: 'Linda',
|
|
1473
|
+
lastName: 'Doe',
|
|
1474
|
+
email: 'linda@example.com',
|
|
1475
|
+
alternativeEmails: ['linda@work.com'],
|
|
1476
|
+
phone: '+32412345678',
|
|
1477
|
+
address: Address.create({
|
|
1478
|
+
street: 'Main street 1',
|
|
1479
|
+
postalCode: '1000',
|
|
1480
|
+
city: 'Brussels',
|
|
1481
|
+
country: Country.Belgium,
|
|
1482
|
+
}),
|
|
1483
|
+
nationalRegisterNumber: '93042012345',
|
|
1484
|
+
createdAt: new Date(0),
|
|
1485
|
+
});
|
|
1486
|
+
|
|
1487
|
+
// Create two clones of this parent with a different ID
|
|
1488
|
+
const parent2 = Parent.create({
|
|
1489
|
+
id: parent1.id,
|
|
1490
|
+
firstName: 'Linda',
|
|
1491
|
+
lastName: 'Doe',
|
|
1492
|
+
createdAt: new Date(1000),
|
|
1493
|
+
});
|
|
1494
|
+
|
|
1495
|
+
const parent3 = Parent.create({
|
|
1496
|
+
id: parent1.id,
|
|
1497
|
+
firstName: 'Linda',
|
|
1498
|
+
lastName: 'Doe',
|
|
1499
|
+
email: 'linda@example.com',
|
|
1500
|
+
alternativeEmails: ['linda@work2.com'],
|
|
1501
|
+
phone: '+32412345679',
|
|
1502
|
+
address: Address.create({
|
|
1503
|
+
street: 'Main street 2',
|
|
1504
|
+
postalCode: '1000',
|
|
1505
|
+
city: 'Brussels',
|
|
1506
|
+
country: Country.Belgium,
|
|
1507
|
+
}),
|
|
1508
|
+
nationalRegisterNumber: '93042012348',
|
|
1509
|
+
createdAt: new Date(3000),
|
|
1510
|
+
});
|
|
1511
|
+
|
|
1512
|
+
const member1 = await new MemberFactory({
|
|
1513
|
+
user,
|
|
1514
|
+
details: MemberDetails.create({
|
|
1515
|
+
firstName: 'John',
|
|
1516
|
+
lastName: 'Doe',
|
|
1517
|
+
parents: [parent1],
|
|
1518
|
+
reviewTimes: ReviewTimes.create({
|
|
1519
|
+
times: [
|
|
1520
|
+
ReviewTime.create({
|
|
1521
|
+
name: 'parents',
|
|
1522
|
+
reviewedAt: new Date(0),
|
|
1523
|
+
}),
|
|
1524
|
+
],
|
|
1525
|
+
}),
|
|
1526
|
+
}),
|
|
1527
|
+
}).create();
|
|
1528
|
+
|
|
1529
|
+
const member2 = await new MemberFactory({
|
|
1530
|
+
user,
|
|
1531
|
+
details: MemberDetails.create({
|
|
1532
|
+
firstName: 'Jane',
|
|
1533
|
+
lastName: 'Doe',
|
|
1534
|
+
parents: [parent2],
|
|
1535
|
+
reviewTimes: ReviewTimes.create({
|
|
1536
|
+
times: [
|
|
1537
|
+
ReviewTime.create({
|
|
1538
|
+
name: 'parents',
|
|
1539
|
+
reviewedAt: new Date(1000),
|
|
1540
|
+
}),
|
|
1541
|
+
],
|
|
1542
|
+
}),
|
|
1543
|
+
}),
|
|
1544
|
+
}).create();
|
|
1545
|
+
|
|
1546
|
+
// Parent3 was reviewed last, so has priority
|
|
1547
|
+
const member3 = await new MemberFactory({
|
|
1548
|
+
user,
|
|
1549
|
+
details: MemberDetails.create({
|
|
1550
|
+
firstName: 'Bob',
|
|
1551
|
+
lastName: 'Doe',
|
|
1552
|
+
parents: [parent3],
|
|
1553
|
+
reviewTimes: ReviewTimes.create({
|
|
1554
|
+
times: [
|
|
1555
|
+
ReviewTime.create({
|
|
1556
|
+
name: 'parents',
|
|
1557
|
+
reviewedAt: new Date(2000),
|
|
1558
|
+
}),
|
|
1559
|
+
],
|
|
1560
|
+
}),
|
|
1561
|
+
}),
|
|
1562
|
+
}).create();
|
|
1563
|
+
|
|
1564
|
+
// Now simulate a change to member1's parents, and check if all parents are updated to the same id and details
|
|
1565
|
+
const admin = await new UserFactory({
|
|
1566
|
+
globalPermissions: Permissions.create({ level: PermissionLevel.Full }),
|
|
1567
|
+
}).create();
|
|
1568
|
+
const token = await Token.createToken(admin);
|
|
1569
|
+
const nonEmtpyArray = new PatchableArray() as PatchableArrayAutoEncoder<Parent>;
|
|
1570
|
+
nonEmtpyArray.addPatch(Parent.patch({
|
|
1571
|
+
id: parent1.id,
|
|
1572
|
+
// no changes
|
|
1573
|
+
}));
|
|
1574
|
+
const arr: Body = new PatchableArray();
|
|
1575
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
1576
|
+
id: member2.id,
|
|
1577
|
+
details: MemberDetails.patch({
|
|
1578
|
+
parents: nonEmtpyArray,
|
|
1579
|
+
// Mark these parents as reviewed last, so it overrides all the same parents
|
|
1580
|
+
reviewTimes: ReviewTimes.patch({
|
|
1581
|
+
times: [
|
|
1582
|
+
ReviewTime.create({
|
|
1583
|
+
name: 'parents',
|
|
1584
|
+
reviewedAt: new Date(),
|
|
1585
|
+
}),
|
|
1586
|
+
],
|
|
1587
|
+
}),
|
|
1588
|
+
}),
|
|
1589
|
+
});
|
|
1590
|
+
arr.addPatch(patch);
|
|
1591
|
+
|
|
1592
|
+
const request = Request.buildJson('PATCH', baseUrl, undefined, arr);
|
|
1593
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
1594
|
+
const response = await testServer.test(endpoint, request);
|
|
1595
|
+
|
|
1596
|
+
// Check returned
|
|
1597
|
+
expect(response.status).toBe(200);
|
|
1598
|
+
expect(response.body.members.length).toBe(1);
|
|
1599
|
+
|
|
1600
|
+
// Load parents again
|
|
1601
|
+
await member1.refresh();
|
|
1602
|
+
await member2.refresh();
|
|
1603
|
+
await member3.refresh();
|
|
1604
|
+
|
|
1605
|
+
// Check all parents equal
|
|
1606
|
+
const expectedParent = Parent.create({
|
|
1607
|
+
...parent2,
|
|
1608
|
+
createdAt: parent1.createdAt, // the oldest one is used
|
|
1609
|
+
});
|
|
1610
|
+
|
|
1611
|
+
expect(member1.details.parents).toEqual([expectedParent]);
|
|
1612
|
+
expect(member2.details.parents).toEqual([expectedParent]);
|
|
1613
|
+
expect(member3.details.parents).toEqual([expectedParent]);
|
|
1614
|
+
});
|
|
1615
|
+
|
|
1616
|
+
test('When adding a new parent that is and old copy, the most recent copy is added instead', async () => {
|
|
1617
|
+
const user = await new UserFactory({}).create();
|
|
1618
|
+
|
|
1619
|
+
/**
|
|
1620
|
+
* This one is the oldest and has been reviewed the most recent
|
|
1621
|
+
*/
|
|
1622
|
+
const latestParent = Parent.create({
|
|
1623
|
+
firstName: 'Linda',
|
|
1624
|
+
lastName: 'Doe',
|
|
1625
|
+
email: 'linda@example.com',
|
|
1626
|
+
alternativeEmails: ['linda@work2.com'],
|
|
1627
|
+
phone: '+32412345679',
|
|
1628
|
+
address: Address.create({
|
|
1629
|
+
street: 'Main street 2',
|
|
1630
|
+
postalCode: '1000',
|
|
1631
|
+
city: 'Brussels',
|
|
1632
|
+
country: Country.Belgium,
|
|
1633
|
+
}),
|
|
1634
|
+
nationalRegisterNumber: '93042012348',
|
|
1635
|
+
createdAt: new Date(3000),
|
|
1636
|
+
updatedAt: new Date(),
|
|
1637
|
+
});
|
|
1638
|
+
|
|
1639
|
+
const oldestParent = Parent.create({
|
|
1640
|
+
id: latestParent.id,
|
|
1641
|
+
firstName: 'Linda',
|
|
1642
|
+
lastName: 'Doe',
|
|
1643
|
+
email: 'ignored@example.com',
|
|
1644
|
+
createdAt: new Date(3000),
|
|
1645
|
+
updatedAt: new Date(10_000),
|
|
1646
|
+
});
|
|
1647
|
+
|
|
1648
|
+
const member1 = await new MemberFactory({
|
|
1649
|
+
user,
|
|
1650
|
+
details: MemberDetails.create({
|
|
1651
|
+
firstName: 'John',
|
|
1652
|
+
lastName: 'Doe',
|
|
1653
|
+
parents: [latestParent],
|
|
1654
|
+
}),
|
|
1655
|
+
}).create();
|
|
1656
|
+
|
|
1657
|
+
const member2 = await new MemberFactory({
|
|
1658
|
+
user,
|
|
1659
|
+
details: MemberDetails.create({
|
|
1660
|
+
firstName: 'Jane',
|
|
1661
|
+
lastName: 'Doe',
|
|
1662
|
+
parents: [],
|
|
1663
|
+
}),
|
|
1664
|
+
}).create();
|
|
1665
|
+
|
|
1666
|
+
// Now simulate a change to member1's contacts, and check if all contacts are updated to the same id and details
|
|
1667
|
+
const admin = await new UserFactory({
|
|
1668
|
+
globalPermissions: Permissions.create({ level: PermissionLevel.Full }),
|
|
1669
|
+
}).create();
|
|
1670
|
+
const token = await Token.createToken(admin);
|
|
1671
|
+
const nonEmtpyArray = new PatchableArray() as PatchableArrayAutoEncoder<Parent>;
|
|
1672
|
+
nonEmtpyArray.addPut(oldestParent);
|
|
1673
|
+
|
|
1674
|
+
const arr: Body = new PatchableArray();
|
|
1675
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
1676
|
+
id: member2.id,
|
|
1677
|
+
details: MemberDetails.patch({
|
|
1678
|
+
parents: nonEmtpyArray,
|
|
1679
|
+
}),
|
|
1680
|
+
});
|
|
1681
|
+
arr.addPatch(patch);
|
|
1682
|
+
|
|
1683
|
+
const request = Request.buildJson('PATCH', baseUrl, undefined, arr);
|
|
1684
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
1685
|
+
const response = await testServer.test(endpoint, request);
|
|
1686
|
+
|
|
1687
|
+
// Check returned
|
|
1688
|
+
expect(response.status).toBe(200);
|
|
1689
|
+
expect(response.body.members.length).toBe(1);
|
|
1690
|
+
|
|
1691
|
+
// Load contacts again
|
|
1692
|
+
await member1.refresh();
|
|
1693
|
+
await member2.refresh();
|
|
1694
|
+
|
|
1695
|
+
// The contact should be equal to contact1, and ignore other changes
|
|
1696
|
+
const expectedParent = Parent.create({
|
|
1697
|
+
...latestParent,
|
|
1698
|
+
});
|
|
1699
|
+
|
|
1700
|
+
expect(member1.details.parents).toEqual([expectedParent]);
|
|
1701
|
+
expect(member2.details.parents).toEqual([expectedParent]);
|
|
1702
|
+
});
|
|
1703
|
+
|
|
1704
|
+
test('It is possible to change the name of a parent without setting updatedAt', async () => {
|
|
1705
|
+
const user = await new UserFactory({}).create();
|
|
1706
|
+
|
|
1707
|
+
/**
|
|
1708
|
+
* This one is the oldest and has been reviewed the most recent
|
|
1709
|
+
*/
|
|
1710
|
+
const latestParent = Parent.create({
|
|
1711
|
+
firstName: 'Linda',
|
|
1712
|
+
lastName: 'Doe',
|
|
1713
|
+
email: 'linda@example.com',
|
|
1714
|
+
alternativeEmails: ['linda@work2.com'],
|
|
1715
|
+
phone: '+32412345679',
|
|
1716
|
+
address: Address.create({
|
|
1717
|
+
street: 'Main street 2',
|
|
1718
|
+
postalCode: '1000',
|
|
1719
|
+
city: 'Brussels',
|
|
1720
|
+
country: Country.Belgium,
|
|
1721
|
+
}),
|
|
1722
|
+
nationalRegisterNumber: '93042012348',
|
|
1723
|
+
createdAt: new Date(3000),
|
|
1724
|
+
updatedAt: new Date(),
|
|
1725
|
+
});
|
|
1726
|
+
|
|
1727
|
+
const oldestParent = Parent.create({
|
|
1728
|
+
id: latestParent.id,
|
|
1729
|
+
firstName: 'Linda',
|
|
1730
|
+
lastName: 'Doe',
|
|
1731
|
+
email: 'ignored@example.com',
|
|
1732
|
+
createdAt: new Date(3000),
|
|
1733
|
+
updatedAt: new Date(10_000),
|
|
1734
|
+
});
|
|
1735
|
+
|
|
1736
|
+
const member1 = await new MemberFactory({
|
|
1737
|
+
user,
|
|
1738
|
+
details: MemberDetails.create({
|
|
1739
|
+
firstName: 'John',
|
|
1740
|
+
lastName: 'Doe',
|
|
1741
|
+
parents: [latestParent],
|
|
1742
|
+
}),
|
|
1743
|
+
}).create();
|
|
1744
|
+
|
|
1745
|
+
const member2 = await new MemberFactory({
|
|
1746
|
+
user,
|
|
1747
|
+
details: MemberDetails.create({
|
|
1748
|
+
firstName: 'Jane',
|
|
1749
|
+
lastName: 'Doe',
|
|
1750
|
+
parents: [oldestParent],
|
|
1751
|
+
}),
|
|
1752
|
+
}).create();
|
|
1753
|
+
|
|
1754
|
+
// Now simulate a change to member1's contacts, and check if all contacts are updated to the same id and details
|
|
1755
|
+
const admin = await new UserFactory({
|
|
1756
|
+
globalPermissions: Permissions.create({ level: PermissionLevel.Full }),
|
|
1757
|
+
}).create();
|
|
1758
|
+
const token = await Token.createToken(admin);
|
|
1759
|
+
const nonEmtpyArray = new PatchableArray() as PatchableArrayAutoEncoder<Parent>;
|
|
1760
|
+
nonEmtpyArray.addPatch(Parent.patch({
|
|
1761
|
+
id: latestParent.id,
|
|
1762
|
+
firstName: 'Linda2',
|
|
1763
|
+
lastName: 'Doe2',
|
|
1764
|
+
// Note that 'by accident' the frontend did not pass the updatedAt value correctly - this should still work as expected
|
|
1765
|
+
}));
|
|
1766
|
+
|
|
1767
|
+
const arr: Body = new PatchableArray();
|
|
1768
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
1769
|
+
id: member2.id,
|
|
1770
|
+
details: MemberDetails.patch({
|
|
1771
|
+
parents: nonEmtpyArray,
|
|
1772
|
+
}),
|
|
1773
|
+
});
|
|
1774
|
+
arr.addPatch(patch);
|
|
1775
|
+
|
|
1776
|
+
const request = Request.buildJson('PATCH', baseUrl, undefined, arr);
|
|
1777
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
1778
|
+
const response = await testServer.test(endpoint, request);
|
|
1779
|
+
|
|
1780
|
+
// Check returned
|
|
1781
|
+
expect(response.status).toBe(200);
|
|
1782
|
+
expect(response.body.members.length).toBe(1);
|
|
1783
|
+
|
|
1784
|
+
// Load contacts again
|
|
1785
|
+
await member1.refresh();
|
|
1786
|
+
await member2.refresh();
|
|
1787
|
+
|
|
1788
|
+
// The contact should be equal to contact1, and ignore other changes
|
|
1789
|
+
const expectedParent = Parent.create({
|
|
1790
|
+
...latestParent,
|
|
1791
|
+
firstName: 'Linda2',
|
|
1792
|
+
lastName: 'Doe2',
|
|
1793
|
+
});
|
|
1794
|
+
|
|
1795
|
+
expect(member1.details.parents).toEqual([expectedParent]);
|
|
1796
|
+
expect(member2.details.parents).toEqual([expectedParent]);
|
|
1797
|
+
});
|
|
1798
|
+
});
|
|
1799
|
+
|
|
1800
|
+
describe('Emergency contacts', () => {
|
|
1801
|
+
test('Duplicate emergency contacts are merged in a family', async () => {
|
|
1802
|
+
const user = await new UserFactory({}).create();
|
|
1803
|
+
|
|
1804
|
+
const contact1 = EmergencyContact.create({
|
|
1805
|
+
name: 'Linda Doe',
|
|
1806
|
+
title: 'Grandmother',
|
|
1807
|
+
phone: '+32412345678',
|
|
1808
|
+
createdAt: new Date(0),
|
|
1809
|
+
});
|
|
1810
|
+
|
|
1811
|
+
// Create two clones of this contact with a different ID
|
|
1812
|
+
const contact2 = EmergencyContact.create({
|
|
1813
|
+
name: 'Linda Doe',
|
|
1814
|
+
createdAt: new Date(2000),
|
|
1815
|
+
});
|
|
1816
|
+
|
|
1817
|
+
const contact3 = EmergencyContact.create({
|
|
1818
|
+
name: 'Linda Doe',
|
|
1819
|
+
title: 'Oma',
|
|
1820
|
+
phone: '+32412345679',
|
|
1821
|
+
createdAt: new Date(4000),
|
|
1822
|
+
});
|
|
1823
|
+
|
|
1824
|
+
const member1 = await new MemberFactory({
|
|
1825
|
+
user,
|
|
1826
|
+
details: MemberDetails.create({
|
|
1827
|
+
firstName: 'John',
|
|
1828
|
+
lastName: 'Doe',
|
|
1829
|
+
emergencyContacts: [contact1],
|
|
1830
|
+
reviewTimes: ReviewTimes.create({
|
|
1831
|
+
times: [
|
|
1832
|
+
ReviewTime.create({
|
|
1833
|
+
name: 'emergencyContacts',
|
|
1834
|
+
reviewedAt: new Date(0),
|
|
1835
|
+
}),
|
|
1836
|
+
],
|
|
1837
|
+
}),
|
|
1838
|
+
}),
|
|
1839
|
+
}).create();
|
|
1840
|
+
|
|
1841
|
+
const member2 = await new MemberFactory({
|
|
1842
|
+
user,
|
|
1843
|
+
details: MemberDetails.create({
|
|
1844
|
+
firstName: 'Jane',
|
|
1845
|
+
lastName: 'Doe',
|
|
1846
|
+
emergencyContacts: [contact2],
|
|
1847
|
+
reviewTimes: ReviewTimes.create({
|
|
1848
|
+
times: [
|
|
1849
|
+
ReviewTime.create({
|
|
1850
|
+
name: 'emergencyContacts',
|
|
1851
|
+
reviewedAt: new Date(1000),
|
|
1852
|
+
}),
|
|
1853
|
+
],
|
|
1854
|
+
}),
|
|
1855
|
+
}),
|
|
1856
|
+
}).create();
|
|
1857
|
+
|
|
1858
|
+
// Parent3 was reviewed last, so has priority
|
|
1859
|
+
const member3 = await new MemberFactory({
|
|
1860
|
+
user,
|
|
1861
|
+
details: MemberDetails.create({
|
|
1862
|
+
firstName: 'Bob',
|
|
1863
|
+
lastName: 'Doe',
|
|
1864
|
+
emergencyContacts: [contact3],
|
|
1865
|
+
reviewTimes: ReviewTimes.create({
|
|
1866
|
+
times: [
|
|
1867
|
+
ReviewTime.create({
|
|
1868
|
+
name: 'emergencyContacts',
|
|
1869
|
+
reviewedAt: new Date(2000),
|
|
1870
|
+
}),
|
|
1871
|
+
],
|
|
1872
|
+
}),
|
|
1873
|
+
}),
|
|
1874
|
+
}).create();
|
|
1875
|
+
|
|
1876
|
+
// Now simulate a change to member1's contacts, and check if all contacts are updated to the same id and details
|
|
1877
|
+
const admin = await new UserFactory({
|
|
1878
|
+
globalPermissions: Permissions.create({ level: PermissionLevel.Full }),
|
|
1879
|
+
}).create();
|
|
1880
|
+
const token = await Token.createToken(admin);
|
|
1881
|
+
const nonEmtpyArray = new PatchableArray() as PatchableArrayAutoEncoder<EmergencyContact>;
|
|
1882
|
+
nonEmtpyArray.addPatch(EmergencyContact.patch({
|
|
1883
|
+
id: contact1.id,
|
|
1884
|
+
// no changes
|
|
1885
|
+
}));
|
|
1886
|
+
const arr: Body = new PatchableArray();
|
|
1887
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
1888
|
+
id: member1.id,
|
|
1889
|
+
details: MemberDetails.patch({
|
|
1890
|
+
emergencyContacts: nonEmtpyArray,
|
|
1891
|
+
}),
|
|
1892
|
+
});
|
|
1893
|
+
arr.addPatch(patch);
|
|
1894
|
+
|
|
1895
|
+
const request = Request.buildJson('PATCH', baseUrl, undefined, arr);
|
|
1896
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
1897
|
+
const response = await testServer.test(endpoint, request);
|
|
1898
|
+
|
|
1899
|
+
// Check returned
|
|
1900
|
+
expect(response.status).toBe(200);
|
|
1901
|
+
expect(response.body.members.length).toBe(1);
|
|
1902
|
+
|
|
1903
|
+
// Load contacts again
|
|
1904
|
+
await member1.refresh();
|
|
1905
|
+
await member2.refresh();
|
|
1906
|
+
await member3.refresh();
|
|
1907
|
+
|
|
1908
|
+
// Check all contacts equal
|
|
1909
|
+
const expectedParent = EmergencyContact.create({
|
|
1910
|
+
...contact3,
|
|
1911
|
+
id: contact1.id, // the oldest contact id is used
|
|
1912
|
+
createdAt: contact1.createdAt,
|
|
1913
|
+
});
|
|
1914
|
+
|
|
1915
|
+
expect(member1.details.emergencyContacts).toEqual([expectedParent]);
|
|
1916
|
+
expect(member2.details.emergencyContacts).toEqual([expectedParent]);
|
|
1917
|
+
expect(member3.details.emergencyContacts).toEqual([expectedParent]);
|
|
1918
|
+
});
|
|
1919
|
+
|
|
1920
|
+
test('Deleting a contact title and phone is possible in a family if all contacts have the same ID', async () => {
|
|
1921
|
+
const user = await new UserFactory({}).create();
|
|
1922
|
+
|
|
1923
|
+
const contact1 = EmergencyContact.create({
|
|
1924
|
+
name: 'Linda Doe',
|
|
1925
|
+
title: 'Grandmother',
|
|
1926
|
+
phone: '+32412345678',
|
|
1927
|
+
});
|
|
1928
|
+
|
|
1929
|
+
// Create two clones of this contact with a different ID
|
|
1930
|
+
const contact2 = EmergencyContact.create({
|
|
1931
|
+
id: contact1.id,
|
|
1932
|
+
name: 'Linda Doe',
|
|
1933
|
+
});
|
|
1934
|
+
|
|
1935
|
+
const contact3 = EmergencyContact.create({
|
|
1936
|
+
id: contact1.id,
|
|
1937
|
+
name: 'Linda Doe',
|
|
1938
|
+
title: 'Oma',
|
|
1939
|
+
phone: '+32412345679',
|
|
1940
|
+
});
|
|
1941
|
+
|
|
1942
|
+
const member1 = await new MemberFactory({
|
|
1943
|
+
user,
|
|
1944
|
+
details: MemberDetails.create({
|
|
1945
|
+
firstName: 'John',
|
|
1946
|
+
lastName: 'Doe',
|
|
1947
|
+
emergencyContacts: [contact1],
|
|
1948
|
+
reviewTimes: ReviewTimes.create({
|
|
1949
|
+
times: [
|
|
1950
|
+
ReviewTime.create({
|
|
1951
|
+
name: 'emergencyContacts',
|
|
1952
|
+
reviewedAt: new Date(0),
|
|
1953
|
+
}),
|
|
1954
|
+
],
|
|
1955
|
+
}),
|
|
1956
|
+
}),
|
|
1957
|
+
}).create();
|
|
1958
|
+
|
|
1959
|
+
const member2 = await new MemberFactory({
|
|
1960
|
+
user,
|
|
1961
|
+
details: MemberDetails.create({
|
|
1962
|
+
firstName: 'Jane',
|
|
1963
|
+
lastName: 'Doe',
|
|
1964
|
+
emergencyContacts: [contact2],
|
|
1965
|
+
reviewTimes: ReviewTimes.create({
|
|
1966
|
+
times: [
|
|
1967
|
+
ReviewTime.create({
|
|
1968
|
+
name: 'emergencyContacts',
|
|
1969
|
+
reviewedAt: new Date(1000),
|
|
1970
|
+
}),
|
|
1971
|
+
],
|
|
1972
|
+
}),
|
|
1973
|
+
}),
|
|
1974
|
+
}).create();
|
|
1975
|
+
|
|
1976
|
+
// Parent3 was reviewed last, so has priority
|
|
1977
|
+
const member3 = await new MemberFactory({
|
|
1978
|
+
user,
|
|
1979
|
+
details: MemberDetails.create({
|
|
1980
|
+
firstName: 'Bob',
|
|
1981
|
+
lastName: 'Doe',
|
|
1982
|
+
emergencyContacts: [contact3],
|
|
1983
|
+
reviewTimes: ReviewTimes.create({
|
|
1984
|
+
times: [
|
|
1985
|
+
ReviewTime.create({
|
|
1986
|
+
name: 'emergencyContacts',
|
|
1987
|
+
reviewedAt: new Date(2000),
|
|
1988
|
+
}),
|
|
1989
|
+
],
|
|
1990
|
+
}),
|
|
1991
|
+
}),
|
|
1992
|
+
}).create();
|
|
1993
|
+
|
|
1994
|
+
// Now simulate a change to member1's contacts, and check if all contacts are updated to the same id and details
|
|
1995
|
+
const admin = await new UserFactory({
|
|
1996
|
+
globalPermissions: Permissions.create({ level: PermissionLevel.Full }),
|
|
1997
|
+
}).create();
|
|
1998
|
+
const token = await Token.createToken(admin);
|
|
1999
|
+
const nonEmtpyArray = new PatchableArray() as PatchableArrayAutoEncoder<EmergencyContact>;
|
|
2000
|
+
nonEmtpyArray.addPatch(EmergencyContact.patch({
|
|
2001
|
+
id: contact1.id,
|
|
2002
|
+
// no changes
|
|
2003
|
+
}));
|
|
2004
|
+
const arr: Body = new PatchableArray();
|
|
2005
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
2006
|
+
id: member2.id,
|
|
2007
|
+
details: MemberDetails.patch({
|
|
2008
|
+
emergencyContacts: nonEmtpyArray,
|
|
2009
|
+
// Mark these contacts as reviewed last, so it overrides all the same contacts
|
|
2010
|
+
reviewTimes: ReviewTimes.patch({
|
|
2011
|
+
times: [
|
|
2012
|
+
ReviewTime.create({
|
|
2013
|
+
name: 'emergencyContacts',
|
|
2014
|
+
reviewedAt: new Date(),
|
|
2015
|
+
}),
|
|
2016
|
+
],
|
|
2017
|
+
}),
|
|
2018
|
+
}),
|
|
2019
|
+
});
|
|
2020
|
+
arr.addPatch(patch);
|
|
2021
|
+
|
|
2022
|
+
const request = Request.buildJson('PATCH', baseUrl, undefined, arr);
|
|
2023
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
2024
|
+
const response = await testServer.test(endpoint, request);
|
|
2025
|
+
|
|
2026
|
+
// Check returned
|
|
2027
|
+
expect(response.status).toBe(200);
|
|
2028
|
+
expect(response.body.members.length).toBe(1);
|
|
2029
|
+
|
|
2030
|
+
// Load contacts again
|
|
2031
|
+
await member1.refresh();
|
|
2032
|
+
await member2.refresh();
|
|
2033
|
+
await member3.refresh();
|
|
2034
|
+
|
|
2035
|
+
// Check all contacts equal
|
|
2036
|
+
const expectedParent = contact2;
|
|
2037
|
+
|
|
2038
|
+
expect(member1.details.emergencyContacts).toEqual([expectedParent]);
|
|
2039
|
+
expect(member2.details.emergencyContacts).toEqual([expectedParent]);
|
|
2040
|
+
expect(member3.details.emergencyContacts).toEqual([expectedParent]);
|
|
2041
|
+
});
|
|
2042
|
+
|
|
2043
|
+
test('When adding a new emergency contact it is automatically merged with existing contacts', async () => {
|
|
2044
|
+
const user = await new UserFactory({}).create();
|
|
2045
|
+
|
|
2046
|
+
const contact1 = EmergencyContact.create({
|
|
2047
|
+
name: 'Linda Doe',
|
|
2048
|
+
title: 'Grandmother',
|
|
2049
|
+
phone: '+32412345678',
|
|
2050
|
+
createdAt: new Date(0),
|
|
2051
|
+
});
|
|
2052
|
+
|
|
2053
|
+
const member1 = await new MemberFactory({
|
|
2054
|
+
user,
|
|
2055
|
+
details: MemberDetails.create({
|
|
2056
|
+
firstName: 'John',
|
|
2057
|
+
lastName: 'Doe',
|
|
2058
|
+
emergencyContacts: [contact1],
|
|
2059
|
+
}),
|
|
2060
|
+
}).create();
|
|
2061
|
+
|
|
2062
|
+
const member2 = await new MemberFactory({
|
|
2063
|
+
user,
|
|
2064
|
+
details: MemberDetails.create({
|
|
2065
|
+
firstName: 'Jane',
|
|
2066
|
+
lastName: 'Doe',
|
|
2067
|
+
emergencyContacts: [],
|
|
2068
|
+
}),
|
|
2069
|
+
}).create();
|
|
2070
|
+
|
|
2071
|
+
// Now simulate a change to member1's contacts, and check if all contacts are updated to the same id and details
|
|
2072
|
+
const admin = await new UserFactory({
|
|
2073
|
+
globalPermissions: Permissions.create({ level: PermissionLevel.Full }),
|
|
2074
|
+
}).create();
|
|
2075
|
+
const token = await Token.createToken(admin);
|
|
2076
|
+
const nonEmtpyArray = new PatchableArray() as PatchableArrayAutoEncoder<EmergencyContact>;
|
|
2077
|
+
nonEmtpyArray.addPut(EmergencyContact.create({
|
|
2078
|
+
name: 'Linda Doe',
|
|
2079
|
+
title: 'Oma',
|
|
2080
|
+
}));
|
|
2081
|
+
|
|
2082
|
+
const arr: Body = new PatchableArray();
|
|
2083
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
2084
|
+
id: member2.id,
|
|
2085
|
+
details: MemberDetails.patch({
|
|
2086
|
+
emergencyContacts: nonEmtpyArray,
|
|
2087
|
+
}),
|
|
2088
|
+
});
|
|
2089
|
+
arr.addPatch(patch);
|
|
2090
|
+
|
|
2091
|
+
const request = Request.buildJson('PATCH', baseUrl, undefined, arr);
|
|
2092
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
2093
|
+
const response = await testServer.test(endpoint, request);
|
|
2094
|
+
|
|
2095
|
+
// Check returned
|
|
2096
|
+
expect(response.status).toBe(200);
|
|
2097
|
+
expect(response.body.members.length).toBe(1);
|
|
2098
|
+
|
|
2099
|
+
// Load contacts again
|
|
2100
|
+
await member1.refresh();
|
|
2101
|
+
await member2.refresh();
|
|
2102
|
+
|
|
2103
|
+
// Check all contacts equal
|
|
2104
|
+
const expectedParent = EmergencyContact.create({
|
|
2105
|
+
...contact1,
|
|
2106
|
+
title: 'Oma',
|
|
2107
|
+
});
|
|
2108
|
+
|
|
2109
|
+
expect(member1.details.emergencyContacts).toEqual([expectedParent]);
|
|
2110
|
+
expect(member2.details.emergencyContacts).toEqual([expectedParent]);
|
|
2111
|
+
});
|
|
2112
|
+
|
|
2113
|
+
test('When adding a new emergency contact that is an old copy, the most recent copy is added instead', async () => {
|
|
2114
|
+
const user = await new UserFactory({}).create();
|
|
2115
|
+
|
|
2116
|
+
/**
|
|
2117
|
+
* This one is the oldest and has been reviewed the most recent
|
|
2118
|
+
*/
|
|
2119
|
+
const contact1 = EmergencyContact.create({
|
|
2120
|
+
name: 'Linda Doe',
|
|
2121
|
+
title: 'Grandmother',
|
|
2122
|
+
phone: '+32412345678',
|
|
2123
|
+
createdAt: new Date(0),
|
|
2124
|
+
updatedAt: new Date(),
|
|
2125
|
+
});
|
|
2126
|
+
|
|
2127
|
+
// The frontend for some reason got and old version of this contact
|
|
2128
|
+
const oldVersionContact = EmergencyContact.create({
|
|
2129
|
+
id: contact1.id,
|
|
2130
|
+
name: 'Linda Doe',
|
|
2131
|
+
title: 'Oma',
|
|
2132
|
+
phone: '+32412345676',
|
|
2133
|
+
createdAt: new Date(0),
|
|
2134
|
+
updatedAt: new Date(10_000), // This one is older
|
|
2135
|
+
});
|
|
2136
|
+
|
|
2137
|
+
const member1 = await new MemberFactory({
|
|
2138
|
+
user,
|
|
2139
|
+
details: MemberDetails.create({
|
|
2140
|
+
firstName: 'John',
|
|
2141
|
+
lastName: 'Doe',
|
|
2142
|
+
emergencyContacts: [contact1],
|
|
2143
|
+
}),
|
|
2144
|
+
}).create();
|
|
2145
|
+
|
|
2146
|
+
const member2 = await new MemberFactory({
|
|
2147
|
+
user,
|
|
2148
|
+
details: MemberDetails.create({
|
|
2149
|
+
firstName: 'Jane',
|
|
2150
|
+
lastName: 'Doe',
|
|
2151
|
+
emergencyContacts: [],
|
|
2152
|
+
}),
|
|
2153
|
+
}).create();
|
|
2154
|
+
|
|
2155
|
+
// Now simulate a change to member1's contacts, and check if all contacts are updated to the same id and details
|
|
2156
|
+
const admin = await new UserFactory({
|
|
2157
|
+
globalPermissions: Permissions.create({ level: PermissionLevel.Full }),
|
|
2158
|
+
}).create();
|
|
2159
|
+
const token = await Token.createToken(admin);
|
|
2160
|
+
const nonEmtpyArray = new PatchableArray() as PatchableArrayAutoEncoder<EmergencyContact>;
|
|
2161
|
+
nonEmtpyArray.addPut(oldVersionContact);
|
|
2162
|
+
|
|
2163
|
+
const arr: Body = new PatchableArray();
|
|
2164
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
2165
|
+
id: member2.id,
|
|
2166
|
+
details: MemberDetails.patch({
|
|
2167
|
+
emergencyContacts: nonEmtpyArray,
|
|
2168
|
+
}),
|
|
2169
|
+
});
|
|
2170
|
+
arr.addPatch(patch);
|
|
2171
|
+
|
|
2172
|
+
const request = Request.buildJson('PATCH', baseUrl, undefined, arr);
|
|
2173
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
2174
|
+
const response = await testServer.test(endpoint, request);
|
|
2175
|
+
|
|
2176
|
+
// Check returned
|
|
2177
|
+
expect(response.status).toBe(200);
|
|
2178
|
+
expect(response.body.members.length).toBe(1);
|
|
2179
|
+
|
|
2180
|
+
// Load contacts again
|
|
2181
|
+
await member1.refresh();
|
|
2182
|
+
await member2.refresh();
|
|
2183
|
+
|
|
2184
|
+
// The contact should be equal to contact1, and ignore other changes
|
|
2185
|
+
const expectedParent = EmergencyContact.create({
|
|
2186
|
+
...contact1,
|
|
2187
|
+
});
|
|
2188
|
+
|
|
2189
|
+
expect(member1.details.emergencyContacts).toEqual([expectedParent]);
|
|
2190
|
+
expect(member2.details.emergencyContacts).toEqual([expectedParent]);
|
|
2191
|
+
});
|
|
2192
|
+
|
|
2193
|
+
test('It is possible to change the name of an emergency contact without setting updatedAt', async () => {
|
|
2194
|
+
const user = await new UserFactory({}).create();
|
|
2195
|
+
|
|
2196
|
+
/**
|
|
2197
|
+
* This one is the oldest and has been reviewed the most recent
|
|
2198
|
+
*/
|
|
2199
|
+
const contact1 = EmergencyContact.create({
|
|
2200
|
+
name: 'Linda Doe',
|
|
2201
|
+
title: 'Grandmother',
|
|
2202
|
+
phone: '+32412345678',
|
|
2203
|
+
createdAt: new Date(0),
|
|
2204
|
+
updatedAt: new Date(),
|
|
2205
|
+
});
|
|
2206
|
+
|
|
2207
|
+
const member1 = await new MemberFactory({
|
|
2208
|
+
user,
|
|
2209
|
+
details: MemberDetails.create({
|
|
2210
|
+
firstName: 'John',
|
|
2211
|
+
lastName: 'Doe',
|
|
2212
|
+
emergencyContacts: [contact1],
|
|
2213
|
+
}),
|
|
2214
|
+
}).create();
|
|
2215
|
+
|
|
2216
|
+
const member2 = await new MemberFactory({
|
|
2217
|
+
user,
|
|
2218
|
+
details: MemberDetails.create({
|
|
2219
|
+
firstName: 'Jane',
|
|
2220
|
+
lastName: 'Doe',
|
|
2221
|
+
emergencyContacts: [contact1],
|
|
2222
|
+
}),
|
|
2223
|
+
}).create();
|
|
2224
|
+
|
|
2225
|
+
// Now simulate a change to member1's contacts, and check if all contacts are updated to the same id and details
|
|
2226
|
+
const admin = await new UserFactory({
|
|
2227
|
+
globalPermissions: Permissions.create({ level: PermissionLevel.Full }),
|
|
2228
|
+
}).create();
|
|
2229
|
+
const token = await Token.createToken(admin);
|
|
2230
|
+
const nonEmtpyArray = new PatchableArray() as PatchableArrayAutoEncoder<EmergencyContact>;
|
|
2231
|
+
nonEmtpyArray.addPatch(EmergencyContact.patch({
|
|
2232
|
+
id: contact1.id,
|
|
2233
|
+
name: 'Linda2 Doe2',
|
|
2234
|
+
// Note that 'by accident' the frontend did not pass the updatedAt value correctly - this should still work as expected
|
|
2235
|
+
}));
|
|
2236
|
+
|
|
2237
|
+
const arr: Body = new PatchableArray();
|
|
2238
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
2239
|
+
id: member2.id,
|
|
2240
|
+
details: MemberDetails.patch({
|
|
2241
|
+
emergencyContacts: nonEmtpyArray,
|
|
2242
|
+
}),
|
|
2243
|
+
});
|
|
2244
|
+
arr.addPatch(patch);
|
|
2245
|
+
|
|
2246
|
+
const request = Request.buildJson('PATCH', baseUrl, undefined, arr);
|
|
2247
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
2248
|
+
const response = await testServer.test(endpoint, request);
|
|
2249
|
+
|
|
2250
|
+
// Check returned
|
|
2251
|
+
expect(response.status).toBe(200);
|
|
2252
|
+
expect(response.body.members.length).toBe(1);
|
|
2253
|
+
|
|
2254
|
+
// Load contacts again
|
|
2255
|
+
await member1.refresh();
|
|
2256
|
+
await member2.refresh();
|
|
2257
|
+
|
|
2258
|
+
// The contact should be equal to contact1, and ignore other changes
|
|
2259
|
+
const expectedContact = EmergencyContact.create({
|
|
2260
|
+
...contact1,
|
|
2261
|
+
name: 'Linda2 Doe2',
|
|
2262
|
+
});
|
|
2263
|
+
|
|
2264
|
+
expect(member1.details.emergencyContacts).toEqual([expectedContact]);
|
|
2265
|
+
expect(member2.details.emergencyContacts).toEqual([expectedContact]);
|
|
2266
|
+
});
|
|
2267
|
+
|
|
2268
|
+
test('It is possible to change the name of an emergency contact with setting updatedAt', async () => {
|
|
2269
|
+
const user = await new UserFactory({}).create();
|
|
2270
|
+
|
|
2271
|
+
/**
|
|
2272
|
+
* This one is the oldest and has been reviewed the most recent
|
|
2273
|
+
*/
|
|
2274
|
+
const contact1 = EmergencyContact.create({
|
|
2275
|
+
name: 'Linda Doe',
|
|
2276
|
+
title: 'Grandmother',
|
|
2277
|
+
phone: '+32412345678',
|
|
2278
|
+
createdAt: new Date(0),
|
|
2279
|
+
updatedAt: new Date(Date.now() - 5_000),
|
|
2280
|
+
});
|
|
2281
|
+
|
|
2282
|
+
const member1 = await new MemberFactory({
|
|
2283
|
+
user,
|
|
2284
|
+
details: MemberDetails.create({
|
|
2285
|
+
firstName: 'John',
|
|
2286
|
+
lastName: 'Doe',
|
|
2287
|
+
emergencyContacts: [contact1],
|
|
2288
|
+
}),
|
|
2289
|
+
}).create();
|
|
2290
|
+
|
|
2291
|
+
const member2 = await new MemberFactory({
|
|
2292
|
+
user,
|
|
2293
|
+
details: MemberDetails.create({
|
|
2294
|
+
firstName: 'Jane',
|
|
2295
|
+
lastName: 'Doe',
|
|
2296
|
+
emergencyContacts: [contact1],
|
|
2297
|
+
}),
|
|
2298
|
+
}).create();
|
|
2299
|
+
|
|
2300
|
+
// Now simulate a change to member1's contacts, and check if all contacts are updated to the same id and details
|
|
2301
|
+
const admin = await new UserFactory({
|
|
2302
|
+
globalPermissions: Permissions.create({ level: PermissionLevel.Full }),
|
|
2303
|
+
}).create();
|
|
2304
|
+
const token = await Token.createToken(admin);
|
|
2305
|
+
const nonEmtpyArray = new PatchableArray() as PatchableArrayAutoEncoder<EmergencyContact>;
|
|
2306
|
+
const d = new Date();
|
|
2307
|
+
nonEmtpyArray.addPatch(EmergencyContact.patch({
|
|
2308
|
+
id: contact1.id,
|
|
2309
|
+
name: 'Linda2 Doe2',
|
|
2310
|
+
title: 'Changed',
|
|
2311
|
+
updatedAt: d,
|
|
2312
|
+
}));
|
|
2313
|
+
|
|
2314
|
+
const arr: Body = new PatchableArray();
|
|
2315
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
2316
|
+
id: member2.id,
|
|
2317
|
+
details: MemberDetails.patch({
|
|
2318
|
+
emergencyContacts: nonEmtpyArray,
|
|
2319
|
+
}),
|
|
2320
|
+
});
|
|
2321
|
+
arr.addPatch(patch);
|
|
2322
|
+
|
|
2323
|
+
const request = Request.buildJson('PATCH', baseUrl, undefined, arr);
|
|
2324
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
2325
|
+
const response = await testServer.test(endpoint, request);
|
|
2326
|
+
|
|
2327
|
+
// Check returned
|
|
2328
|
+
expect(response.status).toBe(200);
|
|
2329
|
+
expect(response.body.members.length).toBe(1);
|
|
2330
|
+
|
|
2331
|
+
// Load contacts again
|
|
2332
|
+
await member1.refresh();
|
|
2333
|
+
await member2.refresh();
|
|
2334
|
+
|
|
2335
|
+
// The contact should be equal to contact1, and ignore other changes
|
|
2336
|
+
const expectedContact = EmergencyContact.create({
|
|
2337
|
+
...contact1,
|
|
2338
|
+
name: 'Linda2 Doe2',
|
|
2339
|
+
title: 'Changed',
|
|
2340
|
+
updatedAt: d,
|
|
2341
|
+
});
|
|
2342
|
+
|
|
2343
|
+
expect(member1.details.emergencyContacts).toEqual([expectedContact]);
|
|
2344
|
+
expect(member2.details.emergencyContacts).toEqual([expectedContact]);
|
|
2345
|
+
});
|
|
2346
|
+
|
|
2347
|
+
test('Adding a completely new emergency contact works correctly', async () => {
|
|
2348
|
+
const user = await new UserFactory({}).create();
|
|
2349
|
+
|
|
2350
|
+
const existing = EmergencyContact.create({
|
|
2351
|
+
name: 'Existing friend',
|
|
2352
|
+
title: 'Friend',
|
|
2353
|
+
phone: '+32412345111',
|
|
2354
|
+
});
|
|
2355
|
+
const member1 = await new MemberFactory({
|
|
2356
|
+
user,
|
|
2357
|
+
details: MemberDetails.create({
|
|
2358
|
+
firstName: 'John',
|
|
2359
|
+
lastName: 'Doe',
|
|
2360
|
+
emergencyContacts: [
|
|
2361
|
+
existing,
|
|
2362
|
+
],
|
|
2363
|
+
}),
|
|
2364
|
+
}).create();
|
|
2365
|
+
|
|
2366
|
+
const newContact = EmergencyContact.create({
|
|
2367
|
+
name: 'New Contact',
|
|
2368
|
+
title: 'Friend',
|
|
2369
|
+
phone: '+32412345670',
|
|
2370
|
+
});
|
|
2371
|
+
|
|
2372
|
+
const admin = await new UserFactory({
|
|
2373
|
+
globalPermissions: Permissions.create({ level: PermissionLevel.Full }),
|
|
2374
|
+
}).create();
|
|
2375
|
+
const token = await Token.createToken(admin);
|
|
2376
|
+
const nonEmtpyArray = new PatchableArray() as PatchableArrayAutoEncoder<EmergencyContact>;
|
|
2377
|
+
nonEmtpyArray.addPut(newContact);
|
|
2378
|
+
|
|
2379
|
+
const arr: Body = new PatchableArray();
|
|
2380
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
2381
|
+
id: member1.id,
|
|
2382
|
+
details: MemberDetails.patch({
|
|
2383
|
+
emergencyContacts: nonEmtpyArray,
|
|
2384
|
+
}),
|
|
2385
|
+
});
|
|
2386
|
+
arr.addPatch(patch);
|
|
2387
|
+
|
|
2388
|
+
const request = Request.buildJson('PATCH', baseUrl, undefined, arr);
|
|
2389
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
2390
|
+
const response = await testServer.test(endpoint, request);
|
|
2391
|
+
|
|
2392
|
+
// Check returned
|
|
2393
|
+
expect(response.status).toBe(200);
|
|
2394
|
+
expect(response.body.members.length).toBe(1);
|
|
2395
|
+
|
|
2396
|
+
// Load contacts again
|
|
2397
|
+
await member1.refresh();
|
|
2398
|
+
|
|
2399
|
+
// Check the new contact is added
|
|
2400
|
+
expect(member1.details.emergencyContacts).toEqual([
|
|
2401
|
+
existing,
|
|
2402
|
+
newContact,
|
|
2403
|
+
]);
|
|
2404
|
+
});
|
|
2405
|
+
|
|
2406
|
+
test('Updating an existing emergency contact\'s details works correctly', async () => {
|
|
2407
|
+
const user = await new UserFactory({}).create();
|
|
2408
|
+
|
|
2409
|
+
const contact1 = EmergencyContact.create({
|
|
2410
|
+
name: 'Linda Doe',
|
|
2411
|
+
title: 'Grandmother',
|
|
2412
|
+
phone: '+32412345678',
|
|
2413
|
+
createdAt: new Date(0),
|
|
2414
|
+
});
|
|
2415
|
+
|
|
2416
|
+
const member1 = await new MemberFactory({
|
|
2417
|
+
user,
|
|
2418
|
+
details: MemberDetails.create({
|
|
2419
|
+
firstName: 'John',
|
|
2420
|
+
lastName: 'Doe',
|
|
2421
|
+
emergencyContacts: [contact1],
|
|
2422
|
+
}),
|
|
2423
|
+
}).create();
|
|
2424
|
+
|
|
2425
|
+
const updatedContact = EmergencyContact.patch({
|
|
2426
|
+
id: contact1.id,
|
|
2427
|
+
phone: '+32412345679',
|
|
2428
|
+
});
|
|
2429
|
+
|
|
2430
|
+
const admin = await new UserFactory({
|
|
2431
|
+
globalPermissions: Permissions.create({ level: PermissionLevel.Full }),
|
|
2432
|
+
}).create();
|
|
2433
|
+
const token = await Token.createToken(admin);
|
|
2434
|
+
const nonEmtpyArray = new PatchableArray() as PatchableArrayAutoEncoder<EmergencyContact>;
|
|
2435
|
+
nonEmtpyArray.addPatch(updatedContact);
|
|
2436
|
+
|
|
2437
|
+
const arr: Body = new PatchableArray();
|
|
2438
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
2439
|
+
id: member1.id,
|
|
2440
|
+
details: MemberDetails.patch({
|
|
2441
|
+
emergencyContacts: nonEmtpyArray,
|
|
2442
|
+
}),
|
|
2443
|
+
});
|
|
2444
|
+
arr.addPatch(patch);
|
|
2445
|
+
|
|
2446
|
+
const request = Request.buildJson('PATCH', baseUrl, undefined, arr);
|
|
2447
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
2448
|
+
const response = await testServer.test(endpoint, request);
|
|
2449
|
+
|
|
2450
|
+
// Check returned
|
|
2451
|
+
expect(response.status).toBe(200);
|
|
2452
|
+
expect(response.body.members.length).toBe(1);
|
|
2453
|
+
|
|
2454
|
+
// Load contacts again
|
|
2455
|
+
await member1.refresh();
|
|
2456
|
+
|
|
2457
|
+
// Check the contact is updated
|
|
2458
|
+
const expectedContact = EmergencyContact.create({
|
|
2459
|
+
...contact1,
|
|
2460
|
+
phone: '+32412345679',
|
|
2461
|
+
});
|
|
2462
|
+
|
|
2463
|
+
expect(member1.details.emergencyContacts).toEqual([expectedContact]);
|
|
2464
|
+
});
|
|
2465
|
+
|
|
2466
|
+
test('Removing an emergency contact works correctly', async () => {
|
|
2467
|
+
const user = await new UserFactory({}).create();
|
|
2468
|
+
|
|
2469
|
+
const contact1 = EmergencyContact.create({
|
|
2470
|
+
name: 'Linda Doe',
|
|
2471
|
+
title: 'Grandmother',
|
|
2472
|
+
phone: '+32412345678',
|
|
2473
|
+
createdAt: new Date(0),
|
|
2474
|
+
});
|
|
2475
|
+
|
|
2476
|
+
const member1 = await new MemberFactory({
|
|
2477
|
+
user,
|
|
2478
|
+
details: MemberDetails.create({
|
|
2479
|
+
firstName: 'John',
|
|
2480
|
+
lastName: 'Doe',
|
|
2481
|
+
emergencyContacts: [contact1],
|
|
2482
|
+
}),
|
|
2483
|
+
}).create();
|
|
2484
|
+
|
|
2485
|
+
const admin = await new UserFactory({
|
|
2486
|
+
globalPermissions: Permissions.create({ level: PermissionLevel.Full }),
|
|
2487
|
+
}).create();
|
|
2488
|
+
const token = await Token.createToken(admin);
|
|
2489
|
+
const nonEmtpyArray = new PatchableArray() as PatchableArrayAutoEncoder<EmergencyContact>;
|
|
2490
|
+
nonEmtpyArray.addDelete(contact1.id);
|
|
2491
|
+
|
|
2492
|
+
const arr: Body = new PatchableArray();
|
|
2493
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
2494
|
+
id: member1.id,
|
|
2495
|
+
details: MemberDetails.patch({
|
|
2496
|
+
emergencyContacts: nonEmtpyArray,
|
|
2497
|
+
}),
|
|
2498
|
+
});
|
|
2499
|
+
arr.addPatch(patch);
|
|
2500
|
+
|
|
2501
|
+
const request = Request.buildJson('PATCH', baseUrl, undefined, arr);
|
|
2502
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
2503
|
+
const response = await testServer.test(endpoint, request);
|
|
2504
|
+
|
|
2505
|
+
// Check returned
|
|
2506
|
+
expect(response.status).toBe(200);
|
|
2507
|
+
expect(response.body.members.length).toBe(1);
|
|
2508
|
+
|
|
2509
|
+
// Load contacts again
|
|
2510
|
+
await member1.refresh();
|
|
2511
|
+
|
|
2512
|
+
// Check the contact is removed
|
|
2513
|
+
expect(member1.details.emergencyContacts).toEqual([]);
|
|
2514
|
+
});
|
|
2515
|
+
|
|
2516
|
+
test('Handling multiple members with different emergency contacts works correctly', async () => {
|
|
2517
|
+
const user = await new UserFactory({}).create();
|
|
2518
|
+
|
|
2519
|
+
const contact1 = EmergencyContact.create({
|
|
2520
|
+
name: 'Linda Doe',
|
|
2521
|
+
title: 'Grandmother',
|
|
2522
|
+
phone: '+32412345678',
|
|
2523
|
+
createdAt: new Date(0),
|
|
2524
|
+
});
|
|
2525
|
+
|
|
2526
|
+
const contact2 = EmergencyContact.create({
|
|
2527
|
+
name: 'John Doe',
|
|
2528
|
+
title: 'Uncle',
|
|
2529
|
+
phone: '+32412345679',
|
|
2530
|
+
createdAt: new Date(1000),
|
|
2531
|
+
});
|
|
2532
|
+
|
|
2533
|
+
const member1 = await new MemberFactory({
|
|
2534
|
+
user,
|
|
2535
|
+
details: MemberDetails.create({
|
|
2536
|
+
firstName: 'John',
|
|
2537
|
+
lastName: 'Doe',
|
|
2538
|
+
emergencyContacts: [contact1],
|
|
2539
|
+
}),
|
|
2540
|
+
}).create();
|
|
2541
|
+
|
|
2542
|
+
const member2 = await new MemberFactory({
|
|
2543
|
+
user,
|
|
2544
|
+
details: MemberDetails.create({
|
|
2545
|
+
firstName: 'Jane',
|
|
2546
|
+
lastName: 'Doe',
|
|
2547
|
+
emergencyContacts: [contact2],
|
|
2548
|
+
}),
|
|
2549
|
+
}).create();
|
|
2550
|
+
|
|
2551
|
+
const admin = await new UserFactory({
|
|
2552
|
+
globalPermissions: Permissions.create({ level: PermissionLevel.Full }),
|
|
2553
|
+
}).create();
|
|
2554
|
+
const token = await Token.createToken(admin);
|
|
2555
|
+
const nonEmtpyArray1 = new PatchableArray() as PatchableArrayAutoEncoder<EmergencyContact>;
|
|
2556
|
+
nonEmtpyArray1.addPatch(EmergencyContact.patch({
|
|
2557
|
+
id: contact1.id,
|
|
2558
|
+
phone: '+32412345680',
|
|
2559
|
+
}));
|
|
2560
|
+
|
|
2561
|
+
const nonEmtpyArray2 = new PatchableArray() as PatchableArrayAutoEncoder<EmergencyContact>;
|
|
2562
|
+
nonEmtpyArray2.addPatch(EmergencyContact.patch({
|
|
2563
|
+
id: contact2.id,
|
|
2564
|
+
phone: '+32412345681',
|
|
2565
|
+
}));
|
|
2566
|
+
|
|
2567
|
+
const arr: Body = new PatchableArray();
|
|
2568
|
+
const patch1 = MemberWithRegistrationsBlob.patch({
|
|
2569
|
+
id: member1.id,
|
|
2570
|
+
details: MemberDetails.patch({
|
|
2571
|
+
emergencyContacts: nonEmtpyArray1,
|
|
2572
|
+
}),
|
|
2573
|
+
});
|
|
2574
|
+
const patch2 = MemberWithRegistrationsBlob.patch({
|
|
2575
|
+
id: member2.id,
|
|
2576
|
+
details: MemberDetails.patch({
|
|
2577
|
+
emergencyContacts: nonEmtpyArray2,
|
|
2578
|
+
}),
|
|
2579
|
+
});
|
|
2580
|
+
arr.addPatch(patch1);
|
|
2581
|
+
arr.addPatch(patch2);
|
|
2582
|
+
|
|
2583
|
+
const request = Request.buildJson('PATCH', baseUrl, undefined, arr);
|
|
2584
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
2585
|
+
const response = await testServer.test(endpoint, request);
|
|
2586
|
+
|
|
2587
|
+
// Check returned
|
|
2588
|
+
expect(response.status).toBe(200);
|
|
2589
|
+
expect(response.body.members.length).toBe(2);
|
|
2590
|
+
|
|
2591
|
+
// Load contacts again
|
|
2592
|
+
await member1.refresh();
|
|
2593
|
+
await member2.refresh();
|
|
2594
|
+
|
|
2595
|
+
// Check the contacts are updated independently
|
|
2596
|
+
const expectedContact1 = EmergencyContact.create({
|
|
2597
|
+
...contact1,
|
|
2598
|
+
phone: '+32412345680',
|
|
2599
|
+
});
|
|
2600
|
+
|
|
2601
|
+
const expectedContact2 = EmergencyContact.create({
|
|
2602
|
+
...contact2,
|
|
2603
|
+
phone: '+32412345681',
|
|
2604
|
+
});
|
|
2605
|
+
|
|
2606
|
+
expect(member1.details.emergencyContacts).toEqual([expectedContact1]);
|
|
2607
|
+
expect(member2.details.emergencyContacts).toEqual([expectedContact2]);
|
|
2608
|
+
});
|
|
2609
|
+
|
|
2610
|
+
test('Handling emergency contacts with different IDs but same details works correctly', async () => {
|
|
2611
|
+
const user = await new UserFactory({}).create();
|
|
2612
|
+
|
|
2613
|
+
const contact1 = EmergencyContact.create({
|
|
2614
|
+
name: 'Linda Doe',
|
|
2615
|
+
title: 'Grandmother',
|
|
2616
|
+
phone: '+32412345678',
|
|
2617
|
+
createdAt: new Date(0),
|
|
2618
|
+
});
|
|
2619
|
+
|
|
2620
|
+
const contact2 = EmergencyContact.create({
|
|
2621
|
+
name: 'Linda Doe',
|
|
2622
|
+
title: 'Grandmother',
|
|
2623
|
+
phone: '+32412345678',
|
|
2624
|
+
createdAt: new Date(1000),
|
|
2625
|
+
});
|
|
2626
|
+
|
|
2627
|
+
const member1 = await new MemberFactory({
|
|
2628
|
+
user,
|
|
2629
|
+
details: MemberDetails.create({
|
|
2630
|
+
firstName: 'John',
|
|
2631
|
+
lastName: 'Doe',
|
|
2632
|
+
emergencyContacts: [contact1],
|
|
2633
|
+
}),
|
|
2634
|
+
}).create();
|
|
2635
|
+
|
|
2636
|
+
const member2 = await new MemberFactory({
|
|
2637
|
+
user,
|
|
2638
|
+
details: MemberDetails.create({
|
|
2639
|
+
firstName: 'Jane',
|
|
2640
|
+
lastName: 'Doe',
|
|
2641
|
+
emergencyContacts: [contact2],
|
|
2642
|
+
}),
|
|
2643
|
+
}).create();
|
|
2644
|
+
|
|
2645
|
+
const admin = await new UserFactory({
|
|
2646
|
+
globalPermissions: Permissions.create({ level: PermissionLevel.Full }),
|
|
2647
|
+
}).create();
|
|
2648
|
+
const token = await Token.createToken(admin);
|
|
2649
|
+
const nonEmtpyArray = new PatchableArray() as PatchableArrayAutoEncoder<EmergencyContact>;
|
|
2650
|
+
nonEmtpyArray.addPatch(EmergencyContact.patch({
|
|
2651
|
+
id: contact1.id,
|
|
2652
|
+
phone: '+32412345679',
|
|
2653
|
+
}));
|
|
2654
|
+
|
|
2655
|
+
const arr: Body = new PatchableArray();
|
|
2656
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
2657
|
+
id: member1.id,
|
|
2658
|
+
details: MemberDetails.patch({
|
|
2659
|
+
emergencyContacts: nonEmtpyArray,
|
|
2660
|
+
}),
|
|
2661
|
+
});
|
|
2662
|
+
arr.addPatch(patch);
|
|
2663
|
+
|
|
2664
|
+
const request = Request.buildJson('PATCH', baseUrl, undefined, arr);
|
|
2665
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
2666
|
+
const response = await testServer.test(endpoint, request);
|
|
2667
|
+
|
|
2668
|
+
// Check returned
|
|
2669
|
+
expect(response.status).toBe(200);
|
|
2670
|
+
expect(response.body.members.length).toBe(1);
|
|
2671
|
+
|
|
2672
|
+
// Load contacts again
|
|
2673
|
+
await member1.refresh();
|
|
2674
|
+
await member2.refresh();
|
|
2675
|
+
|
|
2676
|
+
// Check the contacts are updated correctly
|
|
2677
|
+
const expectedContact = EmergencyContact.create({
|
|
2678
|
+
...contact1,
|
|
2679
|
+
phone: '+32412345679',
|
|
2680
|
+
});
|
|
2681
|
+
|
|
2682
|
+
expect(member1.details.emergencyContacts).toEqual([expectedContact]);
|
|
2683
|
+
expect(member2.details.emergencyContacts).toEqual([expectedContact]);
|
|
2684
|
+
});
|
|
2685
|
+
});
|
|
2686
|
+
});
|