@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.
- package/dist/src/factories/BalanceItemFactory.d.ts +1 -0
- package/dist/src/factories/BalanceItemFactory.d.ts.map +1 -1
- package/dist/src/factories/BalanceItemFactory.js +2 -0
- package/dist/src/factories/BalanceItemFactory.js.map +1 -1
- package/dist/src/factories/UserFactory.d.ts +1 -1
- package/dist/src/factories/UserFactory.d.ts.map +1 -1
- package/dist/src/factories/UserFactory.js +4 -1
- package/dist/src/factories/UserFactory.js.map +1 -1
- package/dist/src/helpers/EmailBuilder.d.ts +5 -5
- package/dist/src/helpers/EmailBuilder.d.ts.map +1 -1
- package/dist/src/helpers/EmailBuilder.js +41 -16
- package/dist/src/helpers/EmailBuilder.js.map +1 -1
- package/dist/src/migrations/1756821154-email-send-as-email.sql +3 -0
- package/dist/src/models/Email.d.ts +14 -0
- package/dist/src/models/Email.d.ts.map +1 -1
- package/dist/src/models/Email.js +269 -196
- package/dist/src/models/Email.js.map +1 -1
- package/dist/src/models/Email.test.js +332 -46
- package/dist/src/models/Email.test.js.map +1 -1
- package/package.json +2 -2
- package/src/factories/BalanceItemFactory.ts +2 -0
- package/src/factories/UserFactory.ts +4 -1
- package/src/helpers/EmailBuilder.ts +52 -26
- package/src/migrations/1756821154-email-send-as-email.sql +3 -0
- package/src/models/Email.test.ts +401 -47
- package/src/models/Email.ts +277 -201
package/src/models/Email.test.ts
CHANGED
|
@@ -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.
|
|
124
|
-
expect(await EmailMocker.
|
|
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.
|
|
181
|
-
expect(await EmailMocker.
|
|
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.
|
|
233
|
-
expect(await EmailMocker.
|
|
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.
|
|
272
|
-
expect(await EmailMocker.
|
|
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.
|
|
322
|
-
expect(await EmailMocker.
|
|
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.
|
|
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.
|
|
362
|
-
expect(await EmailMocker.
|
|
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.
|
|
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.
|
|
404
|
-
expect(await EmailMocker.
|
|
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.
|
|
453
|
-
expect(await EmailMocker.
|
|
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.
|
|
500
|
-
expect(await EmailMocker.
|
|
504
|
+
expect(await EmailMocker.getSucceededCount()).toBe(1);
|
|
505
|
+
expect(await EmailMocker.getFailedCount()).toBe(0);
|
|
501
506
|
|
|
502
507
|
// Check to header
|
|
503
|
-
expect(EmailMocker.
|
|
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.
|
|
544
|
-
expect(await EmailMocker.
|
|
548
|
+
expect(await EmailMocker.getSucceededCount()).toBe(1);
|
|
549
|
+
expect(await EmailMocker.getFailedCount()).toBe(0);
|
|
545
550
|
|
|
546
551
|
// Check to header
|
|
547
|
-
expect(EmailMocker.
|
|
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.
|
|
593
|
-
expect(await EmailMocker.
|
|
597
|
+
expect(await EmailMocker.getSucceededCount()).toBe(1);
|
|
598
|
+
expect(await EmailMocker.getFailedCount()).toBe(0);
|
|
594
599
|
|
|
595
600
|
// Check to header
|
|
596
|
-
expect(EmailMocker.
|
|
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.
|
|
644
|
-
expect(await EmailMocker.
|
|
648
|
+
expect(await EmailMocker.getSucceededCount()).toBe(1);
|
|
649
|
+
expect(await EmailMocker.getFailedCount()).toBe(0);
|
|
645
650
|
|
|
646
651
|
// Check to header
|
|
647
|
-
expect(EmailMocker.
|
|
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.
|
|
692
|
-
expect(await EmailMocker.
|
|
697
|
+
expect(await EmailMocker.getSucceededCount()).toBe(1);
|
|
698
|
+
expect(await EmailMocker.getFailedCount()).toBe(0);
|
|
693
699
|
|
|
694
700
|
// Check to header
|
|
695
|
-
expect(EmailMocker.
|
|
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.
|
|
748
|
-
expect(await EmailMocker.
|
|
754
|
+
expect(await EmailMocker.getSucceededCount()).toBe(1);
|
|
755
|
+
expect(await EmailMocker.getFailedCount()).toBe(0);
|
|
749
756
|
|
|
750
757
|
// Check to header
|
|
751
|
-
expect(EmailMocker.
|
|
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.
|
|
806
|
-
expect(await EmailMocker.
|
|
813
|
+
expect(await EmailMocker.getSucceededCount()).toBe(1);
|
|
814
|
+
expect(await EmailMocker.getFailedCount()).toBe(0);
|
|
807
815
|
|
|
808
816
|
// Check to header
|
|
809
|
-
expect(EmailMocker.
|
|
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.
|
|
856
|
-
expect(await EmailMocker.
|
|
864
|
+
expect(await EmailMocker.getSucceededCount()).toBe(1);
|
|
865
|
+
expect(await EmailMocker.getFailedCount()).toBe(0);
|
|
857
866
|
|
|
858
867
|
// Check to header
|
|
859
|
-
expect(EmailMocker.
|
|
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
|
});
|