@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,822 @@
1
+ import { Database } from '@simonbackx/simple-database';
2
+ import { Member, MemberFactory, MemberResponsibilityRecordFactory, User, UserFactory } from '@stamhoofd/models';
3
+ import { MemberDetails, Parent, UserPermissions } from '@stamhoofd/structures';
4
+ import { TestUtils } from '@stamhoofd/test-utils';
5
+ import { MemberUserSyncer } from './MemberUserSyncer';
6
+
7
+ describe('Helpers.MemberUserSyncer', () => {
8
+ beforeEach(async () => {
9
+ TestUtils.setPermanentEnvironment('userMode', 'platform');
10
+ });
11
+
12
+ afterEach(async () => {
13
+ await Database.delete('DELETE FROM users');
14
+ await Database.delete('DELETE FROM members');
15
+ });
16
+
17
+ test('Each email, parent and unverified email receives an account', async () => {
18
+ const member = await new MemberFactory({
19
+ details: MemberDetails.create({
20
+ firstName: 'John',
21
+ lastName: 'Doe',
22
+ parents: [
23
+ Parent.create({
24
+ firstName: 'Linda',
25
+ lastName: 'Potter',
26
+ email: 'linda@example.com',
27
+ }),
28
+ Parent.create({
29
+ firstName: 'Peter',
30
+ lastName: 'Doe',
31
+ email: 'peter@example.com',
32
+ alternativeEmails: [
33
+ 'peter@work.com',
34
+ ],
35
+ }),
36
+ ],
37
+ email: 'john@example.com',
38
+ alternativeEmails: ['john@work.com'],
39
+ unverifiedEmails: ['untitled@example.com', 'peter@example.com'], // Last one should be ignored
40
+ }),
41
+ }).create();
42
+
43
+ await MemberUserSyncer.onChangeMember(member);
44
+
45
+ const users = await Member.users.load(member);
46
+ expect(users).toIncludeSameMembers([
47
+ // Member
48
+ expect.objectContaining({
49
+ firstName: 'John',
50
+ lastName: 'Doe',
51
+ email: 'john@example.com',
52
+ memberId: member.id,
53
+ }),
54
+ expect.objectContaining({
55
+ firstName: 'John',
56
+ lastName: 'Doe',
57
+ email: 'john@work.com',
58
+ memberId: member.id,
59
+ }),
60
+
61
+ // Parents
62
+ expect.objectContaining({
63
+ firstName: 'Linda',
64
+ lastName: 'Potter',
65
+ email: 'linda@example.com',
66
+ memberId: null,
67
+ permissions: null,
68
+ }),
69
+ expect.objectContaining({
70
+ firstName: 'Peter',
71
+ lastName: 'Doe',
72
+ email: 'peter@example.com',
73
+ memberId: null,
74
+ permissions: null,
75
+ }),
76
+ expect.objectContaining({
77
+ firstName: 'Peter',
78
+ lastName: 'Doe',
79
+ email: 'peter@work.com',
80
+ memberId: null,
81
+ permissions: null,
82
+ }),
83
+
84
+ // Unverified
85
+ expect.objectContaining({
86
+ firstName: null,
87
+ lastName: null,
88
+ email: 'untitled@example.com',
89
+ memberId: null,
90
+ permissions: null,
91
+ }),
92
+ ]);
93
+ });
94
+
95
+ test('Existing users are updated with a member id and name', async () => {
96
+ const user_m1 = await new UserFactory({ email: 'john@example.com' }).create();
97
+ const user_m2 = await new UserFactory({ email: 'john@work.com' }).create();
98
+ const user_p1 = await new UserFactory({ email: 'linda@example.com' }).create();
99
+ const user_p2a = await new UserFactory({ email: 'peter@example.com' }).create();
100
+ const user_p2b = await new UserFactory({ email: 'peter@work.com' }).create();
101
+ const user_unverified = await new UserFactory({ email: 'untitled@example.com' }).create();
102
+
103
+ const member = await new MemberFactory({
104
+ details: MemberDetails.create({
105
+ firstName: 'John',
106
+ lastName: 'Doe',
107
+ parents: [
108
+ Parent.create({
109
+ firstName: 'Linda',
110
+ lastName: 'Potter',
111
+ email: 'linda@example.com',
112
+ }),
113
+ Parent.create({
114
+ firstName: 'Peter',
115
+ lastName: 'Doe',
116
+ email: 'peter@example.com',
117
+ alternativeEmails: [
118
+ 'peter@work.com',
119
+ ],
120
+ }),
121
+ ],
122
+ email: 'john@example.com',
123
+ alternativeEmails: ['john@work.com'],
124
+ unverifiedEmails: ['untitled@example.com', 'peter@example.com'], // Last one should be ignored
125
+ }),
126
+ }).create();
127
+
128
+ await MemberUserSyncer.onChangeMember(member);
129
+
130
+ const users = await Member.users.load(member);
131
+ expect(users).toIncludeSameMembers([
132
+ // Member
133
+ expect.objectContaining({
134
+ id: user_m1.id,
135
+ firstName: 'John',
136
+ lastName: 'Doe',
137
+ email: 'john@example.com',
138
+ memberId: member.id,
139
+ permissions: null,
140
+ }),
141
+ expect.objectContaining({
142
+ id: user_m2.id,
143
+ firstName: 'John',
144
+ lastName: 'Doe',
145
+ email: 'john@work.com',
146
+ memberId: member.id,
147
+ permissions: null,
148
+ }),
149
+
150
+ // Parents
151
+ expect.objectContaining({
152
+ id: user_p1.id,
153
+ firstName: 'Linda',
154
+ lastName: 'Potter',
155
+ email: 'linda@example.com',
156
+ memberId: null,
157
+ permissions: null,
158
+ }),
159
+ expect.objectContaining({
160
+ id: user_p2a.id,
161
+ firstName: 'Peter',
162
+ lastName: 'Doe',
163
+ email: 'peter@example.com',
164
+ memberId: null,
165
+ permissions: null,
166
+ }),
167
+ expect.objectContaining({
168
+ id: user_p2b.id,
169
+ firstName: 'Peter',
170
+ lastName: 'Doe',
171
+ email: 'peter@work.com',
172
+ memberId: null,
173
+ permissions: null,
174
+ }),
175
+
176
+ // Unverified
177
+ expect.objectContaining({
178
+ id: user_unverified.id,
179
+ firstName: null,
180
+ lastName: null,
181
+ email: 'untitled@example.com',
182
+ memberId: null,
183
+ permissions: null,
184
+ }),
185
+ ]);
186
+ });
187
+
188
+ test('Custom user names are maintained for parents', async () => {
189
+ const user_p1 = await new UserFactory({ email: 'linda@example.com', firstName: 'Custom', lastName: 'Name' }).create();
190
+
191
+ const member = await new MemberFactory({
192
+ details: MemberDetails.create({
193
+ firstName: 'John',
194
+ lastName: 'Doe',
195
+ parents: [
196
+ Parent.create({
197
+ firstName: 'Linda',
198
+ lastName: 'Potter',
199
+ email: 'linda@example.com',
200
+ }),
201
+ ],
202
+ }),
203
+ }).create();
204
+
205
+ await MemberUserSyncer.onChangeMember(member);
206
+
207
+ const users = await Member.users.load(member);
208
+ expect(users).toIncludeSameMembers([
209
+ // Member
210
+ expect.objectContaining({
211
+ id: user_p1.id,
212
+ firstName: 'Custom',
213
+ lastName: 'Name',
214
+ email: 'linda@example.com',
215
+ memberId: null,
216
+ permissions: null,
217
+ }),
218
+ ]);
219
+ });
220
+
221
+ test('Parent name is cleared if it equals the member name', async () => {
222
+ const user_p1 = await new UserFactory({ email: 'linda@example.com', firstName: 'John', lastName: 'Doe' }).create();
223
+
224
+ const member = await new MemberFactory({
225
+ details: MemberDetails.create({
226
+ firstName: 'John',
227
+ lastName: 'Doe',
228
+ parents: [
229
+ Parent.create({
230
+ firstName: 'Linda',
231
+ lastName: 'Potter',
232
+ email: 'linda@example.com',
233
+ }),
234
+ ],
235
+ }),
236
+ }).create();
237
+
238
+ await MemberUserSyncer.onChangeMember(member);
239
+
240
+ const users = await Member.users.load(member);
241
+ expect(users).toIncludeSameMembers([
242
+ // Member
243
+ expect.objectContaining({
244
+ id: user_p1.id,
245
+ firstName: 'Linda',
246
+ lastName: 'Potter',
247
+ email: 'linda@example.com',
248
+ memberId: null,
249
+ permissions: null,
250
+ }),
251
+ ]);
252
+ });
253
+
254
+ test('Parent name is cleared if it equals the member name with caps difference', async () => {
255
+ const user_p1 = await new UserFactory({ email: 'linda@example.com', firstName: 'john', lastName: 'doe' }).create();
256
+
257
+ const member = await new MemberFactory({
258
+ details: MemberDetails.create({
259
+ firstName: 'John',
260
+ lastName: 'Doe',
261
+ parents: [
262
+ Parent.create({
263
+ firstName: 'Linda',
264
+ lastName: 'Potter',
265
+ email: 'linda@example.com',
266
+ }),
267
+ ],
268
+ }),
269
+ }).create();
270
+
271
+ await MemberUserSyncer.onChangeMember(member);
272
+
273
+ const users = await Member.users.load(member);
274
+ expect(users).toIncludeSameMembers([
275
+ // Member
276
+ expect.objectContaining({
277
+ id: user_p1.id,
278
+ firstName: 'Linda',
279
+ lastName: 'Potter',
280
+ email: 'linda@example.com',
281
+ memberId: null,
282
+ permissions: null,
283
+ }),
284
+ ]);
285
+ });
286
+
287
+ test('Existing parent user can have the same name as the member', async () => {
288
+ const user_p1 = await new UserFactory({ email: 'linda@example.com', firstName: 'John', lastName: 'Doe' }).create();
289
+
290
+ const member = await new MemberFactory({
291
+ details: MemberDetails.create({
292
+ firstName: 'John',
293
+ lastName: 'Doe',
294
+ parents: [
295
+ Parent.create({
296
+ firstName: 'John',
297
+ lastName: 'Doe',
298
+ email: 'linda@example.com',
299
+ }),
300
+ ],
301
+ }),
302
+ }).create();
303
+
304
+ await MemberUserSyncer.onChangeMember(member);
305
+
306
+ const users = await Member.users.load(member);
307
+ expect(users).toIncludeSameMembers([
308
+ // Member
309
+ expect.objectContaining({
310
+ id: user_p1.id,
311
+ firstName: 'John',
312
+ lastName: 'Doe',
313
+ email: 'linda@example.com',
314
+ memberId: null,
315
+ permissions: null,
316
+ }),
317
+ ]);
318
+ });
319
+
320
+ test('New parent user can have the same name as the member', async () => {
321
+ const member = await new MemberFactory({
322
+ details: MemberDetails.create({
323
+ firstName: 'John',
324
+ lastName: 'Doe',
325
+ parents: [
326
+ Parent.create({
327
+ firstName: 'John',
328
+ lastName: 'Doe',
329
+ email: 'linda@example.com',
330
+ }),
331
+ ],
332
+ }),
333
+ }).create();
334
+
335
+ await MemberUserSyncer.onChangeMember(member);
336
+
337
+ const users = await Member.users.load(member);
338
+ expect(users).toIncludeSameMembers([
339
+ // Member
340
+ expect.objectContaining({
341
+ firstName: 'John',
342
+ lastName: 'Doe',
343
+ email: 'linda@example.com',
344
+ memberId: null,
345
+ permissions: null,
346
+ }),
347
+ ]);
348
+ });
349
+
350
+ describe('Unlinking', () => {
351
+ test('Old emails without account are removed', async () => {
352
+ const member = await new MemberFactory({
353
+ details: MemberDetails.create({
354
+ firstName: 'John',
355
+ lastName: 'Doe',
356
+ parents: [
357
+ Parent.create({
358
+ firstName: 'Linda',
359
+ lastName: 'Potter',
360
+ email: 'linda@example.com',
361
+ }),
362
+ Parent.create({
363
+ firstName: 'Peter',
364
+ lastName: 'Doe',
365
+ email: 'peter@example.com',
366
+ alternativeEmails: [
367
+ 'peter@work.com',
368
+ ],
369
+ }),
370
+ ],
371
+ email: 'john@example.com',
372
+ alternativeEmails: ['john@work.com'],
373
+ unverifiedEmails: ['untitled@example.com', 'peter@example.com'], // Last one should be ignored
374
+ }),
375
+ }).create();
376
+ await new MemberResponsibilityRecordFactory({
377
+ member,
378
+ }).create();
379
+
380
+ await MemberUserSyncer.onChangeMember(member);
381
+
382
+ const users = await Member.users.load(member);
383
+ expect(users).toIncludeSameMembers([
384
+ // Member
385
+ expect.objectContaining({
386
+ firstName: 'John',
387
+ lastName: 'Doe',
388
+ email: 'john@example.com',
389
+ memberId: member.id,
390
+ permissions: expect.any(UserPermissions),
391
+ }),
392
+ expect.objectContaining({
393
+ firstName: 'John',
394
+ lastName: 'Doe',
395
+ email: 'john@work.com',
396
+ memberId: member.id,
397
+ permissions: expect.any(UserPermissions),
398
+ }),
399
+
400
+ // Parents
401
+ expect.objectContaining({
402
+ firstName: 'Linda',
403
+ lastName: 'Potter',
404
+ email: 'linda@example.com',
405
+ memberId: null,
406
+ permissions: null,
407
+ }),
408
+ expect.objectContaining({
409
+ firstName: 'Peter',
410
+ lastName: 'Doe',
411
+ email: 'peter@example.com',
412
+ memberId: null,
413
+ permissions: null,
414
+ }),
415
+ expect.objectContaining({
416
+ firstName: 'Peter',
417
+ lastName: 'Doe',
418
+ email: 'peter@work.com',
419
+ memberId: null,
420
+ permissions: null,
421
+ }),
422
+
423
+ // Unverified
424
+ expect.objectContaining({
425
+ firstName: null,
426
+ lastName: null,
427
+ email: 'untitled@example.com',
428
+ memberId: null,
429
+ permissions: null,
430
+ }),
431
+ ]);
432
+
433
+ // Now remove the work address of peter
434
+ member.details.parents[1].alternativeEmails = [];
435
+ member.details.parents[1].email = null;
436
+ member.details.parents[0].email = null;
437
+ member.details.alternativeEmails = [];
438
+ member.details.unverifiedEmails = [];
439
+
440
+ // Save member
441
+ if (!await member.save()) {
442
+ throw new Error('Failed to save member');
443
+ }
444
+
445
+ // Sync again
446
+ await MemberUserSyncer.onChangeMember(member);
447
+
448
+ const users2 = await Member.users.load(member);
449
+ expect(users2).toIncludeSameMembers([
450
+ // Member
451
+ expect.objectContaining({
452
+ firstName: 'John',
453
+ lastName: 'Doe',
454
+ email: 'john@example.com',
455
+ memberId: member.id,
456
+ permissions: expect.any(UserPermissions),
457
+ }),
458
+ ]);
459
+
460
+ const userWork = await User.select().where('email', 'john@work.com').first(true);
461
+ expect(userWork).toMatchObject({
462
+ firstName: null,
463
+ lastName: null,
464
+ memberId: null,
465
+ permissions: null,
466
+ });
467
+ });
468
+
469
+ test('Old emails with account are not removed', async () => {
470
+ const user_m1 = await new UserFactory({ email: 'john@example.com' }).create();
471
+ const user_m2 = await new UserFactory({ email: 'john@work.com' }).create();
472
+ const user_p1 = await new UserFactory({ email: 'linda@example.com' }).create();
473
+ const user_p2a = await new UserFactory({ email: 'peter@example.com' }).create();
474
+ const user_p2b = await new UserFactory({ email: 'peter@work.com' }).create();
475
+ const user_unverified = await new UserFactory({ email: 'untitled@example.com' }).create();
476
+
477
+ const member = await new MemberFactory({
478
+ details: MemberDetails.create({
479
+ firstName: 'John',
480
+ lastName: 'Doe',
481
+ parents: [
482
+ Parent.create({
483
+ firstName: 'Linda',
484
+ lastName: 'Potter',
485
+ email: 'linda@example.com',
486
+ }),
487
+ Parent.create({
488
+ firstName: 'Peter',
489
+ lastName: 'Doe',
490
+ email: 'peter@example.com',
491
+ alternativeEmails: [
492
+ 'peter@work.com',
493
+ ],
494
+ }),
495
+ ],
496
+ email: 'john@example.com',
497
+ alternativeEmails: ['john@work.com'],
498
+ unverifiedEmails: ['untitled@example.com', 'peter@example.com'], // Last one should be ignored
499
+ }),
500
+ }).create();
501
+
502
+ await new MemberResponsibilityRecordFactory({
503
+ member,
504
+ }).create();
505
+
506
+ await MemberUserSyncer.onChangeMember(member);
507
+
508
+ const users = await Member.users.load(member);
509
+ expect(users).toIncludeSameMembers([
510
+ // Member
511
+ expect.objectContaining({
512
+ id: user_m1.id,
513
+ firstName: 'John',
514
+ lastName: 'Doe',
515
+ email: 'john@example.com',
516
+ memberId: member.id,
517
+ permissions: expect.any(UserPermissions),
518
+ }),
519
+ expect.objectContaining({
520
+ id: user_m2.id,
521
+ firstName: 'John',
522
+ lastName: 'Doe',
523
+ email: 'john@work.com',
524
+ memberId: member.id,
525
+ permissions: expect.any(UserPermissions),
526
+ }),
527
+
528
+ // Parents
529
+ expect.objectContaining({
530
+ id: user_p1.id,
531
+ firstName: 'Linda',
532
+ lastName: 'Potter',
533
+ email: 'linda@example.com',
534
+ memberId: null,
535
+ permissions: null,
536
+ }),
537
+ expect.objectContaining({
538
+ id: user_p2a.id,
539
+ firstName: 'Peter',
540
+ lastName: 'Doe',
541
+ email: 'peter@example.com',
542
+ memberId: null,
543
+ permissions: null,
544
+ }),
545
+ expect.objectContaining({
546
+ id: user_p2b.id,
547
+ firstName: 'Peter',
548
+ lastName: 'Doe',
549
+ email: 'peter@work.com',
550
+ memberId: null,
551
+ permissions: null,
552
+ }),
553
+
554
+ // Unverified
555
+ expect.objectContaining({
556
+ id: user_unverified.id,
557
+ firstName: null,
558
+ lastName: null,
559
+ email: 'untitled@example.com',
560
+ memberId: null,
561
+ permissions: null,
562
+ }),
563
+ ]);
564
+
565
+ // Now remove the work address of peter
566
+ member.details.parents[1].alternativeEmails = [];
567
+ member.details.parents[1].email = null;
568
+ member.details.parents[0].email = null;
569
+ member.details.alternativeEmails = [];
570
+ member.details.unverifiedEmails = [];
571
+
572
+ // Save member
573
+ await member.save();
574
+
575
+ // Sync again
576
+ await MemberUserSyncer.onChangeMember(member);
577
+
578
+ const users2 = await Member.users.load(member);
579
+ expect(users2).toIncludeSameMembers([
580
+ // Member
581
+ expect.objectContaining({
582
+ id: user_m1.id,
583
+ firstName: 'John',
584
+ lastName: 'Doe',
585
+ email: 'john@example.com',
586
+ memberId: member.id,
587
+ permissions: expect.any(UserPermissions),
588
+ }),
589
+ expect.objectContaining({
590
+ id: user_m2.id,
591
+ firstName: null, // this has been reset
592
+ lastName: null, // this has been reset
593
+ email: 'john@work.com',
594
+ memberId: null, // this has been reset
595
+ permissions: null, // this has been reset
596
+ }),
597
+
598
+ // Parents
599
+ expect.objectContaining({
600
+ id: user_p1.id,
601
+ firstName: 'Linda',
602
+ lastName: 'Potter',
603
+ email: 'linda@example.com',
604
+ memberId: null,
605
+ permissions: null,
606
+ }),
607
+ expect.objectContaining({
608
+ id: user_p2a.id,
609
+ firstName: 'Peter',
610
+ lastName: 'Doe',
611
+ email: 'peter@example.com',
612
+ memberId: null,
613
+ permissions: null,
614
+ }),
615
+ expect.objectContaining({
616
+ id: user_p2b.id,
617
+ firstName: 'Peter',
618
+ lastName: 'Doe',
619
+ email: 'peter@work.com',
620
+ memberId: null,
621
+ permissions: null,
622
+ }),
623
+
624
+ // Unverified
625
+ expect.objectContaining({
626
+ id: user_unverified.id,
627
+ firstName: null,
628
+ lastName: null,
629
+ email: 'untitled@example.com',
630
+ memberId: null,
631
+ permissions: null,
632
+ }),
633
+ ]);
634
+ });
635
+ });
636
+
637
+ describe('Members with the same email addresses', () => {
638
+ test('The most recent member is linked to a user if both do not have responsibilities', async () => {
639
+ const member1 = await new MemberFactory({
640
+ details: MemberDetails.create({
641
+ firstName: 'John',
642
+ lastName: 'Doe',
643
+ email: 'john@example.com',
644
+ }),
645
+ }).create();
646
+ await MemberUserSyncer.onChangeMember(member1);
647
+ const users1 = await Member.users.load(member1);
648
+ expect(users1).toIncludeSameMembers([
649
+ expect.objectContaining({
650
+ firstName: 'John',
651
+ lastName: 'Doe',
652
+ email: 'john@example.com',
653
+ memberId: member1.id,
654
+ permissions: null,
655
+ }),
656
+ ]);
657
+
658
+ // Wait 1 second to make sure we save a new timestamp
659
+ await new Promise(resolve => setTimeout(resolve, 2000));
660
+
661
+ // member2 should not seize the memberId
662
+ const member2 = await new MemberFactory({
663
+ details: MemberDetails.create({
664
+ firstName: 'Other',
665
+ lastName: 'Doe',
666
+ email: 'john@example.com',
667
+ }),
668
+ }).create();
669
+
670
+ await MemberUserSyncer.onChangeMember(member2);
671
+ const users2 = await Member.users.load(member2);
672
+ expect(users2).toIncludeSameMembers([
673
+ expect.objectContaining({
674
+ firstName: 'Other',
675
+ lastName: 'Doe',
676
+ email: 'john@example.com',
677
+ memberId: member2.id,
678
+ permissions: null,
679
+ }),
680
+ ]);
681
+
682
+ // Even if called sync again on other member
683
+ await MemberUserSyncer.onChangeMember(member1);
684
+ const users3 = await Member.users.load(member1);
685
+ expect(users3).toIncludeSameMembers([
686
+ expect.objectContaining({
687
+ firstName: 'Other',
688
+ lastName: 'Doe',
689
+ email: 'john@example.com',
690
+ memberId: member2.id,
691
+ permissions: null,
692
+ }),
693
+ ]);
694
+ });
695
+
696
+ test('The most old member is linked to a user if both have responsibilities', async () => {
697
+ const member1 = await new MemberFactory({
698
+ details: MemberDetails.create({
699
+ firstName: 'John',
700
+ lastName: 'Doe',
701
+ email: 'john@example.com',
702
+ }),
703
+ }).create();
704
+ await new MemberResponsibilityRecordFactory({
705
+ member: member1,
706
+ }).create();
707
+
708
+ await MemberUserSyncer.onChangeMember(member1);
709
+
710
+ const users1 = await Member.users.load(member1);
711
+ expect(users1).toIncludeSameMembers([
712
+ expect.objectContaining({
713
+ firstName: 'John',
714
+ lastName: 'Doe',
715
+ email: 'john@example.com',
716
+ memberId: member1.id,
717
+ permissions: expect.any(UserPermissions),
718
+ }),
719
+ ]);
720
+ await new Promise(resolve => setTimeout(resolve, 2000));
721
+
722
+ // member2 should not seize the memberId
723
+ const member2 = await new MemberFactory({
724
+ details: MemberDetails.create({
725
+ firstName: 'Other',
726
+ lastName: 'Doe',
727
+ email: 'john@example.com',
728
+ }),
729
+ }).create();
730
+ await new MemberResponsibilityRecordFactory({
731
+ member: member2,
732
+ }).create();
733
+
734
+ await MemberUserSyncer.onChangeMember(member2);
735
+ const users2 = await Member.users.load(member2);
736
+
737
+ // Stayed the same
738
+ expect(users2).toIncludeSameMembers([
739
+ expect.objectContaining({
740
+ firstName: 'John',
741
+ lastName: 'Doe',
742
+ email: 'john@example.com',
743
+ memberId: member1.id,
744
+ permissions: expect.any(UserPermissions),
745
+ }),
746
+ ]);
747
+
748
+ // Even if called sync again on other member
749
+ await MemberUserSyncer.onChangeMember(member1);
750
+ const users3 = await Member.users.load(member1);
751
+ expect(users3).toIncludeSameMembers([
752
+ expect.objectContaining({
753
+ firstName: 'John',
754
+ lastName: 'Doe',
755
+ email: 'john@example.com',
756
+ memberId: member1.id,
757
+ permissions: expect.any(UserPermissions),
758
+ }),
759
+ ]);
760
+ });
761
+
762
+ test('The member with responsibilities is linked to a user', async () => {
763
+ const member1 = await new MemberFactory({
764
+ details: MemberDetails.create({
765
+ firstName: 'John',
766
+ lastName: 'Doe',
767
+ email: 'john@example.com',
768
+ }),
769
+ }).create();
770
+ await MemberUserSyncer.onChangeMember(member1);
771
+ const users1 = await Member.users.load(member1);
772
+ expect(users1).toIncludeSameMembers([
773
+ expect.objectContaining({
774
+ firstName: 'John',
775
+ lastName: 'Doe',
776
+ email: 'john@example.com',
777
+ memberId: member1.id,
778
+ permissions: null,
779
+ }),
780
+ ]);
781
+
782
+ // member2 should not seize the memberId
783
+ const member2 = await new MemberFactory({
784
+ details: MemberDetails.create({
785
+ firstName: 'Other',
786
+ lastName: 'Doe',
787
+ email: 'john@example.com',
788
+ }),
789
+ }).create();
790
+
791
+ // Attach a responsibility to member2
792
+ await new MemberResponsibilityRecordFactory({
793
+ member: member2,
794
+ }).create();
795
+
796
+ await MemberUserSyncer.onChangeMember(member2);
797
+ const users2 = await Member.users.load(member2);
798
+ expect(users2).toIncludeSameMembers([
799
+ expect.objectContaining({
800
+ firstName: 'Other',
801
+ lastName: 'Doe',
802
+ email: 'john@example.com',
803
+ memberId: member2.id,
804
+ permissions: expect.any(UserPermissions),
805
+ }),
806
+ ]);
807
+
808
+ // Even if called sync again on other member
809
+ await MemberUserSyncer.onChangeMember(member1);
810
+ const users3 = await Member.users.load(member1);
811
+ expect(users3).toIncludeSameMembers([
812
+ expect.objectContaining({
813
+ firstName: 'Other',
814
+ lastName: 'Doe',
815
+ email: 'john@example.com',
816
+ memberId: member2.id,
817
+ permissions: expect.any(UserPermissions),
818
+ }),
819
+ ]);
820
+ });
821
+ });
822
+ });