odac 1.1.0 → 1.3.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/.agent/rules/coding.md +27 -0
- package/.agent/rules/memory.md +42 -0
- package/.agent/rules/project.md +30 -0
- package/.agent/rules/workflow.md +16 -0
- package/.github/workflows/release.yml +68 -1
- package/.github/workflows/test-coverage.yml +6 -5
- package/.husky/pre-commit +10 -0
- package/.husky/pre-push +13 -0
- package/.releaserc.js +1 -1
- package/CHANGELOG.md +99 -0
- package/README.md +16 -0
- package/bin/odac.js +196 -38
- package/client/odac.js +238 -171
- package/docs/backend/01-overview/03-development-server.md +38 -45
- package/docs/backend/02-structure/01-typical-project-layout.md +59 -26
- package/docs/backend/03-config/00-configuration-overview.md +6 -6
- package/docs/backend/03-config/01-database-connection.md +2 -2
- package/docs/backend/03-config/02-static-route-mapping-optional.md +1 -1
- package/docs/backend/03-config/03-request-timeout.md +1 -1
- package/docs/backend/03-config/04-environment-variables.md +4 -4
- package/docs/backend/03-config/05-early-hints.md +2 -2
- package/docs/backend/04-routing/03-api-and-data-routes.md +18 -0
- package/docs/backend/04-routing/07-cron-jobs.md +17 -1
- package/docs/backend/04-routing/09-websocket.md +14 -1
- package/docs/backend/05-controllers/01-how-to-build-a-controller.md +48 -3
- package/docs/backend/05-controllers/03-controller-classes.md +40 -20
- package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +17 -0
- package/docs/backend/07-views/10-styling-and-tailwind.md +93 -0
- package/docs/backend/08-database/01-getting-started.md +2 -2
- package/docs/backend/10-authentication/03-register.md +1 -1
- package/docs/backend/10-authentication/04-odac-register-forms.md +2 -2
- package/docs/backend/10-authentication/05-session-management.md +15 -1
- package/docs/backend/10-authentication/06-odac-login-forms.md +2 -2
- package/docs/backend/10-authentication/07-magic-links.md +1 -1
- package/docs/index.json +5 -1
- package/jest.config.js +1 -1
- package/package.json +18 -14
- package/src/Auth.js +58 -23
- package/src/Config.js +7 -7
- package/src/Database.js +1 -1
- package/src/Env.js +3 -1
- package/src/Ipc.js +7 -0
- package/src/Lang.js +9 -2
- package/src/Mail.js +19 -9
- package/src/Odac.js +56 -44
- package/src/Request.js +6 -2
- package/src/Route/Cron.js +58 -17
- package/src/Route/Internal.js +1 -1
- package/src/Route.js +375 -125
- package/src/Server.js +40 -3
- package/src/Storage.js +4 -0
- package/src/Token.js +6 -4
- package/src/Validator.js +24 -15
- package/src/Var.js +22 -6
- package/src/View/EarlyHints.js +43 -33
- package/src/View/Form.js +17 -11
- package/src/View.js +68 -12
- package/src/WebSocket.js +45 -12
- package/template/package.json +3 -1
- package/template/view/content/home.html +3 -3
- package/template/view/head/main.html +2 -2
- package/test/Client.test.js +168 -0
- package/test/Config.test.js +112 -0
- package/test/Lang.test.js +92 -0
- package/test/Odac.test.js +88 -0
- package/test/{framework/middleware.test.js → Route/Middleware.test.js} +2 -2
- package/test/{framework/Route.test.js → Route.test.js} +105 -1
- package/test/{framework/View → View}/EarlyHints.test.js +1 -1
- package/test/WebSocket.test.js +238 -0
- package/test/scripts/check-coverage.js +4 -4
- package/test/cli/Cli.test.js +0 -36
- package/test/core/Commands.test.js +0 -538
- package/test/core/Config.test.js +0 -1432
- package/test/core/Lang.test.js +0 -250
- package/test/core/Odac.test.js +0 -234
- package/test/core/Process.test.js +0 -156
- package/test/framework/WebSocket.test.js +0 -100
- package/test/server/Api.test.js +0 -647
- package/test/server/DNS.test.js +0 -2050
- package/test/server/DNS.test.js.bak +0 -2084
- package/test/server/Hub.test.js +0 -497
- package/test/server/Log.test.js +0 -73
- package/test/server/Mail.account.test_.js +0 -460
- package/test/server/Mail.init.test_.js +0 -411
- package/test/server/Mail.test_.js +0 -1340
- package/test/server/SSL.test_.js +0 -1491
- package/test/server/Server.test.js +0 -765
- package/test/server/Service.test_.js +0 -1127
- package/test/server/Subdomain.test.js +0 -440
- package/test/server/Web/Firewall.test.js +0 -175
- package/test/server/Web/Proxy.test.js +0 -397
- package/test/server/Web.test.js +0 -1494
- package/test/server/__mocks__/acme-client.js +0 -17
- package/test/server/__mocks__/bcrypt.js +0 -50
- package/test/server/__mocks__/child_process.js +0 -389
- package/test/server/__mocks__/crypto.js +0 -432
- package/test/server/__mocks__/fs.js +0 -450
- package/test/server/__mocks__/globalOdac.js +0 -227
- package/test/server/__mocks__/http.js +0 -575
- package/test/server/__mocks__/https.js +0 -272
- package/test/server/__mocks__/index.js +0 -249
- package/test/server/__mocks__/mail/server.js +0 -100
- package/test/server/__mocks__/mail/smtp.js +0 -31
- package/test/server/__mocks__/mailparser.js +0 -81
- package/test/server/__mocks__/net.js +0 -369
- package/test/server/__mocks__/node-forge.js +0 -328
- package/test/server/__mocks__/os.js +0 -320
- package/test/server/__mocks__/path.js +0 -291
- package/test/server/__mocks__/selfsigned.js +0 -8
- package/test/server/__mocks__/server/src/mail/server.js +0 -100
- package/test/server/__mocks__/server/src/mail/smtp.js +0 -31
- package/test/server/__mocks__/smtp-server.js +0 -106
- package/test/server/__mocks__/sqlite3.js +0 -394
- package/test/server/__mocks__/testFactories.js +0 -299
- package/test/server/__mocks__/testHelpers.js +0 -363
- package/test/server/__mocks__/tls.js +0 -229
- /package/template/{config.json → odac.json} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const Route = require('
|
|
1
|
+
const Route = require('../src/Route')
|
|
2
2
|
|
|
3
3
|
describe('Route', () => {
|
|
4
4
|
let route
|
|
@@ -240,6 +240,110 @@ describe('Route', () => {
|
|
|
240
240
|
})
|
|
241
241
|
})
|
|
242
242
|
|
|
243
|
+
describe('parametric route matching (#controller via check)', () => {
|
|
244
|
+
const createMockOdac = (url, method = 'get') => ({
|
|
245
|
+
Auth: {check: jest.fn().mockResolvedValue(true)},
|
|
246
|
+
Config: {},
|
|
247
|
+
Request: {
|
|
248
|
+
abort: jest.fn().mockReturnValue('aborted'),
|
|
249
|
+
cookie: jest.fn(() => null),
|
|
250
|
+
data: {url: {}},
|
|
251
|
+
header: jest.fn(() => ''),
|
|
252
|
+
host: 'example.com',
|
|
253
|
+
isAjaxLoad: false,
|
|
254
|
+
method,
|
|
255
|
+
page: null,
|
|
256
|
+
res: {finished: false, writableEnded: false},
|
|
257
|
+
route: 'test_route',
|
|
258
|
+
setSession: jest.fn(),
|
|
259
|
+
ssl: false,
|
|
260
|
+
url
|
|
261
|
+
},
|
|
262
|
+
request: jest.fn().mockResolvedValue(null),
|
|
263
|
+
token: jest.fn().mockReturnValue(true)
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
const setupParamRoute = async (routeInstance, urlPattern, handler) => {
|
|
267
|
+
global.Odac.Route.buff = 'test_route'
|
|
268
|
+
routeInstance.set('get', urlPattern, handler, {token: false})
|
|
269
|
+
await Promise.all(routeInstance._pendingRouteLoads)
|
|
270
|
+
routeInstance._pendingRouteLoads = []
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
it('should match single parametric segment and extract params', async () => {
|
|
274
|
+
const handler = jest.fn().mockReturnValue({ok: true})
|
|
275
|
+
await setupParamRoute(route, '/users/{id}', handler)
|
|
276
|
+
|
|
277
|
+
const mockOdac = createMockOdac('/users/42')
|
|
278
|
+
await route.check(mockOdac)
|
|
279
|
+
|
|
280
|
+
expect(handler).toHaveBeenCalled()
|
|
281
|
+
expect(mockOdac.Request.data.url.id).toBe('42')
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
it('should match multi-parameter route and extract all params', async () => {
|
|
285
|
+
const handler = jest.fn().mockReturnValue({ok: true})
|
|
286
|
+
await setupParamRoute(route, '/users/{userId}/posts/{postId}', handler)
|
|
287
|
+
|
|
288
|
+
const mockOdac = createMockOdac('/users/7/posts/99')
|
|
289
|
+
await route.check(mockOdac)
|
|
290
|
+
|
|
291
|
+
expect(handler).toHaveBeenCalled()
|
|
292
|
+
expect(mockOdac.Request.data.url.userId).toBe('7')
|
|
293
|
+
expect(mockOdac.Request.data.url.postId).toBe('99')
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
it('should not match when static segments differ', async () => {
|
|
297
|
+
const handler = jest.fn().mockReturnValue({ok: true})
|
|
298
|
+
await setupParamRoute(route, '/users/{id}', handler)
|
|
299
|
+
|
|
300
|
+
const mockOdac = createMockOdac('/posts/42')
|
|
301
|
+
await route.check(mockOdac)
|
|
302
|
+
|
|
303
|
+
expect(handler).not.toHaveBeenCalled()
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
it('should not match when segment count differs', async () => {
|
|
307
|
+
const handler = jest.fn().mockReturnValue({ok: true})
|
|
308
|
+
await setupParamRoute(route, '/users/{id}', handler)
|
|
309
|
+
|
|
310
|
+
const mockOdac = createMockOdac('/users/42/extra')
|
|
311
|
+
await route.check(mockOdac)
|
|
312
|
+
|
|
313
|
+
expect(handler).not.toHaveBeenCalled()
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
it('should correctly match among multiple parametric routes (no arr mutation bug)', async () => {
|
|
317
|
+
const usersHandler = jest.fn().mockReturnValue({users: true})
|
|
318
|
+
const postsHandler = jest.fn().mockReturnValue({posts: true})
|
|
319
|
+
|
|
320
|
+
await setupParamRoute(route, '/users/{id}', usersHandler)
|
|
321
|
+
await setupParamRoute(route, '/posts/{id}', postsHandler)
|
|
322
|
+
|
|
323
|
+
// Request to /posts/5 should match postsHandler, not usersHandler
|
|
324
|
+
const mockOdac = createMockOdac('/posts/5')
|
|
325
|
+
await route.check(mockOdac)
|
|
326
|
+
|
|
327
|
+
expect(usersHandler).not.toHaveBeenCalled()
|
|
328
|
+
expect(postsHandler).toHaveBeenCalled()
|
|
329
|
+
expect(mockOdac.Request.data.url.id).toBe('5')
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
it('should prefer exact match over parametric match', async () => {
|
|
333
|
+
const exactHandler = jest.fn().mockReturnValue({exact: true})
|
|
334
|
+
const paramHandler = jest.fn().mockReturnValue({param: true})
|
|
335
|
+
|
|
336
|
+
await setupParamRoute(route, '/users/admin', exactHandler)
|
|
337
|
+
await setupParamRoute(route, '/users/{id}', paramHandler)
|
|
338
|
+
|
|
339
|
+
const mockOdac = createMockOdac('/users/admin')
|
|
340
|
+
await route.check(mockOdac)
|
|
341
|
+
|
|
342
|
+
expect(exactHandler).toHaveBeenCalled()
|
|
343
|
+
expect(paramHandler).not.toHaveBeenCalled()
|
|
344
|
+
})
|
|
345
|
+
})
|
|
346
|
+
|
|
243
347
|
describe('WebSocket cleanup', () => {
|
|
244
348
|
it('should call ws() method successfully', () => {
|
|
245
349
|
const handler = jest.fn()
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
const {WebSocketServer} = require('../src/WebSocket.js')
|
|
2
|
+
|
|
3
|
+
describe('WebSocketServer', () => {
|
|
4
|
+
let server
|
|
5
|
+
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
server = new WebSocketServer()
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
describe('route', () => {
|
|
11
|
+
it('should register a route', () => {
|
|
12
|
+
const handler = jest.fn()
|
|
13
|
+
server.route('/chat', handler)
|
|
14
|
+
expect(server.getRoute('/chat').handler).toBe(handler)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('should return null for unregistered route', () => {
|
|
18
|
+
expect(server.getRoute('/unknown')).toBeNull()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('should match parameterized routes', () => {
|
|
22
|
+
const handler = jest.fn()
|
|
23
|
+
server.route('/room/{id}', handler)
|
|
24
|
+
|
|
25
|
+
const result = server.getRoute('/room/123')
|
|
26
|
+
expect(result).toBeDefined()
|
|
27
|
+
expect(result.handler).toBe(handler)
|
|
28
|
+
expect(result.params).toEqual({id: '123'})
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('should match multiple parameters', () => {
|
|
32
|
+
const handler = jest.fn()
|
|
33
|
+
server.route('/chat/{room}/user/{userId}', handler)
|
|
34
|
+
|
|
35
|
+
const result = server.getRoute('/chat/general/user/42')
|
|
36
|
+
expect(result.params).toEqual({room: 'general', userId: '42'})
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
describe('rooms', () => {
|
|
41
|
+
it('should join and leave rooms', () => {
|
|
42
|
+
server.joinRoom('client1', 'room1')
|
|
43
|
+
server.joinRoom('client2', 'room1')
|
|
44
|
+
|
|
45
|
+
server.leaveRoom('client1', 'room1')
|
|
46
|
+
server.leaveRoom('client2', 'room1')
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
describe('broadcast', () => {
|
|
51
|
+
it('should send message to all connected clients', () => {
|
|
52
|
+
const client1 = {id: 'c1', send: jest.fn()}
|
|
53
|
+
const client2 = {id: 'c2', send: jest.fn()}
|
|
54
|
+
|
|
55
|
+
server.clients.set('c1', client1)
|
|
56
|
+
server.clients.set('c2', client2)
|
|
57
|
+
|
|
58
|
+
server.broadcast('hello')
|
|
59
|
+
|
|
60
|
+
expect(client1.send).toHaveBeenCalledWith('hello')
|
|
61
|
+
expect(client2.send).toHaveBeenCalledWith('hello')
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('should exclude specified client from broadcast', () => {
|
|
65
|
+
const client1 = {id: 'c1', send: jest.fn()}
|
|
66
|
+
const client2 = {id: 'c2', send: jest.fn()}
|
|
67
|
+
|
|
68
|
+
server.clients.set('c1', client1)
|
|
69
|
+
server.clients.set('c2', client2)
|
|
70
|
+
|
|
71
|
+
server.broadcast('hello', 'c1')
|
|
72
|
+
|
|
73
|
+
expect(client1.send).not.toHaveBeenCalled()
|
|
74
|
+
expect(client2.send).toHaveBeenCalledWith('hello')
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
describe('clients', () => {
|
|
79
|
+
it('should track client count', () => {
|
|
80
|
+
expect(server.clientCount).toBe(0)
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
describe('cleanup on disconnect', () => {
|
|
85
|
+
it('should be handled by Route.setWs wrapper', () => {
|
|
86
|
+
expect(true).toBe(true)
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
describe('maxPayload', () => {
|
|
90
|
+
it('should close connection if payload exceeds limit', () => {
|
|
91
|
+
const socket = {
|
|
92
|
+
pause: jest.fn(),
|
|
93
|
+
resume: jest.fn(),
|
|
94
|
+
on: jest.fn(),
|
|
95
|
+
write: jest.fn(),
|
|
96
|
+
end: jest.fn(),
|
|
97
|
+
removeAllListeners: jest.fn()
|
|
98
|
+
}
|
|
99
|
+
const {WebSocketClient} = require('../src/WebSocket.js')
|
|
100
|
+
new WebSocketClient(socket, server, 'test-id', {maxPayload: 10})
|
|
101
|
+
|
|
102
|
+
// We must send a MASKED frame because server expects masked frames from client
|
|
103
|
+
const buffer = Buffer.alloc(100)
|
|
104
|
+
buffer[0] = 0x81 // fin + text
|
|
105
|
+
buffer[1] = 0x80 | 20 // masked + length 20
|
|
106
|
+
// Mask key (4 bytes) + Payload (20 bytes) needed but header check happens first
|
|
107
|
+
|
|
108
|
+
const dataHandler = socket.on.mock.calls.find(call => call[0] === 'data')[1]
|
|
109
|
+
dataHandler(buffer)
|
|
110
|
+
|
|
111
|
+
expect(socket.end).toHaveBeenCalled()
|
|
112
|
+
// Verify close frame sent with 1009
|
|
113
|
+
// socket.write is called to send the Close frame
|
|
114
|
+
const writeCall = socket.write.mock.calls[0][0]
|
|
115
|
+
expect(writeCall[2]).toBe(0x03) // 1009 >> 8
|
|
116
|
+
expect(writeCall[3]).toBe(0xf1) // 1009 & 0xff
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
describe('rateLimit', () => {
|
|
121
|
+
it('should close connection if rate limit exceeded', () => {
|
|
122
|
+
const socket = {
|
|
123
|
+
pause: jest.fn(),
|
|
124
|
+
resume: jest.fn(),
|
|
125
|
+
on: jest.fn(),
|
|
126
|
+
write: jest.fn(),
|
|
127
|
+
end: jest.fn(),
|
|
128
|
+
removeAllListeners: jest.fn()
|
|
129
|
+
}
|
|
130
|
+
const {WebSocketClient} = require('../src/WebSocket.js')
|
|
131
|
+
new WebSocketClient(socket, server, 'test-id', {
|
|
132
|
+
rateLimit: {max: 2, window: 1000}
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
// Valid MASKED frame (exact size 7 bytes)
|
|
136
|
+
const buffer = Buffer.alloc(7)
|
|
137
|
+
buffer[0] = 0x81
|
|
138
|
+
buffer[1] = 0x80 | 1 // masked + length 1
|
|
139
|
+
buffer[2] = 0x00
|
|
140
|
+
buffer[3] = 0x00
|
|
141
|
+
buffer[4] = 0x00
|
|
142
|
+
buffer[5] = 0x00 // mask key (0)
|
|
143
|
+
buffer[6] = 0x61 // 'a' (masked with 0 remains 'a')
|
|
144
|
+
|
|
145
|
+
const dataHandler = socket.on.mock.calls.find(call => call[0] === 'data')[1]
|
|
146
|
+
|
|
147
|
+
// Send 3 messages (limit is 2)
|
|
148
|
+
dataHandler(buffer)
|
|
149
|
+
dataHandler(buffer)
|
|
150
|
+
dataHandler(buffer)
|
|
151
|
+
|
|
152
|
+
expect(socket.end).toHaveBeenCalled()
|
|
153
|
+
// Verify close frame sent with 1008
|
|
154
|
+
const writeCalls = socket.write.mock.calls
|
|
155
|
+
const writeCall = writeCalls[writeCalls.length - 1][0]
|
|
156
|
+
expect(writeCall[2]).toBe(0x03) // 1008 >> 8
|
|
157
|
+
expect(writeCall[3]).toBe(0xf0) // 1008 & 0xff
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('should reset count after window', done => {
|
|
161
|
+
const socket = {
|
|
162
|
+
pause: jest.fn(),
|
|
163
|
+
resume: jest.fn(),
|
|
164
|
+
on: jest.fn(),
|
|
165
|
+
write: jest.fn(),
|
|
166
|
+
end: jest.fn(),
|
|
167
|
+
removeAllListeners: jest.fn()
|
|
168
|
+
}
|
|
169
|
+
const {WebSocketClient} = require('../src/WebSocket.js')
|
|
170
|
+
const client = new WebSocketClient(socket, server, 'test-id', {
|
|
171
|
+
rateLimit: {max: 2, window: 200}
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
const buffer = Buffer.alloc(7)
|
|
175
|
+
buffer[0] = 0x81
|
|
176
|
+
buffer[1] = 0x80 | 1
|
|
177
|
+
buffer[2] = 0x00
|
|
178
|
+
buffer[3] = 0x00
|
|
179
|
+
buffer[4] = 0x00
|
|
180
|
+
buffer[5] = 0x00
|
|
181
|
+
buffer[6] = 0x61
|
|
182
|
+
|
|
183
|
+
const dataHandler = socket.on.mock.calls.find(call => call[0] === 'data')[1]
|
|
184
|
+
|
|
185
|
+
// Send 2 messages
|
|
186
|
+
dataHandler(buffer)
|
|
187
|
+
dataHandler(buffer)
|
|
188
|
+
expect(socket.end).not.toHaveBeenCalled()
|
|
189
|
+
|
|
190
|
+
setTimeout(() => {
|
|
191
|
+
// Send 1 more after window reset
|
|
192
|
+
dataHandler(buffer)
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
expect(socket.end).not.toHaveBeenCalled()
|
|
196
|
+
client.close()
|
|
197
|
+
done()
|
|
198
|
+
} catch (error) {
|
|
199
|
+
client.close()
|
|
200
|
+
done(error)
|
|
201
|
+
}
|
|
202
|
+
}, 300)
|
|
203
|
+
})
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
describe('Route WebSocket Integration', () => {
|
|
208
|
+
const Route = require('../src/Route.js')
|
|
209
|
+
|
|
210
|
+
beforeEach(() => {
|
|
211
|
+
global.Odac = {
|
|
212
|
+
Route: new Route()
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
it('should support ws() method', () => {
|
|
217
|
+
expect(typeof Odac.Route.ws).toBe('function')
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
it('should support auth.ws() method', () => {
|
|
221
|
+
expect(typeof Odac.Route.auth.ws).toBe('function')
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
it('should support middleware with ws()', () => {
|
|
225
|
+
const chain = Odac.Route.use('test-middleware')
|
|
226
|
+
expect(typeof chain.ws).toBe('function')
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
it('should support middleware with auth.ws()', () => {
|
|
230
|
+
const chain = Odac.Route.use('test-middleware')
|
|
231
|
+
expect(typeof chain.auth.ws).toBe('function')
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
it('should support auth.use() with ws()', () => {
|
|
235
|
+
const chain = Odac.Route.auth.use('test-middleware')
|
|
236
|
+
expect(typeof chain.ws).toBe('function')
|
|
237
|
+
})
|
|
238
|
+
})
|
|
@@ -17,7 +17,7 @@ function getStagedFiles() {
|
|
|
17
17
|
return output
|
|
18
18
|
.split('\n')
|
|
19
19
|
.filter(file => file.endsWith('.js'))
|
|
20
|
-
.filter(file => file.startsWith('
|
|
20
|
+
.filter(file => file.startsWith('src/') || file.startsWith('client/'))
|
|
21
21
|
.filter(file => !file.includes('.test.js') && !file.includes('.spec.js'))
|
|
22
22
|
} catch (err) {
|
|
23
23
|
console.error('Error getting staged files:', err.message)
|
|
@@ -34,7 +34,7 @@ function checkTestFiles(changedFiles) {
|
|
|
34
34
|
if (!fs.existsSync(file)) continue
|
|
35
35
|
|
|
36
36
|
// Determine test file path
|
|
37
|
-
const testFile = file.replace(/^(
|
|
37
|
+
const testFile = file.replace(/^(src|client)\//, 'test/').replace(/\.js$/, '.test.js')
|
|
38
38
|
|
|
39
39
|
if (!fs.existsSync(testFile)) {
|
|
40
40
|
missingTests.push({
|
|
@@ -60,7 +60,7 @@ function runTestsForFiles(files) {
|
|
|
60
60
|
// Create a pattern to match test files for changed source files
|
|
61
61
|
const testPatterns = files
|
|
62
62
|
.map(file => {
|
|
63
|
-
const testFile = file.replace(/^(
|
|
63
|
+
const testFile = file.replace(/^(src|client)\//, 'test/').replace(/\.js$/, '.test.js')
|
|
64
64
|
return testFile
|
|
65
65
|
})
|
|
66
66
|
.filter(testFile => fs.existsSync(testFile))
|
|
@@ -98,7 +98,7 @@ function main() {
|
|
|
98
98
|
const changedFiles = getStagedFiles()
|
|
99
99
|
|
|
100
100
|
if (changedFiles.length === 0) {
|
|
101
|
-
console.log('✓ No
|
|
101
|
+
console.log('✓ No src or client files changed\n')
|
|
102
102
|
process.exit(0)
|
|
103
103
|
}
|
|
104
104
|
|
package/test/cli/Cli.test.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
const Cli = require('../../cli/src/Cli.js')
|
|
2
|
-
|
|
3
|
-
describe('CLI Prefix Argument Parsing', () => {
|
|
4
|
-
test('parseArg should extract value after prefix', () => {
|
|
5
|
-
const args = ['web', 'create', '-d', 'example.com', '--other', 'value']
|
|
6
|
-
|
|
7
|
-
expect(Cli.parseArg(args, ['-d', '--domain'])).toBe('example.com')
|
|
8
|
-
expect(Cli.parseArg(args, ['--other'])).toBe('value')
|
|
9
|
-
expect(Cli.parseArg(args, ['-x', '--missing'])).toBeNull()
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
test('parseArg should handle multiple prefix options', () => {
|
|
13
|
-
const args = ['mail', 'create', '--email', 'test@example.com', '-p', 'password123']
|
|
14
|
-
|
|
15
|
-
expect(Cli.parseArg(args, ['-e', '--email'])).toBe('test@example.com')
|
|
16
|
-
expect(Cli.parseArg(args, ['-p', '--password'])).toBe('password123')
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
test('parseArg should return null for missing arguments', () => {
|
|
20
|
-
const args = ['web', 'create']
|
|
21
|
-
|
|
22
|
-
expect(Cli.parseArg(args, ['-d', '--domain'])).toBeNull()
|
|
23
|
-
expect(Cli.parseArg(null, ['-d'])).toBeNull()
|
|
24
|
-
expect(Cli.parseArg(args, null)).toBeNull()
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
test('parseArg should handle edge cases', () => {
|
|
28
|
-
const args = ['command', '-d']
|
|
29
|
-
|
|
30
|
-
// Missing value after prefix
|
|
31
|
-
expect(Cli.parseArg(args, ['-d'])).toBeNull()
|
|
32
|
-
|
|
33
|
-
// Empty args
|
|
34
|
-
expect(Cli.parseArg([], ['-d'])).toBeNull()
|
|
35
|
-
})
|
|
36
|
-
})
|