@5minds/node-red-contrib-processcube-tools 1.2.0-develop-d19f89-mg68thdf → 1.2.0-develop-59ef22-mg9d9ja5
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/.mocharc.json +5 -0
- package/package.json +26 -10
- package/src/custom-node-template/custom-node-template.html.template +45 -0
- package/src/custom-node-template/custom-node-template.ts.template +69 -0
- package/src/email-receiver/email-receiver.ts +439 -0
- package/src/email-sender/email-sender.ts +210 -0
- package/{processcube-html-to-text/processcube-html-to-text.html → src/html-to-text/html-to-text.html} +3 -3
- package/src/html-to-text/html-to-text.ts +53 -0
- package/src/index.ts +12 -0
- package/src/interfaces/EmailReceiverMessage.ts +22 -0
- package/src/interfaces/EmailSenderNodeProperties.ts +37 -0
- package/src/interfaces/FetchState.ts +9 -0
- package/src/interfaces/ImapConnectionConfig.ts +14 -0
- package/src/test/framework/advanced-test-patterns.ts +224 -0
- package/src/test/framework/generic-node-test-suite.ts +58 -0
- package/src/test/framework/index.ts +17 -0
- package/src/test/framework/integration-assertions.ts +67 -0
- package/src/test/framework/integration-scenario-builder.ts +77 -0
- package/src/test/framework/integration-test-runner.ts +101 -0
- package/src/test/framework/node-assertions.ts +63 -0
- package/src/test/framework/node-test-runner.ts +260 -0
- package/src/test/framework/test-scenario-builder.ts +74 -0
- package/src/test/framework/types.ts +61 -0
- package/src/test/helpers/email-receiver-test-configs.ts +67 -0
- package/src/test/helpers/email-receiver-test-flows.ts +16 -0
- package/src/test/helpers/email-sender-test-configs.ts +123 -0
- package/src/test/helpers/email-sender-test-flows.ts +16 -0
- package/src/test/integration/email-receiver.integration.test.ts +41 -0
- package/src/test/integration/email-sender.integration.test.ts +129 -0
- package/src/test/interfaces/email-data.ts +10 -0
- package/src/test/interfaces/email-receiver-config.ts +12 -0
- package/src/test/interfaces/email-sender-config.ts +26 -0
- package/src/test/interfaces/imap-config.ts +9 -0
- package/src/test/interfaces/imap-mailbox.ts +5 -0
- package/src/test/interfaces/mail-options.ts +20 -0
- package/src/test/interfaces/parsed-email.ts +11 -0
- package/src/test/interfaces/send-mail-result.ts +7 -0
- package/src/test/mocks/imap-mock.ts +147 -0
- package/src/test/mocks/mailparser-mock.ts +82 -0
- package/src/test/mocks/nodemailer-mock.ts +118 -0
- package/src/test/unit/email-receiver.unit.test.ts +471 -0
- package/src/test/unit/email-sender.unit.test.ts +550 -0
- package/tsconfig.json +23 -0
- package/email-receiver/email-receiver.js +0 -304
- package/email-sender/email-sender.js +0 -178
- package/examples/.gitkeep +0 -0
- package/processcube-html-to-text/processcube-html-to-text.js +0 -22
- package/test/helpers/email-receiver.mocks.js +0 -447
- package/test/helpers/email-sender.mocks.js +0 -368
- package/test/integration/email-receiver.integration.test.js +0 -515
- package/test/integration/email-sender.integration.test.js +0 -239
- package/test/unit/email-receiver.unit.test.js +0 -304
- package/test/unit/email-sender.unit.test.js +0 -570
- /package/{email-receiver → src/email-receiver}/email-receiver.html +0 -0
- /package/{email-sender → src/email-sender}/email-sender.html +0 -0
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
import { expect } from 'chai';
|
|
2
|
+
import emailSenderNode from '../../email-sender/email-sender';
|
|
3
|
+
import { EmailSenderTestConfigs } from '../helpers/email-sender-test-configs';
|
|
4
|
+
|
|
5
|
+
import { createMockNodemailer, withNodemailerMock } from '../mocks/nodemailer-mock';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
TestScenarioBuilder,
|
|
9
|
+
NodeTestRunner,
|
|
10
|
+
NodeAssertions,
|
|
11
|
+
createNodeTestSuite,
|
|
12
|
+
ErrorResilienceTestBuilder,
|
|
13
|
+
EdgeCaseTestBuilder,
|
|
14
|
+
type TestScenario,
|
|
15
|
+
MockNodeREDOptions,
|
|
16
|
+
} from '../framework';
|
|
17
|
+
|
|
18
|
+
describe('E-Mail Sender Node - Unit Tests', function () {
|
|
19
|
+
// ========================================================================
|
|
20
|
+
// USE GENERIC TEST SUITE FOR BASIC FUNCTIONALITY
|
|
21
|
+
// ========================================================================
|
|
22
|
+
|
|
23
|
+
createNodeTestSuite('Email Sender', emailSenderNode, EmailSenderTestConfigs);
|
|
24
|
+
|
|
25
|
+
// ========================================================================
|
|
26
|
+
// SPECIFIC EMAIL SENDER TESTS
|
|
27
|
+
// ========================================================================
|
|
28
|
+
|
|
29
|
+
describe('Email Sender Specific Tests', function () {
|
|
30
|
+
describe('Configuration Validation', function () {
|
|
31
|
+
const configTests = new TestScenarioBuilder()
|
|
32
|
+
.addValidScenario('valid configuration', EmailSenderTestConfigs.valid)
|
|
33
|
+
.addValidScenario('minimal configuration', EmailSenderTestConfigs.minimal)
|
|
34
|
+
.addErrorScenario(
|
|
35
|
+
'missing required config',
|
|
36
|
+
EmailSenderTestConfigs.invalid,
|
|
37
|
+
"Required property 'sender' is missing",
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
configTests.getScenarios().forEach((scenario) => {
|
|
41
|
+
it(`should handle ${scenario.name}`, async function () {
|
|
42
|
+
const context = await NodeTestRunner.runScenario(emailSenderNode, scenario);
|
|
43
|
+
|
|
44
|
+
// Verify node was created
|
|
45
|
+
expect(context.nodeInstance).to.exist;
|
|
46
|
+
|
|
47
|
+
// Check specific expectations
|
|
48
|
+
if (scenario.expectedError) {
|
|
49
|
+
NodeAssertions.expectError(context, scenario.expectedError);
|
|
50
|
+
} else {
|
|
51
|
+
NodeAssertions.expectNoErrors(context);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Verify node properties
|
|
55
|
+
if (scenario.config.name) {
|
|
56
|
+
NodeAssertions.expectNodeProperty(context, 'name', scenario.config.name);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (scenario.config.id) {
|
|
60
|
+
NodeAssertions.expectNodeProperty(context, 'id', scenario.config.id);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('Email Sending Functionality', function () {
|
|
67
|
+
const emailSendingTests = new TestScenarioBuilder()
|
|
68
|
+
.addStatusScenario(
|
|
69
|
+
'successful email send',
|
|
70
|
+
EmailSenderTestConfigs.valid,
|
|
71
|
+
{ fill: 'green', text: 'sent' },
|
|
72
|
+
{ payload: 'test', topic: 'test message' },
|
|
73
|
+
withNodemailerMock({ shouldFail: false }),
|
|
74
|
+
)
|
|
75
|
+
.addStatusScenario(
|
|
76
|
+
'send mail error',
|
|
77
|
+
{ ...EmailSenderTestConfigs.valid, shouldFail: true },
|
|
78
|
+
{ fill: 'red', text: 'error sending' },
|
|
79
|
+
{ payload: 'test', topic: 'test message' },
|
|
80
|
+
withNodemailerMock({ shouldFail: true }),
|
|
81
|
+
)
|
|
82
|
+
.addStatusScenario(
|
|
83
|
+
'rejected email',
|
|
84
|
+
{ ...EmailSenderTestConfigs.valid, rejectedEmails: ['recipient@example.com'] },
|
|
85
|
+
{ fill: 'red', text: 'rejected' },
|
|
86
|
+
{ payload: 'test', topic: 'test message' },
|
|
87
|
+
withNodemailerMock({ rejectedEmails: ['recipient@example.com'] }),
|
|
88
|
+
)
|
|
89
|
+
.addStatusScenario(
|
|
90
|
+
'pending email',
|
|
91
|
+
{ ...EmailSenderTestConfigs.valid, pendingEmails: ['recipient@example.com'] },
|
|
92
|
+
{ fill: 'yellow', text: 'pending' },
|
|
93
|
+
{ payload: 'test', topic: 'test message' },
|
|
94
|
+
withNodemailerMock({ pendingEmails: ['recipient@example.com'] }),
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
emailSendingTests.getScenarios().forEach((scenario) => {
|
|
98
|
+
it(`should handle ${scenario.name}`, async function () {
|
|
99
|
+
const mockOptions: MockNodeREDOptions = scenario.mockOptions || {};
|
|
100
|
+
const context = await NodeTestRunner.runScenario(emailSenderNode, scenario, mockOptions);
|
|
101
|
+
|
|
102
|
+
expect(context.nodeInstance).to.exist;
|
|
103
|
+
|
|
104
|
+
if (scenario.expectedStatus) {
|
|
105
|
+
NodeAssertions.expectStatus(context, scenario.expectedStatus);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (scenario.expectedError) {
|
|
109
|
+
NodeAssertions.expectError(context, scenario.expectedError);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('Attachment Handling', function () {
|
|
116
|
+
const mockDependencies = {
|
|
117
|
+
nodemailer: createMockNodemailer({
|
|
118
|
+
shouldFail: false,
|
|
119
|
+
acceptedEmails: [],
|
|
120
|
+
}),
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const mockOptions: MockNodeREDOptions = {
|
|
124
|
+
dependencies: mockDependencies,
|
|
125
|
+
statusHandler: function (status: any) {
|
|
126
|
+
console.log('📊 Status received:', JSON.stringify(status, null, 2));
|
|
127
|
+
},
|
|
128
|
+
errorHandler: function (err: any) {
|
|
129
|
+
console.log('❌ Error received:', err);
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const attachmentTests = [
|
|
134
|
+
{
|
|
135
|
+
name: 'array of attachments',
|
|
136
|
+
config: {
|
|
137
|
+
...EmailSenderTestConfigs.valid,
|
|
138
|
+
attachments: JSON.stringify([
|
|
139
|
+
{ filename: 'test1.txt', content: 'First file' },
|
|
140
|
+
{ filename: 'test2.txt', content: 'Second file' },
|
|
141
|
+
]),
|
|
142
|
+
attachmentsType: 'json',
|
|
143
|
+
},
|
|
144
|
+
input: { payload: 'test', topic: 'test message' },
|
|
145
|
+
expectedStatus: { fill: 'green', text: 'sent' },
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: 'single attachment object',
|
|
149
|
+
config: {
|
|
150
|
+
...EmailSenderTestConfigs.valid,
|
|
151
|
+
attachments: JSON.stringify({
|
|
152
|
+
filename: 'single-test.txt',
|
|
153
|
+
content: 'Single file content',
|
|
154
|
+
}),
|
|
155
|
+
attachmentsType: 'json',
|
|
156
|
+
},
|
|
157
|
+
input: { payload: 'test', topic: 'test message' },
|
|
158
|
+
expectedStatus: { fill: 'green', text: 'sent' },
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
name: 'empty attachments string',
|
|
162
|
+
config: {
|
|
163
|
+
...EmailSenderTestConfigs.valid,
|
|
164
|
+
attachments: '',
|
|
165
|
+
attachmentsType: 'str',
|
|
166
|
+
},
|
|
167
|
+
input: { payload: 'test', topic: 'test message' },
|
|
168
|
+
expectedStatus: { fill: 'green', text: 'sent' },
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
name: 'malformed attachments',
|
|
172
|
+
config: {
|
|
173
|
+
...EmailSenderTestConfigs.valid,
|
|
174
|
+
attachments: JSON.stringify([
|
|
175
|
+
{ filename: 'valid.txt', content: 'Valid content' },
|
|
176
|
+
{ filename: 'invalid.txt' }, // Missing content
|
|
177
|
+
]),
|
|
178
|
+
attachmentsType: 'json',
|
|
179
|
+
},
|
|
180
|
+
input: { payload: 'test', topic: 'test message' },
|
|
181
|
+
expectedError: "Attachment object is missing 'filename' or 'content' property.",
|
|
182
|
+
},
|
|
183
|
+
];
|
|
184
|
+
|
|
185
|
+
attachmentTests.forEach((testCase) => {
|
|
186
|
+
it(`should handle ${testCase.name}`, async function () {
|
|
187
|
+
const scenario: TestScenario = {
|
|
188
|
+
name: testCase.name,
|
|
189
|
+
config: testCase.config,
|
|
190
|
+
input: testCase.input,
|
|
191
|
+
expectedStatus: testCase.expectedStatus,
|
|
192
|
+
expectedError: testCase.expectedError,
|
|
193
|
+
timeout: 3000,
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const context = await NodeTestRunner.runScenario(emailSenderNode, scenario, mockOptions);
|
|
197
|
+
|
|
198
|
+
expect(context.nodeInstance).to.exist;
|
|
199
|
+
|
|
200
|
+
if (scenario.expectedStatus) {
|
|
201
|
+
NodeAssertions.expectStatus(context, scenario.expectedStatus);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (scenario.expectedError) {
|
|
205
|
+
NodeAssertions.expectError(context, scenario.expectedError);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// ========================================================================
|
|
212
|
+
// DATA-DRIVEN EMAIL CONFIGURATION TESTS
|
|
213
|
+
// ========================================================================
|
|
214
|
+
|
|
215
|
+
describe('Email Sender Data driven tests', function () {
|
|
216
|
+
const mockDependencies = {
|
|
217
|
+
nodemailer: createMockNodemailer({
|
|
218
|
+
shouldFail: false,
|
|
219
|
+
acceptedEmails: [],
|
|
220
|
+
}),
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const mockOptions: MockNodeREDOptions = {
|
|
224
|
+
dependencies: mockDependencies,
|
|
225
|
+
statusHandler: function (status: any) {
|
|
226
|
+
console.log('📊 Status received:', JSON.stringify(status, null, 2));
|
|
227
|
+
},
|
|
228
|
+
errorHandler: function (err: any) {
|
|
229
|
+
console.log('❌ Error received:', err);
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const DataDrivenTests = [
|
|
234
|
+
{
|
|
235
|
+
name: 'basic text email',
|
|
236
|
+
input: {
|
|
237
|
+
payload: 'Hello World',
|
|
238
|
+
topic: 'Test Subject',
|
|
239
|
+
to: 'test@example.com',
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
name: 'HTML email',
|
|
244
|
+
input: {
|
|
245
|
+
payload: '<h1>Hello World</h1>',
|
|
246
|
+
topic: 'HTML Test',
|
|
247
|
+
to: 'test@example.com',
|
|
248
|
+
html: true,
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
name: 'email with custom headers',
|
|
253
|
+
input: {
|
|
254
|
+
payload: 'Custom headers test',
|
|
255
|
+
topic: 'Custom Headers',
|
|
256
|
+
to: 'test@example.com',
|
|
257
|
+
headers: { 'X-Custom': 'test-header' },
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
name: 'empty payload',
|
|
262
|
+
input: {
|
|
263
|
+
payload: '',
|
|
264
|
+
topic: 'Empty Content',
|
|
265
|
+
to: 'test@example.com',
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
name: 'missing recipient',
|
|
270
|
+
input: {
|
|
271
|
+
payload: 'No recipient test',
|
|
272
|
+
topic: 'No Recipient',
|
|
273
|
+
},
|
|
274
|
+
expectedError: /recipient|to|email/i,
|
|
275
|
+
},
|
|
276
|
+
];
|
|
277
|
+
|
|
278
|
+
DataDrivenTests.forEach((testCase) => {
|
|
279
|
+
it(`should handle ${testCase.name}`, async function () {
|
|
280
|
+
const scenario: TestScenario = {
|
|
281
|
+
name: testCase.name,
|
|
282
|
+
config: EmailSenderTestConfigs.minimalDataDriven,
|
|
283
|
+
input: testCase.input,
|
|
284
|
+
expectedError: testCase.expectedError,
|
|
285
|
+
timeout: 10000,
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const context = await NodeTestRunner.runScenario(emailSenderNode, scenario, mockOptions);
|
|
289
|
+
|
|
290
|
+
expect(context.nodeInstance).to.exist;
|
|
291
|
+
|
|
292
|
+
if (scenario.expectedStatus) {
|
|
293
|
+
NodeAssertions.expectStatus(context, scenario.expectedStatus);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (scenario.expectedError) {
|
|
297
|
+
NodeAssertions.expectError(context, scenario.expectedError);
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// ========================================================================
|
|
304
|
+
// ERROR RESILIENCE TESTS
|
|
305
|
+
// ========================================================================
|
|
306
|
+
|
|
307
|
+
describe('Error Resilience', function () {
|
|
308
|
+
const resilience = new ErrorResilienceTestBuilder()
|
|
309
|
+
.addMalformedInputScenario('email input', EmailSenderTestConfigs.valid)
|
|
310
|
+
.addRapidFireScenario('rapid email sending', EmailSenderTestConfigs.valid, 10);
|
|
311
|
+
|
|
312
|
+
const mockDependencies = {
|
|
313
|
+
nodemailer: createMockNodemailer({
|
|
314
|
+
shouldFail: false,
|
|
315
|
+
acceptedEmails: [],
|
|
316
|
+
}),
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const mockOptions: MockNodeREDOptions = {
|
|
320
|
+
dependencies: mockDependencies,
|
|
321
|
+
statusHandler: function (status: any) {
|
|
322
|
+
console.log('📊 Status received:', JSON.stringify(status, null, 2));
|
|
323
|
+
},
|
|
324
|
+
errorHandler: function (err: any) {
|
|
325
|
+
console.log('❌ Error received:', err);
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
// Add email-specific error scenarios
|
|
330
|
+
const emailErrors = new TestScenarioBuilder()
|
|
331
|
+
.addErrorScenario(
|
|
332
|
+
'invalid SMTP config',
|
|
333
|
+
{
|
|
334
|
+
...EmailSenderTestConfigs.valid,
|
|
335
|
+
hostnameost: 'invalid.smtp.server',
|
|
336
|
+
port: 99999,
|
|
337
|
+
},
|
|
338
|
+
/connection|smtp|invalid/i,
|
|
339
|
+
{ payload: 'test', topic: 'test' },
|
|
340
|
+
withNodemailerMock({
|
|
341
|
+
shouldFail: true,
|
|
342
|
+
failureMessage: 'Connection failed: SMTP server not reachable',
|
|
343
|
+
failureCode: 'ECONNREFUSED',
|
|
344
|
+
}),
|
|
345
|
+
)
|
|
346
|
+
.addErrorScenario(
|
|
347
|
+
'authentication failure',
|
|
348
|
+
{
|
|
349
|
+
...EmailSenderTestConfigs.valid,
|
|
350
|
+
user: 'invalid@user.com',
|
|
351
|
+
password: 'wrongpassword',
|
|
352
|
+
},
|
|
353
|
+
/auth|login|credential/i,
|
|
354
|
+
{ payload: 'test', topic: 'test' },
|
|
355
|
+
withNodemailerMock({
|
|
356
|
+
shouldFail: true,
|
|
357
|
+
failureMessage: 'Invalid login: 535 Authentication credentials invalid',
|
|
358
|
+
failureCode: 'EAUTH',
|
|
359
|
+
onSendMail: (mailOptions) => {
|
|
360
|
+
console.log('🔍 Mock sendMail called with shouldFail=true');
|
|
361
|
+
},
|
|
362
|
+
}),
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
[...resilience.getScenarios(), ...emailErrors.getScenarios()].forEach((scenario) => {
|
|
366
|
+
it(`should handle ${scenario.name}`, async function () {
|
|
367
|
+
const testMockOptions = scenario.mockOptions || mockOptions;
|
|
368
|
+
const context = await NodeTestRunner.runScenario(emailSenderNode, scenario, testMockOptions);
|
|
369
|
+
|
|
370
|
+
expect(context.nodeInstance).to.exist;
|
|
371
|
+
|
|
372
|
+
if (scenario.expectedError) {
|
|
373
|
+
NodeAssertions.expectError(context, scenario.expectedError);
|
|
374
|
+
} else {
|
|
375
|
+
// Should handle gracefully without crashing
|
|
376
|
+
const handledGracefully =
|
|
377
|
+
context.errors.length === 0 || context.statuses.some((s) => s.fill === 'red');
|
|
378
|
+
expect(handledGracefully).to.be.true;
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// ========================================================================
|
|
385
|
+
// EDGE CASES FOR EMAIL SENDING
|
|
386
|
+
// ========================================================================
|
|
387
|
+
|
|
388
|
+
describe('Email Edge Cases', function () {
|
|
389
|
+
const edgeCases = new EdgeCaseTestBuilder()
|
|
390
|
+
.addEmptyDataScenarios('empty email data', EmailSenderTestConfigs.valid)
|
|
391
|
+
.addSpecialCharacterScenarios('special characters in emails', EmailSenderTestConfigs.valid);
|
|
392
|
+
|
|
393
|
+
// Email-specific edge cases
|
|
394
|
+
const emailEdgeCases = new TestScenarioBuilder()
|
|
395
|
+
.addCustomScenario({
|
|
396
|
+
name: 'very long subject line',
|
|
397
|
+
config: EmailSenderTestConfigs.valid,
|
|
398
|
+
input: {
|
|
399
|
+
payload: 'test',
|
|
400
|
+
topic: 'a'.repeat(1000), // Very long subject
|
|
401
|
+
},
|
|
402
|
+
})
|
|
403
|
+
.addCustomScenario({
|
|
404
|
+
name: 'Unicode characters in email',
|
|
405
|
+
config: EmailSenderTestConfigs.valid,
|
|
406
|
+
input: {
|
|
407
|
+
payload: 'Héllo Wörld! 🌟',
|
|
408
|
+
topic: 'Tëst Émails 📧',
|
|
409
|
+
to: 'tëst@exämple.com',
|
|
410
|
+
},
|
|
411
|
+
})
|
|
412
|
+
.addCustomScenario({
|
|
413
|
+
name: 'very large email content',
|
|
414
|
+
config: EmailSenderTestConfigs.valid,
|
|
415
|
+
input: {
|
|
416
|
+
payload: 'x'.repeat(100000), // 100KB content
|
|
417
|
+
topic: 'Large Email Test',
|
|
418
|
+
},
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
[...edgeCases.getScenarios(), ...emailEdgeCases.getScenarios()].forEach((scenario) => {
|
|
422
|
+
it(`should handle ${scenario.name}`, async function () {
|
|
423
|
+
const context = await NodeTestRunner.runScenario(emailSenderNode, scenario);
|
|
424
|
+
expect(context.nodeInstance).to.exist;
|
|
425
|
+
|
|
426
|
+
// Should either succeed or fail gracefully
|
|
427
|
+
const handledWell =
|
|
428
|
+
context.errors.length === 0 || context.statuses.some((s) => ['red', 'yellow'].includes(s.fill));
|
|
429
|
+
expect(handledWell).to.be.true;
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
// ========================================================================
|
|
435
|
+
// COMPREHENSIVE EMAIL SCENARIOS
|
|
436
|
+
// ========================================================================
|
|
437
|
+
|
|
438
|
+
describe('Complex Email Scenarios', function () {
|
|
439
|
+
const mockDependencies = {
|
|
440
|
+
nodemailer: createMockNodemailer({
|
|
441
|
+
shouldFail: false,
|
|
442
|
+
acceptedEmails: [],
|
|
443
|
+
}),
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
const mockOptions: MockNodeREDOptions = {
|
|
447
|
+
dependencies: mockDependencies,
|
|
448
|
+
statusHandler: function (status: any) {
|
|
449
|
+
console.log('📊 Status received:', JSON.stringify(status, null, 2));
|
|
450
|
+
},
|
|
451
|
+
errorHandler: function (err: any) {
|
|
452
|
+
console.log('❌ Error received:', err);
|
|
453
|
+
},
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
const complexScenarios = [
|
|
457
|
+
{
|
|
458
|
+
name: 'email with multiple recipients',
|
|
459
|
+
config: EmailSenderTestConfigs.valid,
|
|
460
|
+
input: {
|
|
461
|
+
payload: 'Multi-recipient test',
|
|
462
|
+
topic: 'Multiple Recipients',
|
|
463
|
+
to: 'user1@example.com,user2@example.com,user3@example.com',
|
|
464
|
+
},
|
|
465
|
+
expectedStatus: { fill: 'green', text: 'sent' },
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
name: 'email with CC and BCC',
|
|
469
|
+
config: EmailSenderTestConfigs.valid,
|
|
470
|
+
input: {
|
|
471
|
+
payload: 'CC/BCC test',
|
|
472
|
+
topic: 'Carbon Copy Test',
|
|
473
|
+
to: 'primary@example.com',
|
|
474
|
+
cc: 'cc@example.com',
|
|
475
|
+
bcc: 'bcc@example.com',
|
|
476
|
+
},
|
|
477
|
+
expectedStatus: { fill: 'green', text: 'sent' },
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
name: 'email with mixed attachment types',
|
|
481
|
+
config: {
|
|
482
|
+
...EmailSenderTestConfigs.valid,
|
|
483
|
+
attachments: JSON.stringify([
|
|
484
|
+
{ filename: 'text.txt', content: 'Text file' },
|
|
485
|
+
{ filename: 'data.json', content: '{"test": true}' },
|
|
486
|
+
{ filename: 'image.jpg', content: 'base64encodeddata...' },
|
|
487
|
+
]),
|
|
488
|
+
attachmentsType: 'json',
|
|
489
|
+
},
|
|
490
|
+
input: {
|
|
491
|
+
payload: 'Mixed attachments test',
|
|
492
|
+
topic: 'Multiple Attachment Types',
|
|
493
|
+
},
|
|
494
|
+
expectedStatus: { fill: 'green', text: 'sent' },
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
name: 'email with custom priority',
|
|
498
|
+
config: EmailSenderTestConfigs.valid,
|
|
499
|
+
input: {
|
|
500
|
+
payload: 'High priority email',
|
|
501
|
+
topic: 'URGENT: Priority Test',
|
|
502
|
+
priority: 'high',
|
|
503
|
+
},
|
|
504
|
+
expectedStatus: { fill: 'green', text: 'sent' },
|
|
505
|
+
},
|
|
506
|
+
];
|
|
507
|
+
|
|
508
|
+
complexScenarios.forEach((scenario) => {
|
|
509
|
+
it(`should handle ${scenario.name}`, async function () {
|
|
510
|
+
const testScenario: TestScenario = {
|
|
511
|
+
name: scenario.name,
|
|
512
|
+
config: scenario.config,
|
|
513
|
+
input: scenario.input,
|
|
514
|
+
timeout: 5000,
|
|
515
|
+
expectedStatus: scenario.expectedStatus,
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
const context = await NodeTestRunner.runScenario(emailSenderNode, testScenario, mockOptions);
|
|
519
|
+
|
|
520
|
+
expect(context.nodeInstance).to.exist;
|
|
521
|
+
|
|
522
|
+
// Should either send successfully or handle errors gracefully
|
|
523
|
+
const hasStatus = context.statuses.length > 0;
|
|
524
|
+
expect(hasStatus, 'Should update status for complex scenarios').to.be.true;
|
|
525
|
+
});
|
|
526
|
+
});
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
// ========================================================================
|
|
530
|
+
// LEGACY COMPATIBILITY
|
|
531
|
+
// ========================================================================
|
|
532
|
+
|
|
533
|
+
describe('Legacy Compatibility', function () {
|
|
534
|
+
it('should maintain module export signature', function () {
|
|
535
|
+
expect(emailSenderNode).to.be.a('function');
|
|
536
|
+
expect(emailSenderNode.length).to.equal(1); // Should accept RED parameter
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it('should register with correct node type', async function () {
|
|
540
|
+
const scenario: TestScenario = {
|
|
541
|
+
name: 'node type registration',
|
|
542
|
+
config: EmailSenderTestConfigs.minimal,
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
const context = await NodeTestRunner.runScenario(emailSenderNode, scenario);
|
|
546
|
+
expect(context.mockRED.nodes.lastRegisteredType).to.equal('email-sender');
|
|
547
|
+
});
|
|
548
|
+
});
|
|
549
|
+
});
|
|
550
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"rootDir": "./src",
|
|
4
|
+
"outDir": "./dist",
|
|
5
|
+
//set to commonjs and node for node-red compatibility
|
|
6
|
+
"module": "commonjs",
|
|
7
|
+
"moduleResolution": "node",
|
|
8
|
+
// set to es2021 for node-red compatibility
|
|
9
|
+
"target": "es2018",
|
|
10
|
+
"lib": ["es2018"],
|
|
11
|
+
"declaration": true,
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
"declarationDir": "dist",
|
|
14
|
+
"sourceMap": true,
|
|
15
|
+
"esModuleInterop": true,
|
|
16
|
+
"strict": true,
|
|
17
|
+
"forceConsistentCasingInFileNames": true,
|
|
18
|
+
"types": ["mocha", "chai", "node"],
|
|
19
|
+
"allowSyntheticDefaultImports": true
|
|
20
|
+
},
|
|
21
|
+
"include": ["src/test/**/*", "src/**/*"],
|
|
22
|
+
"exclude": ["node_modules", "dist"]
|
|
23
|
+
}
|