odac 1.4.1 → 1.4.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 (77) hide show
  1. package/.agent/rules/memory.md +5 -0
  2. package/.releaserc.js +9 -2
  3. package/CHANGELOG.md +64 -0
  4. package/README.md +1 -1
  5. package/bin/odac.js +3 -2
  6. package/client/odac.js +124 -28
  7. package/docs/ai/skills/backend/database.md +19 -0
  8. package/docs/ai/skills/backend/forms.md +107 -13
  9. package/docs/ai/skills/backend/migrations.md +8 -2
  10. package/docs/ai/skills/backend/validation.md +132 -32
  11. package/docs/ai/skills/frontend/forms.md +43 -15
  12. package/docs/backend/08-database/02-basics.md +49 -9
  13. package/docs/backend/08-database/04-migrations.md +1 -0
  14. package/package.json +1 -1
  15. package/src/Auth.js +15 -2
  16. package/src/Database/ConnectionFactory.js +1 -0
  17. package/src/Database/Migration.js +26 -1
  18. package/src/Database/nanoid.js +30 -0
  19. package/src/Database.js +122 -11
  20. package/src/Ipc.js +37 -0
  21. package/src/Odac.js +1 -1
  22. package/src/Route/Cron.js +11 -0
  23. package/src/Route.js +49 -30
  24. package/src/Server.js +77 -23
  25. package/src/Storage.js +15 -1
  26. package/src/Validator.js +22 -20
  27. package/test/{Auth.test.js → Auth/check.test.js} +91 -5
  28. package/test/Client/data.test.js +91 -0
  29. package/test/Client/get.test.js +90 -0
  30. package/test/Client/storage.test.js +87 -0
  31. package/test/Client/token.test.js +82 -0
  32. package/test/Client/ws.test.js +118 -0
  33. package/test/Config/deepMerge.test.js +14 -0
  34. package/test/Config/init.test.js +66 -0
  35. package/test/Config/interpolate.test.js +35 -0
  36. package/test/Database/ConnectionFactory/buildConnectionConfig.test.js +13 -0
  37. package/test/Database/ConnectionFactory/buildConnections.test.js +31 -0
  38. package/test/Database/ConnectionFactory/resolveClient.test.js +12 -0
  39. package/test/Database/Migration/migrate_column.test.js +52 -0
  40. package/test/Database/Migration/migrate_files.test.js +70 -0
  41. package/test/Database/Migration/migrate_index.test.js +89 -0
  42. package/test/Database/Migration/migrate_nanoid.test.js +160 -0
  43. package/test/Database/Migration/migrate_seed.test.js +77 -0
  44. package/test/Database/Migration/migrate_table.test.js +88 -0
  45. package/test/Database/Migration/rollback.test.js +61 -0
  46. package/test/Database/Migration/snapshot.test.js +38 -0
  47. package/test/Database/Migration/status.test.js +41 -0
  48. package/test/Database/autoNanoid.test.js +215 -0
  49. package/test/Database/nanoid.test.js +19 -0
  50. package/test/Lang/constructor.test.js +25 -0
  51. package/test/Lang/get.test.js +65 -0
  52. package/test/Lang/set.test.js +49 -0
  53. package/test/Odac/init.test.js +42 -0
  54. package/test/Odac/instance.test.js +58 -0
  55. package/test/Route/{Middleware.test.js → Middleware/chaining.test.js} +5 -29
  56. package/test/Route/Middleware/use.test.js +35 -0
  57. package/test/{Route.test.js → Route/check.test.js} +100 -50
  58. package/test/Route/set.test.js +52 -0
  59. package/test/Route/ws.test.js +23 -0
  60. package/test/View/EarlyHints/cache.test.js +32 -0
  61. package/test/View/EarlyHints/extractFromHtml.test.js +143 -0
  62. package/test/View/EarlyHints/formatLinkHeader.test.js +33 -0
  63. package/test/View/EarlyHints/send.test.js +99 -0
  64. package/test/View/{Form.test.js → Form/generateFieldHtml.test.js} +2 -2
  65. package/test/View/constructor.test.js +22 -0
  66. package/test/View/print.test.js +19 -0
  67. package/test/WebSocket/Client/limits.test.js +55 -0
  68. package/test/WebSocket/Server/broadcast.test.js +33 -0
  69. package/test/WebSocket/Server/route.test.js +37 -0
  70. package/test/Client.test.js +0 -197
  71. package/test/Config.test.js +0 -119
  72. package/test/Database/ConnectionFactory.test.js +0 -80
  73. package/test/Lang.test.js +0 -92
  74. package/test/Migration.test.js +0 -943
  75. package/test/Odac.test.js +0 -88
  76. package/test/View/EarlyHints.test.js +0 -282
  77. package/test/WebSocket.test.js +0 -238
