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