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,450 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock implementation of the fs module for server tests
|
|
3
|
+
* Provides comprehensive mocking of file system operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const mockFiles = new Map()
|
|
7
|
+
const mockDirectories = new Set()
|
|
8
|
+
|
|
9
|
+
// Helper to simulate file system state
|
|
10
|
+
const resetFileSystem = () => {
|
|
11
|
+
mockFiles.clear()
|
|
12
|
+
mockDirectories.clear()
|
|
13
|
+
|
|
14
|
+
// Add some default directories
|
|
15
|
+
mockDirectories.add('/var/candypack')
|
|
16
|
+
mockDirectories.add('/etc/ssl/private')
|
|
17
|
+
mockDirectories.add('/etc/ssl/certs')
|
|
18
|
+
mockDirectories.add('/home/user/.candypack')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Initialize with default state
|
|
22
|
+
resetFileSystem()
|
|
23
|
+
|
|
24
|
+
const fs = {
|
|
25
|
+
// Synchronous file operations
|
|
26
|
+
readFileSync: jest.fn((path, encoding) => {
|
|
27
|
+
if (!mockFiles.has(path)) {
|
|
28
|
+
const error = new Error(`ENOENT: no such file or directory, open '${path}'`)
|
|
29
|
+
error.code = 'ENOENT'
|
|
30
|
+
error.errno = -2
|
|
31
|
+
error.path = path
|
|
32
|
+
throw error
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const content = mockFiles.get(path)
|
|
36
|
+
return encoding ? content : Buffer.from(content)
|
|
37
|
+
}),
|
|
38
|
+
|
|
39
|
+
writeFileSync: jest.fn((path, data, options) => {
|
|
40
|
+
const content = typeof data === 'string' ? data : data.toString()
|
|
41
|
+
mockFiles.set(path, content)
|
|
42
|
+
}),
|
|
43
|
+
|
|
44
|
+
appendFileSync: jest.fn((path, data, options) => {
|
|
45
|
+
const content = typeof data === 'string' ? data : data.toString()
|
|
46
|
+
const existing = mockFiles.get(path) || ''
|
|
47
|
+
mockFiles.set(path, existing + content)
|
|
48
|
+
}),
|
|
49
|
+
|
|
50
|
+
existsSync: jest.fn(path => {
|
|
51
|
+
return mockFiles.has(path) || mockDirectories.has(path)
|
|
52
|
+
}),
|
|
53
|
+
|
|
54
|
+
statSync: jest.fn(path => {
|
|
55
|
+
if (!mockFiles.has(path) && !mockDirectories.has(path)) {
|
|
56
|
+
const error = new Error(`ENOENT: no such file or directory, stat '${path}'`)
|
|
57
|
+
error.code = 'ENOENT'
|
|
58
|
+
error.errno = -2
|
|
59
|
+
error.path = path
|
|
60
|
+
throw error
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const isDirectory = mockDirectories.has(path)
|
|
64
|
+
return {
|
|
65
|
+
isFile: () => !isDirectory,
|
|
66
|
+
isDirectory: () => isDirectory,
|
|
67
|
+
isSymbolicLink: () => false,
|
|
68
|
+
size: isDirectory ? 0 : (mockFiles.get(path) || '').length,
|
|
69
|
+
mtime: new Date(),
|
|
70
|
+
ctime: new Date(),
|
|
71
|
+
atime: new Date(),
|
|
72
|
+
mode: isDirectory ? 16877 : 33188,
|
|
73
|
+
uid: 1000,
|
|
74
|
+
gid: 1000
|
|
75
|
+
}
|
|
76
|
+
}),
|
|
77
|
+
|
|
78
|
+
lstatSync: jest.fn(path => fs.statSync(path)),
|
|
79
|
+
|
|
80
|
+
readdirSync: jest.fn(path => {
|
|
81
|
+
if (!mockDirectories.has(path)) {
|
|
82
|
+
const error = new Error(`ENOENT: no such file or directory, scandir '${path}'`)
|
|
83
|
+
error.code = 'ENOENT'
|
|
84
|
+
error.errno = -2
|
|
85
|
+
error.path = path
|
|
86
|
+
throw error
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const entries = []
|
|
90
|
+
|
|
91
|
+
// Find files in this directory
|
|
92
|
+
for (const filePath of mockFiles.keys()) {
|
|
93
|
+
if (filePath.startsWith(path + '/')) {
|
|
94
|
+
const relativePath = filePath.substring(path.length + 1)
|
|
95
|
+
if (!relativePath.includes('/')) {
|
|
96
|
+
entries.push(relativePath)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Find subdirectories
|
|
102
|
+
for (const dirPath of mockDirectories) {
|
|
103
|
+
if (dirPath.startsWith(path + '/')) {
|
|
104
|
+
const relativePath = dirPath.substring(path.length + 1)
|
|
105
|
+
if (!relativePath.includes('/')) {
|
|
106
|
+
entries.push(relativePath)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return entries
|
|
112
|
+
}),
|
|
113
|
+
|
|
114
|
+
mkdirSync: jest.fn((path, options) => {
|
|
115
|
+
mockDirectories.add(path)
|
|
116
|
+
|
|
117
|
+
// Create parent directories if recursive option is set
|
|
118
|
+
if (options && options.recursive) {
|
|
119
|
+
const parts = path.split('/')
|
|
120
|
+
let currentPath = ''
|
|
121
|
+
for (const part of parts) {
|
|
122
|
+
if (part) {
|
|
123
|
+
currentPath += '/' + part
|
|
124
|
+
mockDirectories.add(currentPath)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}),
|
|
129
|
+
|
|
130
|
+
rmdirSync: jest.fn((path, options) => {
|
|
131
|
+
mockDirectories.delete(path)
|
|
132
|
+
|
|
133
|
+
// Remove files in directory if recursive
|
|
134
|
+
if (options && options.recursive) {
|
|
135
|
+
for (const filePath of mockFiles.keys()) {
|
|
136
|
+
if (filePath.startsWith(path + '/')) {
|
|
137
|
+
mockFiles.delete(filePath)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
for (const dirPath of mockDirectories) {
|
|
142
|
+
if (dirPath.startsWith(path + '/')) {
|
|
143
|
+
mockDirectories.delete(dirPath)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}),
|
|
148
|
+
|
|
149
|
+
unlinkSync: jest.fn(path => {
|
|
150
|
+
if (!mockFiles.has(path)) {
|
|
151
|
+
const error = new Error(`ENOENT: no such file or directory, unlink '${path}'`)
|
|
152
|
+
error.code = 'ENOENT'
|
|
153
|
+
error.errno = -2
|
|
154
|
+
error.path = path
|
|
155
|
+
throw error
|
|
156
|
+
}
|
|
157
|
+
mockFiles.delete(path)
|
|
158
|
+
}),
|
|
159
|
+
|
|
160
|
+
copyFileSync: jest.fn((src, dest) => {
|
|
161
|
+
if (!mockFiles.has(src)) {
|
|
162
|
+
const error = new Error(`ENOENT: no such file or directory, open '${src}'`)
|
|
163
|
+
error.code = 'ENOENT'
|
|
164
|
+
error.errno = -2
|
|
165
|
+
error.path = src
|
|
166
|
+
throw error
|
|
167
|
+
}
|
|
168
|
+
mockFiles.set(dest, mockFiles.get(src))
|
|
169
|
+
}),
|
|
170
|
+
|
|
171
|
+
cpSync: jest.fn((src, dest, options) => {
|
|
172
|
+
// Mock recursive copy operation
|
|
173
|
+
if (mockDirectories.has(src)) {
|
|
174
|
+
// Copy directory
|
|
175
|
+
mockDirectories.add(dest)
|
|
176
|
+
|
|
177
|
+
// Copy all files in the directory
|
|
178
|
+
for (const filePath of mockFiles.keys()) {
|
|
179
|
+
if (filePath.startsWith(src + '/')) {
|
|
180
|
+
const relativePath = filePath.substring(src.length)
|
|
181
|
+
mockFiles.set(dest + relativePath, mockFiles.get(filePath))
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Copy all subdirectories
|
|
186
|
+
for (const dirPath of mockDirectories) {
|
|
187
|
+
if (dirPath.startsWith(src + '/')) {
|
|
188
|
+
const relativePath = dirPath.substring(src.length)
|
|
189
|
+
mockDirectories.add(dest + relativePath)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
} else if (mockFiles.has(src)) {
|
|
193
|
+
// Copy single file
|
|
194
|
+
mockFiles.set(dest, mockFiles.get(src))
|
|
195
|
+
}
|
|
196
|
+
}),
|
|
197
|
+
|
|
198
|
+
rmSync: jest.fn((path, options) => {
|
|
199
|
+
if (mockDirectories.has(path)) {
|
|
200
|
+
// Remove directory
|
|
201
|
+
mockDirectories.delete(path)
|
|
202
|
+
|
|
203
|
+
// Remove files in directory if recursive
|
|
204
|
+
if (options && options.recursive) {
|
|
205
|
+
for (const filePath of mockFiles.keys()) {
|
|
206
|
+
if (filePath.startsWith(path + '/')) {
|
|
207
|
+
mockFiles.delete(filePath)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
for (const dirPath of mockDirectories) {
|
|
212
|
+
if (dirPath.startsWith(path + '/')) {
|
|
213
|
+
mockDirectories.delete(dirPath)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
} else if (mockFiles.has(path)) {
|
|
218
|
+
// Remove single file
|
|
219
|
+
mockFiles.delete(path)
|
|
220
|
+
}
|
|
221
|
+
}),
|
|
222
|
+
|
|
223
|
+
chmodSync: jest.fn((path, mode) => {
|
|
224
|
+
// Mock implementation - just verify file exists
|
|
225
|
+
if (!mockFiles.has(path) && !mockDirectories.has(path)) {
|
|
226
|
+
const error = new Error(`ENOENT: no such file or directory, chmod '${path}'`)
|
|
227
|
+
error.code = 'ENOENT'
|
|
228
|
+
error.errno = -2
|
|
229
|
+
error.path = path
|
|
230
|
+
throw error
|
|
231
|
+
}
|
|
232
|
+
}),
|
|
233
|
+
|
|
234
|
+
// Callback-style asynchronous operations
|
|
235
|
+
readFile: jest.fn((path, encoding, callback) => {
|
|
236
|
+
if (typeof encoding === 'function') {
|
|
237
|
+
callback = encoding
|
|
238
|
+
encoding = 'utf8'
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
setTimeout(() => {
|
|
242
|
+
try {
|
|
243
|
+
const content = fs.readFileSync(path, encoding)
|
|
244
|
+
callback(null, content)
|
|
245
|
+
} catch (error) {
|
|
246
|
+
callback(error)
|
|
247
|
+
}
|
|
248
|
+
}, 0)
|
|
249
|
+
}),
|
|
250
|
+
|
|
251
|
+
writeFile: jest.fn((path, data, encoding, callback) => {
|
|
252
|
+
if (typeof encoding === 'function') {
|
|
253
|
+
callback = encoding
|
|
254
|
+
encoding = 'utf8'
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
setTimeout(() => {
|
|
258
|
+
try {
|
|
259
|
+
fs.writeFileSync(path, data, {encoding})
|
|
260
|
+
callback(null)
|
|
261
|
+
} catch (error) {
|
|
262
|
+
callback(error)
|
|
263
|
+
}
|
|
264
|
+
}, 0)
|
|
265
|
+
}),
|
|
266
|
+
|
|
267
|
+
appendFile: jest.fn((path, data, encoding, callback) => {
|
|
268
|
+
if (typeof encoding === 'function') {
|
|
269
|
+
callback = encoding
|
|
270
|
+
encoding = 'utf8'
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
setTimeout(() => {
|
|
274
|
+
try {
|
|
275
|
+
fs.appendFileSync(path, data, {encoding})
|
|
276
|
+
callback(null)
|
|
277
|
+
} catch (error) {
|
|
278
|
+
callback(error)
|
|
279
|
+
}
|
|
280
|
+
}, 0)
|
|
281
|
+
}),
|
|
282
|
+
|
|
283
|
+
// Asynchronous file operations
|
|
284
|
+
promises: {
|
|
285
|
+
readFile: jest.fn(async (path, encoding) => {
|
|
286
|
+
return fs.readFileSync(path, encoding)
|
|
287
|
+
}),
|
|
288
|
+
|
|
289
|
+
writeFile: jest.fn(async (path, data, options) => {
|
|
290
|
+
return fs.writeFileSync(path, data, options)
|
|
291
|
+
}),
|
|
292
|
+
|
|
293
|
+
appendFile: jest.fn(async (path, data, options) => {
|
|
294
|
+
return fs.appendFileSync(path, data, options)
|
|
295
|
+
}),
|
|
296
|
+
|
|
297
|
+
access: jest.fn(async (path, mode) => {
|
|
298
|
+
if (!mockFiles.has(path) && !mockDirectories.has(path)) {
|
|
299
|
+
const error = new Error(`ENOENT: no such file or directory, access '${path}'`)
|
|
300
|
+
error.code = 'ENOENT'
|
|
301
|
+
error.errno = -2
|
|
302
|
+
error.path = path
|
|
303
|
+
throw error
|
|
304
|
+
}
|
|
305
|
+
}),
|
|
306
|
+
|
|
307
|
+
stat: jest.fn(async path => {
|
|
308
|
+
return fs.statSync(path)
|
|
309
|
+
}),
|
|
310
|
+
|
|
311
|
+
lstat: jest.fn(async path => {
|
|
312
|
+
return fs.lstatSync(path)
|
|
313
|
+
}),
|
|
314
|
+
|
|
315
|
+
readdir: jest.fn(async path => {
|
|
316
|
+
return fs.readdirSync(path)
|
|
317
|
+
}),
|
|
318
|
+
|
|
319
|
+
mkdir: jest.fn(async (path, options) => {
|
|
320
|
+
return fs.mkdirSync(path, options)
|
|
321
|
+
}),
|
|
322
|
+
|
|
323
|
+
rmdir: jest.fn(async (path, options) => {
|
|
324
|
+
return fs.rmdirSync(path, options)
|
|
325
|
+
}),
|
|
326
|
+
|
|
327
|
+
unlink: jest.fn(async path => {
|
|
328
|
+
return fs.unlinkSync(path)
|
|
329
|
+
}),
|
|
330
|
+
|
|
331
|
+
copyFile: jest.fn(async (src, dest) => {
|
|
332
|
+
return fs.copyFileSync(src, dest)
|
|
333
|
+
}),
|
|
334
|
+
|
|
335
|
+
chmod: jest.fn(async (path, mode) => {
|
|
336
|
+
return fs.chmodSync(path, mode)
|
|
337
|
+
})
|
|
338
|
+
},
|
|
339
|
+
|
|
340
|
+
// Stream operations
|
|
341
|
+
createReadStream: jest.fn((path, options) => {
|
|
342
|
+
const {createMockStream} = require('./testHelpers')
|
|
343
|
+
const stream = createMockStream(true, false)
|
|
344
|
+
|
|
345
|
+
// Simulate reading file content
|
|
346
|
+
setTimeout(() => {
|
|
347
|
+
if (mockFiles.has(path)) {
|
|
348
|
+
stream.emit('data', Buffer.from(mockFiles.get(path)))
|
|
349
|
+
stream.emit('end')
|
|
350
|
+
} else {
|
|
351
|
+
const error = new Error(`ENOENT: no such file or directory, open '${path}'`)
|
|
352
|
+
error.code = 'ENOENT'
|
|
353
|
+
stream.emit('error', error)
|
|
354
|
+
}
|
|
355
|
+
}, 0)
|
|
356
|
+
|
|
357
|
+
return stream
|
|
358
|
+
}),
|
|
359
|
+
|
|
360
|
+
createWriteStream: jest.fn((path, options) => {
|
|
361
|
+
const {createMockStream} = require('./testHelpers')
|
|
362
|
+
const stream = createMockStream(false, true)
|
|
363
|
+
|
|
364
|
+
let content = ''
|
|
365
|
+
stream.write.mockImplementation(chunk => {
|
|
366
|
+
content += chunk.toString()
|
|
367
|
+
return true
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
stream.end.mockImplementation(chunk => {
|
|
371
|
+
if (chunk) content += chunk.toString()
|
|
372
|
+
mockFiles.set(path, content)
|
|
373
|
+
stream.emit('finish')
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
return stream
|
|
377
|
+
}),
|
|
378
|
+
|
|
379
|
+
// Watch operations
|
|
380
|
+
watch: jest.fn((filename, options, listener) => {
|
|
381
|
+
const {createMockEventEmitter} = require('./testHelpers')
|
|
382
|
+
const watcher = createMockEventEmitter()
|
|
383
|
+
|
|
384
|
+
watcher.close = jest.fn()
|
|
385
|
+
|
|
386
|
+
if (typeof options === 'function') {
|
|
387
|
+
listener = options
|
|
388
|
+
options = {}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (listener) {
|
|
392
|
+
watcher.on('change', listener)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return watcher
|
|
396
|
+
}),
|
|
397
|
+
|
|
398
|
+
watchFile: jest.fn((filename, options, listener) => {
|
|
399
|
+
if (typeof options === 'function') {
|
|
400
|
+
listener = options
|
|
401
|
+
options = {}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Mock implementation - just call listener immediately
|
|
405
|
+
if (listener) {
|
|
406
|
+
setTimeout(() => {
|
|
407
|
+
const stats = fs.statSync(filename)
|
|
408
|
+
listener(stats, stats)
|
|
409
|
+
}, 0)
|
|
410
|
+
}
|
|
411
|
+
}),
|
|
412
|
+
|
|
413
|
+
unwatchFile: jest.fn((filename, listener) => {
|
|
414
|
+
// Mock implementation - no-op
|
|
415
|
+
}),
|
|
416
|
+
|
|
417
|
+
// Constants
|
|
418
|
+
constants: {
|
|
419
|
+
F_OK: 0,
|
|
420
|
+
R_OK: 4,
|
|
421
|
+
W_OK: 2,
|
|
422
|
+
X_OK: 1,
|
|
423
|
+
O_RDONLY: 0,
|
|
424
|
+
O_WRONLY: 1,
|
|
425
|
+
O_RDWR: 2,
|
|
426
|
+
O_CREAT: 64,
|
|
427
|
+
O_EXCL: 128,
|
|
428
|
+
O_TRUNC: 512,
|
|
429
|
+
O_APPEND: 1024
|
|
430
|
+
},
|
|
431
|
+
|
|
432
|
+
// Test helpers
|
|
433
|
+
__setMockFiles: files => {
|
|
434
|
+
mockFiles.clear()
|
|
435
|
+
Object.entries(files).forEach(([path, content]) => {
|
|
436
|
+
mockFiles.set(path, content)
|
|
437
|
+
})
|
|
438
|
+
},
|
|
439
|
+
|
|
440
|
+
__setMockDirectories: directories => {
|
|
441
|
+
mockDirectories.clear()
|
|
442
|
+
directories.forEach(dir => mockDirectories.add(dir))
|
|
443
|
+
},
|
|
444
|
+
|
|
445
|
+
__getMockFiles: () => new Map(mockFiles),
|
|
446
|
+
__getMockDirectories: () => new Set(mockDirectories),
|
|
447
|
+
__resetFileSystem: resetFileSystem
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
module.exports = fs
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comprehensive mock for the global Candy object used in server tests
|
|
3
|
+
* Provides mocked implementations of core(), server(), cli(), and watchdog() methods
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class MockCandyPack {
|
|
7
|
+
constructor() {
|
|
8
|
+
this._registry = new Map()
|
|
9
|
+
this._singletons = new Map()
|
|
10
|
+
this._mocks = new Map()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Mock the core method to return mocked core modules
|
|
14
|
+
core(name, singleton = true) {
|
|
15
|
+
const key = `core:${name}`
|
|
16
|
+
|
|
17
|
+
if (this._mocks.has(key)) {
|
|
18
|
+
return this._mocks.get(key)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Default mocks for common core modules
|
|
22
|
+
const coreMocks = {
|
|
23
|
+
Config: {
|
|
24
|
+
config: {
|
|
25
|
+
server: {
|
|
26
|
+
pid: null,
|
|
27
|
+
started: null,
|
|
28
|
+
watchdog: null,
|
|
29
|
+
os: 'linux',
|
|
30
|
+
arch: 'x64'
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
force: jest.fn(),
|
|
34
|
+
reload: jest.fn(),
|
|
35
|
+
init: jest.fn()
|
|
36
|
+
},
|
|
37
|
+
Lang: {
|
|
38
|
+
get: jest.fn(key => key),
|
|
39
|
+
init: jest.fn()
|
|
40
|
+
},
|
|
41
|
+
Process: {
|
|
42
|
+
spawn: jest.fn(),
|
|
43
|
+
kill: jest.fn(),
|
|
44
|
+
init: jest.fn()
|
|
45
|
+
},
|
|
46
|
+
Commands: {
|
|
47
|
+
execute: jest.fn(),
|
|
48
|
+
init: jest.fn()
|
|
49
|
+
},
|
|
50
|
+
Log: {
|
|
51
|
+
init: jest.fn(function (moduleName) {
|
|
52
|
+
return {
|
|
53
|
+
log: jest.fn(),
|
|
54
|
+
error: jest.fn()
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const mock = coreMocks[name] || {
|
|
61
|
+
init: jest.fn()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this._mocks.set(key, mock)
|
|
65
|
+
return mock
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Mock the server method to return mocked server modules
|
|
69
|
+
server(name, singleton = true) {
|
|
70
|
+
const key = `server:${name}`
|
|
71
|
+
|
|
72
|
+
if (this._mocks.has(key)) {
|
|
73
|
+
return this._mocks.get(key)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Default mocks for server modules
|
|
77
|
+
const serverMocks = {
|
|
78
|
+
Api: {
|
|
79
|
+
init: jest.fn(),
|
|
80
|
+
start: jest.fn(),
|
|
81
|
+
stop: jest.fn(),
|
|
82
|
+
result: jest.fn((success, data) => ({success, data}))
|
|
83
|
+
},
|
|
84
|
+
Client: {
|
|
85
|
+
auth: jest.fn(),
|
|
86
|
+
call: jest.fn(),
|
|
87
|
+
init: jest.fn()
|
|
88
|
+
},
|
|
89
|
+
DNS: {
|
|
90
|
+
init: jest.fn(),
|
|
91
|
+
start: jest.fn(),
|
|
92
|
+
stop: jest.fn(),
|
|
93
|
+
add: jest.fn(),
|
|
94
|
+
delete: jest.fn(),
|
|
95
|
+
list: jest.fn()
|
|
96
|
+
},
|
|
97
|
+
Mail: {
|
|
98
|
+
init: jest.fn(),
|
|
99
|
+
start: jest.fn(),
|
|
100
|
+
stop: jest.fn(),
|
|
101
|
+
create: jest.fn(),
|
|
102
|
+
delete: jest.fn(),
|
|
103
|
+
list: jest.fn(),
|
|
104
|
+
password: jest.fn(),
|
|
105
|
+
send: jest.fn()
|
|
106
|
+
},
|
|
107
|
+
SSL: {
|
|
108
|
+
init: jest.fn(),
|
|
109
|
+
renew: jest.fn(),
|
|
110
|
+
check: jest.fn()
|
|
111
|
+
},
|
|
112
|
+
Server: {
|
|
113
|
+
init: jest.fn(),
|
|
114
|
+
start: jest.fn(),
|
|
115
|
+
stop: jest.fn()
|
|
116
|
+
},
|
|
117
|
+
Service: {
|
|
118
|
+
init: jest.fn(),
|
|
119
|
+
add: jest.fn(),
|
|
120
|
+
start: jest.fn(),
|
|
121
|
+
stop: jest.fn(),
|
|
122
|
+
list: jest.fn(),
|
|
123
|
+
status: jest.fn()
|
|
124
|
+
},
|
|
125
|
+
Subdomain: {
|
|
126
|
+
init: jest.fn(),
|
|
127
|
+
create: jest.fn(),
|
|
128
|
+
delete: jest.fn(),
|
|
129
|
+
list: jest.fn()
|
|
130
|
+
},
|
|
131
|
+
Web: {
|
|
132
|
+
init: jest.fn(),
|
|
133
|
+
start: jest.fn(),
|
|
134
|
+
stop: jest.fn(),
|
|
135
|
+
create: jest.fn(),
|
|
136
|
+
delete: jest.fn(),
|
|
137
|
+
list: jest.fn()
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const mock = serverMocks[name] || {
|
|
142
|
+
init: jest.fn()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
this._mocks.set(key, mock)
|
|
146
|
+
return mock
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Mock the cli method
|
|
150
|
+
cli(name, singleton = true) {
|
|
151
|
+
const key = `cli:${name}`
|
|
152
|
+
|
|
153
|
+
if (this._mocks.has(key)) {
|
|
154
|
+
return this._mocks.get(key)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const mock = {
|
|
158
|
+
init: jest.fn(),
|
|
159
|
+
execute: jest.fn()
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
this._mocks.set(key, mock)
|
|
163
|
+
return mock
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Mock the watchdog method
|
|
167
|
+
watchdog(name, singleton = true) {
|
|
168
|
+
const key = `watchdog:${name}`
|
|
169
|
+
|
|
170
|
+
if (this._mocks.has(key)) {
|
|
171
|
+
return this._mocks.get(key)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const mock = {
|
|
175
|
+
init: jest.fn(),
|
|
176
|
+
start: jest.fn(),
|
|
177
|
+
stop: jest.fn()
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
this._mocks.set(key, mock)
|
|
181
|
+
return mock
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Helper method to set custom mocks
|
|
185
|
+
setMock(type, name, mock) {
|
|
186
|
+
const key = `${type}:${name}`
|
|
187
|
+
this._mocks.set(key, mock)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Helper method to get a mock
|
|
191
|
+
getMock(type, name) {
|
|
192
|
+
const key = `${type}:${name}`
|
|
193
|
+
return this._mocks.get(key)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Helper method to clear all mocks
|
|
197
|
+
clearMocks() {
|
|
198
|
+
this._mocks.forEach(mock => {
|
|
199
|
+
if (mock && typeof mock === 'object') {
|
|
200
|
+
Object.values(mock).forEach(fn => {
|
|
201
|
+
if (jest.isMockFunction(fn)) {
|
|
202
|
+
fn.mockClear()
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Helper method to reset all mocks
|
|
210
|
+
resetMocks() {
|
|
211
|
+
this._mocks.clear()
|
|
212
|
+
this._registry.clear()
|
|
213
|
+
this._singletons.clear()
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Create the global mock instance
|
|
218
|
+
const mockCandy = new MockCandyPack()
|
|
219
|
+
|
|
220
|
+
// Mock the global __ function
|
|
221
|
+
const mockLangGet = jest.fn(key => key)
|
|
222
|
+
|
|
223
|
+
module.exports = {
|
|
224
|
+
mockCandy,
|
|
225
|
+
mockLangGet,
|
|
226
|
+
MockCandyPack
|
|
227
|
+
}
|