@@ -0,0 +1,215 @@
1
+ 'use strict'
2
+
3
+ const path = require('node:path')
4
+ const fs = require('node:fs')
5
+ const os = require('node:os')
6
+
7
+ /**
8
+ * Tests the Database.js proxy's auto-nanoid insert interception and schema metadata loading.
9
+ * Why: Validates that columns with type 'nanoid' are auto-populated on insert
10
+ * without requiring manual Odac.DB.nanoid() calls — core to ODAC's zero-config philosophy.
11
+ */
12
+
13
+ let tmpDir, knexLib, db
14
+
15
+ beforeEach(() => {
16
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'odac-db-autonanoid-'))
17
+ knexLib = require('knex')
18
+ db = knexLib({client: 'sqlite3', connection: {filename: ':memory:'}, useNullAsDefault: true})
19
+ })
20
+
21
+ afterEach(async () => {
22
+ await db.destroy()
23
+ fs.rmSync(tmpDir, {recursive: true, force: true})
24
+ jest.resetModules()
25
+ })
26
+
27
+ function writeSchema(name, content) {
28
+ const dir = path.join(tmpDir, 'schema')
29
+ fs.mkdirSync(dir, {recursive: true})
30
+ fs.writeFileSync(path.join(dir, `${name}.js`), `module.exports = ${JSON.stringify(content, null, 2)}`)
31
+ }
32
+
33
+ describe('Database.js - Auto NanoID Insert', () => {
34
+ it('should auto-generate nanoid on single-row insert', async () => {
35
+ await db.schema.createTable('posts', table => {
36
+ table.string('id', 21).primary()
37
+ table.string('title', 255)
38
+ })
39
+
40
+ const DB = require('../../src/Database')
41
+ DB.connections = {default: db}
42
+ db._odacConnectionKey = 'default'
43
+ DB._nanoidColumns = {default: {posts: [{column: 'id', size: 21}]}}
44
+
45
+ // Access through the proxy — Odac.DB.posts.insert()
46
+ await DB.posts.insert({title: 'Hello World'})
47
+
48
+ const rows = await db('posts').select()
49
+ expect(rows).toHaveLength(1)
50
+ expect(rows[0].title).toBe('Hello World')
51
+ expect(typeof rows[0].id).toBe('string')
52
+ expect(rows[0].id.length).toBe(21)
53
+ expect(rows[0].id).toMatch(/^[a-zA-Z0-9]+$/)
54
+ })
55
+
56
+ it('should auto-generate unique nanoid for bulk inserts', async () => {
57
+ await db.schema.createTable('items', table => {
58
+ table.string('id', 21).primary()
59
+ table.string('name', 100)
60
+ })
61
+
62
+ const DB = require('../../src/Database')
63
+ DB.connections = {default: db}
64
+ db._odacConnectionKey = 'default'
65
+ DB._nanoidColumns = {default: {items: [{column: 'id', size: 21}]}}
66
+
67
+ await DB.items.insert([{name: 'Item A'}, {name: 'Item B'}, {name: 'Item C'}])
68
+
69
+ const rows = await db('items').select()
70
+ expect(rows).toHaveLength(3)
71
+
72
+ const ids = rows.map(r => r.id)
73
+ expect(new Set(ids).size).toBe(3) // all unique
74
+
75
+ for (const id of ids) {
76
+ expect(id.length).toBe(21)
77
+ expect(id).toMatch(/^[a-zA-Z0-9]+$/)
78
+ }
79
+ })
80
+
81
+ it('should NOT overwrite user-provided id', async () => {
82
+ await db.schema.createTable('docs', table => {
83
+ table.string('id', 21).primary()
84
+ table.string('content', 500)
85
+ })
86
+
87
+ const DB = require('../../src/Database')
88
+ DB.connections = {default: db}
89
+ db._odacConnectionKey = 'default'
90
+ DB._nanoidColumns = {default: {docs: [{column: 'id', size: 21}]}}
91
+
92
+ await DB.docs.insert({id: 'MY_CUSTOM_ID_1234567', content: 'Test'})
93
+
94
+ const rows = await db('docs').select()
95
+ expect(rows[0].id).toBe('MY_CUSTOM_ID_1234567')
96
+ })
97
+
98
+ it('should support custom nanoid length per column', async () => {
99
+ await db.schema.createTable('tokens', table => {
100
+ table.string('code', 8).primary()
101
+ table.string('label', 100)
102
+ })
103
+
104
+ const DB = require('../../src/Database')
105
+ DB.connections = {default: db}
106
+ db._odacConnectionKey = 'default'
107
+ DB._nanoidColumns = {default: {tokens: [{column: 'code', size: 8}]}}
108
+
109
+ await DB.tokens.insert({label: 'discount'})
110
+
111
+ const rows = await db('tokens').select()
112
+ expect(rows[0].code.length).toBe(8)
113
+ expect(rows[0].code).toMatch(/^[a-zA-Z0-9]+$/)
114
+ })
115
+
116
+ it('should NOT interfere with tables without nanoid columns', async () => {
117
+ await db.schema.createTable('logs', table => {
118
+ table.increments('id')
119
+ table.string('message', 500)
120
+ })
121
+
122
+ const DB = require('../../src/Database')
123
+ DB.connections = {default: db}
124
+ db._odacConnectionKey = 'default'
125
+ DB._nanoidColumns = {} // no nanoid metadata
126
+
127
+ await DB.logs.insert({message: 'test log'})
128
+
129
+ const rows = await db('logs').select()
130
+ expect(rows).toHaveLength(1)
131
+ expect(rows[0].message).toBe('test log')
132
+ expect(rows[0].id).toBe(1) // auto-increment
133
+ })
134
+ })
135
+
136
+ describe('Database._loadNanoidMeta()', () => {
137
+ it('should detect nanoid columns from schema files', () => {
138
+ writeSchema('users', {
139
+ columns: {
140
+ id: {type: 'nanoid', primary: true},
141
+ name: {type: 'string', length: 255}
142
+ }
143
+ })
144
+
145
+ const DB = require('../../src/Database')
146
+ global.__dir = tmpDir
147
+ DB._nanoidColumns = {}
148
+ DB._loadNanoidMeta()
149
+
150
+ expect(DB._nanoidColumns).toHaveProperty('default')
151
+ expect(DB._nanoidColumns.default).toHaveProperty('users')
152
+ expect(DB._nanoidColumns.default.users).toEqual([{column: 'id', size: 21}])
153
+ })
154
+
155
+ it('should detect multiple nanoid columns in a single table', () => {
156
+ writeSchema('events', {
157
+ columns: {
158
+ id: {type: 'nanoid', primary: true},
159
+ public_id: {type: 'nanoid', length: 12},
160
+ name: {type: 'string'}
161
+ }
162
+ })
163
+
164
+ const DB = require('../../src/Database')
165
+ global.__dir = tmpDir
166
+ DB._nanoidColumns = {}
167
+ DB._loadNanoidMeta()
168
+
169
+ expect(DB._nanoidColumns.default.events).toEqual([
170
+ {column: 'id', size: 21},
171
+ {column: 'public_id', size: 12}
172
+ ])
173
+ })
174
+
175
+ it('should skip tables without nanoid columns', () => {
176
+ writeSchema('logs', {
177
+ columns: {
178
+ id: {type: 'increments'},
179
+ message: {type: 'text'}
180
+ }
181
+ })
182
+
183
+ const DB = require('../../src/Database')
184
+ global.__dir = tmpDir
185
+ DB._nanoidColumns = {}
186
+ DB._loadNanoidMeta()
187
+
188
+ expect(DB._nanoidColumns.default).not.toHaveProperty('logs')
189
+ })
190
+
191
+ it('should handle missing schema directory gracefully', () => {
192
+ const DB = require('../../src/Database')
193
+ global.__dir = tmpDir
194
+ DB._nanoidColumns = {}
195
+ DB._loadNanoidMeta()
196
+
197
+ expect(DB._nanoidColumns).toEqual({})
198
+ })
199
+
200
+ it('should detect nanoid in subdirectory schemas (named connections)', () => {
201
+ const subDir = path.join(tmpDir, 'schema', 'analytics')
202
+ fs.mkdirSync(subDir, {recursive: true})
203
+ fs.writeFileSync(
204
+ path.join(subDir, 'metrics.js'),
205
+ `module.exports = ${JSON.stringify({columns: {id: {type: 'nanoid', length: 16}, value: {type: 'integer'}}})}`
206
+ )
207
+
208
+ const DB = require('../../src/Database')
209
+ global.__dir = tmpDir
210
+ DB._nanoidColumns = {}
211
+ DB._loadNanoidMeta()
212
+
213
+ expect(DB._nanoidColumns.analytics.metrics).toEqual([{column: 'id', size: 16}])
214
+ })
215
+ })
@@ -0,0 +1,19 @@
1
+ const DB = require('../../src/Database')
2
+
3
+ describe('Database.nanoid()', () => {
4
+ it('should generate a string of default length (21)', () => {
5
+ const id = DB.nanoid()
6
+ expect(typeof id).toBe('string')
7
+ expect(id.length).toBe(21)
8
+ })
9
+
10
+ it('should generate a string of specified length', () => {
11
+ const id = DB.nanoid(10)
12
+ expect(id.length).toBe(10)
13
+ })
14
+
15
+ it('should generate only alphanumeric characters', () => {
16
+ const id = DB.nanoid(100)
17
+ expect(id).toMatch(/^[a-zA-Z0-9]+$/)
18
+ })
19
+ })
@@ -0,0 +1,25 @@
1
+ const Lang = require('../../src/Lang')
2
+
3
+ describe('Lang.constructor()', () => {
4
+ let mockOdac
5
+
6
+ beforeEach(() => {
7
+ mockOdac = {
8
+ Config: {lang: {default: 'en'}},
9
+ Var: jest.fn(() => ({is: () => true})),
10
+ Request: {header: jest.fn()}
11
+ }
12
+ global.__dir = '/mock'
13
+ })
14
+
15
+ it('should initialize successfully', () => {
16
+ const lang = new Lang(mockOdac)
17
+ expect(lang).toBeDefined()
18
+ })
19
+
20
+ it('should call Var if lang is provided', () => {
21
+ const lang = new Lang(mockOdac)
22
+ lang.set('en')
23
+ expect(mockOdac.Var).toHaveBeenCalledWith('en')
24
+ })
25
+ })
@@ -0,0 +1,65 @@
1
+ const fs = require('fs')
2
+ const Lang = require('../../src/Lang')
3
+
4
+ jest.mock('fs')
5
+
6
+ describe('Lang.get()', () => {
7
+ let mockOdac
8
+ let lang
9
+
10
+ beforeEach(() => {
11
+ jest.clearAllMocks()
12
+ global.__dir = '/mock'
13
+
14
+ mockOdac = {
15
+ Config: {lang: {default: 'en'}},
16
+ Var: jest.fn(val => ({
17
+ is: jest.fn(type => type === 'alpha' && /^[a-zA-Z]+$/.test(val))
18
+ })),
19
+ Request: {
20
+ header: jest.fn()
21
+ }
22
+ }
23
+
24
+ fs.existsSync.mockReturnValue(false)
25
+ fs.mkdirSync.mockImplementation(() => {})
26
+ fs.writeFileSync.mockImplementation(() => {})
27
+ fs.readFileSync.mockImplementation(() => {
28
+ throw new Error('ENOENT')
29
+ })
30
+
31
+ lang = new Lang(mockOdac)
32
+ })
33
+
34
+ it('should return matching string and support placeholders', () => {
35
+ fs.existsSync.mockImplementation(path => path.includes('/tr.json'))
36
+ fs.readFileSync.mockReturnValue(
37
+ JSON.stringify({
38
+ welcome: 'Merhaba %s!'
39
+ })
40
+ )
41
+
42
+ lang.set('tr')
43
+ expect(lang.get('welcome', 'Emre')).toBe('Merhaba Emre!')
44
+ })
45
+
46
+ it('should support numbered placeholders', () => {
47
+ fs.existsSync.mockImplementation(path => path.includes('/en.json'))
48
+ fs.readFileSync.mockReturnValue(
49
+ JSON.stringify({
50
+ order: 'First: %s1, Second: %s2'
51
+ })
52
+ )
53
+
54
+ lang.set('en')
55
+ expect(lang.get('order', 'A', 'B')).toBe('First: A, Second: B')
56
+ })
57
+
58
+ it('should auto-save new keys', () => {
59
+ lang.set('en')
60
+ const result = lang.get('new_key')
61
+
62
+ expect(result).toBe('new_key')
63
+ expect(fs.writeFileSync).toHaveBeenCalledWith(expect.stringContaining('/en.json'), expect.stringContaining('"new_key": "new_key"'))
64
+ })
65
+ })
@@ -0,0 +1,49 @@
1
+ const fs = require('fs')
2
+ const Lang = require('../../src/Lang')
3
+
4
+ jest.mock('fs')
5
+
6
+ describe('Lang.set()', () => {
7
+ let mockOdac
8
+ let lang
9
+
10
+ beforeEach(() => {
11
+ jest.clearAllMocks()
12
+ global.__dir = '/mock'
13
+
14
+ mockOdac = {
15
+ Config: {lang: {default: 'en'}},
16
+ Var: jest.fn(val => ({
17
+ is: jest.fn(type => type === 'alpha' && /^[a-zA-Z]+$/.test(val))
18
+ })),
19
+ Request: {
20
+ header: jest.fn()
21
+ }
22
+ }
23
+
24
+ fs.existsSync.mockReturnValue(false)
25
+ fs.readFileSync.mockImplementation(() => {
26
+ throw new Error('ENOENT')
27
+ })
28
+ })
29
+
30
+ it('should default to en when no config or header', () => {
31
+ lang = new Lang(mockOdac)
32
+ lang.set()
33
+ // No Var call here because lang is falsy
34
+ })
35
+
36
+ it('should use lang from header if available', () => {
37
+ mockOdac.Request.header.mockReturnValue('tr-TR')
38
+ lang = new Lang(mockOdac)
39
+ lang.set()
40
+ // Verifying it uses 'tr' can be tricky since it's private, but constructor calls set()
41
+ expect(mockOdac.Request.header).toHaveBeenCalledWith('ACCEPT-LANGUAGE')
42
+ })
43
+
44
+ it('should use explicit lang in set()', () => {
45
+ lang = new Lang(mockOdac)
46
+ lang.set('fr')
47
+ expect(mockOdac.Var).toHaveBeenCalledWith('fr')
48
+ })
49
+ })
@@ -0,0 +1,42 @@
1
+ const Odac = require('../../src/Odac')
2
+
3
+ // Mock all dependencies
4
+ jest.mock('../../src/Storage', () => ({init: jest.fn()}))
5
+ jest.mock('../../src/Env', () => ({init: jest.fn(), get: jest.fn()}))
6
+ jest.mock('../../src/Config', () => ({
7
+ init: jest.fn(),
8
+ request: {timeout: 10000},
9
+ lang: {default: 'en'}
10
+ }))
11
+ jest.mock('../../src/Database', () => ({init: jest.fn()}))
12
+ jest.mock('../../src/Ipc', () => ({init: jest.fn(), subscribe: jest.fn(), unsubscribe: jest.fn()}))
13
+ jest.mock('../../src/Route', () => {
14
+ return jest.fn().mockImplementation(() => ({
15
+ init: jest.fn(),
16
+ routes: {
17
+ www: {}
18
+ }
19
+ }))
20
+ })
21
+ jest.mock('../../src/Server', () => ({init: jest.fn()}))
22
+
23
+ describe('Odac.init()', () => {
24
+ beforeEach(() => {
25
+ jest.clearAllMocks()
26
+ global.__dir = '/mock/project'
27
+ delete global.Odac
28
+ })
29
+
30
+ it('should initialize all components and set global.Odac', async () => {
31
+ await Odac.init()
32
+ expect(global.Odac).toBeDefined()
33
+ expect(global.Odac.Storage).toBeDefined()
34
+ expect(global.Odac.Config).toBeDefined()
35
+ expect(global.Odac.Env).toBeDefined()
36
+ expect(global.Odac.Database).toBeDefined()
37
+ expect(global.Odac.Ipc).toBeDefined()
38
+ expect(global.Odac.Route).toBeDefined()
39
+ expect(global.Odac.Server).toBeDefined()
40
+ expect(typeof global.__).toBe('function')
41
+ })
42
+ })
@@ -0,0 +1,58 @@
1
+ const Odac = require('../../src/Odac')
2
+
3
+ describe('Odac.instance()', () => {
4
+ let mockOdac
5
+
6
+ beforeEach(() => {
7
+ mockOdac = {
8
+ Config: {request: {timeout: 1000}},
9
+ DB: {init: jest.fn(), close: jest.fn()},
10
+ Auth: {constructor: jest.fn()},
11
+ Route: {
12
+ init: jest.fn(),
13
+ routes: {www: {}}
14
+ },
15
+ Storage: {get: jest.fn(), put: jest.fn()},
16
+ Env: {get: jest.fn()}
17
+ }
18
+ global.Odac = mockOdac
19
+ global.__dir = '/mock'
20
+ })
21
+
22
+ afterEach(() => {
23
+ delete global.Odac
24
+ delete global.__dir
25
+ })
26
+
27
+ it('should create a context object with req/res', () => {
28
+ const mockReq = {
29
+ method: 'GET',
30
+ url: '/',
31
+ headers: {host: 'www.example.com'},
32
+ connection: {remoteAddress: '127.0.0.1'},
33
+ on: jest.fn()
34
+ }
35
+ const mockRes = {on: jest.fn(), writeHead: jest.fn(), end: jest.fn()}
36
+
37
+ const ctx = Odac.instance('id', mockReq, mockRes)
38
+
39
+ expect(ctx.Request).toBeDefined()
40
+ expect(ctx.Request.id).toBe('id')
41
+ })
42
+
43
+ it('should provide helper methods on the context', () => {
44
+ const mockReq = {
45
+ method: 'GET',
46
+ url: '/',
47
+ headers: {host: 'www.example.com'},
48
+ connection: {remoteAddress: '127.0.0.1'},
49
+ on: jest.fn()
50
+ }
51
+ const mockRes = {on: jest.fn()}
52
+ const ctx = Odac.instance('id', mockReq, mockRes)
53
+
54
+ expect(typeof ctx.abort).toBe('function')
55
+ expect(typeof ctx.cookie).toBe('function')
56
+ expect(typeof ctx.env).toBe('function')
57
+ })
58
+ })
@@ -1,6 +1,6 @@
1
- const Route = require('../../src/Route.js')
1
+ const Route = require('../../../src/Route.js')
2
2
 
