@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.
Files changed (33) hide show
  1. package/.env.ci.json +19 -8
  2. package/index.ts +8 -1
  3. package/package.json +13 -12
  4. package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +1 -1
  5. package/src/endpoints/auth/CreateAdminEndpoint.ts +1 -1
  6. package/src/endpoints/auth/CreateTokenEndpoint.ts +1 -1
  7. package/src/endpoints/auth/ForgotPasswordEndpoint.ts +1 -1
  8. package/src/endpoints/auth/PatchUserEndpoint.ts +1 -1
  9. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +2686 -0
  10. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +206 -20
  11. package/src/endpoints/global/members/shouldCheckIfMemberIsDuplicate.ts +9 -21
  12. package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +1 -1
  13. package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +2 -2
  14. package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +15 -14
  15. package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +3 -23
  16. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +12 -0
  17. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +2 -3
  18. package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +2 -2
  19. package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.test.ts +164 -0
  20. package/src/helpers/AdminPermissionChecker.ts +41 -2
  21. package/src/helpers/AuthenticatedStructures.ts +77 -20
  22. package/src/helpers/ForwardHandler.test.ts +16 -5
  23. package/src/helpers/ForwardHandler.ts +21 -9
  24. package/src/helpers/MemberUserSyncer.test.ts +822 -0
  25. package/src/helpers/MemberUserSyncer.ts +137 -108
  26. package/src/helpers/TagHelper.ts +3 -3
  27. package/src/seeds/1734596144-fill-previous-period-id.ts +1 -1
  28. package/src/seeds/1741008870-fix-auditlog-description.ts +50 -0
  29. package/src/seeds/1741011468-fix-auditlog-description-uft16.ts +88 -0
  30. package/src/services/FileSignService.ts +1 -1
  31. package/src/services/PlatformMembershipService.ts +7 -2
  32. package/src/services/SSOService.ts +1 -1
  33. 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
+ });