odac 0.9.0
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/.editorconfig +21 -0
- package/.github/workflows/auto-pr-description.yml +49 -0
- package/.github/workflows/release.yml +32 -0
- package/.github/workflows/test-coverage.yml +58 -0
- package/.husky/pre-commit +2 -0
- package/.kiro/steering/code-style.md +56 -0
- package/.kiro/steering/product.md +20 -0
- package/.kiro/steering/structure.md +77 -0
- package/.kiro/steering/tech.md +87 -0
- package/.prettierrc +10 -0
- package/.releaserc.js +134 -0
- package/AGENTS.md +84 -0
- package/CHANGELOG.md +181 -0
- package/CODE_OF_CONDUCT.md +83 -0
- package/CONTRIBUTING.md +63 -0
- package/LICENSE +661 -0
- package/README.md +57 -0
- package/SECURITY.md +26 -0
- package/bin/candy +10 -0
- package/bin/candypack +10 -0
- package/cli/index.js +3 -0
- package/cli/src/Cli.js +348 -0
- package/cli/src/Connector.js +93 -0
- package/cli/src/Monitor.js +416 -0
- package/core/Candy.js +87 -0
- package/core/Commands.js +239 -0
- package/core/Config.js +1094 -0
- package/core/Lang.js +52 -0
- package/core/Log.js +43 -0
- package/core/Process.js +26 -0
- package/docs/backend/01-overview/01-whats-in-the-candy-box.md +9 -0
- package/docs/backend/01-overview/02-super-handy-helper-functions.md +9 -0
- package/docs/backend/01-overview/03-development-server.md +79 -0
- package/docs/backend/02-structure/01-typical-project-layout.md +39 -0
- package/docs/backend/03-config/00-configuration-overview.md +214 -0
- package/docs/backend/03-config/01-database-connection.md +60 -0
- package/docs/backend/03-config/02-static-route-mapping-optional.md +20 -0
- package/docs/backend/03-config/03-request-timeout.md +11 -0
- package/docs/backend/03-config/04-environment-variables.md +227 -0
- package/docs/backend/03-config/05-early-hints.md +352 -0
- package/docs/backend/04-routing/01-basic-page-routes.md +28 -0
- package/docs/backend/04-routing/02-controller-less-view-routes.md +43 -0
- package/docs/backend/04-routing/03-api-and-data-routes.md +20 -0
- package/docs/backend/04-routing/04-authentication-aware-routes.md +48 -0
- package/docs/backend/04-routing/05-advanced-routing.md +14 -0
- package/docs/backend/04-routing/06-error-pages.md +101 -0
- package/docs/backend/04-routing/07-cron-jobs.md +149 -0
- package/docs/backend/05-controllers/01-how-to-build-a-controller.md +17 -0
- package/docs/backend/05-controllers/02-your-trusty-candy-assistant.md +20 -0
- package/docs/backend/05-controllers/03-controller-classes.md +93 -0
- package/docs/backend/05-forms/01-custom-forms.md +395 -0
- package/docs/backend/05-forms/02-automatic-database-insert.md +297 -0
- package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +96 -0
- package/docs/backend/06-request-and-response/02-sending-a-response-replying-to-the-user.md +40 -0
- package/docs/backend/07-views/01-the-view-directory.md +73 -0
- package/docs/backend/07-views/02-rendering-a-view.md +179 -0
- package/docs/backend/07-views/03-template-syntax.md +181 -0
- package/docs/backend/07-views/03-variables.md +328 -0
- package/docs/backend/07-views/04-request-data.md +231 -0
- package/docs/backend/07-views/05-conditionals.md +290 -0
- package/docs/backend/07-views/06-loops.md +353 -0
- package/docs/backend/07-views/07-translations.md +358 -0
- package/docs/backend/07-views/08-backend-javascript.md +398 -0
- package/docs/backend/07-views/09-comments.md +297 -0
- package/docs/backend/08-database/01-database-connection.md +99 -0
- package/docs/backend/08-database/02-using-mysql.md +322 -0
- package/docs/backend/09-validation/01-the-validator-service.md +424 -0
- package/docs/backend/10-authentication/01-user-logins-with-authjs.md +53 -0
- package/docs/backend/10-authentication/02-foiling-villains-with-csrf-protection.md +55 -0
- package/docs/backend/10-authentication/03-register.md +134 -0
- package/docs/backend/10-authentication/04-candy-register-forms.md +676 -0
- package/docs/backend/10-authentication/05-session-management.md +159 -0
- package/docs/backend/10-authentication/06-candy-login-forms.md +596 -0
- package/docs/backend/11-mail/01-the-mail-service.md +42 -0
- package/docs/backend/12-streaming/01-streaming-overview.md +300 -0
- package/docs/backend/13-utilities/01-candy-var.md +504 -0
- package/docs/frontend/01-overview/01-introduction.md +146 -0
- package/docs/frontend/02-ajax-navigation/01-quick-start.md +608 -0
- package/docs/frontend/02-ajax-navigation/02-configuration.md +370 -0
- package/docs/frontend/02-ajax-navigation/03-advanced-usage.md +519 -0
- package/docs/frontend/03-forms/01-form-handling.md +420 -0
- package/docs/frontend/04-api-requests/01-get-post.md +443 -0
- package/docs/frontend/05-streaming/01-client-streaming.md +163 -0
- package/docs/index.json +452 -0
- package/docs/server/01-installation/01-quick-install.md +19 -0
- package/docs/server/01-installation/02-manual-installation-via-npm.md +9 -0
- package/docs/server/02-get-started/01-core-concepts.md +7 -0
- package/docs/server/02-get-started/02-basic-commands.md +57 -0
- package/docs/server/02-get-started/03-cli-reference.md +276 -0
- package/docs/server/02-get-started/04-cli-quick-reference.md +102 -0
- package/docs/server/03-service/01-start-a-new-service.md +57 -0
- package/docs/server/03-service/02-delete-a-service.md +48 -0
- package/docs/server/04-web/01-create-a-website.md +36 -0
- package/docs/server/04-web/02-list-websites.md +9 -0
- package/docs/server/04-web/03-delete-a-website.md +29 -0
- package/docs/server/05-subdomain/01-create-a-subdomain.md +32 -0
- package/docs/server/05-subdomain/02-list-subdomains.md +33 -0
- package/docs/server/05-subdomain/03-delete-a-subdomain.md +41 -0
- package/docs/server/06-ssl/01-renew-an-ssl-certificate.md +34 -0
- package/docs/server/07-mail/01-create-a-mail-account.md +23 -0
- package/docs/server/07-mail/02-delete-a-mail-account.md +20 -0
- package/docs/server/07-mail/03-list-mail-accounts.md +20 -0
- package/docs/server/07-mail/04-change-account-password.md +23 -0
- package/eslint.config.mjs +120 -0
- package/framework/index.js +4 -0
- package/framework/src/Auth.js +309 -0
- package/framework/src/Candy.js +81 -0
- package/framework/src/Config.js +79 -0
- package/framework/src/Env.js +60 -0
- package/framework/src/Lang.js +57 -0
- package/framework/src/Mail.js +83 -0
- package/framework/src/Mysql.js +575 -0
- package/framework/src/Request.js +301 -0
- package/framework/src/Route/Cron.js +128 -0
- package/framework/src/Route/Internal.js +439 -0
- package/framework/src/Route.js +455 -0
- package/framework/src/Server.js +15 -0
- package/framework/src/Stream.js +163 -0
- package/framework/src/Token.js +37 -0
- package/framework/src/Validator.js +271 -0
- package/framework/src/Var.js +211 -0
- package/framework/src/View/EarlyHints.js +190 -0
- package/framework/src/View/Form.js +600 -0
- package/framework/src/View.js +513 -0
- package/framework/web/candy.js +838 -0
- package/jest.config.js +22 -0
- package/locale/de-DE.json +80 -0
- package/locale/en-US.json +79 -0
- package/locale/es-ES.json +80 -0
- package/locale/fr-FR.json +80 -0
- package/locale/pt-BR.json +80 -0
- package/locale/ru-RU.json +80 -0
- package/locale/tr-TR.json +85 -0
- package/locale/zh-CN.json +80 -0
- package/package.json +86 -0
- package/server/index.js +5 -0
- package/server/src/Api.js +88 -0
- package/server/src/DNS.js +940 -0
- package/server/src/Hub.js +535 -0
- package/server/src/Mail.js +571 -0
- package/server/src/SSL.js +180 -0
- package/server/src/Server.js +27 -0
- package/server/src/Service.js +248 -0
- package/server/src/Subdomain.js +64 -0
- package/server/src/Web/Firewall.js +170 -0
- package/server/src/Web/Proxy.js +134 -0
- package/server/src/Web.js +451 -0
- package/server/src/mail/imap.js +1091 -0
- package/server/src/mail/server.js +32 -0
- package/server/src/mail/smtp.js +786 -0
- package/test/cli/Cli.test.js +36 -0
- package/test/core/Candy.test.js +234 -0
- package/test/core/Commands.test.js +538 -0
- package/test/core/Config.test.js +1435 -0
- package/test/core/Lang.test.js +250 -0
- package/test/core/Process.test.js +156 -0
- package/test/framework/Route.test.js +239 -0
- package/test/framework/View/EarlyHints.test.js +282 -0
- package/test/scripts/check-coverage.js +132 -0
- package/test/server/Api.test.js +647 -0
- package/test/server/Client.test.js +338 -0
- package/test/server/DNS.test.js +2050 -0
- package/test/server/DNS.test.js.bak +2084 -0
- package/test/server/Log.test.js +73 -0
- package/test/server/Mail.account.test_.js +460 -0
- package/test/server/Mail.init.test_.js +411 -0
- package/test/server/Mail.test_.js +1340 -0
- package/test/server/SSL.test_.js +1491 -0
- package/test/server/Server.test.js +765 -0
- package/test/server/Service.test_.js +1127 -0
- package/test/server/Subdomain.test.js +440 -0
- package/test/server/Web/Firewall.test.js +175 -0
- package/test/server/Web.test_.js +1562 -0
- package/test/server/__mocks__/acme-client.js +17 -0
- package/test/server/__mocks__/bcrypt.js +50 -0
- package/test/server/__mocks__/child_process.js +389 -0
- package/test/server/__mocks__/crypto.js +432 -0
- package/test/server/__mocks__/fs.js +450 -0
- package/test/server/__mocks__/globalCandy.js +227 -0
- package/test/server/__mocks__/http-proxy.js +105 -0
- package/test/server/__mocks__/http.js +575 -0
- package/test/server/__mocks__/https.js +272 -0
- package/test/server/__mocks__/index.js +249 -0
- package/test/server/__mocks__/mail/server.js +100 -0
- package/test/server/__mocks__/mail/smtp.js +31 -0
- package/test/server/__mocks__/mailparser.js +81 -0
- package/test/server/__mocks__/net.js +369 -0
- package/test/server/__mocks__/node-forge.js +328 -0
- package/test/server/__mocks__/os.js +320 -0
- package/test/server/__mocks__/path.js +291 -0
- package/test/server/__mocks__/selfsigned.js +8 -0
- package/test/server/__mocks__/server/src/mail/server.js +100 -0
- package/test/server/__mocks__/server/src/mail/smtp.js +31 -0
- package/test/server/__mocks__/smtp-server.js +106 -0
- package/test/server/__mocks__/sqlite3.js +394 -0
- package/test/server/__mocks__/testFactories.js +299 -0
- package/test/server/__mocks__/testHelpers.js +363 -0
- package/test/server/__mocks__/tls.js +229 -0
- package/watchdog/index.js +3 -0
- package/watchdog/src/Watchdog.js +156 -0
- package/web/config.json +5 -0
- package/web/controller/page/about.js +27 -0
- package/web/controller/page/index.js +34 -0
- package/web/package.json +18 -0
- package/web/public/assets/css/style.css +1835 -0
- package/web/public/assets/js/app.js +96 -0
- package/web/route/www.js +19 -0
- package/web/skeleton/main.html +22 -0
- package/web/view/content/about.html +65 -0
- package/web/view/content/home.html +205 -0
- package/web/view/footer/main.html +11 -0
- package/web/view/head/main.html +5 -0
- package/web/view/header/main.html +14 -0
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for Mail.js server initialization and database setup
|
|
3
|
+
* Tests SMTP/IMAP server creation and database initialization
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const {setupGlobalMocks, cleanupGlobalMocks, createMockEventEmitter} = require('./__mocks__/testHelpers')
|
|
7
|
+
const {createMockWebsiteConfig} = require('./__mocks__/testFactories')
|
|
8
|
+
|
|
9
|
+
// Mock external dependencies
|
|
10
|
+
jest.mock('smtp-server')
|
|
11
|
+
jest.mock('sqlite3')
|
|
12
|
+
jest.mock('fs')
|
|
13
|
+
jest.mock('os')
|
|
14
|
+
jest.mock('../../server/src/mail/server', () => require('./__mocks__/server/src/mail/server'))
|
|
15
|
+
jest.mock('tls')
|
|
16
|
+
|
|
17
|
+
const {SMTPServer} = require('smtp-server')
|
|
18
|
+
const sqlite3 = require('sqlite3')
|
|
19
|
+
const fs = require('fs')
|
|
20
|
+
const os = require('os')
|
|
21
|
+
const mailServer = require('../../server/src/mail/server')
|
|
22
|
+
const tls = require('tls')
|
|
23
|
+
|
|
24
|
+
describe('Mail Module - Server Initialization and Database Setup', () => {
|
|
25
|
+
let Mail
|
|
26
|
+
let mockDb
|
|
27
|
+
let mockConfig
|
|
28
|
+
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
setupGlobalMocks()
|
|
31
|
+
|
|
32
|
+
// Setup mock database
|
|
33
|
+
mockDb = {
|
|
34
|
+
serialize: jest.fn(callback => callback && callback()),
|
|
35
|
+
run: jest.fn((sql, params, callback) => {
|
|
36
|
+
if (typeof params === 'function') {
|
|
37
|
+
callback = params
|
|
38
|
+
}
|
|
39
|
+
if (callback) callback(null)
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Setup sqlite3 mock
|
|
44
|
+
sqlite3.verbose.mockReturnValue({
|
|
45
|
+
Database: jest.fn().mockImplementation((path, callback) => {
|
|
46
|
+
if (callback) callback(null)
|
|
47
|
+
return mockDb
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
// Setup mock config with MX records
|
|
52
|
+
mockConfig = {
|
|
53
|
+
config: {
|
|
54
|
+
websites: {
|
|
55
|
+
'example.com': createMockWebsiteConfig('example.com', {
|
|
56
|
+
DNS: {
|
|
57
|
+
MX: [{name: 'example.com', value: 'mail.example.com', priority: 10}]
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Setup global Candy mocks
|
|
65
|
+
global.Candy.setMock('core', 'Config', mockConfig)
|
|
66
|
+
global.Candy.setMock('server', 'Log', {
|
|
67
|
+
init: jest.fn().mockReturnValue({
|
|
68
|
+
log: jest.fn(),
|
|
69
|
+
error: jest.fn()
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
// Setup os mock
|
|
74
|
+
os.homedir.mockReturnValue('/home/user')
|
|
75
|
+
|
|
76
|
+
// Setup fs mock
|
|
77
|
+
fs.existsSync.mockReturnValue(true)
|
|
78
|
+
fs.mkdirSync.mockImplementation(() => {})
|
|
79
|
+
fs.readFileSync.mockReturnValue('mock-cert-content')
|
|
80
|
+
|
|
81
|
+
// Setup SMTP server mock
|
|
82
|
+
const mockSMTPServer = createMockEventEmitter()
|
|
83
|
+
mockSMTPServer.listen = jest.fn()
|
|
84
|
+
SMTPServer.mockImplementation(() => mockSMTPServer)
|
|
85
|
+
|
|
86
|
+
// Setup mail server mock
|
|
87
|
+
const mockMailServer = createMockEventEmitter()
|
|
88
|
+
mockMailServer.listen = jest.fn()
|
|
89
|
+
mailServer.mockImplementation(() => mockMailServer)
|
|
90
|
+
|
|
91
|
+
// Setup TLS mock
|
|
92
|
+
tls.createSecureContext.mockReturnValue({})
|
|
93
|
+
|
|
94
|
+
// Clear module cache and require fresh instance
|
|
95
|
+
jest.resetModules()
|
|
96
|
+
Mail = require('../../server/src/Mail')
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
afterEach(() => {
|
|
100
|
+
cleanupGlobalMocks()
|
|
101
|
+
jest.clearAllMocks()
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
describe('SMTP Server Initialization', () => {
|
|
105
|
+
test('should initialize SMTP server on port 25', () => {
|
|
106
|
+
// Act
|
|
107
|
+
Mail.init()
|
|
108
|
+
|
|
109
|
+
// Assert
|
|
110
|
+
expect(SMTPServer).toHaveBeenCalledWith(
|
|
111
|
+
expect.objectContaining({
|
|
112
|
+
logger: true,
|
|
113
|
+
secure: false,
|
|
114
|
+
banner: 'CandyPack',
|
|
115
|
+
size: 1024 * 1024 * 10,
|
|
116
|
+
authOptional: true
|
|
117
|
+
})
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
const smtpInstance = SMTPServer.mock.results[0].value
|
|
121
|
+
expect(smtpInstance.listen).toHaveBeenCalledWith(25)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test('should initialize secure SMTP server on port 465', () => {
|
|
125
|
+
// Act
|
|
126
|
+
Mail.init()
|
|
127
|
+
|
|
128
|
+
// Assert
|
|
129
|
+
expect(SMTPServer).toHaveBeenCalledTimes(2)
|
|
130
|
+
|
|
131
|
+
const secureSmtpInstance = SMTPServer.mock.results[1].value
|
|
132
|
+
expect(secureSmtpInstance.listen).toHaveBeenCalledWith(465)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test('should configure secure SMTP server with SSL options', () => {
|
|
136
|
+
// Act
|
|
137
|
+
Mail.init()
|
|
138
|
+
|
|
139
|
+
// Assert
|
|
140
|
+
const secureSmtpOptions = SMTPServer.mock.calls[1][0]
|
|
141
|
+
expect(secureSmtpOptions.secure).toBe(true)
|
|
142
|
+
expect(secureSmtpOptions).toHaveProperty('SNICallback')
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
describe('IMAP Server Initialization', () => {
|
|
147
|
+
test('should initialize IMAP server on port 143', () => {
|
|
148
|
+
// Act
|
|
149
|
+
Mail.init()
|
|
150
|
+
|
|
151
|
+
// Assert
|
|
152
|
+
expect(mailServer).toHaveBeenCalledWith(
|
|
153
|
+
expect.objectContaining({
|
|
154
|
+
logger: true,
|
|
155
|
+
secure: false,
|
|
156
|
+
banner: 'CandyPack'
|
|
157
|
+
})
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
const imapInstance = mailServer.mock.results[0].value
|
|
161
|
+
expect(imapInstance.listen).toHaveBeenCalledWith(143)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
test('should initialize secure IMAP server on port 993', () => {
|
|
165
|
+
// Act
|
|
166
|
+
Mail.init()
|
|
167
|
+
|
|
168
|
+
// Assert
|
|
169
|
+
expect(mailServer).toHaveBeenCalledTimes(2)
|
|
170
|
+
|
|
171
|
+
const secureImapInstance = mailServer.mock.results[1].value
|
|
172
|
+
expect(secureImapInstance.listen).toHaveBeenCalledWith(993)
|
|
173
|
+
})
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
describe('Database Setup', () => {
|
|
177
|
+
test('should create SQLite database with correct path', () => {
|
|
178
|
+
// Act
|
|
179
|
+
Mail.init()
|
|
180
|
+
|
|
181
|
+
// Assert
|
|
182
|
+
expect(sqlite3.verbose().Database).toHaveBeenCalledWith('/home/user/.candypack/db/mail', expect.any(Function))
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
test('should create mail database tables on initialization', () => {
|
|
186
|
+
// Act
|
|
187
|
+
Mail.init()
|
|
188
|
+
|
|
189
|
+
// Assert
|
|
190
|
+
expect(mockDb.run).toHaveBeenCalledWith(expect.stringContaining('CREATE TABLE IF NOT EXISTS mail_received'))
|
|
191
|
+
expect(mockDb.run).toHaveBeenCalledWith(expect.stringContaining('CREATE TABLE IF NOT EXISTS mail_account'))
|
|
192
|
+
expect(mockDb.run).toHaveBeenCalledWith(expect.stringContaining('CREATE TABLE IF NOT EXISTS mail_box'))
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
test('should create database indexes on initialization', () => {
|
|
196
|
+
// Act
|
|
197
|
+
Mail.init()
|
|
198
|
+
|
|
199
|
+
// Assert
|
|
200
|
+
expect(mockDb.run).toHaveBeenCalledWith(expect.stringContaining('CREATE INDEX IF NOT EXISTS idx_email ON mail_account'))
|
|
201
|
+
expect(mockDb.run).toHaveBeenCalledWith(expect.stringContaining('CREATE INDEX IF NOT EXISTS idx_domain ON mail_account'))
|
|
202
|
+
expect(mockDb.run).toHaveBeenCalledWith(expect.stringContaining('CREATE INDEX IF NOT EXISTS idx_uid ON mail_received'))
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
test('should create database directory if it does not exist', () => {
|
|
206
|
+
// Arrange
|
|
207
|
+
fs.existsSync.mockReturnValue(false)
|
|
208
|
+
|
|
209
|
+
// Act
|
|
210
|
+
Mail.init()
|
|
211
|
+
|
|
212
|
+
// Assert
|
|
213
|
+
expect(fs.mkdirSync).toHaveBeenCalledWith('/home/user/.candypack/db', {recursive: true})
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
test('should handle database connection errors', () => {
|
|
217
|
+
// Arrange
|
|
218
|
+
const dbError = new Error('Database connection failed')
|
|
219
|
+
const mockError = jest.fn()
|
|
220
|
+
|
|
221
|
+
sqlite3.verbose.mockReturnValue({
|
|
222
|
+
Database: jest.fn().mockImplementation((path, callback) => {
|
|
223
|
+
callback(dbError)
|
|
224
|
+
return mockDb
|
|
225
|
+
})
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
global.Candy.setMock('server', 'Log', {
|
|
229
|
+
init: jest.fn().mockReturnValue({
|
|
230
|
+
log: jest.fn(),
|
|
231
|
+
error: mockError
|
|
232
|
+
})
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
// Clear module cache and require fresh instance
|
|
236
|
+
jest.resetModules()
|
|
237
|
+
const FreshMail = require('../../server/src/Mail')
|
|
238
|
+
|
|
239
|
+
// Act
|
|
240
|
+
FreshMail.init()
|
|
241
|
+
|
|
242
|
+
// Assert
|
|
243
|
+
expect(mockError).toHaveBeenCalledWith(dbError.message)
|
|
244
|
+
})
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
describe('SSL/TLS Configuration', () => {
|
|
248
|
+
test('should setup SNI callback for SSL certificate selection', () => {
|
|
249
|
+
// Arrange
|
|
250
|
+
mockConfig.config.ssl = {
|
|
251
|
+
key: '/etc/ssl/private/default.key',
|
|
252
|
+
cert: '/etc/ssl/certs/default.crt'
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Act
|
|
256
|
+
Mail.init()
|
|
257
|
+
|
|
258
|
+
// Assert
|
|
259
|
+
const smtpOptions = SMTPServer.mock.calls[1][0] // Second call is for secure SMTP
|
|
260
|
+
expect(smtpOptions).toHaveProperty('SNICallback')
|
|
261
|
+
expect(tls.createSecureContext).toHaveBeenCalled()
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
test('should use website-specific SSL certificates when available', () => {
|
|
265
|
+
// Arrange
|
|
266
|
+
const mockWebsite = createMockWebsiteConfig('test.com')
|
|
267
|
+
mockConfig.config.websites['test.com'] = mockWebsite
|
|
268
|
+
mockConfig.config.ssl = {
|
|
269
|
+
key: '/etc/ssl/private/default.key',
|
|
270
|
+
cert: '/etc/ssl/certs/default.crt'
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
fs.existsSync.mockImplementation(path => {
|
|
274
|
+
return path.includes('test.com') // Only test.com certs exist
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
// Act
|
|
278
|
+
Mail.init()
|
|
279
|
+
|
|
280
|
+
// Get the SNI callback
|
|
281
|
+
const smtpOptions = SMTPServer.mock.calls[1][0]
|
|
282
|
+
const sniCallback = smtpOptions.SNICallback
|
|
283
|
+
const mockCallback = jest.fn()
|
|
284
|
+
|
|
285
|
+
// Test SNI callback with existing cert
|
|
286
|
+
sniCallback('test.com', mockCallback)
|
|
287
|
+
|
|
288
|
+
// Assert
|
|
289
|
+
expect(fs.existsSync).toHaveBeenCalledWith(mockWebsite.cert.ssl.key)
|
|
290
|
+
expect(fs.existsSync).toHaveBeenCalledWith(mockWebsite.cert.ssl.cert)
|
|
291
|
+
expect(fs.readFileSync).toHaveBeenCalledWith(mockWebsite.cert.ssl.key)
|
|
292
|
+
expect(fs.readFileSync).toHaveBeenCalledWith(mockWebsite.cert.ssl.cert)
|
|
293
|
+
expect(tls.createSecureContext).toHaveBeenCalledWith({
|
|
294
|
+
key: 'mock-cert-content',
|
|
295
|
+
cert: 'mock-cert-content'
|
|
296
|
+
})
|
|
297
|
+
expect(mockCallback).toHaveBeenCalledWith(null, expect.any(Object))
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
test('should fallback to default SSL certificates when website certs not found', () => {
|
|
301
|
+
// Arrange
|
|
302
|
+
mockConfig.config.ssl = {
|
|
303
|
+
key: '/etc/ssl/private/default.key',
|
|
304
|
+
cert: '/etc/ssl/certs/default.crt'
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
fs.existsSync.mockImplementation(path => {
|
|
308
|
+
return !path.includes('test.com') // Website certs don't exist
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
// Act
|
|
312
|
+
Mail.init()
|
|
313
|
+
|
|
314
|
+
// Get the SNI callback
|
|
315
|
+
const smtpOptions = SMTPServer.mock.calls[1][0]
|
|
316
|
+
const sniCallback = smtpOptions.SNICallback
|
|
317
|
+
const mockCallback = jest.fn()
|
|
318
|
+
|
|
319
|
+
// Test SNI callback with missing website cert
|
|
320
|
+
sniCallback('test.com', mockCallback)
|
|
321
|
+
|
|
322
|
+
// Assert
|
|
323
|
+
expect(fs.readFileSync).toHaveBeenCalledWith('/etc/ssl/private/default.key')
|
|
324
|
+
expect(fs.readFileSync).toHaveBeenCalledWith('/etc/ssl/certs/default.crt')
|
|
325
|
+
expect(tls.createSecureContext).toHaveBeenCalledWith({
|
|
326
|
+
key: 'mock-cert-content',
|
|
327
|
+
cert: 'mock-cert-content'
|
|
328
|
+
})
|
|
329
|
+
expect(mockCallback).toHaveBeenCalledWith(null, expect.any(Object))
|
|
330
|
+
})
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
describe('Initialization Conditions', () => {
|
|
334
|
+
test('should not initialize if no domains have MX records', () => {
|
|
335
|
+
// Arrange
|
|
336
|
+
mockConfig.config.websites = {
|
|
337
|
+
'example.com': createMockWebsiteConfig('example.com', {
|
|
338
|
+
DNS: {
|
|
339
|
+
A: [{name: 'example.com', value: '127.0.0.1'}]
|
|
340
|
+
// No MX records
|
|
341
|
+
}
|
|
342
|
+
})
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Act
|
|
346
|
+
Mail.init()
|
|
347
|
+
|
|
348
|
+
// Assert
|
|
349
|
+
expect(SMTPServer).not.toHaveBeenCalled()
|
|
350
|
+
expect(mailServer).not.toHaveBeenCalled()
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
test('should not initialize twice if already started', () => {
|
|
354
|
+
// Act
|
|
355
|
+
Mail.init()
|
|
356
|
+
Mail.init() // Second call
|
|
357
|
+
|
|
358
|
+
// Assert
|
|
359
|
+
expect(SMTPServer).toHaveBeenCalledTimes(2) // Only from first init
|
|
360
|
+
expect(mailServer).toHaveBeenCalledTimes(2) // Only from first init
|
|
361
|
+
})
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
describe('Error Handling', () => {
|
|
365
|
+
test('should handle SMTP server errors', () => {
|
|
366
|
+
// Arrange
|
|
367
|
+
const mockLog = jest.fn()
|
|
368
|
+
global.Candy.setMock('server', 'Log', {
|
|
369
|
+
init: jest.fn().mockReturnValue({
|
|
370
|
+
log: mockLog,
|
|
371
|
+
error: jest.fn()
|
|
372
|
+
})
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
// Act
|
|
376
|
+
Mail.init()
|
|
377
|
+
|
|
378
|
+
// Simulate SMTP server error
|
|
379
|
+
const smtpInstance = SMTPServer.mock.results[0].value
|
|
380
|
+
const errorHandler = smtpInstance.on.mock.calls.find(call => call[0] === 'error')[1]
|
|
381
|
+
const testError = new Error('SMTP Server Error')
|
|
382
|
+
errorHandler(testError)
|
|
383
|
+
|
|
384
|
+
// Assert
|
|
385
|
+
expect(mockLog).toHaveBeenCalledWith('SMTP Server Error: ', testError)
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
test('should handle secure SMTP server errors', () => {
|
|
389
|
+
// Arrange
|
|
390
|
+
const mockError = jest.fn()
|
|
391
|
+
global.Candy.setMock('server', 'Log', {
|
|
392
|
+
init: jest.fn().mockReturnValue({
|
|
393
|
+
log: jest.fn(),
|
|
394
|
+
error: mockError
|
|
395
|
+
})
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
// Act
|
|
399
|
+
Mail.init()
|
|
400
|
+
|
|
401
|
+
// Simulate secure SMTP server error
|
|
402
|
+
const secureSmtpInstance = SMTPServer.mock.results[1].value
|
|
403
|
+
const errorHandler = secureSmtpInstance.on.mock.calls.find(call => call[0] === 'error')[1]
|
|
404
|
+
const testError = new Error('Secure SMTP Server Error')
|
|
405
|
+
errorHandler(testError)
|
|
406
|
+
|
|
407
|
+
// Assert
|
|
408
|
+
expect(mockError).toHaveBeenCalledWith('SMTP Server Error: ', testError)
|
|
409
|
+
})
|
|
410
|
+
})
|
|
411
|
+
})
|