@5minds/node-red-contrib-processcube-tools 1.0.1-develop-8bf22b-mffg2ve3 → 1.0.1-develop-4412ca-mfm8hzs3
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/.env.template +4 -0
- package/email-receiver/email-receiver.html +165 -133
- package/email-receiver/email-receiver.js +49 -35
- package/email-sender/email-sender.html +306 -0
- package/email-sender/email-sender.js +174 -0
- package/package.json +8 -7
- package/{processcube-html-to-text.html → processcube-html-to-text/processcube-html-to-text.html} +9 -9
- package/{processcube-html-to-text.js → processcube-html-to-text/processcube-html-to-text.js} +0 -3
- package/test/helpers/email-receiver.mocks.js +394 -399
- package/test/helpers/email-sender.mocks.js +368 -0
- package/test/integration/email-receiver.integration.test.js +473 -482
- package/test/integration/email-sender.integration.test.js +239 -0
- package/test/unit/email-receiver.unit.test.js +284 -289
- package/test/unit/email-sender.unit.test.js +570 -0
|
@@ -3,450 +3,445 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Create mock Node-RED object for unit testing
|
|
7
7
|
*/
|
|
8
|
-
function
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
8
|
+
function createMockNodeRED(options = {}) {
|
|
9
|
+
// Store input callback in the mock RED context
|
|
10
|
+
let storedInputCallback = null;
|
|
11
|
+
let nodeInstance = null;
|
|
12
|
+
|
|
13
|
+
const mockRED = {
|
|
14
|
+
nodes: {
|
|
15
|
+
createNode: function (node, config) {
|
|
16
|
+
nodeInstance = node; // Capture the node instance
|
|
17
|
+
|
|
18
|
+
// Apply config properties to node
|
|
19
|
+
Object.assign(node, {
|
|
20
|
+
id: config.id || 'mock-node-id',
|
|
21
|
+
type: config.type || 'email-receiver',
|
|
22
|
+
name: config.name || 'Mock Node',
|
|
23
|
+
on: function (event, callback) {
|
|
24
|
+
if (event === 'input') {
|
|
25
|
+
storedInputCallback = callback;
|
|
26
|
+
// Store the callback on the node instance for easy access
|
|
27
|
+
node.inputCallback = callback;
|
|
28
|
+
}
|
|
29
|
+
// Call the original onHandler if provided
|
|
30
|
+
if (options.onHandler) {
|
|
31
|
+
options.onHandler.call(node, event, callback);
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
status: options.statusHandler || function () {},
|
|
35
|
+
error: options.errorHandler || function () {},
|
|
36
|
+
send: options.sendHandler || function () {},
|
|
37
|
+
log: options.logHandler || function () {},
|
|
38
|
+
warn: options.warnHandler || function () {},
|
|
39
|
+
debug: options.debugHandler || function () {},
|
|
40
|
+
});
|
|
41
|
+
return node;
|
|
42
|
+
},
|
|
43
|
+
registerType: function (type, constructor) {
|
|
44
|
+
// Store registration for verification in tests
|
|
45
|
+
this.lastRegisteredType = type;
|
|
46
|
+
this.lastRegisteredConstructor = constructor;
|
|
47
|
+
},
|
|
48
|
+
// Helper method to get the stored input callback
|
|
49
|
+
getInputCallback: function () {
|
|
50
|
+
return storedInputCallback;
|
|
51
|
+
},
|
|
52
|
+
// Helper method to get the node instance
|
|
53
|
+
getNodeInstance: function () {
|
|
54
|
+
return nodeInstance;
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
util: {
|
|
58
|
+
evaluateNodeProperty: function (value, type, node, msg, callback) {
|
|
59
|
+
if (type === 'json') {
|
|
60
|
+
try {
|
|
61
|
+
// Simulate parsing a JSON string into an object
|
|
62
|
+
return JSON.parse(JSON.stringify(value));
|
|
63
|
+
} catch (e) {
|
|
64
|
+
if (callback) {
|
|
65
|
+
callback(e, null);
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
32
70
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
71
|
+
// Simple mock implementation
|
|
72
|
+
if (callback) {
|
|
73
|
+
callback(null, value);
|
|
74
|
+
}
|
|
75
|
+
return value;
|
|
76
|
+
},
|
|
77
|
+
encrypt: function (value) {
|
|
78
|
+
return 'encrypted:' + value;
|
|
79
|
+
},
|
|
80
|
+
decrypt: function (value) {
|
|
81
|
+
return value.replace('encrypted:', '');
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
log: {
|
|
85
|
+
info: options.logInfo || function () {},
|
|
86
|
+
warn: options.logWarn || function () {},
|
|
87
|
+
error: options.logError || function () {},
|
|
88
|
+
debug: options.logDebug || function () {},
|
|
89
|
+
},
|
|
42
90
|
};
|
|
43
91
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
setTimeout(() => {
|
|
47
|
-
// Return mock message IDs
|
|
48
|
-
callback(null, [123, 456, 789]);
|
|
49
|
-
}, 10);
|
|
50
|
-
};
|
|
92
|
+
return mockRED;
|
|
93
|
+
}
|
|
51
94
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
} else if (messageEvent === 'attributes') {
|
|
95
|
+
/**
|
|
96
|
+
* Mock IMAP implementation for testing
|
|
97
|
+
*/
|
|
98
|
+
function createMockImap() {
|
|
99
|
+
return function MockImap(config) {
|
|
100
|
+
this.config = config;
|
|
101
|
+
this.events = {};
|
|
102
|
+
|
|
103
|
+
// Simulate connection behavior
|
|
104
|
+
this.connect = () => {
|
|
105
|
+
// Check if we should simulate a connection error
|
|
106
|
+
if (this.config.host && this.config.host.includes('invalid')) {
|
|
107
|
+
// Simulate connection error
|
|
108
|
+
if (this.events && this.events.error) {
|
|
67
109
|
setTimeout(() => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
size: 1024
|
|
73
|
-
});
|
|
74
|
-
}, 5);
|
|
75
|
-
}
|
|
76
|
-
},
|
|
77
|
-
once: (messageEvent, messageCallback) => {
|
|
78
|
-
if (messageEvent === 'end') {
|
|
79
|
-
setTimeout(() => messageCallback(), 15);
|
|
80
|
-
}
|
|
110
|
+
const error = new Error('Connection failed');
|
|
111
|
+
error.code = 'ENOTFOUND';
|
|
112
|
+
this.events.error(error);
|
|
113
|
+
}, 10);
|
|
81
114
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
setTimeout(() => callback(), 20);
|
|
90
|
-
} else if (event === 'error') {
|
|
91
|
-
// Store error callback for potential use
|
|
92
|
-
this.errorCallback = callback;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
// Store the fetch emitter so we can trigger events manually in tests
|
|
98
|
-
this.lastFetchEmitter = fetchEmitter;
|
|
99
|
-
return fetchEmitter;
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
// Simulate closing connection
|
|
103
|
-
this.end = () => {
|
|
104
|
-
if (this.events && this.events.end) {
|
|
105
|
-
setTimeout(() => this.events.end(), 5);
|
|
106
|
-
}
|
|
107
|
-
};
|
|
115
|
+
} else {
|
|
116
|
+
// Simulate successful connection by emitting 'ready' event
|
|
117
|
+
if (this.events && this.events.ready) {
|
|
118
|
+
setTimeout(() => this.events.ready(), 10);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
108
122
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
123
|
+
// Simulate opening a mailbox
|
|
124
|
+
this.openBox = (folder, readOnly, callback) => {
|
|
125
|
+
setTimeout(() => {
|
|
126
|
+
callback(null, {
|
|
127
|
+
messages: { total: 1 },
|
|
128
|
+
name: folder,
|
|
129
|
+
readOnly: readOnly,
|
|
130
|
+
});
|
|
131
|
+
}, 10);
|
|
132
|
+
};
|
|
114
133
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
134
|
+
// Simulate searching for emails
|
|
135
|
+
this.search = (criteria, callback) => {
|
|
136
|
+
setTimeout(() => {
|
|
137
|
+
// Return mock message IDs
|
|
138
|
+
callback(null, [123, 456, 789]);
|
|
139
|
+
}, 10);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// Simulate fetching email messages
|
|
143
|
+
this.fetch = (results, options) => {
|
|
144
|
+
const fetchEmitter = {
|
|
145
|
+
on: (event, callback) => {
|
|
146
|
+
if (event === 'message') {
|
|
147
|
+
setTimeout(() => {
|
|
148
|
+
const mockMessage = {
|
|
149
|
+
on: (messageEvent, messageCallback) => {
|
|
150
|
+
if (messageEvent === 'body') {
|
|
151
|
+
setTimeout(() => {
|
|
152
|
+
// Return a mock email body that will be parsed
|
|
153
|
+
const mockEmailContent = `From: sender@test.com\r\nTo: recipient@test.com\r\nSubject: Mock Email Subject\r\n\r\nThis is a mock email body for testing purposes.`;
|
|
154
|
+
messageCallback(Buffer.from(mockEmailContent));
|
|
155
|
+
}, 5);
|
|
156
|
+
} else if (messageEvent === 'attributes') {
|
|
157
|
+
setTimeout(() => {
|
|
158
|
+
messageCallback({
|
|
159
|
+
uid: 123,
|
|
160
|
+
flags: ['\\Seen'],
|
|
161
|
+
date: new Date(),
|
|
162
|
+
size: 1024,
|
|
163
|
+
});
|
|
164
|
+
}, 5);
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
once: (messageEvent, messageCallback) => {
|
|
168
|
+
if (messageEvent === 'end') {
|
|
169
|
+
setTimeout(() => messageCallback(), 15);
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
callback(mockMessage);
|
|
174
|
+
}, 10);
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
once: (event, callback) => {
|
|
178
|
+
if (event === 'end') {
|
|
179
|
+
setTimeout(() => callback(), 20);
|
|
180
|
+
} else if (event === 'error') {
|
|
181
|
+
// Store error callback for potential use
|
|
182
|
+
this.errorCallback = callback;
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
};
|
|
119
186
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
187
|
+
// Store the fetch emitter so we can trigger events manually in tests
|
|
188
|
+
this.lastFetchEmitter = fetchEmitter;
|
|
189
|
+
return fetchEmitter;
|
|
190
|
+
};
|
|
124
191
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
192
|
+
// Simulate closing connection
|
|
193
|
+
this.end = () => {
|
|
194
|
+
if (this.events && this.events.end) {
|
|
195
|
+
setTimeout(() => this.events.end(), 5);
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// Event listener setup
|
|
200
|
+
this.once = (event, callback) => {
|
|
201
|
+
if (!this.events) this.events = {};
|
|
202
|
+
this.events[event] = callback;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
this.on = (event, callback) => {
|
|
206
|
+
if (!this.events) this.events = {};
|
|
207
|
+
this.events[event] = callback;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
// Additional IMAP methods that might be used
|
|
211
|
+
this.addFlags = (source, flags, callback) => {
|
|
212
|
+
setTimeout(() => callback(null), 5);
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
this.removeFlags = (source, flags, callback) => {
|
|
216
|
+
setTimeout(() => callback(null), 5);
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// Helper method to trigger the email processing flow
|
|
220
|
+
this.simulateNewEmail = (emailData = {}) => {
|
|
221
|
+
if (this.events && this.events.mail) {
|
|
222
|
+
setTimeout(() => {
|
|
223
|
+
this.events.mail(1); // Simulate 1 new email
|
|
224
|
+
}, 10);
|
|
225
|
+
}
|
|
226
|
+
};
|
|
128
227
|
|
|
129
|
-
|
|
130
|
-
this.simulateNewEmail = (emailData = {}) => {
|
|
131
|
-
if (this.events && this.events.mail) {
|
|
132
|
-
setTimeout(() => {
|
|
133
|
-
this.events.mail(1); // Simulate 1 new email
|
|
134
|
-
}, 10);
|
|
135
|
-
}
|
|
228
|
+
return this;
|
|
136
229
|
};
|
|
137
|
-
|
|
138
|
-
return this;
|
|
139
|
-
};
|
|
140
230
|
}
|
|
141
231
|
|
|
142
232
|
/**
|
|
143
233
|
* Mock Mailparser implementation for testing
|
|
144
234
|
*/
|
|
145
235
|
function createMockMailparser() {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
236
|
+
return {
|
|
237
|
+
simpleParser: function (source, options = {}) {
|
|
238
|
+
return Promise.resolve({
|
|
239
|
+
subject: options.subject || 'Mock Email Subject',
|
|
240
|
+
text: options.text || 'This is a mock email body for testing purposes.',
|
|
241
|
+
html: options.html || '<p>This is a mock email body for testing purposes.</p>',
|
|
242
|
+
from: {
|
|
243
|
+
text: options.from || 'sender@test.com',
|
|
244
|
+
value: [{ address: options.from || 'sender@test.com', name: 'Test Sender' }],
|
|
245
|
+
},
|
|
246
|
+
to: {
|
|
247
|
+
text: options.to || 'recipient@test.com',
|
|
248
|
+
value: [{ address: options.to || 'recipient@test.com', name: 'Test Recipient' }],
|
|
249
|
+
},
|
|
250
|
+
date: options.date || new Date(),
|
|
251
|
+
messageId: options.messageId || '<mock-message-id@test.com>',
|
|
252
|
+
headers: new Map([
|
|
253
|
+
['message-id', '<mock-message-id@test.com>'],
|
|
254
|
+
['subject', options.subject || 'Mock Email Subject'],
|
|
255
|
+
['from', options.from || 'sender@test.com'],
|
|
256
|
+
['to', options.to || 'recipient@test.com'],
|
|
257
|
+
]),
|
|
258
|
+
attachments: options.attachments || [],
|
|
259
|
+
});
|
|
159
260
|
},
|
|
160
|
-
|
|
161
|
-
messageId: options.messageId || '<mock-message-id@test.com>',
|
|
162
|
-
headers: new Map([
|
|
163
|
-
['message-id', '<mock-message-id@test.com>'],
|
|
164
|
-
['subject', options.subject || 'Mock Email Subject'],
|
|
165
|
-
['from', options.from || 'sender@test.com'],
|
|
166
|
-
['to', options.to || 'recipient@test.com']
|
|
167
|
-
]),
|
|
168
|
-
attachments: options.attachments || []
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
};
|
|
261
|
+
};
|
|
172
262
|
}
|
|
173
263
|
|
|
174
264
|
/**
|
|
175
|
-
*
|
|
265
|
+
* Enhanced module mocks setup with better email simulation
|
|
176
266
|
*/
|
|
177
|
-
function
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
nodeInstance = node; // Capture the node instance
|
|
186
|
-
|
|
187
|
-
// Apply config properties to node
|
|
188
|
-
Object.assign(node, {
|
|
189
|
-
id: config.id || 'mock-node-id',
|
|
190
|
-
type: config.type || 'email-receiver',
|
|
191
|
-
name: config.name || 'Mock Node',
|
|
192
|
-
on: function(event, callback) {
|
|
193
|
-
if (event === 'input') {
|
|
194
|
-
storedInputCallback = callback;
|
|
195
|
-
// Store the callback on the node instance for easy access
|
|
196
|
-
node.inputCallback = callback;
|
|
197
|
-
}
|
|
198
|
-
// Call the original onHandler if provided
|
|
199
|
-
if (options.onHandler) {
|
|
200
|
-
options.onHandler.call(node, event, callback);
|
|
201
|
-
}
|
|
202
|
-
},
|
|
203
|
-
status: options.statusHandler || function() {},
|
|
204
|
-
error: options.errorHandler || function() {},
|
|
205
|
-
send: options.sendHandler || function() {},
|
|
206
|
-
log: options.logHandler || function() {},
|
|
207
|
-
warn: options.warnHandler || function() {},
|
|
208
|
-
debug: options.debugHandler || function() {}
|
|
209
|
-
});
|
|
210
|
-
return node;
|
|
211
|
-
},
|
|
212
|
-
registerType: function(type, constructor) {
|
|
213
|
-
// Store registration for verification in tests
|
|
214
|
-
this.lastRegisteredType = type;
|
|
215
|
-
this.lastRegisteredConstructor = constructor;
|
|
216
|
-
},
|
|
217
|
-
// Helper method to get the stored input callback
|
|
218
|
-
getInputCallback: function() {
|
|
219
|
-
return storedInputCallback;
|
|
220
|
-
},
|
|
221
|
-
// Helper method to get the node instance
|
|
222
|
-
getNodeInstance: function() {
|
|
223
|
-
return nodeInstance;
|
|
224
|
-
}
|
|
225
|
-
},
|
|
226
|
-
util: {
|
|
227
|
-
evaluateNodeProperty: function(value, type, node, msg, callback) {
|
|
228
|
-
if (type === 'json') {
|
|
229
|
-
try {
|
|
230
|
-
// Simulate parsing a JSON string into an object
|
|
231
|
-
return JSON.parse(JSON.stringify(value));
|
|
232
|
-
} catch (e) {
|
|
233
|
-
if (callback) {
|
|
234
|
-
callback(e, null);
|
|
235
|
-
}
|
|
236
|
-
return null;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
267
|
+
function setupModuleMocks() {
|
|
268
|
+
const mockModules = {
|
|
269
|
+
'node-imap': createMockImap(),
|
|
270
|
+
mailparser: createMockMailparser(),
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const Module = require('module');
|
|
274
|
+
const originalLoad = Module._load;
|
|
239
275
|
|
|
240
|
-
|
|
241
|
-
if (
|
|
242
|
-
|
|
276
|
+
Module._load = function (request, parent) {
|
|
277
|
+
if (mockModules[request]) {
|
|
278
|
+
return mockModules[request];
|
|
243
279
|
}
|
|
244
|
-
return
|
|
245
|
-
|
|
246
|
-
encrypt: function(value) {
|
|
247
|
-
return 'encrypted:' + value;
|
|
248
|
-
},
|
|
249
|
-
decrypt: function(value) {
|
|
250
|
-
return value.replace('encrypted:', '');
|
|
251
|
-
}
|
|
252
|
-
},
|
|
253
|
-
log: {
|
|
254
|
-
info: options.logInfo || function() {},
|
|
255
|
-
warn: options.logWarn || function() {},
|
|
256
|
-
error: options.logError || function() {},
|
|
257
|
-
debug: options.logDebug || function() {}
|
|
258
|
-
}
|
|
259
|
-
};
|
|
260
|
-
|
|
261
|
-
return mockRED;
|
|
262
|
-
}
|
|
280
|
+
return originalLoad.apply(this, arguments);
|
|
281
|
+
};
|
|
263
282
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const mockModules = {
|
|
269
|
-
'node-imap': createMockImap(),
|
|
270
|
-
'mailparser': createMockMailparser()
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
const Module = require('module');
|
|
274
|
-
const originalLoad = Module._load;
|
|
275
|
-
|
|
276
|
-
Module._load = function(request, parent) {
|
|
277
|
-
if (mockModules[request]) {
|
|
278
|
-
return mockModules[request];
|
|
279
|
-
}
|
|
280
|
-
return originalLoad.apply(this, arguments);
|
|
281
|
-
};
|
|
282
|
-
|
|
283
|
-
// Return cleanup function
|
|
284
|
-
return function cleanup() {
|
|
285
|
-
Module._load = originalLoad;
|
|
286
|
-
};
|
|
283
|
+
// Return cleanup function
|
|
284
|
+
return function cleanup() {
|
|
285
|
+
Module._load = originalLoad;
|
|
286
|
+
};
|
|
287
287
|
}
|
|
288
288
|
|
|
289
289
|
/**
|
|
290
290
|
* Create test configurations for different scenarios
|
|
291
291
|
*/
|
|
292
292
|
const testConfigs = {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
293
|
+
valid: {
|
|
294
|
+
id: 'test-node-1',
|
|
295
|
+
type: 'email-receiver',
|
|
296
|
+
name: 'Test Email Receiver',
|
|
297
|
+
host: 'imap.test.com',
|
|
298
|
+
hostType: 'str',
|
|
299
|
+
port: 993,
|
|
300
|
+
portType: 'num',
|
|
301
|
+
tls: true,
|
|
302
|
+
tlsType: 'bool',
|
|
303
|
+
user: 'test@test.com',
|
|
304
|
+
userType: 'str',
|
|
305
|
+
password: 'testpass',
|
|
306
|
+
passwordType: 'str',
|
|
307
|
+
folder: ['INBOX'],
|
|
308
|
+
folderType: 'str',
|
|
309
|
+
markseen: true,
|
|
310
|
+
markseenType: 'bool',
|
|
311
|
+
},
|
|
312
|
+
|
|
313
|
+
arrayFolders: {
|
|
314
|
+
id: 'test-node-3',
|
|
315
|
+
type: 'email-receiver',
|
|
316
|
+
name: 'Array Folders Test',
|
|
317
|
+
host: 'imap.test.com',
|
|
318
|
+
hostType: 'str',
|
|
319
|
+
port: 993,
|
|
320
|
+
portType: 'num',
|
|
321
|
+
user: 'test@test.com',
|
|
322
|
+
userType: 'str',
|
|
323
|
+
password: 'testpass',
|
|
324
|
+
passwordType: 'str',
|
|
325
|
+
folder: ['INBOX', 'Junk', 'Drafts'],
|
|
326
|
+
folderType: 'json',
|
|
327
|
+
markseen: false,
|
|
328
|
+
markseenType: 'bool',
|
|
329
|
+
},
|
|
330
|
+
|
|
331
|
+
invalidFolderType: {
|
|
332
|
+
id: 'test-node-4',
|
|
333
|
+
type: 'email-receiver',
|
|
334
|
+
name: 'Invalid Config Test',
|
|
335
|
+
host: '', // Missing host
|
|
336
|
+
hostType: 'str',
|
|
337
|
+
port: 993,
|
|
338
|
+
portType: 'num',
|
|
339
|
+
user: 'test@test.com',
|
|
340
|
+
userType: 'str',
|
|
341
|
+
password: '', // Missing password
|
|
342
|
+
passwordType: 'str',
|
|
343
|
+
folder: 123,
|
|
344
|
+
folderType: 'num',
|
|
345
|
+
},
|
|
346
|
+
|
|
347
|
+
invalidConfig: {
|
|
348
|
+
id: 'test-node-4',
|
|
349
|
+
type: 'email-receiver',
|
|
350
|
+
name: 'Invalid Config Test',
|
|
351
|
+
host: '', // Missing host
|
|
352
|
+
hostType: 'str',
|
|
353
|
+
port: 993,
|
|
354
|
+
portType: 'num',
|
|
355
|
+
user: 'test@test.com',
|
|
356
|
+
userType: 'str',
|
|
357
|
+
password: '', // Missing password
|
|
358
|
+
passwordType: 'str',
|
|
359
|
+
folder: ['Inbox'],
|
|
360
|
+
folderType: 'num',
|
|
361
|
+
},
|
|
362
|
+
|
|
363
|
+
minimal: {
|
|
364
|
+
id: 'test-node-5',
|
|
365
|
+
type: 'email-receiver',
|
|
366
|
+
host: 'imap.minimal.com',
|
|
367
|
+
hostType: 'str',
|
|
368
|
+
port: 993,
|
|
369
|
+
portType: 'num',
|
|
370
|
+
user: 'minimal@test.com',
|
|
371
|
+
userType: 'str',
|
|
372
|
+
password: 'minimalpass',
|
|
373
|
+
passwordType: 'str',
|
|
374
|
+
folder: 'INBOX',
|
|
375
|
+
folderType: 'str',
|
|
376
|
+
},
|
|
377
377
|
};
|
|
378
378
|
|
|
379
379
|
/**
|
|
380
380
|
* Create test flows for Node-RED integration tests
|
|
381
381
|
*/
|
|
382
382
|
const testFlows = {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
multiOutput: [
|
|
398
|
-
{ ...testConfigs.valid, wires: [['h1', 'h2']] },
|
|
399
|
-
{ id: 'h1', type: 'helper' },
|
|
400
|
-
{ id: 'h2', type: 'helper' }
|
|
401
|
-
]
|
|
383
|
+
single: [testConfigs.valid],
|
|
384
|
+
|
|
385
|
+
withHelper: [testConfigs.valid, { id: 'h1', type: 'helper' }],
|
|
386
|
+
|
|
387
|
+
connected: [
|
|
388
|
+
{ ...testConfigs.valid, wires: [['h1']] },
|
|
389
|
+
{ id: 'h1', type: 'helper' },
|
|
390
|
+
],
|
|
391
|
+
|
|
392
|
+
multiOutput: [
|
|
393
|
+
{ ...testConfigs.valid, wires: [['h1', 'h2']] },
|
|
394
|
+
{ id: 'h1', type: 'helper' },
|
|
395
|
+
{ id: 'h2', type: 'helper' },
|
|
396
|
+
],
|
|
402
397
|
};
|
|
403
398
|
|
|
404
399
|
/**
|
|
405
400
|
* Utility functions for test assertions and email simulation
|
|
406
401
|
*/
|
|
407
402
|
const testUtils = {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
403
|
+
/**
|
|
404
|
+
* Wait for a specified amount of time
|
|
405
|
+
*/
|
|
406
|
+
wait: (ms = 100) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Create a promise that resolves when a node receives a message
|
|
410
|
+
*/
|
|
411
|
+
waitForMessage: (node, timeout = 1000) => {
|
|
412
|
+
return new Promise((resolve, reject) => {
|
|
413
|
+
const timer = setTimeout(() => {
|
|
414
|
+
reject(new Error('Timeout waiting for message'));
|
|
415
|
+
}, timeout);
|
|
416
|
+
|
|
417
|
+
node.on('input', (msg) => {
|
|
418
|
+
clearTimeout(timer);
|
|
419
|
+
resolve(msg);
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
},
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Verify that a message has expected properties
|
|
426
|
+
*/
|
|
427
|
+
verifyMessage: (msg, expectedProps = {}) => {
|
|
428
|
+
const should = require('should');
|
|
429
|
+
should.exist(msg);
|
|
430
|
+
|
|
431
|
+
Object.keys(expectedProps).forEach((prop) => {
|
|
432
|
+
if (expectedProps[prop] !== undefined) {
|
|
433
|
+
msg.should.have.property(prop, expectedProps[prop]);
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
},
|
|
442
437
|
};
|
|
443
438
|
|
|
444
439
|
module.exports = {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
};
|
|
440
|
+
createMockNodeRED,
|
|
441
|
+
createMockImap,
|
|
442
|
+
createMockMailparser,
|
|
443
|
+
setupModuleMocks,
|
|
444
|
+
testConfigs,
|
|
445
|
+
testFlows,
|
|
446
|
+
testUtils,
|
|
447
|
+
};
|