@stamhoofd/models 2.96.2 → 2.97.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.
@@ -1,10 +1,13 @@
1
- import { EmailRecipientFilter, EmailRecipientFilterType, EmailRecipientsStatus, EmailRecipient as EmailRecipientStruct, EmailRecipientSubfilter, EmailStatus, LimitedFilteredRequest, OrganizationMetaData, PaginatedResponse } from '@stamhoofd/structures';
1
+ import { BalanceItemType, EmailRecipientFilter, EmailRecipientFilterType, EmailRecipientsStatus, EmailRecipient as EmailRecipientStruct, EmailRecipientSubfilter, EmailStatus, LimitedFilteredRequest, OrganizationMetaData, PaginatedResponse } from '@stamhoofd/structures';
2
2
  import { Email } from './Email';
3
3
  import { EmailRecipient } from './EmailRecipient';
4
4
  import { EmailMocker } from '@stamhoofd/email';
5
5
  import { STExpect, TestUtils } from '@stamhoofd/test-utils';
6
6
  import { OrganizationFactory } from '../factories/OrganizationFactory';
7
7
  import { Platform } from './Platform';
8
+ import { BalanceItemFactory, MemberFactory, UserFactory } from '../factories';
9
+ import { CachedBalance } from './CachedBalance';
10
+ import { Formatter } from '@stamhoofd/utility';
8
11
 
