@5minds/node-red-contrib-processcube-tools 1.0.1-feature-607796-mfdmqdc3 → 1.0.1-feature-e7a81a-mfdq8poq
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.
|
@@ -123,56 +123,65 @@
|
|
|
123
123
|
<script type="text/html" data-help-name="email-receiver">
|
|
124
124
|
<p>A Node-RED node that fetches unseen emails from a specified IMAP server. Each fetched email is sent as a separate message on the output.</p>
|
|
125
125
|
|
|
126
|
-
<p>All fields can be configured as a <strong>string</strong>, from a <strong>message property (msg)</strong>, <strong>flow</strong> or <strong>global</strong> context, or an <strong>environment variable</strong>.</p>
|
|
127
|
-
|
|
128
|
-
<p><strong>Security Tip:</strong> It's a best practice to avoid storing sensitive information like passwords directly in your Node-RED flow. This prevents them from being exposed in your flow file. Instead, use an <strong>environment variable</strong>. You can define these variables in a <code>.env</code> file, which is especially useful when deploying your application.</p>
|
|
129
|
-
|
|
130
|
-
<p>A <code>.env</code> file should look like this:</p>
|
|
131
|
-
<pre>
|
|
132
|
-
EMAIL_SEND_PORT=123
|
|
133
|
-
EMAIL_SEND_HOST=smtp.gmail.com
|
|
134
|
-
EMAIL_SEND_USER=myTestMail@company.com
|
|
135
|
-
EMAIL_SEND_PASSWORD=mySecretPassword
|
|
136
|
-
</pre>
|
|
137
|
-
|
|
138
|
-
<p>
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
<
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
<
|
|
152
|
-
</
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
<dl class="message-properties">
|
|
157
|
-
<dt>
|
|
158
|
-
<span class="property-type">
|
|
159
|
-
</dt>
|
|
160
|
-
<dd>The
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
<
|
|
165
|
-
<
|
|
166
|
-
<
|
|
167
|
-
</
|
|
168
|
-
|
|
169
|
-
<
|
|
170
|
-
<
|
|
171
|
-
</
|
|
172
|
-
|
|
173
|
-
<
|
|
174
|
-
<
|
|
175
|
-
</
|
|
176
|
-
|
|
177
|
-
</
|
|
126
|
+
<p>All fields can be configured as a <strong>string</strong>, from a <strong>message property (msg)</strong>, <strong>flow</strong> or <strong>global</strong> context, or an <strong>environment variable</strong>.</p>
|
|
127
|
+
|
|
128
|
+
<p><strong>Security Tip:</strong> It's a best practice to avoid storing sensitive information like passwords directly in your Node-RED flow. This prevents them from being exposed in your flow file. Instead, use an <strong>environment variable</strong>. You can define these variables in a <code>.env</code> file, which is especially useful when deploying your application with Docker.</p>
|
|
129
|
+
|
|
130
|
+
<p>A <code>.env</code> file should look like this:</p>
|
|
131
|
+
<pre>
|
|
132
|
+
EMAIL_SEND_PORT=123
|
|
133
|
+
EMAIL_SEND_HOST=smtp.gmail.com
|
|
134
|
+
EMAIL_SEND_USER=myTestMail@company.com
|
|
135
|
+
EMAIL_SEND_PASSWORD=mySecretPassword
|
|
136
|
+
</pre>
|
|
137
|
+
|
|
138
|
+
<p>To ensure Docker loads these variables from the <code>.env</code> file, you need to add the following line to your <code>docker-compose.yaml</code> file:</p>
|
|
139
|
+
<pre>
|
|
140
|
+
services:
|
|
141
|
+
your_service_name:
|
|
142
|
+
...
|
|
143
|
+
env_file:
|
|
144
|
+
- .env
|
|
145
|
+
</pre>
|
|
146
|
+
|
|
147
|
+
<p>In your flow, you can then access the password using the environment variable <code>EMAIL_SEND_PASSWORD</code>.</p>
|
|
148
|
+
|
|
149
|
+
Inputs
|
|
150
|
+
<dl class="message-properties">
|
|
151
|
+
<dt>payload</dt>
|
|
152
|
+
<dd>The node is triggered by any incoming message. The node's configuration can be overridden by properties of the incoming <code>msg</code> object.</dd>
|
|
153
|
+
</dl>
|
|
154
|
+
|
|
155
|
+
Outputs
|
|
156
|
+
<dl class="message-properties">
|
|
157
|
+
<dt>payload
|
|
158
|
+
<span class="property-type">string</span>
|
|
159
|
+
</dt>
|
|
160
|
+
<dd>The text body of the email.</dd>
|
|
161
|
+
</dl>
|
|
162
|
+
|
|
163
|
+
Optional Message Properties
|
|
164
|
+
<p>You can override default settings by passing the following properties in the incoming <code>msg</code> object:</p>
|
|
165
|
+
<dl class="message-properties">
|
|
166
|
+
<dt>msg.imap_connTimeout
|
|
167
|
+
<span class="property-type">number</span>
|
|
168
|
+
</dt>
|
|
169
|
+
<dd>The connection timeout in milliseconds (default: 10000).</dd>
|
|
170
|
+
<dt>msg.imap_authTimeout
|
|
171
|
+
<span class="property-type">number</span>
|
|
172
|
+
</dt>
|
|
173
|
+
<dd>The authentication timeout in milliseconds (default: 5000).</dd>
|
|
174
|
+
<dt>msg.imap_keepalive
|
|
175
|
+
<span class="property-type">boolean</span>
|
|
176
|
+
</dt>
|
|
177
|
+
<dd>If set to <code>true</code>, a periodic NOOP command is sent to keep the connection alive (default: <code>true</code>).</dd>
|
|
178
|
+
<dt>msg.imap_autotls
|
|
179
|
+
<span class="property-type">string</span>
|
|
180
|
+
</dt>
|
|
181
|
+
<dd>Controls STARTTLS behavior. Set to <code>never</code> to disable it (default: <code>never</code>).</dd>
|
|
182
|
+
<dt>msg.imap_tlsOptions
|
|
183
|
+
<span class="property-type">object</span>
|
|
184
|
+
</dt>
|
|
185
|
+
<dd>An object containing TLS options for the connection.</dd>
|
|
186
|
+
</dl>
|
|
178
187
|
</script>
|
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@5minds/node-red-contrib-processcube-tools",
|
|
3
|
-
"version": "1.0.1-feature-
|
|
3
|
+
"version": "1.0.1-feature-e7a81a-mfdq8poq",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Node-RED tools nodes for ProcessCube",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"lint": "prettier --write --config ./.prettierrc.json \"**/*.{html,js}\"",
|
|
8
|
-
"test": "mocha test
|
|
9
|
-
"test:
|
|
8
|
+
"test:unit": "mocha test/unit/**/*.test.js --timeout 10000",
|
|
9
|
+
"test:integration": "mocha test/integration/**/*.test.js --timeout 10000",
|
|
10
|
+
"test": "npm run test:unit && npm run test:integration",
|
|
11
|
+
"test:debug": "mocha test/unit/**/*.test.js test/integration/**/*.test.js --timeout 0 --reporter spec"
|
|
10
12
|
},
|
|
11
13
|
"authors": [
|
|
12
14
|
{
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared mock objects and utilities for Email Receiver Node tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Mock IMAP implementation for testing
|
|
7
|
+
*/
|
|
8
|
+
function createMockImap() {
|
|
9
|
+
return function MockImap(config) {
|
|
10
|
+
this.config = config;
|
|
11
|
+
this.events = {};
|
|
12
|
+
|
|
13
|
+
// Simulate connection behavior
|
|
14
|
+
this.connect = () => {
|
|
15
|
+
// Simulate successful connection by emitting 'ready' event
|
|
16
|
+
if (this.events && this.events.ready) {
|
|
17
|
+
// Use setTimeout to simulate async behavior
|
|
18
|
+
setTimeout(() => this.events.ready(), 10);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Simulate opening a mailbox
|
|
23
|
+
this.openBox = (folder, readOnly, callback) => {
|
|
24
|
+
setTimeout(() => {
|
|
25
|
+
callback(null, {
|
|
26
|
+
messages: { total: 1 },
|
|
27
|
+
name: folder,
|
|
28
|
+
readOnly: readOnly
|
|
29
|
+
});
|
|
30
|
+
}, 10);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Simulate searching for emails
|
|
34
|
+
this.search = (criteria, callback) => {
|
|
35
|
+
setTimeout(() => {
|
|
36
|
+
// Return mock message IDs
|
|
37
|
+
callback(null, [123, 456, 789]);
|
|
38
|
+
}, 10);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Simulate fetching email messages
|
|
42
|
+
this.fetch = (results, options) => {
|
|
43
|
+
return {
|
|
44
|
+
on: (event, callback) => {
|
|
45
|
+
if (event === 'message') {
|
|
46
|
+
setTimeout(() => {
|
|
47
|
+
const mockMessage = {
|
|
48
|
+
on: (messageEvent, messageCallback) => {
|
|
49
|
+
if (messageEvent === 'body') {
|
|
50
|
+
setTimeout(() => {
|
|
51
|
+
messageCallback(Buffer.from('mock email body'));
|
|
52
|
+
}, 5);
|
|
53
|
+
} else if (messageEvent === 'attributes') {
|
|
54
|
+
setTimeout(() => {
|
|
55
|
+
messageCallback({
|
|
56
|
+
uid: 123,
|
|
57
|
+
flags: ['\\Seen'],
|
|
58
|
+
date: new Date(),
|
|
59
|
+
size: 1024
|
|
60
|
+
});
|
|
61
|
+
}, 5);
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
once: (messageEvent, messageCallback) => {
|
|
65
|
+
if (messageEvent === 'end') {
|
|
66
|
+
setTimeout(() => messageCallback(), 15);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
callback(mockMessage);
|
|
71
|
+
}, 10);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
once: (event, callback) => {
|
|
75
|
+
if (event === 'end') {
|
|
76
|
+
setTimeout(() => callback(), 20);
|
|
77
|
+
} else if (event === 'error') {
|
|
78
|
+
// Store error callback for potential use
|
|
79
|
+
this.errorCallback = callback;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Simulate closing connection
|
|
86
|
+
this.end = () => {
|
|
87
|
+
if (this.events && this.events.end) {
|
|
88
|
+
setTimeout(() => this.events.end(), 5);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Event listener setup
|
|
93
|
+
this.once = (event, callback) => {
|
|
94
|
+
if (!this.events) this.events = {};
|
|
95
|
+
this.events[event] = callback;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Additional IMAP methods that might be used
|
|
99
|
+
this.addFlags = (source, flags, callback) => {
|
|
100
|
+
setTimeout(() => callback(null), 5);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
this.removeFlags = (source, flags, callback) => {
|
|
104
|
+
setTimeout(() => callback(null), 5);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
return this;
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Mock Mailparser implementation for testing
|
|
113
|
+
*/
|
|
114
|
+
function createMockMailparser() {
|
|
115
|
+
return {
|
|
116
|
+
simpleParser: function(source, options = {}) {
|
|
117
|
+
return Promise.resolve({
|
|
118
|
+
subject: options.subject || 'Mock Email Subject',
|
|
119
|
+
text: options.text || 'This is a mock email body for testing purposes.',
|
|
120
|
+
html: options.html || '<p>This is a mock email body for testing purposes.</p>',
|
|
121
|
+
from: {
|
|
122
|
+
text: options.from || 'sender@test.com',
|
|
123
|
+
value: [{ address: options.from || 'sender@test.com', name: 'Test Sender' }]
|
|
124
|
+
},
|
|
125
|
+
to: {
|
|
126
|
+
text: options.to || 'recipient@test.com',
|
|
127
|
+
value: [{ address: options.to || 'recipient@test.com', name: 'Test Recipient' }]
|
|
128
|
+
},
|
|
129
|
+
date: options.date || new Date(),
|
|
130
|
+
messageId: options.messageId || '<mock-message-id@test.com>',
|
|
131
|
+
headers: new Map([
|
|
132
|
+
['message-id', '<mock-message-id@test.com>'],
|
|
133
|
+
['subject', options.subject || 'Mock Email Subject'],
|
|
134
|
+
['from', options.from || 'sender@test.com'],
|
|
135
|
+
['to', options.to || 'recipient@test.com']
|
|
136
|
+
]),
|
|
137
|
+
attachments: options.attachments || []
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Create mock Node-RED object for unit testing
|
|
145
|
+
*/
|
|
146
|
+
function createMockNodeRED(options = {}) {
|
|
147
|
+
return {
|
|
148
|
+
nodes: {
|
|
149
|
+
createNode: function(node, config) {
|
|
150
|
+
// Apply config properties to node
|
|
151
|
+
Object.assign(node, {
|
|
152
|
+
id: config.id || 'mock-node-id',
|
|
153
|
+
type: config.type || 'email-receiver',
|
|
154
|
+
name: config.name || 'Mock Node',
|
|
155
|
+
on: options.onHandler || function() {},
|
|
156
|
+
status: options.statusHandler || function() {},
|
|
157
|
+
error: options.errorHandler || function() {},
|
|
158
|
+
send: options.sendHandler || function() {},
|
|
159
|
+
log: options.logHandler || function() {},
|
|
160
|
+
warn: options.warnHandler || function() {},
|
|
161
|
+
debug: options.debugHandler || function() {}
|
|
162
|
+
});
|
|
163
|
+
return node;
|
|
164
|
+
},
|
|
165
|
+
registerType: function(type, constructor) {
|
|
166
|
+
// Store registration for verification in tests
|
|
167
|
+
this.lastRegisteredType = type;
|
|
168
|
+
this.lastRegisteredConstructor = constructor;
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
util: {
|
|
172
|
+
evaluateNodeProperty: function(value, type, node, msg, callback) {
|
|
173
|
+
if (type === 'json') {
|
|
174
|
+
try {
|
|
175
|
+
// Simulate parsing a JSON string into an object
|
|
176
|
+
return JSON.parse(JSON.stringify(value));
|
|
177
|
+
} catch (e) {
|
|
178
|
+
if (callback) {
|
|
179
|
+
callback(e, null);
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Simple mock implementation
|
|
186
|
+
if (callback) {
|
|
187
|
+
callback(null, value);
|
|
188
|
+
}
|
|
189
|
+
return value;
|
|
190
|
+
},
|
|
191
|
+
encrypt: function(value) {
|
|
192
|
+
return 'encrypted:' + value;
|
|
193
|
+
},
|
|
194
|
+
decrypt: function(value) {
|
|
195
|
+
return value.replace('encrypted:', '');
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
log: {
|
|
199
|
+
info: options.logInfo || function() {},
|
|
200
|
+
warn: options.logWarn || function() {},
|
|
201
|
+
error: options.logError || function() {},
|
|
202
|
+
debug: options.logDebug || function() {}
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Set up module mocks for require() calls
|
|
209
|
+
*/
|
|
210
|
+
function setupModuleMocks() {
|
|
211
|
+
const mockModules = {
|
|
212
|
+
'node-imap': createMockImap(),
|
|
213
|
+
'mailparser': createMockMailparser()
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const Module = require('module');
|
|
217
|
+
const originalLoad = Module._load;
|
|
218
|
+
|
|
219
|
+
Module._load = function(request, parent) {
|
|
220
|
+
if (mockModules[request]) {
|
|
221
|
+
return mockModules[request];
|
|
222
|
+
}
|
|
223
|
+
return originalLoad.apply(this, arguments);
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
// Return cleanup function
|
|
227
|
+
return function cleanup() {
|
|
228
|
+
Module._load = originalLoad;
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Create test configurations for different scenarios
|
|
234
|
+
*/
|
|
235
|
+
const testConfigs = {
|
|
236
|
+
valid: {
|
|
237
|
+
id: 'test-node-1',
|
|
238
|
+
type: 'email-receiver',
|
|
239
|
+
name: 'Test Email Receiver',
|
|
240
|
+
host: 'imap.test.com',
|
|
241
|
+
hostType: 'str',
|
|
242
|
+
port: 993,
|
|
243
|
+
portType: 'num',
|
|
244
|
+
tls: true,
|
|
245
|
+
tlsType: 'bool',
|
|
246
|
+
user: 'test@test.com',
|
|
247
|
+
userType: 'str',
|
|
248
|
+
password: 'testpass',
|
|
249
|
+
passwordType: 'str',
|
|
250
|
+
folder: 'INBOX',
|
|
251
|
+
folderType: 'str',
|
|
252
|
+
markseen: true,
|
|
253
|
+
markseenType: 'bool'
|
|
254
|
+
},
|
|
255
|
+
|
|
256
|
+
arrayFolders: {
|
|
257
|
+
id: 'test-node-3',
|
|
258
|
+
type: 'email-receiver',
|
|
259
|
+
name: 'Array Folders Test',
|
|
260
|
+
host: 'imap.test.com',
|
|
261
|
+
hostType: 'str',
|
|
262
|
+
port: 993,
|
|
263
|
+
portType: 'num',
|
|
264
|
+
user: 'test@test.com',
|
|
265
|
+
userType: 'str',
|
|
266
|
+
password: 'testpass',
|
|
267
|
+
passwordType: 'str',
|
|
268
|
+
folder: ['INBOX', 'Junk', 'Drafts'],
|
|
269
|
+
folderType: 'json',
|
|
270
|
+
markseen: false,
|
|
271
|
+
markseenType: 'bool'
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
invalidConfig: {
|
|
275
|
+
id: 'test-node-4',
|
|
276
|
+
type: 'email-receiver',
|
|
277
|
+
name: 'Invalid Config Test',
|
|
278
|
+
host: '', // Missing host
|
|
279
|
+
hostType: 'str',
|
|
280
|
+
port: 993,
|
|
281
|
+
portType: 'num',
|
|
282
|
+
user: 'test@test.com',
|
|
283
|
+
userType: 'str',
|
|
284
|
+
password: '', // Missing password
|
|
285
|
+
passwordType: 'str',
|
|
286
|
+
folder: 123, // Wrong type
|
|
287
|
+
folderType: 'num'
|
|
288
|
+
},
|
|
289
|
+
|
|
290
|
+
minimal: {
|
|
291
|
+
id: 'test-node-5',
|
|
292
|
+
type: 'email-receiver',
|
|
293
|
+
host: 'imap.minimal.com',
|
|
294
|
+
hostType: 'str',
|
|
295
|
+
port: 993,
|
|
296
|
+
portType: 'num',
|
|
297
|
+
user: 'minimal@test.com',
|
|
298
|
+
userType: 'str',
|
|
299
|
+
password: 'minimalpass',
|
|
300
|
+
passwordType: 'str',
|
|
301
|
+
folder: 'INBOX',
|
|
302
|
+
folderType: 'str'
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Create test flows for Node-RED integration tests
|
|
308
|
+
*/
|
|
309
|
+
const testFlows = {
|
|
310
|
+
single: [
|
|
311
|
+
testConfigs.valid
|
|
312
|
+
],
|
|
313
|
+
|
|
314
|
+
withHelper: [
|
|
315
|
+
testConfigs.valid,
|
|
316
|
+
{ id: 'h1', type: 'helper' }
|
|
317
|
+
],
|
|
318
|
+
|
|
319
|
+
connected: [
|
|
320
|
+
{ ...testConfigs.valid, wires: [['h1']] },
|
|
321
|
+
{ id: 'h1', type: 'helper' }
|
|
322
|
+
],
|
|
323
|
+
|
|
324
|
+
multiOutput: [
|
|
325
|
+
{ ...testConfigs.valid, wires: [['h1', 'h2']] },
|
|
326
|
+
{ id: 'h1', type: 'helper' },
|
|
327
|
+
{ id: 'h2', type: 'helper' }
|
|
328
|
+
]
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Utility functions for test assertions
|
|
333
|
+
*/
|
|
334
|
+
const testUtils = {
|
|
335
|
+
/**
|
|
336
|
+
* Wait for a specified amount of time
|
|
337
|
+
*/
|
|
338
|
+
wait: (ms = 100) => new Promise(resolve => setTimeout(resolve, ms)),
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Create a promise that resolves when a node receives a message
|
|
342
|
+
*/
|
|
343
|
+
waitForMessage: (node, timeout = 1000) => {
|
|
344
|
+
return new Promise((resolve, reject) => {
|
|
345
|
+
const timer = setTimeout(() => {
|
|
346
|
+
reject(new Error('Timeout waiting for message'));
|
|
347
|
+
}, timeout);
|
|
348
|
+
|
|
349
|
+
node.on('input', (msg) => {
|
|
350
|
+
clearTimeout(timer);
|
|
351
|
+
resolve(msg);
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
},
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Verify that a message has expected properties
|
|
358
|
+
*/
|
|
359
|
+
verifyMessage: (msg, expectedProps = {}) => {
|
|
360
|
+
const should = require('should');
|
|
361
|
+
should.exist(msg);
|
|
362
|
+
|
|
363
|
+
Object.keys(expectedProps).forEach(prop => {
|
|
364
|
+
if (expectedProps[prop] !== undefined) {
|
|
365
|
+
msg.should.have.property(prop, expectedProps[prop]);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
module.exports = {
|
|
372
|
+
createMockImap,
|
|
373
|
+
createMockMailparser,
|
|
374
|
+
createMockNodeRED,
|
|
375
|
+
setupModuleMocks,
|
|
376
|
+
testConfigs,
|
|
377
|
+
testFlows,
|
|
378
|
+
testUtils
|
|
379
|
+
};
|
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
const should = require('should');
|
|
2
|
+
const helper = require('node-red-node-test-helper');
|
|
3
|
+
|
|
4
|
+
describe('Email Receiver Node - Integration Tests', function() {
|
|
5
|
+
// Set a reasonable timeout for integration tests
|
|
6
|
+
this.timeout(10000);
|
|
7
|
+
|
|
8
|
+
let emailReceiverNode;
|
|
9
|
+
let originalLoad;
|
|
10
|
+
|
|
11
|
+
before(function(done) {
|
|
12
|
+
// Set up mocks for dependencies before loading the node
|
|
13
|
+
setupMocks();
|
|
14
|
+
|
|
15
|
+
// Load the node with mocked dependencies
|
|
16
|
+
emailReceiverNode = require('../../email-receiver/email-receiver.js');
|
|
17
|
+
|
|
18
|
+
// CRITICAL: Initialize the helper with Node-RED
|
|
19
|
+
helper.init(require.resolve('node-red'));
|
|
20
|
+
done();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
after(function() {
|
|
24
|
+
// Restore original module loading
|
|
25
|
+
if (originalLoad) {
|
|
26
|
+
const Module = require('module');
|
|
27
|
+
Module._load = originalLoad;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
beforeEach(function(done) {
|
|
32
|
+
helper.startServer(done);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
afterEach(function(done) {
|
|
36
|
+
helper.unload();
|
|
37
|
+
helper.stopServer(done);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
function setupMocks() {
|
|
41
|
+
// Create mock IMAP module
|
|
42
|
+
const mockImap = function(config) {
|
|
43
|
+
this.config = config;
|
|
44
|
+
this.connect = () => {
|
|
45
|
+
// Simulate a successful connection by immediately emitting 'ready'
|
|
46
|
+
if (this.events && this.events.ready) {
|
|
47
|
+
this.events.ready();
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
this.openBox = (folder, readOnly, callback) => {
|
|
51
|
+
callback(null, { messages: { total: 1 } });
|
|
52
|
+
};
|
|
53
|
+
this.search = (criteria, callback) => {
|
|
54
|
+
callback(null, [123]);
|
|
55
|
+
};
|
|
56
|
+
this.fetch = (results, options) => {
|
|
57
|
+
return {
|
|
58
|
+
on: (event, cb) => {
|
|
59
|
+
if (event === 'message') {
|
|
60
|
+
cb({ on: (e, bodyCb) => { if (e === 'body') bodyCb({}); } });
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
once: (event, cb) => {
|
|
64
|
+
if (event === 'end') { cb(); }
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
this.end = () => {};
|
|
69
|
+
this.once = (event, callback) => {
|
|
70
|
+
if (!this.events) this.events = {};
|
|
71
|
+
this.events[event] = callback;
|
|
72
|
+
};
|
|
73
|
+
return this;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Create mock mailparser module
|
|
77
|
+
const mockMailparser = {
|
|
78
|
+
simpleParser: function() {
|
|
79
|
+
return Promise.resolve({
|
|
80
|
+
subject: 'test integration email',
|
|
81
|
+
text: 'test integration body',
|
|
82
|
+
html: '<p>test integration</p>',
|
|
83
|
+
from: { text: 'integration@test.com' },
|
|
84
|
+
date: new Date(),
|
|
85
|
+
headers: new Map(),
|
|
86
|
+
attachments: []
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const mockModules = {
|
|
92
|
+
'node-imap': mockImap,
|
|
93
|
+
'mailparser': mockMailparser
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Override require to use mocks
|
|
97
|
+
const Module = require('module');
|
|
98
|
+
originalLoad = Module._load;
|
|
99
|
+
Module._load = function(request, parent) {
|
|
100
|
+
if (mockModules[request]) {
|
|
101
|
+
return mockModules[request];
|
|
102
|
+
}
|
|
103
|
+
return originalLoad.apply(this, arguments);
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
describe('Node Loading', function() {
|
|
108
|
+
it('should load in Node-RED test environment', function(done) {
|
|
109
|
+
// ARRANGE: Set up Node-RED flow with proper configuration
|
|
110
|
+
const flow = [
|
|
111
|
+
{
|
|
112
|
+
id: "n1",
|
|
113
|
+
type: "email-receiver",
|
|
114
|
+
name: "test node",
|
|
115
|
+
host: "imap.test.com",
|
|
116
|
+
hostType: "str",
|
|
117
|
+
port: "993",
|
|
118
|
+
portType: "str",
|
|
119
|
+
tls: true,
|
|
120
|
+
tlsType: "bool",
|
|
121
|
+
user: "test@example.com",
|
|
122
|
+
userType: "str",
|
|
123
|
+
password: "testpass",
|
|
124
|
+
passwordType: "str",
|
|
125
|
+
folder: "INBOX",
|
|
126
|
+
folderType: "str",
|
|
127
|
+
markseen: true,
|
|
128
|
+
markseenType: "bool"
|
|
129
|
+
}
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
// ACT: Load the node in the test helper environment
|
|
133
|
+
helper.load(emailReceiverNode, flow, function() {
|
|
134
|
+
try {
|
|
135
|
+
// ASSERT: Verify the node loaded correctly
|
|
136
|
+
const n1 = helper.getNode("n1");
|
|
137
|
+
should.exist(n1);
|
|
138
|
+
n1.should.have.property('name', 'test node');
|
|
139
|
+
n1.should.have.property('type', 'email-receiver');
|
|
140
|
+
done();
|
|
141
|
+
} catch (err) {
|
|
142
|
+
done(err);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should load with minimal configuration', function(done) {
|
|
148
|
+
// ARRANGE: Set up minimal flow configuration
|
|
149
|
+
const flow = [
|
|
150
|
+
{
|
|
151
|
+
id: "n1",
|
|
152
|
+
type: "email-receiver",
|
|
153
|
+
host: "imap.minimal.com",
|
|
154
|
+
hostType: "str",
|
|
155
|
+
port: "993",
|
|
156
|
+
portType: "str",
|
|
157
|
+
user: "minimal@test.com",
|
|
158
|
+
userType: "str",
|
|
159
|
+
password: "minimalpass",
|
|
160
|
+
passwordType: "str",
|
|
161
|
+
folder: "INBOX",
|
|
162
|
+
folderType: "str"
|
|
163
|
+
}
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
// ACT: Load the node
|
|
167
|
+
helper.load(emailReceiverNode, flow, function() {
|
|
168
|
+
try {
|
|
169
|
+
// ASSERT: Verify the node loaded with minimal config
|
|
170
|
+
const n1 = helper.getNode("n1");
|
|
171
|
+
should.exist(n1);
|
|
172
|
+
n1.should.have.property('type', 'email-receiver');
|
|
173
|
+
done();
|
|
174
|
+
} catch (err) {
|
|
175
|
+
done(err);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe('Node Connections', function() {
|
|
182
|
+
it('should create wired connections correctly', function(done) {
|
|
183
|
+
// ARRANGE: Set up flow with helper node to catch output
|
|
184
|
+
const flow = [
|
|
185
|
+
{
|
|
186
|
+
id: "n1",
|
|
187
|
+
type: "email-receiver",
|
|
188
|
+
name: "test node",
|
|
189
|
+
host: "imap.test.com",
|
|
190
|
+
hostType: "str",
|
|
191
|
+
port: "993",
|
|
192
|
+
portType: "str",
|
|
193
|
+
tls: true,
|
|
194
|
+
tlsType: "bool",
|
|
195
|
+
user: "test@example.com",
|
|
196
|
+
userType: "str",
|
|
197
|
+
password: "testpass",
|
|
198
|
+
passwordType: "str",
|
|
199
|
+
folder: "INBOX",
|
|
200
|
+
folderType: "str",
|
|
201
|
+
markseen: true,
|
|
202
|
+
markseenType: "bool",
|
|
203
|
+
wires: [["n2"]]
|
|
204
|
+
},
|
|
205
|
+
{ id: "n2", type: "helper" }
|
|
206
|
+
];
|
|
207
|
+
|
|
208
|
+
// ACT: Load nodes and verify connections
|
|
209
|
+
helper.load(emailReceiverNode, flow, function() {
|
|
210
|
+
try {
|
|
211
|
+
const n1 = helper.getNode("n1");
|
|
212
|
+
const n2 = helper.getNode("n2");
|
|
213
|
+
|
|
214
|
+
// ASSERT: Both nodes should exist and be connected
|
|
215
|
+
should.exist(n1);
|
|
216
|
+
should.exist(n2);
|
|
217
|
+
n1.should.have.property('name', 'test node');
|
|
218
|
+
n2.should.have.property('type', 'helper');
|
|
219
|
+
|
|
220
|
+
done();
|
|
221
|
+
} catch (err) {
|
|
222
|
+
done(err);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('should handle multiple output connections', function(done) {
|
|
228
|
+
// ARRANGE: Set up flow with multiple helper nodes
|
|
229
|
+
const flow = [
|
|
230
|
+
{
|
|
231
|
+
id: "n1",
|
|
232
|
+
type: "email-receiver",
|
|
233
|
+
name: "multi-output node",
|
|
234
|
+
host: "imap.test.com",
|
|
235
|
+
hostType: "str",
|
|
236
|
+
port: "993",
|
|
237
|
+
portType: "str",
|
|
238
|
+
user: "test@example.com",
|
|
239
|
+
userType: "str",
|
|
240
|
+
password: "testpass",
|
|
241
|
+
passwordType: "str",
|
|
242
|
+
folder: "INBOX",
|
|
243
|
+
folderType: "str",
|
|
244
|
+
markseen: true,
|
|
245
|
+
markseenType: "bool",
|
|
246
|
+
wires: [["n2", "n3"]]
|
|
247
|
+
},
|
|
248
|
+
{ id: "n2", type: "helper" },
|
|
249
|
+
{ id: "n3", type: "helper" }
|
|
250
|
+
];
|
|
251
|
+
|
|
252
|
+
// ACT: Load nodes
|
|
253
|
+
helper.load(emailReceiverNode, flow, function() {
|
|
254
|
+
try {
|
|
255
|
+
const n1 = helper.getNode("n1");
|
|
256
|
+
const n2 = helper.getNode("n2");
|
|
257
|
+
const n3 = helper.getNode("n3");
|
|
258
|
+
|
|
259
|
+
// ASSERT: All nodes should exist
|
|
260
|
+
should.exist(n1);
|
|
261
|
+
should.exist(n2);
|
|
262
|
+
should.exist(n3);
|
|
263
|
+
n1.should.have.property('name', 'multi-output node');
|
|
264
|
+
|
|
265
|
+
done();
|
|
266
|
+
} catch (err) {
|
|
267
|
+
done(err);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
describe('Message Flow', function() {
|
|
274
|
+
it('should handle input without crashing', function(done) {
|
|
275
|
+
// ARRANGE: Set up minimal flow
|
|
276
|
+
const flow = [
|
|
277
|
+
{
|
|
278
|
+
id: "n1",
|
|
279
|
+
type: "email-receiver",
|
|
280
|
+
name: "test node",
|
|
281
|
+
host: "imap.test.com",
|
|
282
|
+
hostType: "str",
|
|
283
|
+
port: "993",
|
|
284
|
+
portType: "str",
|
|
285
|
+
tls: true,
|
|
286
|
+
tlsType: "bool",
|
|
287
|
+
user: "test@example.com",
|
|
288
|
+
userType: "str",
|
|
289
|
+
password: "testpass",
|
|
290
|
+
passwordType: "str",
|
|
291
|
+
folder: "INBOX",
|
|
292
|
+
folderType: "str",
|
|
293
|
+
markseen: true,
|
|
294
|
+
markseenType: "bool"
|
|
295
|
+
}
|
|
296
|
+
];
|
|
297
|
+
|
|
298
|
+
// ACT: Load node and send input
|
|
299
|
+
helper.load(emailReceiverNode, flow, function() {
|
|
300
|
+
try {
|
|
301
|
+
const n1 = helper.getNode("n1");
|
|
302
|
+
should.exist(n1);
|
|
303
|
+
|
|
304
|
+
// Send input - this should not crash due to mocked IMAP
|
|
305
|
+
n1.receive({ payload: "test input" });
|
|
306
|
+
|
|
307
|
+
// ASSERT: If we reach here, the node handled input gracefully
|
|
308
|
+
setTimeout(() => {
|
|
309
|
+
done(); // Success if no errors thrown
|
|
310
|
+
}, 500);
|
|
311
|
+
|
|
312
|
+
} catch (err) {
|
|
313
|
+
done(err);
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it('should process messages through connected nodes', function(done) {
|
|
319
|
+
// ARRANGE: Set up flow with helper to capture output
|
|
320
|
+
const flow = [
|
|
321
|
+
{
|
|
322
|
+
id: "n1",
|
|
323
|
+
type: "email-receiver",
|
|
324
|
+
name: "sender node",
|
|
325
|
+
host: "imap.test.com",
|
|
326
|
+
hostType: "str",
|
|
327
|
+
port: "993",
|
|
328
|
+
portType: "str",
|
|
329
|
+
user: "test@example.com",
|
|
330
|
+
userType: "str",
|
|
331
|
+
password: "testpass",
|
|
332
|
+
passwordType: "str",
|
|
333
|
+
folder: "INBOX",
|
|
334
|
+
folderType: "str",
|
|
335
|
+
markseen: true,
|
|
336
|
+
markseenType: "bool",
|
|
337
|
+
wires: [["n2"]]
|
|
338
|
+
},
|
|
339
|
+
{ id: "n2", type: "helper" }
|
|
340
|
+
];
|
|
341
|
+
|
|
342
|
+
// ACT: Load nodes and set up message listener
|
|
343
|
+
helper.load(emailReceiverNode, flow, function() {
|
|
344
|
+
try {
|
|
345
|
+
const n1 = helper.getNode("n1");
|
|
346
|
+
const n2 = helper.getNode("n2");
|
|
347
|
+
|
|
348
|
+
// Set up listener for messages from email receiver
|
|
349
|
+
n2.on("input", function(msg) {
|
|
350
|
+
try {
|
|
351
|
+
// ASSERT: Should receive a message with expected properties
|
|
352
|
+
should.exist(msg);
|
|
353
|
+
should.exist(msg.payload);
|
|
354
|
+
msg.payload.should.equal('test integration body');
|
|
355
|
+
done();
|
|
356
|
+
} catch (err) {
|
|
357
|
+
done(err);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// Trigger the email receiver
|
|
362
|
+
n1.receive({ payload: "trigger" });
|
|
363
|
+
|
|
364
|
+
} catch (err) {
|
|
365
|
+
done(err);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
describe('Configuration Validation', function() {
|
|
372
|
+
it('should handle invalid configuration gracefully', function(done) {
|
|
373
|
+
// ARRANGE: Set up flow with missing required config
|
|
374
|
+
const flow = [
|
|
375
|
+
{
|
|
376
|
+
id: "n1",
|
|
377
|
+
type: "email-receiver",
|
|
378
|
+
name: "invalid config node",
|
|
379
|
+
host: "", // Missing host
|
|
380
|
+
hostType: "str",
|
|
381
|
+
port: "993",
|
|
382
|
+
portType: "str",
|
|
383
|
+
user: "test@example.com",
|
|
384
|
+
userType: "str",
|
|
385
|
+
password: "testpass",
|
|
386
|
+
passwordType: "str",
|
|
387
|
+
folder: "INBOX",
|
|
388
|
+
folderType: "str"
|
|
389
|
+
}
|
|
390
|
+
];
|
|
391
|
+
|
|
392
|
+
// ACT: Load node with invalid config
|
|
393
|
+
helper.load(emailReceiverNode, flow, function() {
|
|
394
|
+
try {
|
|
395
|
+
const n1 = helper.getNode("n1");
|
|
396
|
+
should.exist(n1);
|
|
397
|
+
|
|
398
|
+
// ASSERT: Node should exist but handle invalid config appropriately
|
|
399
|
+
// Send input to trigger validation
|
|
400
|
+
n1.receive({ payload: "test" });
|
|
401
|
+
|
|
402
|
+
// If we get here without crashing, the validation worked
|
|
403
|
+
setTimeout(() => {
|
|
404
|
+
done();
|
|
405
|
+
}, 300);
|
|
406
|
+
|
|
407
|
+
} catch (err) {
|
|
408
|
+
done(err);
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('should load with different folder configurations', function(done) {
|
|
414
|
+
// ARRANGE: Set up flow with array folder config
|
|
415
|
+
const flow = [
|
|
416
|
+
{
|
|
417
|
+
id: "n1",
|
|
418
|
+
type: "email-receiver",
|
|
419
|
+
name: "array folder node",
|
|
420
|
+
host: "imap.test.com",
|
|
421
|
+
hostType: "str",
|
|
422
|
+
port: "993",
|
|
423
|
+
portType: "str",
|
|
424
|
+
user: "test@example.com",
|
|
425
|
+
userType: "str",
|
|
426
|
+
password: "testpass",
|
|
427
|
+
passwordType: "str",
|
|
428
|
+
folder: ["INBOX", "Sent", "Drafts"],
|
|
429
|
+
folderType: "json",
|
|
430
|
+
markseen: true,
|
|
431
|
+
markseenType: "bool"
|
|
432
|
+
}
|
|
433
|
+
];
|
|
434
|
+
|
|
435
|
+
// ACT: Load node with array folder config
|
|
436
|
+
helper.load(emailReceiverNode, flow, function() {
|
|
437
|
+
try {
|
|
438
|
+
const n1 = helper.getNode("n1");
|
|
439
|
+
|
|
440
|
+
// ASSERT: Node should load successfully with array config
|
|
441
|
+
should.exist(n1);
|
|
442
|
+
n1.should.have.property('name', 'array folder node');
|
|
443
|
+
done();
|
|
444
|
+
|
|
445
|
+
} catch (err) {
|
|
446
|
+
done(err);
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
describe('Node Lifecycle', function() {
|
|
453
|
+
it('should clean up properly on unload', function(done) {
|
|
454
|
+
// ARRANGE: Set up flow
|
|
455
|
+
const flow = [
|
|
456
|
+
{
|
|
457
|
+
id: "n1",
|
|
458
|
+
type: "email-receiver",
|
|
459
|
+
name: "cleanup test node",
|
|
460
|
+
host: "imap.test.com",
|
|
461
|
+
hostType: "str",
|
|
462
|
+
port: "993",
|
|
463
|
+
portType: "str",
|
|
464
|
+
user: "test@example.com",
|
|
465
|
+
userType: "str",
|
|
466
|
+
password: "testpass",
|
|
467
|
+
passwordType: "str",
|
|
468
|
+
folder: "INBOX",
|
|
469
|
+
folderType: "str"
|
|
470
|
+
}
|
|
471
|
+
];
|
|
472
|
+
|
|
473
|
+
// ACT: Load and then unload the node
|
|
474
|
+
helper.load(emailReceiverNode, flow, function() {
|
|
475
|
+
try {
|
|
476
|
+
const n1 = helper.getNode("n1");
|
|
477
|
+
should.exist(n1);
|
|
478
|
+
|
|
479
|
+
// Simulate some activity
|
|
480
|
+
n1.receive({ payload: "test" });
|
|
481
|
+
|
|
482
|
+
// ASSERT: Unloading should not throw errors
|
|
483
|
+
helper.unload();
|
|
484
|
+
done();
|
|
485
|
+
|
|
486
|
+
} catch (err) {
|
|
487
|
+
done(err);
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const should = require('should');
|
|
2
2
|
|
|
3
|
-
describe('Email Receiver Node', function() {
|
|
3
|
+
describe('Email Receiver Node - Unit Tests', function() {
|
|
4
4
|
// Set a reasonable timeout
|
|
5
5
|
this.timeout(10000);
|
|
6
6
|
|
|
@@ -72,7 +72,7 @@ describe('Email Receiver Node', function() {
|
|
|
72
72
|
};
|
|
73
73
|
|
|
74
74
|
// Load the node with mocked dependencies
|
|
75
|
-
emailReceiverNode = require('
|
|
75
|
+
emailReceiverNode = require('../../email-receiver/email-receiver.js');
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
after(function() {
|
|
@@ -83,7 +83,7 @@ describe('Email Receiver Node', function() {
|
|
|
83
83
|
}
|
|
84
84
|
});
|
|
85
85
|
|
|
86
|
-
describe('
|
|
86
|
+
describe('Module Export', function() {
|
|
87
87
|
it('should export a function', function() {
|
|
88
88
|
// ARRANGE: Node module is already loaded
|
|
89
89
|
|
|
@@ -92,7 +92,9 @@ describe('Email Receiver Node', function() {
|
|
|
92
92
|
// ASSERT: Should be a function
|
|
93
93
|
emailReceiverNode.should.be.type('function');
|
|
94
94
|
});
|
|
95
|
+
});
|
|
95
96
|
|
|
97
|
+
describe('Node Registration', function() {
|
|
96
98
|
it('should register node type without errors', function() {
|
|
97
99
|
// ARRANGE: Set up mock RED object and capture registration calls
|
|
98
100
|
let registeredType;
|
|
@@ -132,8 +134,10 @@ describe('Email Receiver Node', function() {
|
|
|
132
134
|
registeredType.should.equal('email-receiver');
|
|
133
135
|
registeredConstructor.should.be.type('function');
|
|
134
136
|
});
|
|
137
|
+
});
|
|
135
138
|
|
|
136
|
-
|
|
139
|
+
describe('Node Instantiation', function() {
|
|
140
|
+
it('should handle node instantiation with valid config', function() {
|
|
137
141
|
// ARRANGE: Set up mock RED object and node instance tracking
|
|
138
142
|
let nodeInstance;
|
|
139
143
|
|
|
@@ -190,46 +194,11 @@ describe('Email Receiver Node', function() {
|
|
|
190
194
|
should.exist(nodeInstance);
|
|
191
195
|
nodeInstance.should.have.property('name', 'Test Email Receiver');
|
|
192
196
|
});
|
|
197
|
+
});
|
|
193
198
|
|
|
194
|
-
|
|
195
|
-
// ARRANGE: Mock the Node-RED and IMAP environment
|
|
196
|
-
let nodeInstance;
|
|
197
|
-
let inputCallback;
|
|
198
|
-
const mockRED = {
|
|
199
|
-
nodes: {
|
|
200
|
-
createNode: function(node, config) {
|
|
201
|
-
nodeInstance = node;
|
|
202
|
-
node.on = (event, callback) => { if (event === 'input') inputCallback = callback; };
|
|
203
|
-
node.status = () => {};
|
|
204
|
-
node.error = () => {};
|
|
205
|
-
node.send = (msg) => {
|
|
206
|
-
should.exist(msg);
|
|
207
|
-
msg.payload.should.equal('test body');
|
|
208
|
-
done();
|
|
209
|
-
};
|
|
210
|
-
return node;
|
|
211
|
-
},
|
|
212
|
-
registerType: (type, constructor) => {
|
|
213
|
-
new constructor({
|
|
214
|
-
host: "imap.test.com", hostType: "str",
|
|
215
|
-
port: 993, portType: "num",
|
|
216
|
-
user: "test@test.com", userType: "str",
|
|
217
|
-
password: "testpass", passwordType: "str",
|
|
218
|
-
folder: "INBOX, Spam, Sent", folderType: 'str',
|
|
219
|
-
markseen: true, markseenType: 'bool'
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
},
|
|
223
|
-
util: { evaluateNodeProperty: (value) => value },
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
// ACT: Register the node, then simulate input
|
|
227
|
-
emailReceiverNode(mockRED);
|
|
228
|
-
inputCallback({});
|
|
229
|
-
});
|
|
230
|
-
|
|
199
|
+
describe('Folder Configuration', function() {
|
|
231
200
|
it('should handle an array of folders', function(done) {
|
|
232
|
-
// ARRANGE: Mock the Node-RED
|
|
201
|
+
// ARRANGE: Mock the Node-RED environment
|
|
233
202
|
let nodeInstance;
|
|
234
203
|
let inputCallback;
|
|
235
204
|
const mockRED = {
|
|
@@ -264,7 +233,9 @@ describe('Email Receiver Node', function() {
|
|
|
264
233
|
emailReceiverNode(mockRED);
|
|
265
234
|
inputCallback({});
|
|
266
235
|
});
|
|
236
|
+
});
|
|
267
237
|
|
|
238
|
+
describe('Error Handling', function() {
|
|
268
239
|
it('should call node.error for invalid folder type', function(done) {
|
|
269
240
|
// ARRANGE: Mock the node instance to capture errors
|
|
270
241
|
let errorCalled = false;
|
|
@@ -301,7 +272,7 @@ describe('Email Receiver Node', function() {
|
|
|
301
272
|
host: "imap.test.com", hostType: "str",
|
|
302
273
|
port: 993, portType: "num",
|
|
303
274
|
user: "test@test.com", userType: "str",
|
|
304
|
-
password: "", passwordType: "str",
|
|
275
|
+
password: "", passwordType: "str", // Empty password should trigger error
|
|
305
276
|
folder: "INBOX", folderType: "str"
|
|
306
277
|
},
|
|
307
278
|
on: (event, callback) => { if (event === 'input') nodeInstance.inputCallback = callback; },
|
|
@@ -326,152 +297,4 @@ describe('Email Receiver Node', function() {
|
|
|
326
297
|
nodeInstance.inputCallback({});
|
|
327
298
|
});
|
|
328
299
|
});
|
|
329
|
-
|
|
330
|
-
describe('Integration Tests with Node-RED Helper', function() {
|
|
331
|
-
const helper = require('node-red-node-test-helper');
|
|
332
|
-
|
|
333
|
-
// CRITICAL: Initialize the helper with Node-RED
|
|
334
|
-
before(function(done) {
|
|
335
|
-
// This is the missing piece that was causing the clearRegistry error
|
|
336
|
-
helper.init(require.resolve('node-red'));
|
|
337
|
-
done();
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
beforeEach(function(done) {
|
|
341
|
-
helper.startServer(done);
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
afterEach(function(done) {
|
|
345
|
-
helper.unload();
|
|
346
|
-
helper.stopServer(done);
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
it('should load in Node-RED test environment', function(done) {
|
|
350
|
-
// ARRANGE: Set up Node-RED flow with proper configuration
|
|
351
|
-
const flow = [
|
|
352
|
-
{
|
|
353
|
-
id: "n1",
|
|
354
|
-
type: "email-receiver",
|
|
355
|
-
name: "test node",
|
|
356
|
-
host: "imap.test.com",
|
|
357
|
-
hostType: "str",
|
|
358
|
-
port: "993",
|
|
359
|
-
portType: "str",
|
|
360
|
-
tls: true,
|
|
361
|
-
tlsType: "bool",
|
|
362
|
-
user: "test@example.com",
|
|
363
|
-
userType: "str",
|
|
364
|
-
password: "testpass",
|
|
365
|
-
passwordType: "str",
|
|
366
|
-
folder: "INBOX",
|
|
367
|
-
folderType: "str",
|
|
368
|
-
markseen: true,
|
|
369
|
-
markseenType: "bool"
|
|
370
|
-
}
|
|
371
|
-
];
|
|
372
|
-
|
|
373
|
-
// ACT: Load the node in the test helper environment
|
|
374
|
-
helper.load(emailReceiverNode, flow, function() {
|
|
375
|
-
try {
|
|
376
|
-
// ASSERT: Verify the node loaded correctly
|
|
377
|
-
const n1 = helper.getNode("n1");
|
|
378
|
-
should.exist(n1);
|
|
379
|
-
n1.should.have.property('name', 'test node');
|
|
380
|
-
n1.should.have.property('type', 'email-receiver');
|
|
381
|
-
done();
|
|
382
|
-
} catch (err) {
|
|
383
|
-
done(err);
|
|
384
|
-
}
|
|
385
|
-
});
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
it('should create wired connections correctly', function(done) {
|
|
389
|
-
// ARRANGE: Set up flow with helper node to catch output
|
|
390
|
-
const flow = [
|
|
391
|
-
{
|
|
392
|
-
id: "n1",
|
|
393
|
-
type: "email-receiver",
|
|
394
|
-
name: "test node",
|
|
395
|
-
host: "imap.test.com",
|
|
396
|
-
hostType: "str",
|
|
397
|
-
port: "993",
|
|
398
|
-
portType: "str",
|
|
399
|
-
tls: true,
|
|
400
|
-
tlsType: "bool",
|
|
401
|
-
user: "test@example.com",
|
|
402
|
-
userType: "str",
|
|
403
|
-
password: "testpass",
|
|
404
|
-
passwordType: "str",
|
|
405
|
-
folder: "INBOX",
|
|
406
|
-
folderType: "str",
|
|
407
|
-
markseen: true,
|
|
408
|
-
markseenType: "bool",
|
|
409
|
-
wires: [["n2"]]
|
|
410
|
-
},
|
|
411
|
-
{ id: "n2", type: "helper" }
|
|
412
|
-
];
|
|
413
|
-
|
|
414
|
-
// ACT: Load nodes and verify connections
|
|
415
|
-
helper.load(emailReceiverNode, flow, function() {
|
|
416
|
-
try {
|
|
417
|
-
const n1 = helper.getNode("n1");
|
|
418
|
-
const n2 = helper.getNode("n2");
|
|
419
|
-
|
|
420
|
-
// ASSERT: Both nodes should exist and be connected
|
|
421
|
-
should.exist(n1);
|
|
422
|
-
should.exist(n2);
|
|
423
|
-
n1.should.have.property('name', 'test node');
|
|
424
|
-
n2.should.have.property('type', 'helper');
|
|
425
|
-
|
|
426
|
-
done();
|
|
427
|
-
} catch (err) {
|
|
428
|
-
done(err);
|
|
429
|
-
}
|
|
430
|
-
});
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
it('should handle input without crashing', function(done) {
|
|
434
|
-
// ARRANGE: Set up minimal flow
|
|
435
|
-
const flow = [
|
|
436
|
-
{
|
|
437
|
-
id: "n1",
|
|
438
|
-
type: "email-receiver",
|
|
439
|
-
name: "test node",
|
|
440
|
-
host: "imap.test.com",
|
|
441
|
-
hostType: "str",
|
|
442
|
-
port: "993",
|
|
443
|
-
portType: "str",
|
|
444
|
-
tls: true,
|
|
445
|
-
tlsType: "bool",
|
|
446
|
-
user: "test@example.com",
|
|
447
|
-
userType: "str",
|
|
448
|
-
password: "testpass",
|
|
449
|
-
passwordType: "str",
|
|
450
|
-
folder: "INBOX",
|
|
451
|
-
folderType: "str",
|
|
452
|
-
markseen: true,
|
|
453
|
-
markseenType: "bool"
|
|
454
|
-
}
|
|
455
|
-
];
|
|
456
|
-
|
|
457
|
-
// ACT: Load node and send input
|
|
458
|
-
helper.load(emailReceiverNode, flow, function() {
|
|
459
|
-
try {
|
|
460
|
-
const n1 = helper.getNode("n1");
|
|
461
|
-
should.exist(n1);
|
|
462
|
-
|
|
463
|
-
// Send input - this should not crash due to mocked IMAP
|
|
464
|
-
n1.receive({ payload: "test input" });
|
|
465
|
-
|
|
466
|
-
// ASSERT: If we reach here, the node handled input gracefully
|
|
467
|
-
setTimeout(() => {
|
|
468
|
-
done(); // Success if no errors thrown
|
|
469
|
-
}, 500);
|
|
470
|
-
|
|
471
|
-
} catch (err) {
|
|
472
|
-
done(err);
|
|
473
|
-
}
|
|
474
|
-
});
|
|
475
|
-
});
|
|
476
|
-
});
|
|
477
|
-
});
|
|
300
|
+
});
|