3
- describe('Middleware System', () => {
3
+ describe('Middleware Chaining Integration', () => {
4
4
  let route
5
5
 
6
6
  beforeEach(() => {
@@ -9,38 +9,21 @@ describe('Middleware System', () => {
9
9
  global.__dir = __dirname
10
10
  })
11
11
 
12
- test('use() should return MiddlewareChain', () => {
13
- const chain = route.use('auth', 'logger')
14
- expect(chain).not.toBe(route)
15
- expect(chain._middlewares).toEqual(['auth', 'logger'])
16
- })
17
-
18
- test('use() should support chaining with more middlewares', () => {
19
- const chain = route.use('auth').use('logger')
20
- expect(chain._middlewares).toEqual(['auth', 'logger'])
21
- })
22
-
23
- test('page() should return this for chaining', () => {
12
+ test('page() should return this for chaining when not on a chain', () => {
24
13
  const result = route.page('/', 'index')
25
14
  expect(result).toBe(route)
26
15
  })
27
16
 
28
- test('post() should return this for chaining', () => {
17
+ test('post() should return this for chaining when not on a chain', () => {
29
18
  const result = route.post('/api', 'api')
30
19
  expect(result).toBe(route)
31
20
  })
32
21
 
33
- test('get() should return this for chaining', () => {
22
+ test('get() should return this for chaining when not on a chain', () => {
34
23
  const result = route.get('/api', 'api')
35
24
  expect(result).toBe(route)
36
25
  })
37
26
 
38
- test('auth.use() should return MiddlewareChain', () => {
39
- const chain = route.auth.use('admin')
40
- expect(chain).not.toBe(route)
41
- expect(chain._middlewares).toEqual(['admin'])
42
- })
43
-
44
27
  test('chaining should work: use().page().page()', () => {
45
28
  route
46
29
  .use('auth')
@@ -75,11 +58,4 @@ describe('Middleware System', () => {
75
58
  route.use('cors', 'rateLimit').post('/api/upload', () => {})
76
59
  expect(route.routes.test.post['/api/upload'].middlewares).toEqual(['cors', 'rateLimit'])
77
60
  })
78
-
79
- test('separate use() chains should be independent', () => {
80
- route.use('auth').page('/profile', () => {})
81
- route.use('cors').page('/api', () => {})
82
- expect(route.routes.test.page['/profile'].middlewares).toEqual(['auth'])
83
- expect(route.routes.test.page['/api'].middlewares).toEqual(['cors'])
84
- })
85
61
  })
@@ -0,0 +1,35 @@
1
+ const Route = require('../../../src/Route.js')
2
+
3
+ describe('MiddlewareChain.use()', () => {
4
+ let route
5
+
6
+ beforeEach(() => {
7
+ route = new Route()
8
+ global.Odac = {Route: {buff: 'test'}}
9
+ global.__dir = __dirname
10
+ })
11
+
12
+ test('use() should return MiddlewareChain', () => {
13
+ const chain = route.use('auth', 'logger')
14
+ expect(chain).not.toBe(route)
15
+ expect(chain._middlewares).toEqual(['auth', 'logger'])
16
+ })
17
+
18
+ test('use() should support chaining with more middlewares', () => {
19
+ const chain = route.use('auth').use('logger')
20
+ expect(chain._middlewares).toEqual(['auth', 'logger'])
21
+ })
22
+
23
+ test('auth.use() should return MiddlewareChain', () => {
24
+ const chain = route.auth.use('admin')
25
+ expect(chain).not.toBe(route)
26
+ expect(chain._middlewares).toEqual(['admin'])
27
+ })
28
+
29
+ test('separate use() chains should be independent', () => {
30
+ route.use('auth').page('/profile', () => {})
31
+ route.use('cors').page('/api', () => {})
32
+ expect(route.routes.test.page['/profile'].middlewares).toEqual(['auth'])
33
+ expect(route.routes.test.page['/api'].middlewares).toEqual(['cors'])
34
+ })
35
+ })