emailengine-app 2.61.1 → 2.61.3

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.
Files changed (136) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/data/google-crawlers.json +1 -1
  3. package/lib/account/account-state.js +248 -0
  4. package/lib/account.js +45 -193
  5. package/lib/api-routes/account-routes.js +1023 -0
  6. package/lib/api-routes/message-routes.js +1377 -0
  7. package/lib/consts.js +12 -2
  8. package/lib/email-client/base-client.js +282 -771
  9. package/lib/email-client/gmail/gmail-api.js +243 -0
  10. package/lib/email-client/gmail-client.js +145 -53
  11. package/lib/email-client/imap/mailbox.js +24 -698
  12. package/lib/email-client/imap/sync-operations.js +812 -0
  13. package/lib/email-client/imap-client.js +1 -1
  14. package/lib/email-client/message-builder.js +566 -0
  15. package/lib/email-client/notification-handler.js +314 -0
  16. package/lib/email-client/outlook/graph-api.js +326 -0
  17. package/lib/email-client/outlook-client.js +159 -113
  18. package/lib/email-client/smtp-pool-manager.js +196 -0
  19. package/lib/imapproxy/imap-server.js +3 -12
  20. package/lib/oauth/gmail.js +4 -4
  21. package/lib/oauth/mail-ru.js +30 -5
  22. package/lib/oauth/outlook.js +57 -3
  23. package/lib/oauth/pubsub/google.js +30 -11
  24. package/lib/oauth/scope-checker.js +202 -0
  25. package/lib/oauth2-apps.js +8 -4
  26. package/lib/redis-operations.js +484 -0
  27. package/lib/routes-ui.js +283 -2582
  28. package/lib/tools.js +4 -196
  29. package/lib/ui-routes/account-routes.js +1931 -0
  30. package/lib/ui-routes/admin-config-routes.js +1233 -0
  31. package/lib/ui-routes/admin-entities-routes.js +2367 -0
  32. package/lib/ui-routes/oauth-routes.js +992 -0
  33. package/lib/utils/network.js +237 -0
  34. package/package.json +10 -10
  35. package/sbom.json +1 -1
  36. package/static/js/app.js +5 -5
  37. package/static/licenses.html +79 -19
  38. package/translations/de.mo +0 -0
  39. package/translations/de.po +97 -86
  40. package/translations/en.mo +0 -0
  41. package/translations/en.po +80 -75
  42. package/translations/et.mo +0 -0
  43. package/translations/et.po +96 -86
  44. package/translations/fr.mo +0 -0
  45. package/translations/fr.po +97 -86
  46. package/translations/ja.mo +0 -0
  47. package/translations/ja.po +96 -86
  48. package/translations/messages.pot +105 -91
  49. package/translations/nl.mo +0 -0
  50. package/translations/nl.po +98 -86
  51. package/translations/pl.mo +0 -0
  52. package/translations/pl.po +96 -86
  53. package/views/account/security.hbs +4 -4
  54. package/views/accounts/account.hbs +13 -13
  55. package/views/accounts/register/imap-server.hbs +12 -12
  56. package/views/config/document-store/pre-processing/index.hbs +4 -2
  57. package/views/config/oauth/app.hbs +6 -7
  58. package/views/config/oauth/index.hbs +2 -2
  59. package/views/config/service.hbs +3 -4
  60. package/views/dashboard.hbs +5 -7
  61. package/views/error.hbs +22 -7
  62. package/views/gateways/gateway.hbs +2 -2
  63. package/views/partials/add_account_modal.hbs +7 -10
  64. package/views/partials/document_store_header.hbs +1 -1
  65. package/views/partials/editor_scope_info.hbs +0 -1
  66. package/views/partials/oauth_config_header.hbs +1 -1
  67. package/views/partials/side_menu.hbs +3 -3
  68. package/views/partials/webhook_form.hbs +2 -2
  69. package/views/templates/index.hbs +1 -1
  70. package/views/templates/template.hbs +8 -8
  71. package/views/tokens/index.hbs +6 -6
  72. package/views/tokens/new.hbs +1 -1
  73. package/views/webhooks/index.hbs +4 -4
  74. package/views/webhooks/webhook.hbs +7 -7
  75. package/workers/api.js +148 -2436
  76. package/workers/smtp.js +2 -1
  77. package/lib/imapproxy/imap-core/test/client.js +0 -46
  78. package/lib/imapproxy/imap-core/test/fixtures/append.eml +0 -1196
  79. package/lib/imapproxy/imap-core/test/fixtures/chunks.js +0 -44
  80. package/lib/imapproxy/imap-core/test/fixtures/fix1.eml +0 -6
  81. package/lib/imapproxy/imap-core/test/fixtures/fix2.eml +0 -599
  82. package/lib/imapproxy/imap-core/test/fixtures/fix3.eml +0 -32
  83. package/lib/imapproxy/imap-core/test/fixtures/fix4.eml +0 -6
  84. package/lib/imapproxy/imap-core/test/fixtures/mimetorture.eml +0 -599
  85. package/lib/imapproxy/imap-core/test/fixtures/mimetorture.js +0 -2740
  86. package/lib/imapproxy/imap-core/test/fixtures/mimetorture.json +0 -1411
  87. package/lib/imapproxy/imap-core/test/fixtures/mimetree.js +0 -85
  88. package/lib/imapproxy/imap-core/test/fixtures/nodemailer.eml +0 -582
  89. package/lib/imapproxy/imap-core/test/fixtures/ryan_finnie_mime_torture.eml +0 -599
  90. package/lib/imapproxy/imap-core/test/fixtures/simple.eml +0 -42
  91. package/lib/imapproxy/imap-core/test/fixtures/simple.json +0 -164
  92. package/lib/imapproxy/imap-core/test/imap-compile-stream-test.js +0 -671
  93. package/lib/imapproxy/imap-core/test/imap-compiler-test.js +0 -272
  94. package/lib/imapproxy/imap-core/test/imap-indexer-test.js +0 -236
  95. package/lib/imapproxy/imap-core/test/imap-parser-test.js +0 -922
  96. package/lib/imapproxy/imap-core/test/memory-notifier.js +0 -129
  97. package/lib/imapproxy/imap-core/test/prepare.sh +0 -74
  98. package/lib/imapproxy/imap-core/test/protocol-test.js +0 -1756
  99. package/lib/imapproxy/imap-core/test/search-test.js +0 -1356
  100. package/lib/imapproxy/imap-core/test/test-client.js +0 -152
  101. package/lib/imapproxy/imap-core/test/test-server.js +0 -623
  102. package/lib/imapproxy/imap-core/test/tools-test.js +0 -22
  103. package/test/api-test.js +0 -899
  104. package/test/autoreply-test.js +0 -327
  105. package/test/bounce-test.js +0 -151
  106. package/test/complaint-test.js +0 -256
  107. package/test/fixtures/autoreply/LICENSE +0 -27
  108. package/test/fixtures/autoreply/rfc3834-01.eml +0 -23
  109. package/test/fixtures/autoreply/rfc3834-02.eml +0 -24
  110. package/test/fixtures/autoreply/rfc3834-03.eml +0 -26
  111. package/test/fixtures/autoreply/rfc3834-04.eml +0 -48
  112. package/test/fixtures/autoreply/rfc3834-05.eml +0 -19
  113. package/test/fixtures/autoreply/rfc3834-06.eml +0 -59
  114. package/test/fixtures/bounces/163.eml +0 -2521
  115. package/test/fixtures/bounces/fastmail.eml +0 -242
  116. package/test/fixtures/bounces/gmail.eml +0 -252
  117. package/test/fixtures/bounces/hotmail.eml +0 -655
  118. package/test/fixtures/bounces/mailru.eml +0 -121
  119. package/test/fixtures/bounces/outlook.eml +0 -1107
  120. package/test/fixtures/bounces/postfix.eml +0 -101
  121. package/test/fixtures/bounces/rambler.eml +0 -116
  122. package/test/fixtures/bounces/workmail.eml +0 -142
  123. package/test/fixtures/bounces/yahoo.eml +0 -139
  124. package/test/fixtures/bounces/zoho.eml +0 -83
  125. package/test/fixtures/bounces/zonemta.eml +0 -100
  126. package/test/fixtures/complaints/LICENSE +0 -27
  127. package/test/fixtures/complaints/amazonses.eml +0 -72
  128. package/test/fixtures/complaints/dmarc.eml +0 -59
  129. package/test/fixtures/complaints/hotmail.eml +0 -49
  130. package/test/fixtures/complaints/optout.eml +0 -40
  131. package/test/fixtures/complaints/standard-arf.eml +0 -68
  132. package/test/fixtures/complaints/yahoo.eml +0 -68
  133. package/test/oauth2-apps-test.js +0 -301
  134. package/test/sendonly-test.js +0 -160
  135. package/test/test-config.js +0 -34
  136. package/test/webhooks-server.js +0 -39
