@5minds/node-red-contrib-processcube-tools 1.0.1 → 1.0.2-develop-e3d5d9-mfm8oea8

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.
@@ -0,0 +1,239 @@
1
+ const { expect } = require('chai');
2
+ const helper = require('node-red-node-test-helper');
3
+ const {
4
+ setupModuleMocks,
5
+ emailSenderConfigs,
6
+ testFlows,
7
+ testUtils,
8
+ } = require('../../test/helpers/email-sender.mocks.js');
9
+
10
+ describe('E-Mail Sender Node - Integration Tests', function () {
11
+ // Set a reasonable timeout for integration tests
12
+ this.timeout(10000);
13
+
14
+ let emailSenderNode;
15
+ let cleanupMocks;
16
+
17
+ beforeEach(function (done) {
18
+ // Set up mocks using helper
19
+ cleanupMocks = setupModuleMocks();
20
+
21
+ // Load the node with mocked dependencies
22
+ emailSenderNode = require('../../email-sender/email-sender.js');
23
+
24
+ // CRITICAL: Initialize the helper with Node-RED
25
+ helper.init(require.resolve('node-red'));
26
+ done();
27
+ });
28
+
29
+ afterEach(function () {
30
+ // Clean up mocks using helper cleanup function
31
+ if (cleanupMocks) {
32
+ cleanupMocks();
33
+ }
34
+ });
35
+
36
+ beforeEach(function (done) {
37
+ helper.startServer(done);
38
+ });
39
+
40
+ afterEach(function (done) {
41
+ helper.unload();
42
+ helper.stopServer(done);
43
+ });
44
+
45
+ describe('Node Loading', function () {
46
+ it('should load in Node-RED test environment', function (done) {
47
+ // ARRANGE: Use test flow from helpers
48
+ const flow = [emailSenderConfigs.valid];
49
+
50
+ // ACT: Load the node in the test helper environment
51
+ helper.load(emailSenderNode, flow, function () {
52
+ try {
53
+ // ASSERT: Verify the node loaded correctly
54
+ const n1 = helper.getNode(emailSenderConfigs.valid.id);
55
+ expect(n1).to.exist;
56
+ expect(n1).to.have.property('name', emailSenderConfigs.valid.name);
57
+ expect(n1).to.have.property('type', 'email-sender');
58
+ done();
59
+ } catch (err) {
60
+ done(err);
61
+ }
62
+ });
63
+ });
64
+
65
+ it('should load with minimal configuration', function (done) {
66
+ // ARRANGE: Use minimal test config from helpers
67
+ const flow = [emailSenderConfigs.minimal];
68
+
69
+ // ACT: Load the node
70
+ helper.load(emailSenderNode, flow, function () {
71
+ try {
72
+ // ASSERT: Verify the node loaded with minimal config
73
+ const n1 = helper.getNode(emailSenderConfigs.minimal.id);
74
+ expect(n1).to.exist;
75
+ expect(n1).to.have.property('type', 'email-sender');
76
+ done();
77
+ } catch (err) {
78
+ done(err);
79
+ }
80
+ });
81
+ });
82
+ });
83
+
84
+ describe('Node Connections', function () {
85
+ it('should create wired connections correctly', function (done) {
86
+ // ARRANGE: Use connected test flow from helpers
87
+ const flow = testFlows.connected;
88
+
89
+ // ACT: Load nodes and verify connections
90
+ helper.load(emailSenderNode, flow, function () {
91
+ try {
92
+ const n1 = helper.getNode(emailSenderConfigs.valid.id);
93
+ const h1 = helper.getNode('h1');
94
+
95
+ // ASSERT: Both nodes should exist and be connected
96
+ expect(n1).to.exist;
97
+ expect(h1).to.exist;
98
+ expect(n1).to.have.property('name', emailSenderConfigs.valid.name);
99
+ expect(h1).to.have.property('type', 'helper');
100
+
101
+ done();
102
+ } catch (err) {
103
+ done(err);
104
+ }
105
+ });
106
+ });
107
+
108
+ it('should handle multiple output connections', function (done) {
109
+ // ARRANGE: Use multi-output test flow from helpers
110
+ const flow = testFlows.multiOutput;
111
+
112
+ // ACT: Load nodes
113
+ helper.load(emailSenderNode, flow, function () {
114
+ try {
115
+ const n1 = helper.getNode(emailSenderConfigs.valid.id);
116
+ const h1 = helper.getNode('h1');
117
+ const h2 = helper.getNode('h2');
118
+
119
+ // ASSERT: All nodes should exist
120
+ expect(n1).to.exist;
121
+ expect(h1).to.exist;
122
+ expect(h2).to.exist;
123
+ expect(n1).to.have.property('name', emailSenderConfigs.valid.name);
124
+
125
+ done();
126
+ } catch (err) {
127
+ done(err);
128
+ }
129
+ });
130
+ });
131
+ });
132
+
133
+ describe('Message Flow', function () {
134
+ it('should handle input without crashing', async function () {
135
+ // ARRANGE: Use test flow from helpers
136
+ const flow = testFlows.single;
137
+
138
+ return new Promise((resolve, reject) => {
139
+ helper.load(emailSenderNode, flow, function () {
140
+ try {
141
+ const n1 = helper.getNode(emailSenderConfigs.valid.id);
142
+ expect(n1).to.exist;
143
+
144
+ // Send input - this should not crash due to mocked IMAP
145
+ n1.receive({ payload: 'test input' });
146
+
147
+ // ASSERT: If we reach here, the node handled input gracefully
148
+ testUtils.wait(500).then(() => {
149
+ resolve(); // Success if no errors thrown
150
+ });
151
+ } catch (err) {
152
+ reject(err);
153
+ }
154
+ });
155
+ });
156
+ });
157
+
158
+ it('should process messages through connected nodes', function (done) {
159
+ // ARRANGE: Use connected test flow from helpers
160
+ const flow = testFlows.connected;
161
+
162
+ // ACT: Load nodes and set up message listener
163
+ helper.load(emailSenderNode, flow, function () {
164
+ try {
165
+ const n1 = helper.getNode(emailSenderConfigs.valid.id);
166
+ const h1 = helper.getNode('h1');
167
+
168
+ // Set up listener for messages from email receiver
169
+ h1.on('input', function (msg) {
170
+ try {
171
+ // ASSERT: Should receive a message with expected properties
172
+ expect(msg).to.exist;
173
+ expect(msg.payload).to.exist;
174
+ done();
175
+ } catch (err) {
176
+ done(err);
177
+ }
178
+ });
179
+
180
+ // Simulate the email processing
181
+ // The email-sender node likely starts processing emails automatically
182
+ // Let's trigger the mock IMAP flow by simulating what happens when emails are found
183
+ setTimeout(() => {
184
+ // Simulate the email receiver processing emails and sending a message
185
+ // This is what your email-sender node should do internally
186
+ try {
187
+ const mockEmailMessage = {
188
+ payload: 'This is a mock email body for testing purposes.',
189
+ topic: 'email',
190
+ from: 'sender@test.com',
191
+ subject: 'Mock Email Subject',
192
+ };
193
+
194
+ // Directly send a message through the node (simulating internal processing)
195
+ n1.send(mockEmailMessage);
196
+ } catch (err) {
197
+ done(err);
198
+ }
199
+ }, 100);
200
+ } catch (err) {
201
+ done(err);
202
+ }
203
+ });
204
+ });
205
+
206
+ it('should handle message timeout gracefully', async function () {
207
+ // ARRANGE: Use connected test flow
208
+ const flow = testFlows.connected;
209
+
210
+ return new Promise((resolve, reject) => {
211
+ helper.load(emailSenderNode, flow, function () {
212
+ try {
213
+ const n1 = helper.getNode(emailSenderConfigs.valid.id);
214
+ const h1 = helper.getNode('h1');
215
+
216
+ // Use testUtils.waitForMessage with timeout
217
+ testUtils
218
+ .waitForMessage(h1, 1000)
219
+ .then((msg) => {
220
+ // ASSERT: Should receive message within timeout
221
+ expect(msg).to.exist;
222
+ resolve();
223
+ })
224
+ .catch((err) => {
225
+ // ASSERT: Should handle timeout appropriately
226
+ expect(err.message).to.include('Timeout waiting for message');
227
+ resolve(); // This is expected behavior for this test
228
+ });
229
+
230
+ // Don't trigger anything to test timeout behavior
231
+ // The timeout should occur as expected
232
+ } catch (err) {
233
+ reject(err);
234
+ }
235
+ });
236
+ });
237
+ });
238
+ });
239
+ });
@@ -0,0 +1,304 @@
1
+ const { expect } = require('chai');
2
+ const { createMockNodeRED, setupModuleMocks, testConfigs, testUtils } = require('../helpers/email-receiver.mocks.js');
3
+
4
+ describe('E-Mail Receiver Node - Unit Tests', function () {
5
+ this.timeout(10000);
6
+
7
+ let emailReceiverNode;
8
+ let cleanupMocks;
9
+
10
+ before(function () {
11
+ // Set up module mocks using helper
12
+ cleanupMocks = setupModuleMocks();
13
+
14
+ // Load the node with mocked dependencies
15
+ emailReceiverNode = require('../../email-receiver/email-receiver.js');
16
+ });
17
+
18
+ after(function () {
19
+ // Clean up mocks
20
+ if (cleanupMocks) {
21
+ cleanupMocks();
22
+ }
23
+ });
24
+
25
+ describe('Module Export', function () {
26
+ it('should export a function', function () {
27
+ expect(emailReceiverNode).to.be.a('function');
28
+ });
29
+ });
30
+
31
+ describe('Node Registration', function () {
32
+ it('should register node type without errors', function () {
33
+ // ARRANGE: Create mock Node-RED with tracking
34
+ const mockRED = createMockNodeRED();
35
+
36
+ // ACT: Register the node
37
+ emailReceiverNode(mockRED);
38
+
39
+ // ASSERT: Verify registration
40
+ expect(mockRED.nodes.lastRegisteredType).to.equal('email-receiver');
41
+ expect(mockRED.nodes.lastRegisteredConstructor).to.be.a('function');
42
+ });
43
+ });
44
+
45
+ describe('Node Instantiation', function () {
46
+ it('should handle node instantiation with valid config', function () {
47
+ // ARRANGE: Track node creation
48
+ let createdNode = null;
49
+ const mockRED = createMockNodeRED({
50
+ onHandler: function (event, callback) {
51
+ createdNode = this;
52
+ },
53
+ });
54
+
55
+ // ACT: Register and create node instance
56
+ emailReceiverNode(mockRED);
57
+ new mockRED.nodes.lastRegisteredConstructor(testConfigs.valid);
58
+
59
+ // ASSERT: Verify node was created with correct properties
60
+ expect(createdNode).to.exist;
61
+ expect(createdNode).to.have.property('name', testConfigs.valid.name);
62
+ expect(createdNode).to.have.property('id', testConfigs.valid.id);
63
+ });
64
+
65
+ it('should handle minimal config', function () {
66
+ // ARRANGE: Use minimal test config
67
+ let createdNode = null;
68
+ const mockRED = createMockNodeRED({
69
+ onHandler: function (event, callback) {
70
+ createdNode = this;
71
+ },
72
+ });
73
+
74
+ // ACT: Register and create node with minimal config
75
+ emailReceiverNode(mockRED);
76
+ new mockRED.nodes.lastRegisteredConstructor(testConfigs.minimal);
77
+
78
+ // ASSERT: Verify node creation
79
+ expect(createdNode).to.exist;
80
+ expect(createdNode).to.have.property('id', testConfigs.minimal.id);
81
+ });
82
+ });
83
+
84
+ describe('Folder Configuration', function () {
85
+ it('should handle array of folders', async function () {
86
+ // ARRANGE: Set up message tracking
87
+ let sentMessage = null;
88
+ const mockRED = createMockNodeRED({
89
+ sendHandler: function (msg) {
90
+ sentMessage = msg;
91
+ },
92
+ });
93
+
94
+ // ACT: Register node and create instance with array folders
95
+ emailReceiverNode(mockRED);
96
+ const nodeConstructor = mockRED.nodes.lastRegisteredConstructor;
97
+ const nodeInstance = new nodeConstructor(testConfigs.arrayFolders);
98
+
99
+ // Wait for processing
100
+ await testUtils.wait(50);
101
+
102
+ // ASSERT: Should handle array folders without error
103
+ expect(nodeInstance).to.exist;
104
+ expect(nodeInstance).to.have.property('name', testConfigs.arrayFolders.name);
105
+ });
106
+ });
107
+
108
+ describe('Error Handling', function () {
109
+ it('should call node.error for invalid folder type', function (done) {
110
+ // ARRANGE: Set up error tracking
111
+ const mockRED = createMockNodeRED({
112
+ onHandler: function (event, callback) {
113
+ if (event === 'input') {
114
+ this.inputCallback = callback;
115
+ }
116
+ },
117
+ errorHandler: function (err) {
118
+ // ASSERT: Should receive appropriate error message
119
+ expect(err).to.include("The 'folders' property must be an array of strings.");
120
+ done();
121
+ },
122
+ });
123
+
124
+ // ACT: Register node and create instance with invalid config
125
+ emailReceiverNode(mockRED);
126
+ const nodeConstructor = mockRED.nodes.lastRegisteredConstructor;
127
+ const nodeInstance = new nodeConstructor(testConfigs.invalidFolderType);
128
+
129
+ // Trigger the error by sending an input message
130
+ // Use a small delay to ensure the constructor has completed
131
+ setTimeout(() => {
132
+ if (nodeInstance.inputCallback) {
133
+ nodeInstance.inputCallback({ payload: 'test' });
134
+ } else {
135
+ done(new Error('inputCallback was not set on the node instance'));
136
+ }
137
+ }, 10);
138
+ });
139
+
140
+ it('should call node.error for missing config', function (done) {
141
+ // ARRANGE: Set up error and status tracking
142
+ let statusCalled = false;
143
+ const mockRED = createMockNodeRED({
144
+ onHandler: function (event, callback) {
145
+ if (event === 'input') {
146
+ this.inputCallback = callback;
147
+ }
148
+ },
149
+ statusHandler: function (status) {
150
+ statusCalled = true;
151
+ if (status.fill) {
152
+ expect(status.fill).to.equal('red');
153
+ }
154
+ },
155
+ errorHandler: function (err) {
156
+ // ASSERT: Should receive config error
157
+ expect(err).to.include('Missing required IMAP config');
158
+ expect(statusCalled).to.be.true;
159
+ done();
160
+ },
161
+ });
162
+
163
+ // ACT: Register node and create instance with invalid config
164
+ emailReceiverNode(mockRED);
165
+ const nodeConstructor = mockRED.nodes.lastRegisteredConstructor;
166
+ const nodeInstance = new nodeConstructor(testConfigs.invalidConfig);
167
+
168
+ // Trigger the error by sending an input message
169
+ // Use a small delay to ensure the constructor has completed
170
+ setTimeout(() => {
171
+ if (nodeInstance.inputCallback) {
172
+ nodeInstance.inputCallback({ payload: 'test' });
173
+ } else {
174
+ done(new Error('inputCallback was not set on the node instance'));
175
+ }
176
+ }, 10);
177
+ });
178
+
179
+ it('should handle connection errors gracefully', function (done) {
180
+ // ARRANGE: Set up connection error scenario with done() protection
181
+ let testCompleted = false;
182
+
183
+ const completeDone = () => {
184
+ if (!testCompleted) {
185
+ testCompleted = true;
186
+ done();
187
+ }
188
+ };
189
+
190
+ const mockRED = createMockNodeRED({
191
+ onHandler: function (event, callback) {
192
+ if (event === 'input') {
193
+ this.inputCallback = callback;
194
+ }
195
+ },
196
+ statusHandler: function (status) {
197
+ if (status.fill === 'red' && status.text && status.text.includes('error')) {
198
+ completeDone(); // Success - error status was set
199
+ }
200
+ },
201
+ errorHandler: function (err) {
202
+ // Also accept errors as valid completion
203
+ expect(err).to.exist;
204
+ completeDone();
205
+ },
206
+ });
207
+
208
+ // Use a config that should cause connection issues
209
+ const badConfig = {
210
+ ...testConfigs.valid,
211
+ host: 'nonexistent.invalid.host.com',
212
+ port: 12345, // Invalid port
213
+ };
214
+
215
+ // ACT: Register node and create instance with invalid config
216
+ emailReceiverNode(mockRED);
217
+ const nodeConstructor = mockRED.nodes.lastRegisteredConstructor;
218
+ const nodeInstance = new nodeConstructor(badConfig);
219
+
220
+ // Trigger the error by sending an input message
221
+ // Use a small delay to ensure the constructor has completed
222
+ setTimeout(() => {
223
+ if (nodeInstance.inputCallback) {
224
+ nodeInstance.inputCallback({ payload: 'test' });
225
+ } else {
226
+ completeDone(new Error('inputCallback was not set on the node instance'));
227
+ }
228
+ }, 10);
229
+ });
230
+ });
231
+
232
+ describe('IMAP Connection', function () {
233
+ it('should handle connection success', function (done) {
234
+ // ARRANGE: Set up connection tracking
235
+ const mockRED = createMockNodeRED({
236
+ onHandler: function (event, callback) {
237
+ if (event === 'input') {
238
+ this.inputCallback = callback;
239
+ }
240
+ },
241
+ statusHandler: function (status) {
242
+ // ASSERT: Check for 'connected' status and then complete the test
243
+ if (status.fill === 'green' && status.text === 'connected') {
244
+ done();
245
+ }
246
+ },
247
+ });
248
+
249
+ // ACT: Create node with config that should fail
250
+ emailReceiverNode(mockRED);
251
+ const nodeConstructor = mockRED.nodes.lastRegisteredConstructor;
252
+ const nodeInstance = new nodeConstructor(testConfigs.valid);
253
+
254
+ // Trigger the connection attempt by sending an input message
255
+ setTimeout(() => {
256
+ if (nodeInstance.inputCallback) {
257
+ nodeInstance.inputCallback({ payload: 'test' });
258
+ } else {
259
+ done(new Error('inputCallback was not set on the node instance'));
260
+ }
261
+ }, 10);
262
+ });
263
+
264
+ it('should handle connection errors', function (done) {
265
+ // ARRANGE: Set up error tracking
266
+ const mockRED = createMockNodeRED({
267
+ onHandler: function (event, callback) {
268
+ if (event === 'input') {
269
+ // Store the callback on the node instance
270
+ this.inputCallback = callback;
271
+ }
272
+ },
273
+ errorHandler: function (err) {
274
+ // ASSERT: Should handle connection errors gracefully
275
+ expect(err).to.exist;
276
+ done();
277
+ },
278
+ statusHandler: function (status) {
279
+ if (status.fill === 'red') {
280
+ // Connection failed status
281
+ expect(status.text).to.include('error');
282
+ }
283
+ },
284
+ });
285
+
286
+ // ACT: Create node with config that should fail
287
+ emailReceiverNode(mockRED);
288
+ const nodeConstructor = mockRED.nodes.lastRegisteredConstructor;
289
+
290
+ // Use invalid config to trigger connection error
291
+ const invalidConfig = { ...testConfigs.valid, host: 'invalid.host.com' };
292
+ const nodeInstance = new nodeConstructor(invalidConfig);
293
+
294
+ // Trigger the connection attempt by sending an input message
295
+ setTimeout(() => {
296
+ if (nodeInstance.inputCallback) {
297
+ nodeInstance.inputCallback({ payload: 'test' });
298
+ } else {
299
+ done(new Error('inputCallback was not set on the node instance'));
300
+ }
301
+ }, 10);
302
+ });
303
+ });
304
+ });