@stamhoofd/backend 2.78.4 → 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 (29) hide show
  1. package/index.ts +1 -1
  2. package/package.json +13 -12
  3. package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +1 -1
  4. package/src/endpoints/auth/CreateAdminEndpoint.ts +1 -1
  5. package/src/endpoints/auth/CreateTokenEndpoint.ts +1 -1
  6. package/src/endpoints/auth/ForgotPasswordEndpoint.ts +1 -1
  7. package/src/endpoints/auth/PatchUserEndpoint.ts +1 -1
  8. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +1964 -4
  9. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +175 -2
  10. package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +1 -1
  11. package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +2 -2
  12. package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +7 -1
  13. package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +3 -23
  14. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +12 -0
  15. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +2 -3
  16. package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.test.ts +164 -0
  17. package/src/helpers/AdminPermissionChecker.ts +22 -1
  18. package/src/helpers/AuthenticatedStructures.ts +77 -20
  19. package/src/helpers/ForwardHandler.test.ts +16 -5
  20. package/src/helpers/ForwardHandler.ts +21 -9
  21. package/src/helpers/MemberUserSyncer.test.ts +822 -0
  22. package/src/helpers/MemberUserSyncer.ts +137 -108
  23. package/src/helpers/TagHelper.ts +3 -3
  24. package/src/seeds/1734596144-fill-previous-period-id.ts +1 -1
  25. package/src/seeds/1741008870-fix-auditlog-description.ts +50 -0
  26. package/src/seeds/1741011468-fix-auditlog-description-uft16.ts +88 -0
  27. package/src/services/PlatformMembershipService.ts +7 -2
  28. package/src/services/SSOService.ts +1 -1
  29. package/tests/e2e/register.test.ts +2 -2
@@ -1,8 +1,8 @@
1
1
  import { Database } from '@simonbackx/simple-database';
2
- import { PatchableArray, PatchMap } from '@simonbackx/simple-encoding';
2
+ import { PatchableArray, PatchableArrayAutoEncoder, PatchMap } from '@simonbackx/simple-encoding';
3
3
  import { Endpoint, Request } from '@simonbackx/simple-endpoints';
4
4
  import { GroupFactory, MemberFactory, OrganizationFactory, OrganizationTagFactory, Platform, RegistrationFactory, Token, UserFactory } from '@stamhoofd/models';
5
- import { MemberDetails, MemberWithRegistrationsBlob, OrganizationMetaData, OrganizationRecordsConfiguration, Parent, PatchAnswers, PermissionLevel, Permissions, PermissionsResourceType, RecordCategory, RecordSettings, RecordTextAnswer, ResourcePermissions, UserPermissions } from '@stamhoofd/structures';
5
+ import { Address, Country, EmergencyContact, MemberDetails, MemberWithRegistrationsBlob, OrganizationMetaData, OrganizationRecordsConfiguration, Parent, PatchAnswers, PermissionLevel, Permissions, PermissionsResourceType, RecordCategory, RecordSettings, RecordTextAnswer, ResourcePermissions, ReviewTime, ReviewTimes } from '@stamhoofd/structures';
6
6
  import { TestUtils } from '@stamhoofd/test-utils';
7
7
  import { testServer } from '../../../../tests/helpers/TestServer';
8
8
  import { PatchOrganizationMembersEndpoint } from './PatchOrganizationMembersEndpoint';
@@ -579,7 +579,7 @@ describe('Endpoint.PatchOrganizationMembersEndpoint', () => {
579
579
  ],
580
580
  });
581
581
 
582
- const platform = await Platform.getShared();
582
+ const platform = await Platform.getForEditing();
583
583
  platform.config.recordsConfiguration.recordCategories.push(recordCategory);
584
584
  await platform.save();
585
585
 
@@ -657,7 +657,7 @@ describe('Endpoint.PatchOrganizationMembersEndpoint', () => {
657
657
  ],
658
658
  });
659
659
 
660
- const platform = await Platform.getShared();
660
+ const platform = await Platform.getForEditing();
661
661
  platform.config.recordsConfiguration.recordCategories.push(recordCategory);
662
662
  await platform.save();
663
663
 
@@ -723,4 +723,1964 @@ describe('Endpoint.PatchOrganizationMembersEndpoint', () => {
723
723
  });
724
724
  });
725
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
+ });
726
2686
  });