@@ -1,301 +0,0 @@
1
- 'use strict';
2
-
3
- require('dotenv').config({ quiet: true });
4
-
5
- const test = require('node:test');
6
- const assert = require('node:assert').strict;
7
-
8
- const { formatExtraScopes } = require('../lib/oauth2-apps');
9
- const { Account } = require('../lib/account');
10
-
11
- test('formatExtraScopes', async t => {
12
- t.after(async () => {
13
- // force close because we loaded ../lib/oauth2-apps that spawns the db connection and queues
14
- setTimeout(() => process.exit(), 5000).unref();
15
- });
16
-
17
- await t.test('should filter out Gmail scopes using short form', async () => {
18
- const defaultScopes = [
19
- 'https://www.googleapis.com/auth/gmail.modify',
20
- 'https://www.googleapis.com/auth/gmail.send',
21
- 'https://www.googleapis.com/auth/gmail.labels'
22
- ];
23
-
24
- const result = formatExtraScopes([], null, defaultScopes, ['gmail.modify'], null);
25
-
26
- assert.deepStrictEqual(result, ['https://www.googleapis.com/auth/gmail.send', 'https://www.googleapis.com/auth/gmail.labels']);
27
- });
28
-
29
- await t.test('should filter out Gmail scopes using full URL', async () => {
30
- const defaultScopes = [
31
- 'https://www.googleapis.com/auth/gmail.modify',
32
- 'https://www.googleapis.com/auth/gmail.send',
33
- 'https://www.googleapis.com/auth/gmail.labels'
34
- ];
35
-
36
- const result = formatExtraScopes([], null, defaultScopes, ['https://www.googleapis.com/auth/gmail.modify'], null);
37
-
38
- assert.deepStrictEqual(result, ['https://www.googleapis.com/auth/gmail.send', 'https://www.googleapis.com/auth/gmail.labels']);
39
- });
40
-
41
- await t.test('should filter multiple Gmail scopes', async () => {
42
- const defaultScopes = [
43
- 'https://www.googleapis.com/auth/gmail.modify',
44
- 'https://www.googleapis.com/auth/gmail.readonly',
45
- 'https://www.googleapis.com/auth/gmail.send',
46
- 'https://www.googleapis.com/auth/gmail.labels'
47
- ];
48
-
49
- const result = formatExtraScopes([], null, defaultScopes, ['gmail.modify', 'gmail.readonly', 'gmail.labels'], null);
50
-
51
- assert.deepStrictEqual(result, ['https://www.googleapis.com/auth/gmail.send']);
52
- });
53
-
54
- await t.test('should add extra Gmail scopes', async () => {
55
- const defaultScopes = ['https://www.googleapis.com/auth/gmail.modify'];
56
-
57
- const result = formatExtraScopes(['https://www.googleapis.com/auth/gmail.send'], null, defaultScopes, [], null);
58
-
59
- assert.deepStrictEqual(result, ['https://www.googleapis.com/auth/gmail.send', 'https://www.googleapis.com/auth/gmail.modify']);
60
- });
61
-
62
- await t.test('should add extra scopes and filter out unwanted ones', async () => {
63
- const defaultScopes = ['https://www.googleapis.com/auth/gmail.modify', 'https://www.googleapis.com/auth/gmail.labels'];
64
-
65
- const result = formatExtraScopes(['https://www.googleapis.com/auth/gmail.send'], null, defaultScopes, ['gmail.modify'], null);
66
-
67
- assert.deepStrictEqual(result, ['https://www.googleapis.com/auth/gmail.send', 'https://www.googleapis.com/auth/gmail.labels']);
68
- });
69
-
70
- await t.test('should handle send-only configuration', async () => {
71
- const defaultScopes = ['https://www.googleapis.com/auth/gmail.modify'];
72
-
73
- const result = formatExtraScopes(['https://www.googleapis.com/auth/gmail.send'], null, defaultScopes, ['gmail.modify'], null);
74
-
75
- assert.deepStrictEqual(result, ['https://www.googleapis.com/auth/gmail.send']);
76
- });
77
-
78
- await t.test('should filter out Outlook scopes using short form', async () => {
79
- const defaultScopes = [
80
- 'https://outlook.office.com/IMAP.AccessAsUser.All',
81
- 'https://outlook.office.com/SMTP.Send',
82
- 'https://outlook.office.com/User.Read'
83
- ];
84
-
85
- const result = formatExtraScopes([], null, defaultScopes, ['IMAP.AccessAsUser.All'], null);
86
-
87
- assert.deepStrictEqual(result, ['https://outlook.office.com/SMTP.Send', 'https://outlook.office.com/User.Read']);
88
- });
89
-
90
- await t.test('should filter out Microsoft Graph scopes', async () => {
91
- const defaultScopes = ['https://graph.microsoft.com/Mail.ReadWrite', 'https://graph.microsoft.com/Mail.Send', 'https://graph.microsoft.com/User.Read'];
92
-
93
- const result = formatExtraScopes([], null, defaultScopes, ['Mail.ReadWrite'], null);
94
-
95
- assert.deepStrictEqual(result, ['https://graph.microsoft.com/Mail.Send', 'https://graph.microsoft.com/User.Read']);
96
- });
97
-
98
- await t.test('should filter out full URL Outlook scopes', async () => {
99
- const defaultScopes = ['https://outlook.office.com/IMAP.AccessAsUser.All', 'https://outlook.office.com/SMTP.Send'];
100
-
101
- const result = formatExtraScopes([], null, defaultScopes, ['https://outlook.office.com/IMAP.AccessAsUser.All'], null);
102
-
103
- assert.deepStrictEqual(result, ['https://outlook.office.com/SMTP.Send']);
104
- });
105
-
106
- await t.test('should return default scopes when no extras or skips', async () => {
107
- const defaultScopes = ['scope1', 'scope2'];
108
-
109
- const result = formatExtraScopes(null, null, defaultScopes, [], null);
110
-
111
- assert.deepStrictEqual(result, ['scope1', 'scope2']);
112
- });
113
-
114
- await t.test('should handle empty skipScopes array', async () => {
115
- const defaultScopes = ['scope1', 'scope2'];
116
-
117
- const result = formatExtraScopes(['scope3'], null, defaultScopes, [], null);
118
-
119
- assert.deepStrictEqual(result, ['scope3', 'scope1', 'scope2']);
120
- });
121
-
122
- await t.test('should not duplicate scopes already in defaults', async () => {
123
- const defaultScopes = ['scope1', 'scope2'];
124
-
125
- const result = formatExtraScopes(['scope1', 'scope3'], null, defaultScopes, [], null);
126
-
127
- assert.deepStrictEqual(result, ['scope3', 'scope1', 'scope2']);
128
- });
129
-
130
- await t.test('should handle scopePrefix correctly', async () => {
131
- const defaultScopes = ['prefix/scope1', 'prefix/scope2'];
132
-
133
- const result = formatExtraScopes(['scope1', 'scope3'], null, defaultScopes, [], 'prefix');
134
-
135
- assert.deepStrictEqual(result, ['scope3', 'prefix/scope1', 'prefix/scope2']);
136
- });
137
- });
138
-
139
- test('checkAccountScopes - Outlook', async t => {
140
- let account;
141
-
142
- t.beforeEach(() => {
143
- // Create a mock account instance with minimal required properties
144
- account = new Account({
145
- redis: {},
146
- account: 'test-account',
147
- secret: 'test-secret',
148
- logger: {
149
- warn: () => {},
150
- error: () => {},
151
- info: () => {},
152
- debug: () => {}
153
- }
154
- });
155
- });
156
-
157
- await t.test('should detect send-only Outlook account (global cloud)', async () => {
158
- const scopes = ['https://graph.microsoft.com/Mail.Send', 'offline_access'];
159
- const result = account.checkAccountScopes('outlook', scopes);
160
-
161
- assert.deepStrictEqual(result, { hasSendScope: true, hasReadScope: false });
162
- });
163
-
164
- await t.test('should detect full-access Outlook account (global cloud)', async () => {
165
- const scopes = ['https://graph.microsoft.com/Mail.ReadWrite', 'https://graph.microsoft.com/Mail.Send', 'offline_access'];
166
- const result = account.checkAccountScopes('outlook', scopes);
167
-
168
- assert.deepStrictEqual(result, { hasSendScope: true, hasReadScope: true });
169
- });
170
-
171
- await t.test('should detect send-only Outlook account (GCC-High cloud)', async () => {
172
- const scopes = ['https://graph.microsoft.us/Mail.Send', 'offline_access'];
173
- const result = account.checkAccountScopes('outlook', scopes);
174
-
175
- assert.deepStrictEqual(result, { hasSendScope: true, hasReadScope: false });
176
- });
177
-
178
- await t.test('should detect full-access Outlook account (GCC-High cloud)', async () => {
179
- const scopes = ['https://graph.microsoft.us/Mail.ReadWrite', 'https://graph.microsoft.us/Mail.Send', 'offline_access'];
180
- const result = account.checkAccountScopes('outlook', scopes);
181
-
182
- assert.deepStrictEqual(result, { hasSendScope: true, hasReadScope: true });
183
- });
184
-
185
- await t.test('should detect send-only Outlook account (DoD cloud)', async () => {
186
- const scopes = ['https://dod-graph.microsoft.us/Mail.Send', 'offline_access'];
187
- const result = account.checkAccountScopes('outlook', scopes);
188
-
189
- assert.deepStrictEqual(result, { hasSendScope: true, hasReadScope: false });
190
- });
191
-
192
- await t.test('should detect full-access Outlook account (DoD cloud)', async () => {
193
- const scopes = ['https://dod-graph.microsoft.us/Mail.ReadWrite', 'https://dod-graph.microsoft.us/Mail.Send', 'offline_access'];
194
- const result = account.checkAccountScopes('outlook', scopes);
195
-
196
- assert.deepStrictEqual(result, { hasSendScope: true, hasReadScope: true });
197
- });
198
-
199
- await t.test('should detect send-only Outlook account (China cloud)', async () => {
200
- const scopes = ['https://microsoftgraph.chinacloudapi.cn/Mail.Send', 'offline_access'];
201
- const result = account.checkAccountScopes('outlook', scopes);
202
-
203
- assert.deepStrictEqual(result, { hasSendScope: true, hasReadScope: false });
204
- });
205
-
206
- await t.test('should detect full-access Outlook account (China cloud)', async () => {
207
- const scopes = ['https://microsoftgraph.chinacloudapi.cn/Mail.ReadWrite', 'https://microsoftgraph.chinacloudapi.cn/Mail.Send', 'offline_access'];
208
- const result = account.checkAccountScopes('outlook', scopes);
209
-
210
- assert.deepStrictEqual(result, { hasSendScope: true, hasReadScope: true });
211
- });
212
-
213
- await t.test('should detect read-only Outlook account with Mail.Read', async () => {
214
- const scopes = ['https://graph.microsoft.com/Mail.Read', 'offline_access'];
215
- const result = account.checkAccountScopes('outlook', scopes);
216
-
217
- assert.deepStrictEqual(result, { hasSendScope: false, hasReadScope: true });
218
- });
219
-
220
- await t.test('should handle scopes with trailing slashes', async () => {
221
- const scopes = ['https://graph.microsoft.com/Mail.Send/', 'offline_access'];
222
- const result = account.checkAccountScopes('outlook', scopes);
223
-
224
- assert.deepStrictEqual(result, { hasSendScope: true, hasReadScope: false });
225
- });
226
-
227
- await t.test('should handle scopes with query parameters', async () => {
228
- const scopes = ['https://graph.microsoft.com/Mail.Send?foo=bar', 'offline_access'];
229
- const result = account.checkAccountScopes('outlook', scopes);
230
-
231
- assert.deepStrictEqual(result, { hasSendScope: true, hasReadScope: false });
232
- });
233
-
234
- await t.test('should handle scopes with fragments', async () => {
235
- const scopes = ['https://graph.microsoft.com/Mail.Send#section', 'offline_access'];
236
- const result = account.checkAccountScopes('outlook', scopes);
237
-
238
- assert.deepStrictEqual(result, { hasSendScope: true, hasReadScope: false });
239
- });
240
-
241
- await t.test('should handle plain scope names', async () => {
242
- const scopes = ['offline_access', 'openid', 'profile'];
243
- const result = account.checkAccountScopes('outlook', scopes);
244
-
245
- assert.deepStrictEqual(result, { hasSendScope: false, hasReadScope: false });
246
- });
247
-
248
- await t.test('should handle invalid protocol (http instead of https)', async () => {
249
- const scopes = ['http://graph.microsoft.com/Mail.Send', 'offline_access'];
250
- const result = account.checkAccountScopes('outlook', scopes);
251
-
252
- assert.deepStrictEqual(result, { hasSendScope: false, hasReadScope: false });
253
- });
254
-
255
- await t.test('should handle non-Microsoft Graph domains', async () => {
256
- const scopes = ['https://evil.com/Mail.Send', 'offline_access'];
257
- const result = account.checkAccountScopes('outlook', scopes);
258
-
259
- assert.deepStrictEqual(result, { hasSendScope: false, hasReadScope: false });
260
- });
261
-
262
- await t.test('should handle malformed URLs', async () => {
263
- const scopes = ['not-a-url', 'offline_access'];
264
- const result = account.checkAccountScopes('outlook', scopes);
265
-
266
- assert.deepStrictEqual(result, { hasSendScope: false, hasReadScope: false });
267
- });
268
-
269
- await t.test('should handle empty scopes array', async () => {
270
- const scopes = [];
271
- const result = account.checkAccountScopes('outlook', scopes);
272
-
273
- assert.deepStrictEqual(result, { hasSendScope: false, hasReadScope: false });
274
- });
275
-
276
- await t.test('should handle null scopes', async () => {
277
- const result = account.checkAccountScopes('outlook', null);
278
-
279
- assert.deepStrictEqual(result, { hasSendScope: false, hasReadScope: false });
280
- });
281
-
282
- await t.test('should handle undefined scopes', async () => {
283
- const result = account.checkAccountScopes('outlook', undefined);
284
-
285
- assert.deepStrictEqual(result, { hasSendScope: false, hasReadScope: false });
286
- });
287
-
288
- await t.test('should handle mixed cloud scopes', async () => {
289
- const scopes = ['https://graph.microsoft.com/Mail.Send', 'https://graph.microsoft.us/Mail.ReadWrite', 'offline_access'];
290
- const result = account.checkAccountScopes('outlook', scopes);
291
-
292
- assert.deepStrictEqual(result, { hasSendScope: true, hasReadScope: true });
293
- });
294
-
295
- await t.test('should handle User.Read scope (not mail-related)', async () => {
296
- const scopes = ['https://graph.microsoft.com/User.Read', 'offline_access'];
297
- const result = account.checkAccountScopes('outlook', scopes);
298
-
299
- assert.deepStrictEqual(result, { hasSendScope: false, hasReadScope: false });
300
- });
301
- });
@@ -1,160 +0,0 @@
1
- 'use strict';
2
-
3
- require('dotenv').config({ quiet: true });
4
-
5
- const config = require('@zone-eu/wild-config');
6
- const testConfig = require('./test-config');
7
- const supertest = require('supertest');
8
- const test = require('node:test');
9
- const assert = require('node:assert').strict;
10
- const webhooksServer = require('./webhooks-server');
11
-
12
- const accessToken = '2aa97ad0456d6624a55d30780aa2ff61bfb7edc6fa00935b40814b271e718660';
13
- const server = supertest.agent(`http://127.0.0.1:${config.api.port}`).auth(accessToken, { type: 'bearer' });
14
-
15
- const gmailSendOnlyAccountId = 'gmail-sendonly-test';
16
-
17
- // Helper function for polling with timeout
18
- async function waitForCondition(checkFn, options = {}) {
19
- const { interval = testConfig.POLL_INTERVAL, timeout = testConfig.DEFAULT_TIMEOUT, message = 'Condition not met within timeout' } = options;
20
-
21
- const startTime = Date.now();
22
-
23
- while (Date.now() - startTime < timeout) {
24
- const result = await checkFn();
25
- if (result) {
26
- return result;
27
- }
28
- await new Promise(r => setTimeout(r, interval));
29
- }
30
-
31
- throw new Error(`Timeout: ${message}`);
32
- }
33
-
34
- test('Gmail send-only account - isolated send test', async t => {
35
- let oauth2PubsubId;
36
- let oauth2SendOnlyAppId;
37
-
38
- t.before(async () => {
39
- await webhooksServer.init();
40
- });
41
-
42
- t.after(async () => {
43
- await webhooksServer.quit();
44
- });
45
-
46
- await t.test('Create Gmail API OAuth2 service project', { timeout: 30000 }, async () => {
47
- let gmailServiceData = {
48
- name: 'Gmail API Pub/Sub',
49
- provider: 'gmailService',
50
- baseScopes: 'pubsub',
51
- googleProjectId: process.env.GMAIL_API_PROJECT_ID,
52
- serviceClient: process.env.GMAIL_API_SERVICE_CLIENT,
53
- serviceClientEmail: process.env.GMAIL_API_SERVICE_EMAIL,
54
- serviceKey: process.env.GMAIL_API_SERVICE_KEY
55
- };
56
-
57
- const response = await server.post(`/v1/oauth2`).send(gmailServiceData).expect(200);
58
-
59
- oauth2PubsubId = response.body.id;
60
- assert.ok(oauth2PubsubId);
61
- });
62
-
63
- await t.test('Create Gmail send-only OAuth2 client project', { timeout: 30000 }, async () => {
64
- let gmailSendOnlyClientData = {
65
- name: 'Gmail API Send-Only Client',
66
- provider: 'gmail',
67
- baseScopes: 'api',
68
- googleProjectId: process.env.GMAIL_SENDONLY_PROJECT_ID,
69
- clientId: process.env.GMAIL_SENDONLY_CLIENT_ID,
70
- clientSecret: process.env.GMAIL_SENDONLY_CLIENT_SECRET,
71
- extraScopes: ['gmail.send'],
72
- skipScopes: ['gmail.modify'],
73
- redirectUrl: 'http://127.0.0.1:3000/oauth'
74
- };
75
-
76
- const response = await server.post(`/v1/oauth2`).send(gmailSendOnlyClientData).expect(200);
77
-
78
- oauth2SendOnlyAppId = response.body.id;
79
- assert.ok(oauth2SendOnlyAppId);
80
- });
81
-
82
- await t.test('Register Gmail send-only account', { timeout: 30000 }, async () => {
83
- const response = await server
84
- .post(`/v1/account`)
85
- .send({
86
- account: gmailSendOnlyAccountId,
87
- name: 'Gmail Send-Only Test User',
88
- email: process.env.GMAIL_SENDONLY_ACCOUNT_EMAIL,
89
- oauth2: {
90
- provider: oauth2SendOnlyAppId,
91
- auth: {
92
- user: process.env.GMAIL_SENDONLY_ACCOUNT_EMAIL
93
- },
94
- refreshToken: process.env.GMAIL_SENDONLY_ACCOUNT_REFRESH
95
- }
96
- })
97
- .expect(200);
98
-
99
- assert.strictEqual(response.body.state, 'new');
100
- });
101
-
102
- await t.test('Wait until Gmail send-only account is connected', { timeout: 180000 }, async () => {
103
- await waitForCondition(
104
- async () => {
105
- const response = await server.get(`/v1/account/${gmailSendOnlyAccountId}`).expect(200);
106
- switch (response.body.state) {
107
- case 'authenticationError':
108
- case 'connectError':
109
- throw new Error('Invalid account state ' + response.body.state);
110
- case 'connected':
111
- return true;
112
- }
113
- return false;
114
- },
115
- { timeout: testConfig.GMAIL_TIMEOUT, message: `Gmail send-only account connection timeout` }
116
- );
117
-
118
- // Verify account type is 'sending'
119
- const response = await server.get(`/v1/account/${gmailSendOnlyAccountId}`).expect(200);
120
- assert.strictEqual(response.body.sendOnly, true, 'Account should be detected as send-only');
121
- });
122
-
123
- await t.test('Submit email from send-only account (no receive check)', { timeout: 60000 }, async () => {
124
- let messageId = `<sendonly-isolated-test-${Date.now()}@example.com>`;
125
-
126
- const response = await server
127
- .post(`/v1/account/${gmailSendOnlyAccountId}/submit`)
128
- .send({
129
- to: [
130
- {
131
- name: 'Test Recipient',
132
- address: process.env.GMAIL_API_ACCOUNT_EMAIL_2 || 'test@example.com'
133
- }
134
- ],
135
- subject: 'Isolated send-only test',
136
- text: 'This is a test message from send-only account - isolated test',
137
- html: '<p>This is a test message from <strong>send-only</strong> account - isolated test</p>',
138
- messageId
139
- })
140
- .expect(200);
141
-
142
- assert.ok(response.body.messageId, 'Should have messageId in response');
143
- assert.ok(response.body.queueId, 'Should have queueId in response');
144
-
145
- // Wait for messageSent webhook only - don't check for receive
146
- const messageSentWebhook = await waitForCondition(
147
- async () => {
148
- let webhooks = webhooksServer.webhooks.get(gmailSendOnlyAccountId);
149
- return webhooks?.find(wh => wh.event === 'messageSent' && wh.data.originalMessageId === messageId);
150
- },
151
- { timeout: 30000, message: 'Gmail send-only message sent webhook timeout' }
152
- );
153
-
154
- assert.ok(messageSentWebhook, 'Should receive messageSent webhook');
155
- });
156
-
157
- await t.test('Cleanup - delete send-only account', { timeout: 10000 }, async () => {
158
- await server.delete(`/v1/account/${gmailSendOnlyAccountId}`).expect(200);
159
- });
160
- });
@@ -1,34 +0,0 @@
1
- 'use strict';
2
-
3
- /**
4
- * Test configuration for EmailEngine test suite
5
- * Timeout values are configured to handle Gmail API operations
6
- * which may take longer due to network latency and API processing
7
- */
8
-
9
- module.exports = {
10
- // Default timeout for standard operations
11
- DEFAULT_TIMEOUT: 30000, // 30 seconds
12
-
13
- // Timeout for Gmail API operations
14
- GMAIL_TIMEOUT: 90000, // 90 seconds
15
-
16
- // Timeout for waiting for account connections
17
- CONNECTION_TIMEOUT: 60000, // 60 seconds
18
-
19
- // Timeout for webhook notifications
20
- WEBHOOK_TIMEOUT: 30000, // 30 seconds
21
-
22
- // Polling interval for checking conditions
23
- POLL_INTERVAL: 1000, // 1 second
24
-
25
- // Environment-specific overrides
26
- ...(process.env.CI
27
- ? {
28
- // Increase timeouts in CI environment
29
- GMAIL_TIMEOUT: 120000, // 2 minutes in CI
30
- CONNECTION_TIMEOUT: 90000, // 90 seconds in CI
31
- WEBHOOK_TIMEOUT: 60000 // 60 seconds in CI
32
- }
33
- : {})
34
- };
@@ -1,39 +0,0 @@
1
- 'use strict';
2
-
3
- const config = require('@zone-eu/wild-config');
4
- const Hapi = require('@hapi/hapi');
5
-
6
- const webhooks = new Map();
7
-
8
- const server = Hapi.server({
9
- port: config.webhooksServer.port,
10
- host: '0.0.0.0'
11
- });
12
-
13
- server.route({
14
- method: 'POST',
15
- path: '/webhooks',
16
- handler: async (request, h) => {
17
- let account = request.payload.account || '';
18
- if (!webhooks.has(account)) {
19
- webhooks.set(account, []);
20
- }
21
- console.log('WEBHOOK', JSON.stringify(request.payload));
22
- webhooks.get(account).push(request.payload);
23
- return h.response('OK').code(200);
24
- }
25
- });
26
-
27
- const init = async () => {
28
- await server.start();
29
- console.log('Webhooks Server running on %s', server.info.uri);
30
- };
31
-
32
- module.exports = {
33
- init,
34
- webhooks,
35
- async quit() {
36
- await server.stop({ timeout: 5 * 1000 });
37
- console.log('Webhooks Server closed');
38
- }
39
- };