9
12
  async function buildEmail(data: {
10
13
  recipients: EmailRecipientStruct[];
@@ -37,13 +40,14 @@ async function buildEmail(data: {
37
40
  });
38
41
 
39
42
  model.subject = data.subject ?? 'This is a test email';
40
- model.html = data.html ?? '<p>This is a test email</p>';
41
- model.text = data.text ?? 'This is a test email';
43
+ model.html = data.html ?? '<p>This is a test email</p> {{unsubscribeUrl}}';
44
+ model.text = data.text ?? 'This is a test email {{unsubscribeUrl}}';
42
45
  model.json = data.json ?? {};
43
46
  model.status = data.status ?? EmailStatus.Draft;
44
47
  model.attachments = [];
45
48
  model.fromAddress = data.fromAddress ?? 'test@stamhoofd.be';
46
49
  model.fromName = data.fromName ?? null;
50
+ model.emailType = data.emailType ?? null;
47
51
 
48
52
  await model.save();
49
53
 
@@ -79,13 +83,14 @@ async function buildFailEmail(data: {
79
83
  });
80
84
 
81
85
  model.subject = data.subject ?? 'This is a test email';
82
- model.html = data.html ?? '<p>This is a test email</p>';
83
- model.text = data.text ?? 'This is a test email';
86
+ model.html = data.html ?? '<p>This is a test email</p> {{unsubscribeUrl}}';
87
+ model.text = data.text ?? 'This is a test email {{unsubscribeUrl}}';
84
88
  model.json = data.json ?? {};
85
89
  model.status = data.status ?? EmailStatus.Draft;
86
90
  model.attachments = [];
87
91
  model.fromAddress = data.fromAddress ?? 'test@stamhoofd.be';
88
92
  model.fromName = data.fromName ?? null;
93
+ model.emailType = data.emailType ?? null;
89
94
 
90
95
  await model.save();
91
96
 
@@ -120,8 +125,8 @@ describe('Model.Email', () => {
120
125
  expect(model.emailErrors).toBe(null);
121
126
  expect(model.recipientsErrors).toBe(null);
122
127
  expect(model.status).toBe(EmailStatus.Sent);
123
- expect(await EmailMocker.broadcast.getSucceededCount()).toBe(1);
124
- expect(await EmailMocker.broadcast.getFailedCount()).toBe(0); // never tried to send any failed emails (whitelist)
128
+ expect(await EmailMocker.getSucceededCount()).toBe(1);
129
+ expect(await EmailMocker.getFailedCount()).toBe(0); // never tried to send any failed emails (whitelist)
125
130
 
126
131
  // Load recipietns
127
132
  const recipients = await EmailRecipient.select().where('emailId', model.id).fetch();
@@ -177,8 +182,8 @@ describe('Model.Email', () => {
177
182
  expect(model.softFailedCount).toBe(0);
178
183
 
179
184
  // Both have succeeded
180
- expect(await EmailMocker.broadcast.getSucceededCount()).toBe(2);
181
- expect(await EmailMocker.broadcast.getFailedCount()).toBe(1); // One retry
185
+ expect(await EmailMocker.getSucceededCount()).toBe(2);
186
+ expect(await EmailMocker.getFailedCount()).toBe(1); // One retry
182
187
 
183
188
  // Load recipietns
184
189
  const recipients = await EmailRecipient.select().where('emailId', model.id).fetch();
@@ -229,8 +234,8 @@ describe('Model.Email', () => {
229
234
  expect(model.softFailedCount).toBe(0);
230
235
 
231
236
  // Both have succeeded
232
- expect(await EmailMocker.broadcast.getSucceededCount()).toBe(0);
233
- expect(await EmailMocker.broadcast.getFailedCount()).toBe(0); // One retry
237
+ expect(await EmailMocker.getSucceededCount()).toBe(0);
238
+ expect(await EmailMocker.getFailedCount()).toBe(0); // One retry
234
239
  }, 15_000);
235
240
 
236
241
  it('Marks email recipient as failed if fails three times', async () => {
@@ -268,8 +273,8 @@ describe('Model.Email', () => {
268
273
  expect(model.softFailedCount).toBe(0);
269
274
 
270
275
  // Both have succeeded
271
- expect(await EmailMocker.broadcast.getSucceededCount()).toBe(0);
272
- expect(await EmailMocker.broadcast.getFailedCount()).toBe(6); // Two retries for each recipient
276
+ expect(await EmailMocker.getSucceededCount()).toBe(0);
277
+ expect(await EmailMocker.getFailedCount()).toBe(6); // Two retries for each recipient
273
278
 
274
279
  // Load recipietns
275
280
  const recipients = await EmailRecipient.select().where('emailId', model.id).fetch();
@@ -318,8 +323,8 @@ describe('Model.Email', () => {
318
323
  expect(model.softFailedCount).toBe(0);
319
324
 
320
325
  // Both have succeeded
321
- expect(await EmailMocker.broadcast.getSucceededCount()).toBe(1);
322
- expect(await EmailMocker.broadcast.getFailedCount()).toBe(0);
326
+ expect(await EmailMocker.getSucceededCount()).toBe(1);
327
+ expect(await EmailMocker.getFailedCount()).toBe(0);
323
328
 
324
329
  // Load recipietns
325
330
  const recipients = await EmailRecipient.select().where('emailId', model.id).fetch();
@@ -332,7 +337,7 @@ describe('Model.Email', () => {
332
337
  ]);
333
338
 
334
339
  // Check to header
335
- expect(EmailMocker.broadcast.getSucceededEmail(0).to).toEqual('example@domain.be');
340
+ expect(EmailMocker.getSucceededEmail(0).to).toEqual('example@domain.be');
336
341
  }, 15_000);
337
342
 
338
343
  it('Includes recipient names in mail header', async () => {
@@ -358,8 +363,8 @@ describe('Model.Email', () => {
358
363
  expect(model.softFailedCount).toBe(0);
359
364
 
360
365
  // Both have succeeded
361
- expect(await EmailMocker.broadcast.getSucceededCount()).toBe(1);
362
- expect(await EmailMocker.broadcast.getFailedCount()).toBe(0);
366
+ expect(await EmailMocker.getSucceededCount()).toBe(1);
367
+ expect(await EmailMocker.getFailedCount()).toBe(0);
363
368
 
364
369
  // Load recipietns
365
370
  const recipients = await EmailRecipient.select().where('emailId', model.id).fetch();
@@ -374,7 +379,7 @@ describe('Model.Email', () => {
374
379
  ]);
375
380
 
376
381
  // Check to header
377
- expect(EmailMocker.broadcast.getSucceededEmail(0).to).toEqual('"John Von Doe" <example@domain.be>');
382
+ expect(EmailMocker.getSucceededEmail(0).to).toEqual('"John Von Doe" <example@domain.be>');
378
383
  }, 15_000);
379
384
 
380
385
  it('Skips invalid email addresses', async () => {
@@ -400,8 +405,8 @@ describe('Model.Email', () => {
400
405
  expect(model.softFailedCount).toBe(0);
401
406
 
402
407
  // Both have succeeded
403
- expect(await EmailMocker.broadcast.getSucceededCount()).toBe(0);
404
- expect(await EmailMocker.broadcast.getFailedCount()).toBe(0);
408
+ expect(await EmailMocker.getSucceededCount()).toBe(0);
409
+ expect(await EmailMocker.getFailedCount()).toBe(0);
405
410
 
406
411
  // Load recipietns
407
412
  const recipients = await EmailRecipient.select().where('emailId', model.id).fetch();
@@ -449,8 +454,8 @@ describe('Model.Email', () => {
449
454
  expect(model.softFailedCount).toBe(0);
450
455
 
451
456
  // Both have succeeded
452
- expect(await EmailMocker.broadcast.getSucceededCount()).toBe(1);
453
- expect(await EmailMocker.broadcast.getFailedCount()).toBe(0);
457
+ expect(await EmailMocker.getSucceededCount()).toBe(1);
458
+ expect(await EmailMocker.getFailedCount()).toBe(0);
454
459
 
455
460
  // Load recipietns
456
461
  const recipients = await EmailRecipient.select().where('emailId', model.id).fetch();
@@ -496,11 +501,11 @@ describe('Model.Email', () => {
496
501
  expect(model.softFailedCount).toBe(0);
497
502
 
498
503
  // Both have succeeded
499
- expect(await EmailMocker.broadcast.getSucceededCount()).toBe(1);
500
- expect(await EmailMocker.broadcast.getFailedCount()).toBe(0);
504
+ expect(await EmailMocker.getSucceededCount()).toBe(1);
505
+ expect(await EmailMocker.getFailedCount()).toBe(0);
501
506
 
502
507
  // Check to header
503
- expect(EmailMocker.broadcast.getSucceededEmail(0)).toMatchObject({
508
+ expect(EmailMocker.getSucceededEmail(0)).toMatchObject({
504
509
  to: 'example@domain.be',
505
510
  from: '"My Platform" <info@my-platform.com>',
506
511
  replyTo: undefined,
@@ -540,11 +545,11 @@ describe('Model.Email', () => {
540
545
  expect(model.softFailedCount).toBe(0);
541
546
 
542
547
  // Both have succeeded
543
- expect(await EmailMocker.broadcast.getSucceededCount()).toBe(1);
544
- expect(await EmailMocker.broadcast.getFailedCount()).toBe(0);
548
+ expect(await EmailMocker.getSucceededCount()).toBe(1);
549
+ expect(await EmailMocker.getFailedCount()).toBe(0);
545
550
 
546
551
  // Check to header
547
- expect(EmailMocker.broadcast.getSucceededEmail(0)).toMatchObject({
552
+ expect(EmailMocker.getSucceededEmail(0)).toMatchObject({
548
553
  to: 'example@domain.be',
549
554
  from: '"My Platform" <noreply@broadcast.my-platform.com>', // domain has changed here
550
555
  replyTo: '"My Platform" <info@other-platform.com>', // Reply to should be set
@@ -589,11 +594,11 @@ describe('Model.Email', () => {
589
594
  expect(model.softFailedCount).toBe(0);
590
595
 
591
596
  // Both have succeeded
592
- expect(await EmailMocker.broadcast.getSucceededCount()).toBe(1);
593
- expect(await EmailMocker.broadcast.getFailedCount()).toBe(0);
597
+ expect(await EmailMocker.getSucceededCount()).toBe(1);
598
+ expect(await EmailMocker.getFailedCount()).toBe(0);
594
599
 
595
600
  // Check to header
596
- expect(EmailMocker.broadcast.getSucceededEmail(0)).toMatchObject({
601
+ expect(EmailMocker.getSucceededEmail(0)).toMatchObject({
597
602
  to: 'example@domain.be',
598
603
  from: '"My Platform" <noreply-uritest@broadcast.my-platform.com>', // domain has changed here
599
604
  replyTo: '"My Platform" <info@my-platform.com>', // Reply to should be set
@@ -640,11 +645,11 @@ describe('Model.Email', () => {
640
645
  expect(model.softFailedCount).toBe(0);
641
646
 
642
647
  // Both have succeeded
643
- expect(await EmailMocker.broadcast.getSucceededCount()).toBe(1);
644
- expect(await EmailMocker.broadcast.getFailedCount()).toBe(0);
648
+ expect(await EmailMocker.getSucceededCount()).toBe(1);
649
+ expect(await EmailMocker.getFailedCount()).toBe(0);
645
650
 
646
651
  // Check to header
647
- expect(EmailMocker.broadcast.getSucceededEmail(0)).toMatchObject({
652
+ expect(EmailMocker.getSucceededEmail(0)).toMatchObject({
648
653
  to: 'example@domain.be',
649
654
  from: '"My Platform" <info@my-platform.com>', // domain has changed here
650
655
  replyTo: undefined,
@@ -677,6 +682,7 @@ describe('Model.Email', () => {
677
682
  html: '{{fromAddress}}',
678
683
  text: '{{fromAddress}}',
679
684
  subject: '{{fromAddress}}',
685
+ emailType: 'system-test', // Makes sure we don't need to include unsubscribeUrl
680
686
  });
681
687
 
682
688
  await model.queueForSending(true);
@@ -688,11 +694,11 @@ describe('Model.Email', () => {
688
694
  expect(model.status).toBe(EmailStatus.Sent);
689
695
 
690
696
  // Both have succeeded
691
- expect(await EmailMocker.broadcast.getSucceededCount()).toBe(1);
692
- expect(await EmailMocker.broadcast.getFailedCount()).toBe(0);
697
+ expect(await EmailMocker.getSucceededCount()).toBe(1);
698
+ expect(await EmailMocker.getFailedCount()).toBe(0);
693
699
 
694
700
  // Check to header
695
- expect(EmailMocker.broadcast.getSucceededEmail(0)).toMatchObject({
701
+ expect(EmailMocker.getSucceededEmail(0)).toMatchObject({
696
702
  to: 'example@domain.be',
697
703
  from: '"Custom Name" <noreply-' + organization.uri + '@broadcast.my-platform.com>',
698
704
  replyTo: '"Custom Name" <custom@customdomain.com>', // domain has changed here
@@ -733,6 +739,7 @@ describe('Model.Email', () => {
733
739
  html: '{{primaryColor}};{{primaryColorContrast}};{{organizationName}};{{fromName}}',
734
740
  text: '{{primaryColor}};{{primaryColorContrast}};{{organizationName}};{{fromName}}',
735
741
  subject: '{{primaryColor}};{{primaryColorContrast}};{{organizationName}};{{fromName}}',
742
+ emailType: 'system-test', // Makes sure we don't need to include unsubscribeUrl
736
743
  });
737
744
 
738
745
  await model.queueForSending(true);
@@ -744,11 +751,11 @@ describe('Model.Email', () => {
744
751
  expect(model.status).toBe(EmailStatus.Sent);
745
752
 
746
753
  // Both have succeeded
747
- expect(await EmailMocker.broadcast.getSucceededCount()).toBe(1);
748
- expect(await EmailMocker.broadcast.getFailedCount()).toBe(0);
754
+ expect(await EmailMocker.getSucceededCount()).toBe(1);
755
+ expect(await EmailMocker.getFailedCount()).toBe(0);
749
756
 
750
757
  // Check to header
751
- expect(EmailMocker.broadcast.getSucceededEmail(0)).toMatchObject({
758
+ expect(EmailMocker.getSucceededEmail(0)).toMatchObject({
752
759
  subject: `${brightYellow};${expectedContrastColor};${organization.name};${organization.name}`,
753
760
  html: `${brightYellow};${expectedContrastColor};${organization.name};${organization.name}`,
754
761
  text: `${brightYellow};${expectedContrastColor};${organization.name};${organization.name}`,
@@ -791,6 +798,7 @@ describe('Model.Email', () => {
791
798
  html: '{{primaryColor}};{{primaryColorContrast}};{{organizationName}};{{fromName}}',
792
799
  text: '{{primaryColor}};{{primaryColorContrast}};{{organizationName}};{{fromName}}',
793
800
  subject: '{{primaryColor}};{{primaryColorContrast}};{{organizationName}};{{fromName}}',
801
+ emailType: 'system-test', // Makes sure we don't need to include unsubscribeUrl
794
802
  });
795
803
 
796
804
  await model.queueForSending(true);
@@ -802,11 +810,11 @@ describe('Model.Email', () => {
802
810
  expect(model.status).toBe(EmailStatus.Sent);
803
811
 
804
812
  // Both have succeeded
805
- expect(await EmailMocker.broadcast.getSucceededCount()).toBe(1);
806
- expect(await EmailMocker.broadcast.getFailedCount()).toBe(0);
813
+ expect(await EmailMocker.getSucceededCount()).toBe(1);
814
+ expect(await EmailMocker.getFailedCount()).toBe(0);
807
815
 
808
816
  // Check to header
809
- expect(EmailMocker.broadcast.getSucceededEmail(0)).toMatchObject({
817
+ expect(EmailMocker.getSucceededEmail(0)).toMatchObject({
810
818
  subject: `${brightBlue};${expectedContrastColor};${organization.name};Custom Name`,
811
819
  html: `${brightBlue};${expectedContrastColor};${organization.name};Custom Name`,
812
820
  text: `${brightBlue};${expectedContrastColor};${organization.name};Custom Name`,
@@ -841,6 +849,7 @@ describe('Model.Email', () => {
841
849
  html: '{{primaryColor}};{{primaryColorContrast}};{{organizationName}};{{fromName}}',
842
850
  text: '{{primaryColor}};{{primaryColorContrast}};{{organizationName}};{{fromName}}',
843
851
  subject: '{{primaryColor}};{{primaryColorContrast}};{{organizationName}};{{fromName}}',
852
+ emailType: 'system-test', // Makes sure we don't need to include unsubscribeUrl
844
853
  });
845
854
 
846
855
  await model.queueForSending(true);
@@ -852,15 +861,360 @@ describe('Model.Email', () => {
852
861
  expect(model.status).toBe(EmailStatus.Sent);
853
862
 
854
863
  // Both have succeeded
855
- expect(await EmailMocker.broadcast.getSucceededCount()).toBe(1);
856
- expect(await EmailMocker.broadcast.getFailedCount()).toBe(0);
864
+ expect(await EmailMocker.getSucceededCount()).toBe(1);
865
+ expect(await EmailMocker.getFailedCount()).toBe(0);
857
866
 
858
867
  // Check to header
859
- expect(EmailMocker.broadcast.getSucceededEmail(0)).toMatchObject({
868
+ expect(EmailMocker.getSucceededEmail(0)).toMatchObject({
860
869
  subject: `${darkRed};${expectedContrastColor};${platform.config.name};${platform.config.name}`,
861
870
  html: `${darkRed};${expectedContrastColor};${platform.config.name};${platform.config.name}`,
862
871
  text: `${darkRed};${expectedContrastColor};${platform.config.name};${platform.config.name}`,
863
872
  });
864
873
  }, 15_000);
874
+
875
+ describe('User based replacements', () => {
876
+ it('The balance is added for existing users', async () => {
877
+ TestUtils.setEnvironment('domains', {
878
+ ...STAMHOOFD.domains,
879
+ defaultTransactionalEmail: {
880
+ '': 'my-platform.com',
881
+ },
882
+ defaultBroadcastEmail: {
883
+ '': 'broadcast.my-platform.com',
884
+ },
885
+ });
886
+
887
+ const organization = await new OrganizationFactory({}).create();
888
+ const existingUser = await new UserFactory({
889
+ organization,
890
+ }).create();
891
+
892
+ // Add outstanding balance items for this user
893
+ const balanceItem = await new BalanceItemFactory({
894
+ userId: existingUser.id,
895
+ organizationId: organization.id,
896
+ type: BalanceItemType.Other,
897
+ amount: 2,
898
+ unitPrice: 12_99,
899
+ description: 'Test balance item',
900
+ }).create();
901
+ await CachedBalance.updateForUsers(organization.id, [existingUser.id]);
902
+
903
+ const model = await buildEmail({
904
+ organizationId: organization.id,
905
+ recipients: [
906
+ EmailRecipientStruct.create({
907
+ email: existingUser.email,
908
+ userId: existingUser.id,
909
+ }),
910
+ ],
911
+ fromAddress: 'custom@customdomain.com',
912
+ html: '<p>{{outstandingBalance}}</p>{{balanceTable}}',
913
+ subject: '{{outstandingBalance}}',
914
+ emailType: 'system-test', // Makes sure we don't need to include unsubscribeUrl
915
+ });
916
+
917
+ await model.queueForSending(true);
918
+ await model.refresh();
919
+
920
+ // Check if it was sent correctly
921
+ expect(model.recipientsStatus).toBe(EmailRecipientsStatus.Created);
922
+ expect(model.emailRecipientsCount).toBe(1);
923
+ expect(model.status).toBe(EmailStatus.Sent);
924
+
925
+ // Both have succeeded
926
+ expect(await EmailMocker.getSucceededCount()).toBe(1);
927
+ expect(await EmailMocker.getFailedCount()).toBe(0);
928
+
929
+ const expectedAmount = Formatter.price(balanceItem.unitPrice * balanceItem.amount);
930
+
931
+ // Check to header
932
+ expect(EmailMocker.getSucceededEmail(0)).toMatchObject({
933
+ subject: `${expectedAmount}`,
934
+ });
935
+
936
+ expect(EmailMocker.getSucceededEmail(0).html).toInclude('<p>' + expectedAmount + '</p>');
937
+ expect(EmailMocker.getSucceededEmail(0).text).toInclude(expectedAmount);
938
+
939
+ // Check if the table is correct
940
+ expect(EmailMocker.getSucceededEmail(0).html).toInclude('<table');
941
+ expect(EmailMocker.getSucceededEmail(0).html).toInclude('2 x '); // amount
942
+ expect(EmailMocker.getSucceededEmail(0).html).toInclude(Formatter.price(12_99)); // unit price
943
+ expect(EmailMocker.getSucceededEmail(0).html).toInclude('<td>' + expectedAmount); // total price in table
944
+ expect(EmailMocker.getSucceededEmail(0).html).toInclude('Test balance item'); // description
945
+
946
+ expect(EmailMocker.getSucceededEmail(0).text).toInclude('2 x '); // amount
947
+ expect(EmailMocker.getSucceededEmail(0).text).toInclude(Formatter.price(12_99)); // unit price
948
+ expect(EmailMocker.getSucceededEmail(0).text).toInclude(expectedAmount); // total price in table
949
+ expect(EmailMocker.getSucceededEmail(0).text?.toLowerCase()).toInclude('test balance item'); // description
950
+ }, 15_000);
951
+
952
+ it('The balance is zero for unknown users', async () => {
953
+ TestUtils.setEnvironment('domains', {
954
+ ...STAMHOOFD.domains,
955
+ defaultTransactionalEmail: {
956
+ '': 'my-platform.com',
957
+ },
958
+ defaultBroadcastEmail: {
959
+ '': 'broadcast.my-platform.com',
960
+ },
961
+ });
962
+
963
+ const organization = await new OrganizationFactory({}).create();
964
+
965
+ const model = await buildEmail({
966
+ organizationId: organization.id,
967
+ recipients: [
968
+ EmailRecipientStruct.create({
969
+ email: 'unknown-user@example.com',
970
+ }),
971
+ ],
972
+ fromAddress: 'custom@customdomain.com',
973
+ html: '<p>{{outstandingBalance}}</p>{{balanceTable}}',
974
+ subject: '{{outstandingBalance}}',
975
+ emailType: 'system-test', // Makes sure we don't need to include unsubscribeUrl
976
+ });
977
+
978
+ await model.queueForSending(true);
979
+ await model.refresh();
980
+
981
+ // Check if it was sent correctly
982
+ expect(model.recipientsStatus).toBe(EmailRecipientsStatus.Created);
983
+ expect(model.emailRecipientsCount).toBe(1);
984
+ expect(model.status).toBe(EmailStatus.Sent);
985
+
986
+ // Both have succeeded
987
+ expect(await EmailMocker.getSucceededCount()).toBe(1);
988
+ expect(await EmailMocker.getFailedCount()).toBe(0);
989
+
990
+ const expectedAmount = Formatter.price(0);
991
+
992
+ // Check to header
993
+ expect(EmailMocker.getSucceededEmail(0)).toMatchObject({
994
+ subject: `${expectedAmount}`,
995
+ });
996
+
997
+ expect(EmailMocker.getSucceededEmail(0).html).toInclude('<p>' + expectedAmount + '</p>');
998
+ expect(EmailMocker.getSucceededEmail(0).text).toInclude(expectedAmount);
999
+
1000
+ // Check if the table is correct
1001
+ expect(EmailMocker.getSucceededEmail(0).html).not.toInclude('<table');
1002
+ expect(EmailMocker.getSucceededEmail(0).html).not.toInclude(' x '); // amount
1003
+ expect(EmailMocker.getSucceededEmail(0).html).toInclude('<p class="description">' + $t('4c4f6571-f7b5-469d-a16f-b1547b43a610') + '</p>');
1004
+
1005
+ expect(EmailMocker.getSucceededEmail(0).text).not.toInclude(' x '); // amount
1006
+ expect(EmailMocker.getSucceededEmail(0).text?.toLowerCase()).toInclude($t('4c4f6571-f7b5-469d-a16f-b1547b43a610').toLowerCase());
1007
+ }, 15_000);
1008
+
1009
+ it('loginDetails are added for existing users without password', async () => {
1010
+ TestUtils.setEnvironment('domains', {
1011
+ ...STAMHOOFD.domains,
1012
+ defaultTransactionalEmail: {
1013
+ '': 'my-platform.com',
1014
+ },
1015
+ defaultBroadcastEmail: {
1016
+ '': 'broadcast.my-platform.com',
1017
+ },
1018
+ });
1019
+
1020
+ const organization = await new OrganizationFactory({}).create();
1021
+ const existingUser = await new UserFactory({
1022
+ organization,
1023
+ password: null,
1024
+ }).create();
1025
+
1026
+ const model = await buildEmail({
1027
+ organizationId: organization.id,
1028
+ recipients: [
1029
+ EmailRecipientStruct.create({
1030
+ email: existingUser.email,
1031
+ userId: existingUser.id,
1032
+ }),
1033
+ ],
1034
+ fromAddress: 'custom@customdomain.com',
1035
+ html: '{{loginDetails}}',
1036
+ emailType: 'system-test', // Makes sure we don't need to include unsubscribeUrl
1037
+ });
1038
+
1039
+ await model.queueForSending(true);
1040
+ await model.refresh();
1041
+
1042
+ // Check if it was sent correctly
1043
+ expect(model.recipientsStatus).toBe(EmailRecipientsStatus.Created);
1044
+ expect(model.emailRecipientsCount).toBe(1);
1045
+ expect(model.status).toBe(EmailStatus.Sent);
1046
+
1047
+ // Both have succeeded
1048
+ expect(await EmailMocker.getSucceededCount()).toBe(1);
1049
+ expect(await EmailMocker.getFailedCount()).toBe(0);
1050
+
1051
+ expect(EmailMocker.getSucceededEmail(0).html).toInclude(
1052
+ $t('3ab6ddc1-7ddc-4671-95d2-64994a5d36cc', { email: existingUser.email }),
1053
+ );
1054
+ expect(EmailMocker.getSucceededEmail(0).text).toInclude(
1055
+ $t('3ab6ddc1-7ddc-4671-95d2-64994a5d36cc', { email: existingUser.email }),
1056
+ );
1057
+ }, 15_000);
1058
+
1059
+ it('loginDetails are added for inexisting users', async () => {
1060
+ TestUtils.setEnvironment('domains', {
1061
+ ...STAMHOOFD.domains,
1062
+ defaultTransactionalEmail: {
1063
+ '': 'my-platform.com',
1064
+ },
1065
+ defaultBroadcastEmail: {
1066
+ '': 'broadcast.my-platform.com',
1067
+ },
1068
+ });
1069
+
1070
+ const organization = await new OrganizationFactory({}).create();
1071
+
1072
+ const model = await buildEmail({
1073
+ organizationId: organization.id,
1074
+ recipients: [
1075
+ EmailRecipientStruct.create({
1076
+ email: 'unknown@example.com',
1077
+ }),
1078
+ ],
1079
+ fromAddress: 'custom@customdomain.com',
1080
+ html: '{{loginDetails}}',
1081
+ emailType: 'system-test', // Makes sure we don't need to include unsubscribeUrl
1082
+ });
1083
+
1084
+ await model.queueForSending(true);
1085
+ await model.refresh();
1086
+
1087
+ // Check if it was sent correctly
1088
+ expect(model.recipientsStatus).toBe(EmailRecipientsStatus.Created);
1089
+ expect(model.emailRecipientsCount).toBe(1);
1090
+ expect(model.status).toBe(EmailStatus.Sent);
1091
+
1092
+ // Both have succeeded
1093
+ expect(await EmailMocker.getSucceededCount()).toBe(1);
1094
+ expect(await EmailMocker.getFailedCount()).toBe(0);
1095
+
1096
+ expect(EmailMocker.getSucceededEmail(0).html).toInclude(
1097
+ $t('3ab6ddc1-7ddc-4671-95d2-64994a5d36cc', { email: 'unknown@example.com' }),
1098
+ );
1099
+ expect(EmailMocker.getSucceededEmail(0).text).toInclude(
1100
+ $t('3ab6ddc1-7ddc-4671-95d2-64994a5d36cc', { email: 'unknown@example.com' }),
1101
+ );
1102
+ }, 15_000);
1103
+
1104
+ it('loginDetails are added for existing users with password', async () => {
1105
+ TestUtils.setEnvironment('domains', {
1106
+ ...STAMHOOFD.domains,
1107
+ defaultTransactionalEmail: {
1108
+ '': 'my-platform.com',
1109
+ },
1110
+ defaultBroadcastEmail: {
1111
+ '': 'broadcast.my-platform.com',
1112
+ },
1113
+ });
1114
+
1115
+ const organization = await new OrganizationFactory({}).create();
1116
+ const existingUser = await new UserFactory({
1117
+ organization,
1118
+ password: 'existing',
1119
+ }).create();
1120
+
1121
+ const model = await buildEmail({
1122
+ organizationId: organization.id,
1123
+ recipients: [
1124
+ EmailRecipientStruct.create({
1125
+ email: existingUser.email,
1126
+ userId: existingUser.id,
1127
+ }),
1128
+ ],
1129
+ fromAddress: 'custom@customdomain.com',
1130
+ html: '{{loginDetails}}',
1131
+ emailType: 'system-test', // Makes sure we don't need to include unsubscribeUrl
1132
+ });
1133
+
1134
+ await model.queueForSending(true);
1135
+ await model.refresh();
1136
+
1137
+ // Check if it was sent correctly
1138
+ expect(model.recipientsStatus).toBe(EmailRecipientsStatus.Created);
1139
+ expect(model.emailRecipientsCount).toBe(1);
1140
+ expect(model.status).toBe(EmailStatus.Sent);
1141
+
1142
+ // Both have succeeded
1143
+ expect(await EmailMocker.getSucceededCount()).toBe(1);
1144
+ expect(await EmailMocker.getFailedCount()).toBe(0);
1145
+
1146
+ expect(EmailMocker.getSucceededEmail(0).html).toInclude(
1147
+ $t('5403b466-98fe-48ac-beff-38acf7c9734d', { email: existingUser.email }),
1148
+ );
1149
+ expect(EmailMocker.getSucceededEmail(0).text).toInclude(
1150
+ $t('5403b466-98fe-48ac-beff-38acf7c9734d', { email: existingUser.email }),
1151
+ );
1152
+ }, 15_000);
1153
+
1154
+ it('loginDetails include member security keys for existing users without password', async () => {
1155
+ TestUtils.setEnvironment('userMode', 'platform');
1156
+ TestUtils.setEnvironment('domains', {
1157
+ ...STAMHOOFD.domains,
1158
+ defaultTransactionalEmail: {
1159
+ '': 'my-platform.com',
1160
+ },
1161
+ defaultBroadcastEmail: {
1162
+ '': 'broadcast.my-platform.com',
1163
+ },
1164
+ });
1165
+
1166
+ const organization = await new OrganizationFactory({}).create();
1167
+ const existingUser = await new UserFactory({
1168
+ organization,
1169
+ password: null,
1170
+ }).create();
1171
+ const member = await new MemberFactory({
1172
+ organization,
1173
+ user: existingUser,
1174
+ }).create();
1175
+ member.details.securityCode = '123456790123456'; // 16 chars
1176
+ await member.save();
1177
+
1178
+ // Add a linked member
1179
+
1180
+ const model = await buildEmail({
1181
+ organizationId: organization.id,
1182
+ recipients: [
1183
+ EmailRecipientStruct.create({
1184
+ email: existingUser.email,
1185
+ userId: existingUser.id,
1186
+ }),
1187
+ ],
1188
+ fromAddress: 'custom@customdomain.com',
1189
+ html: '{{loginDetails}}',
1190
+ emailType: 'system-test', // Makes sure we don't need to include unsubscribeUrl
1191
+ });
1192
+
1193
+ await model.queueForSending(true);
1194
+ await model.refresh();
1195
+
1196
+ // Check if it was sent correctly
1197
+ expect(model.recipientsStatus).toBe(EmailRecipientsStatus.Created);
1198
+ expect(model.emailRecipientsCount).toBe(1);
1199
+ expect(model.status).toBe(EmailStatus.Sent);
1200
+
1201
+ // Both have succeeded
1202
+ expect(await EmailMocker.getSucceededCount()).toBe(1);
1203
+ expect(await EmailMocker.getFailedCount()).toBe(0);
1204
+
1205
+ expect(EmailMocker.getSucceededEmail(0).html).toInclude(
1206
+ $t('e2519632-c495-4629-9ddb-334a4f00e272', {
1207
+ firstName: Formatter.escapeHtml(member.firstName),
1208
+ securityCode: `<span class="style-inline-code">${Formatter.escapeHtml(Formatter.spaceString(member.details.securityCode ?? '', 4, '-'))}</span>`,
1209
+ }),
1210
+ );
1211
+ expect(EmailMocker.getSucceededEmail(0).text).toInclude(
1212
+ $t('e2519632-c495-4629-9ddb-334a4f00e272', {
1213
+ firstName: member.firstName,
1214
+ securityCode: Formatter.spaceString(member.details.securityCode ?? '', 4, '-'),
1215
+ }),
1216
+ );
1217
+ }, 15_000);
1218
+ });
865
1219
  });
866
1220
  });