@declaro/core 2.0.0-y.0 → 2.1.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/{LICENSE → LICENSE.md} +1 -1
- package/README.md +203 -0
- package/dist/browser/index.js +28 -0
- package/dist/browser/index.js.map +133 -0
- package/dist/browser/scope/index.js +3 -0
- package/dist/browser/scope/index.js.map +9 -0
- package/dist/bun/index.js +19011 -0
- package/dist/bun/index.js.map +132 -0
- package/dist/bun/scope/index.js +4 -0
- package/dist/bun/scope/index.js.map +9 -0
- package/dist/node/index.cjs +19039 -0
- package/dist/node/index.cjs.map +132 -0
- package/dist/node/index.js +19010 -0
- package/dist/node/index.js.map +132 -0
- package/dist/node/scope/index.cjs +69 -0
- package/dist/node/scope/index.cjs.map +9 -0
- package/dist/node/scope/index.js +3 -0
- package/dist/node/scope/index.js.map +9 -0
- package/dist/ts/app/app-context.d.ts +9 -0
- package/dist/ts/app/app-context.d.ts.map +1 -0
- package/dist/ts/app/app-lifecycle.d.ts +6 -0
- package/dist/ts/app/app-lifecycle.d.ts.map +1 -0
- package/dist/ts/app/app.d.ts +24 -0
- package/dist/ts/app/app.d.ts.map +1 -0
- package/dist/{app → ts/app}/index.d.ts +1 -0
- package/dist/ts/app/index.d.ts.map +1 -0
- package/dist/ts/application/create-request-context.d.ts +4 -0
- package/dist/ts/application/create-request-context.d.ts.map +1 -0
- package/dist/ts/application/create-request-context.test.d.ts +2 -0
- package/dist/ts/application/create-request-context.test.d.ts.map +1 -0
- package/dist/ts/application/use-declaro.d.ts +3 -0
- package/dist/ts/application/use-declaro.d.ts.map +1 -0
- package/dist/{auth → ts/auth}/permission-validator.d.ts +1 -0
- package/dist/ts/auth/permission-validator.d.ts.map +1 -0
- package/dist/ts/auth/permission-validator.test.d.ts +2 -0
- package/dist/ts/auth/permission-validator.test.d.ts.map +1 -0
- package/dist/ts/context/async-context.d.ts +54 -0
- package/dist/ts/context/async-context.d.ts.map +1 -0
- package/dist/ts/context/async-context.test.d.ts +2 -0
- package/dist/ts/context/async-context.test.d.ts.map +1 -0
- package/dist/{context → ts/context}/context-consumer.d.ts +4 -0
- package/dist/ts/context/context-consumer.d.ts.map +1 -0
- package/dist/ts/context/context.circular-deps.test.d.ts +2 -0
- package/dist/ts/context/context.circular-deps.test.d.ts.map +1 -0
- package/dist/ts/context/context.d.ts +452 -0
- package/dist/ts/context/context.d.ts.map +1 -0
- package/dist/ts/context/context.test.d.ts +2 -0
- package/dist/ts/context/context.test.d.ts.map +1 -0
- package/dist/ts/context/legacy-context.test.d.ts +2 -0
- package/dist/ts/context/legacy-context.test.d.ts.map +1 -0
- package/dist/{context → ts/context}/validators.d.ts +2 -1
- package/dist/ts/context/validators.d.ts.map +1 -0
- package/dist/ts/dataflow/index.d.ts +2 -0
- package/dist/ts/dataflow/index.d.ts.map +1 -0
- package/dist/ts/dataflow/objects.d.ts +7 -0
- package/dist/ts/dataflow/objects.d.ts.map +1 -0
- package/dist/ts/dataflow/objects.test.d.ts +2 -0
- package/dist/ts/dataflow/objects.test.d.ts.map +1 -0
- package/dist/{errors → ts/errors}/errors.d.ts +16 -3
- package/dist/ts/errors/errors.d.ts.map +1 -0
- package/dist/ts/events/event-manager.d.ts +19 -0
- package/dist/ts/events/event-manager.d.ts.map +1 -0
- package/dist/ts/events/event-manager.spec.d.ts +2 -0
- package/dist/ts/events/event-manager.spec.d.ts.map +1 -0
- package/dist/ts/events/index.d.ts +2 -0
- package/dist/ts/events/index.d.ts.map +1 -0
- package/dist/ts/http/headers.d.ts +21 -0
- package/dist/ts/http/headers.d.ts.map +1 -0
- package/dist/ts/http/headers.spec.d.ts +2 -0
- package/dist/ts/http/headers.spec.d.ts.map +1 -0
- package/dist/ts/http/request-context.d.ts +17 -0
- package/dist/ts/http/request-context.d.ts.map +1 -0
- package/dist/ts/http/request-context.spec.d.ts +2 -0
- package/dist/ts/http/request-context.spec.d.ts.map +1 -0
- package/dist/ts/http/request.d.ts +31 -0
- package/dist/ts/http/request.d.ts.map +1 -0
- package/dist/ts/http/request.spec.d.ts +2 -0
- package/dist/ts/http/request.spec.d.ts.map +1 -0
- package/dist/{http → ts/http}/url.d.ts +5 -4
- package/dist/ts/http/url.d.ts.map +1 -0
- package/dist/ts/http/url.spec.d.ts +2 -0
- package/dist/ts/http/url.spec.d.ts.map +1 -0
- package/dist/ts/index.d.ts +47 -0
- package/dist/ts/index.d.ts.map +1 -0
- package/dist/{pipelines → ts/pipelines}/index.d.ts +1 -0
- package/dist/ts/pipelines/index.d.ts.map +1 -0
- package/dist/{pipelines → ts/pipelines}/pipeline-action.d.ts +1 -0
- package/dist/ts/pipelines/pipeline-action.d.ts.map +1 -0
- package/dist/ts/pipelines/pipeline-action.test.d.ts +2 -0
- package/dist/ts/pipelines/pipeline-action.test.d.ts.map +1 -0
- package/dist/{pipelines → ts/pipelines}/pipeline.d.ts +3 -2
- package/dist/ts/pipelines/pipeline.d.ts.map +1 -0
- package/dist/ts/pipelines/pipeline.test.d.ts +2 -0
- package/dist/ts/pipelines/pipeline.test.d.ts.map +1 -0
- package/dist/ts/schema/json-schema.d.ts +12 -0
- package/dist/ts/schema/json-schema.d.ts.map +1 -0
- package/dist/ts/schema/labels.d.ts +14 -0
- package/dist/ts/schema/labels.d.ts.map +1 -0
- package/dist/ts/schema/model-schema.d.ts +75 -0
- package/dist/ts/schema/model-schema.d.ts.map +1 -0
- package/dist/ts/schema/model-schema.test.d.ts +2 -0
- package/dist/ts/schema/model-schema.test.d.ts.map +1 -0
- package/dist/ts/schema/model.d.ts +35 -0
- package/dist/ts/schema/model.d.ts.map +1 -0
- package/dist/ts/schema/schema-mixin.d.ts +24 -0
- package/dist/ts/schema/schema-mixin.d.ts.map +1 -0
- package/dist/ts/schema/test/mock-model.d.ts +8 -0
- package/dist/ts/schema/test/mock-model.d.ts.map +1 -0
- package/dist/ts/scope/index.d.ts +34 -0
- package/dist/ts/scope/index.d.ts.map +1 -0
- package/dist/ts/shared/utils/action-descriptor.d.ts +28 -0
- package/dist/ts/shared/utils/action-descriptor.d.ts.map +1 -0
- package/dist/ts/shared/utils/action-descriptor.test.d.ts +2 -0
- package/dist/ts/shared/utils/action-descriptor.test.d.ts.map +1 -0
- package/dist/ts/shared/utils/schema-utils.d.ts +3 -0
- package/dist/ts/shared/utils/schema-utils.d.ts.map +1 -0
- package/dist/ts/shared/utils/schema-utils.test.d.ts +2 -0
- package/dist/ts/shared/utils/schema-utils.test.d.ts.map +1 -0
- package/dist/ts/shims/async-local-storage.d.ts +36 -0
- package/dist/ts/shims/async-local-storage.d.ts.map +1 -0
- package/dist/ts/shims/async-local-storage.test.d.ts +2 -0
- package/dist/ts/shims/async-local-storage.test.d.ts.map +1 -0
- package/dist/{timing.d.ts → ts/timing.d.ts} +1 -0
- package/dist/ts/timing.d.ts.map +1 -0
- package/dist/{typescript → ts/typescript}/arrays.d.ts +1 -0
- package/dist/ts/typescript/arrays.d.ts.map +1 -0
- package/dist/{typescript → ts/typescript}/baseModel.d.ts +1 -0
- package/dist/ts/typescript/baseModel.d.ts.map +1 -0
- package/dist/{typescript → ts/typescript}/classes.d.ts +1 -0
- package/dist/ts/typescript/classes.d.ts.map +1 -0
- package/dist/{typescript → ts/typescript}/constant-manipulation/snake-case.d.ts +1 -0
- package/dist/ts/typescript/constant-manipulation/snake-case.d.ts.map +1 -0
- package/dist/{typescript → ts/typescript}/errors.d.ts +1 -0
- package/dist/ts/typescript/errors.d.ts.map +1 -0
- package/dist/ts/typescript/fetch.d.ts +3 -0
- package/dist/ts/typescript/fetch.d.ts.map +1 -0
- package/dist/{typescript → ts/typescript}/generics.d.ts +1 -0
- package/dist/ts/typescript/generics.d.ts.map +1 -0
- package/dist/{typescript → ts/typescript}/index.d.ts +1 -0
- package/dist/ts/typescript/index.d.ts.map +1 -0
- package/dist/ts/typescript/objects.d.ts +26 -0
- package/dist/ts/typescript/objects.d.ts.map +1 -0
- package/dist/{typescript → ts/typescript}/promises.d.ts +1 -0
- package/dist/ts/typescript/promises.d.ts.map +1 -0
- package/dist/{validation → ts/validation}/index.d.ts +1 -0
- package/dist/ts/validation/index.d.ts.map +1 -0
- package/dist/{validation → ts/validation}/validation.d.ts +1 -0
- package/dist/ts/validation/validation.d.ts.map +1 -0
- package/dist/{validation → ts/validation}/validator.d.ts +1 -0
- package/dist/ts/validation/validator.d.ts.map +1 -0
- package/dist/ts/validation/validator.test.d.ts +2 -0
- package/dist/ts/validation/validator.test.d.ts.map +1 -0
- package/package.json +46 -13
- package/src/app/app-context.ts +4 -5
- package/src/app/app-lifecycle.ts +4 -3
- package/src/app/app.ts +7 -5
- package/src/application/create-request-context.test.ts +345 -0
- package/src/application/create-request-context.ts +19 -0
- package/src/application/use-declaro.ts +27 -0
- package/src/auth/permission-validator.test.ts +238 -2
- package/src/auth/permission-validator.ts +3 -3
- package/src/context/async-context.test.ts +348 -0
- package/src/context/async-context.ts +129 -0
- package/src/context/context-consumer.ts +4 -4
- package/src/context/context.circular-deps.test.ts +1047 -0
- package/src/context/context.test.ts +420 -3
- package/src/context/context.ts +590 -87
- package/src/context/legacy-context.test.ts +9 -9
- package/src/dataflow/objects.test.ts +7 -7
- package/src/dataflow/objects.ts +10 -9
- package/src/errors/errors.ts +19 -3
- package/src/events/event-manager.spec.ts +129 -0
- package/src/events/event-manager.ts +25 -14
- package/src/http/headers.ts +17 -2
- package/src/http/request-context.ts +24 -15
- package/src/http/request.ts +27 -6
- package/src/http/url.ts +3 -3
- package/src/index.ts +34 -3
- package/src/pipelines/pipeline.test.ts +11 -9
- package/src/schema/json-schema.ts +16 -0
- package/src/schema/labels.ts +23 -23
- package/src/schema/model-schema.test.ts +282 -0
- package/src/schema/model-schema.ts +197 -0
- package/src/schema/model.ts +143 -0
- package/src/schema/schema-mixin.ts +51 -0
- package/src/schema/test/mock-model.ts +19 -0
- package/src/scope/index.ts +33 -0
- package/src/shared/utils/action-descriptor.test.ts +182 -0
- package/src/shared/utils/action-descriptor.ts +102 -0
- package/src/shared/utils/schema-utils.test.ts +33 -0
- package/src/shared/utils/schema-utils.ts +17 -0
- package/src/shims/async-local-storage.test.ts +258 -0
- package/src/shims/async-local-storage.ts +82 -0
- package/src/typescript/objects.ts +32 -1
- package/src/validation/validator.test.ts +12 -20
- package/dist/app/app-context.d.ts +0 -8
- package/dist/app/app-lifecycle.d.ts +0 -4
- package/dist/app/app.d.ts +0 -22
- package/dist/auth/permission-validator.test.d.ts +0 -1
- package/dist/context/context.d.ts +0 -161
- package/dist/context/context.test.d.ts +0 -1
- package/dist/context/legacy-context.test.d.ts +0 -1
- package/dist/dataflow/index.d.ts +0 -1
- package/dist/dataflow/objects.d.ts +0 -5
- package/dist/dataflow/objects.test.d.ts +0 -1
- package/dist/events/event-manager.d.ts +0 -16
- package/dist/events/event-manager.spec.d.ts +0 -1
- package/dist/events/index.d.ts +0 -1
- package/dist/helpers/index.d.ts +0 -1
- package/dist/helpers/ucfirst.d.ts +0 -1
- package/dist/http/headers.d.ts +0 -4
- package/dist/http/headers.spec.d.ts +0 -1
- package/dist/http/request-context.d.ts +0 -12
- package/dist/http/request-context.spec.d.ts +0 -1
- package/dist/http/request.d.ts +0 -8
- package/dist/http/request.spec.d.ts +0 -1
- package/dist/http/url.spec.d.ts +0 -1
- package/dist/index.d.ts +0 -19
- package/dist/pipelines/pipeline-action.test.d.ts +0 -1
- package/dist/pipelines/pipeline.test.d.ts +0 -1
- package/dist/pkg.cjs +0 -30
- package/dist/pkg.mjs +0 -56612
- package/dist/schema/application.d.ts +0 -83
- package/dist/schema/application.test.d.ts +0 -1
- package/dist/schema/define-model.d.ts +0 -10
- package/dist/schema/define-model.test.d.ts +0 -1
- package/dist/schema/formats.d.ts +0 -10
- package/dist/schema/index.d.ts +0 -10
- package/dist/schema/labels.d.ts +0 -13
- package/dist/schema/labels.test.d.ts +0 -1
- package/dist/schema/module.d.ts +0 -7
- package/dist/schema/module.test.d.ts +0 -1
- package/dist/schema/properties.d.ts +0 -19
- package/dist/schema/response.d.ts +0 -31
- package/dist/schema/response.test.d.ts +0 -1
- package/dist/schema/supported-types.d.ts +0 -12
- package/dist/schema/supported-types.test.d.ts +0 -1
- package/dist/schema/transform-model.d.ts +0 -4
- package/dist/schema/transform-model.test.d.ts +0 -1
- package/dist/schema/types.d.ts +0 -95
- package/dist/schema/types.test.d.ts +0 -1
- package/dist/typescript/fetch.d.ts +0 -2
- package/dist/typescript/objects.d.ts +0 -12
- package/dist/validation/validator.test.d.ts +0 -1
- package/src/helpers/index.ts +0 -1
- package/src/helpers/ucfirst.ts +0 -3
- package/src/schema/application.test.ts +0 -286
- package/src/schema/application.ts +0 -150
- package/src/schema/define-model.test.ts +0 -81
- package/src/schema/define-model.ts +0 -50
- package/src/schema/formats.ts +0 -23
- package/src/schema/index.ts +0 -10
- package/src/schema/labels.test.ts +0 -60
- package/src/schema/module.test.ts +0 -39
- package/src/schema/module.ts +0 -6
- package/src/schema/properties.ts +0 -40
- package/src/schema/response.test.ts +0 -101
- package/src/schema/response.ts +0 -93
- package/src/schema/supported-types.test.ts +0 -20
- package/src/schema/supported-types.ts +0 -15
- package/src/schema/transform-model.test.ts +0 -31
- package/src/schema/transform-model.ts +0 -24
- package/src/schema/types.test.ts +0 -28
- package/src/schema/types.ts +0 -163
- package/tsconfig.json +0 -11
- package/vite.config.ts +0 -24
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { createRequest } from 'node-mocks-http'
|
|
2
|
+
import { beforeEach, describe, expect, it } from 'vitest'
|
|
3
|
+
import { Context, type DeclaroRequestScope, type DeclaroScope } from '../context/context'
|
|
4
|
+
import type { Request } from '../http/request'
|
|
5
|
+
import { createRequestContext } from './create-request-context'
|
|
6
|
+
import { useDeclaro } from './use-declaro'
|
|
7
|
+
|
|
8
|
+
// Extended scopes for testing
|
|
9
|
+
interface TestAppScope extends DeclaroScope {
|
|
10
|
+
testAppValue?: string
|
|
11
|
+
sharedKey?: string
|
|
12
|
+
appValue?: string
|
|
13
|
+
appFactory?: string
|
|
14
|
+
appClass?: TestClass
|
|
15
|
+
eagerDep?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface TestRequestScope extends DeclaroRequestScope {
|
|
19
|
+
middlewareTest?: string
|
|
20
|
+
asyncTest?: string
|
|
21
|
+
middleware1?: string
|
|
22
|
+
middleware2?: string
|
|
23
|
+
sharedKey?: string
|
|
24
|
+
appValue?: string
|
|
25
|
+
appFactory?: string
|
|
26
|
+
appClass?: TestClass
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
class TestClass {
|
|
30
|
+
getValue() {
|
|
31
|
+
return 'class-result'
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe('createRequestContext', () => {
|
|
36
|
+
let appContext: Context<TestAppScope>
|
|
37
|
+
let mockRequest: Request
|
|
38
|
+
|
|
39
|
+
beforeEach(async () => {
|
|
40
|
+
// Create app context with Declaro middleware
|
|
41
|
+
appContext = new Context<TestAppScope>()
|
|
42
|
+
await appContext.use(useDeclaro())
|
|
43
|
+
|
|
44
|
+
// Create mock request
|
|
45
|
+
mockRequest = createRequest({
|
|
46
|
+
method: 'POST',
|
|
47
|
+
url: 'https://example.com/api/test?param1=value1¶m2=value2',
|
|
48
|
+
headers: {
|
|
49
|
+
'Content-Type': 'application/json',
|
|
50
|
+
Authorization: 'Bearer test-token-123',
|
|
51
|
+
'X-Custom-Header': 'custom-value',
|
|
52
|
+
'User-Agent': 'test-agent/1.0',
|
|
53
|
+
},
|
|
54
|
+
body: {
|
|
55
|
+
test: 'data',
|
|
56
|
+
},
|
|
57
|
+
}) as Request
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
describe('basic functionality', () => {
|
|
61
|
+
it('should create a request context from app context', async () => {
|
|
62
|
+
const requestContext = await createRequestContext(appContext, mockRequest)
|
|
63
|
+
|
|
64
|
+
expect(requestContext).toBeInstanceOf(Context)
|
|
65
|
+
expect(requestContext).not.toBe(appContext)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('should extend the app context', async () => {
|
|
69
|
+
// Add a value to app context
|
|
70
|
+
appContext.registerValue('testAppValue', 'app-test-value')
|
|
71
|
+
|
|
72
|
+
const requestContext = (await createRequestContext(appContext, mockRequest)) as Context<TestRequestScope>
|
|
73
|
+
|
|
74
|
+
// Should be able to access app context values
|
|
75
|
+
expect((requestContext as any).resolve('testAppValue')).toBe('app-test-value')
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('should run request middleware', async () => {
|
|
79
|
+
let middlewareRan = false
|
|
80
|
+
|
|
81
|
+
appContext.scope.requestMiddleware.push((context) => {
|
|
82
|
+
middlewareRan = true
|
|
83
|
+
;(context as any).registerValue('middlewareTest', 'middleware-value')
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
const requestContext = await createRequestContext(appContext, mockRequest)
|
|
87
|
+
|
|
88
|
+
expect(middlewareRan).toBe(true)
|
|
89
|
+
expect((requestContext as any).resolve('middlewareTest')).toBe('middleware-value')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('should initialize eager dependencies', async () => {
|
|
93
|
+
let eagerFactoryRan = false
|
|
94
|
+
|
|
95
|
+
appContext.registerFactory(
|
|
96
|
+
'eagerDep',
|
|
97
|
+
() => {
|
|
98
|
+
eagerFactoryRan = true
|
|
99
|
+
return 'eager-value'
|
|
100
|
+
},
|
|
101
|
+
[],
|
|
102
|
+
{ eager: true },
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
await createRequestContext(appContext, mockRequest)
|
|
106
|
+
|
|
107
|
+
expect(eagerFactoryRan).toBe(true)
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
describe('request injection', () => {
|
|
112
|
+
it('should inject the request object', async () => {
|
|
113
|
+
const requestContext = await createRequestContext(appContext, mockRequest)
|
|
114
|
+
|
|
115
|
+
const injectedRequest = requestContext.resolve('request')
|
|
116
|
+
|
|
117
|
+
expect(injectedRequest).toBe(mockRequest)
|
|
118
|
+
expect(injectedRequest.method).toBe('POST')
|
|
119
|
+
expect(injectedRequest.url).toBe('https://example.com/api/test?param1=value1¶m2=value2')
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('should provide request via scope property', async () => {
|
|
123
|
+
const requestContext = await createRequestContext(appContext, mockRequest)
|
|
124
|
+
|
|
125
|
+
expect(requestContext.scope.request).toBe(mockRequest)
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
describe('headers injection', () => {
|
|
130
|
+
it('should inject headers object', async () => {
|
|
131
|
+
const requestContext = await createRequestContext(appContext, mockRequest)
|
|
132
|
+
|
|
133
|
+
const headers = requestContext.resolve('headers')
|
|
134
|
+
|
|
135
|
+
expect(headers).toBeDefined()
|
|
136
|
+
expect(headers['content-type']).toBe('application/json')
|
|
137
|
+
expect(headers['authorization']).toBe('Bearer test-token-123')
|
|
138
|
+
expect(headers['x-custom-header']).toBe('custom-value')
|
|
139
|
+
expect(headers['user-agent']).toBe('test-agent/1.0')
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('should provide headers via scope property', async () => {
|
|
143
|
+
const requestContext = await createRequestContext(appContext, mockRequest)
|
|
144
|
+
|
|
145
|
+
expect(requestContext.scope.headers).toBeDefined()
|
|
146
|
+
expect(requestContext.scope.headers).toBe(mockRequest.headers)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('should handle missing headers gracefully', async () => {
|
|
150
|
+
const requestWithoutHeaders = createRequest({
|
|
151
|
+
method: 'GET',
|
|
152
|
+
url: '/test',
|
|
153
|
+
// No headers
|
|
154
|
+
}) as Request
|
|
155
|
+
|
|
156
|
+
const requestContext = await createRequestContext(appContext, requestWithoutHeaders)
|
|
157
|
+
|
|
158
|
+
const headers = requestContext.resolve('headers')
|
|
159
|
+
expect(headers).toBeDefined()
|
|
160
|
+
expect(Object.keys(headers).length).toBeGreaterThanOrEqual(0)
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
describe('header function injection', () => {
|
|
165
|
+
it('should inject header function', async () => {
|
|
166
|
+
const requestContext = await createRequestContext(appContext, mockRequest)
|
|
167
|
+
|
|
168
|
+
const headerFunction = requestContext.resolve('header')
|
|
169
|
+
|
|
170
|
+
expect(typeof headerFunction).toBe('function')
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it('should provide header function via scope property', async () => {
|
|
174
|
+
const requestContext = await createRequestContext(appContext, mockRequest)
|
|
175
|
+
|
|
176
|
+
expect(typeof requestContext.scope.header).toBe('function')
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('should retrieve specific headers', async () => {
|
|
180
|
+
const requestContext = await createRequestContext(appContext, mockRequest)
|
|
181
|
+
|
|
182
|
+
const header = requestContext.scope.header
|
|
183
|
+
|
|
184
|
+
expect(header('content-type')).toBe('application/json')
|
|
185
|
+
expect(header('authorization')).toBe('Bearer test-token-123')
|
|
186
|
+
expect(header('x-custom-header')).toBe('custom-value')
|
|
187
|
+
expect(header('user-agent')).toBe('test-agent/1.0')
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
it('should return undefined for non-existent headers', async () => {
|
|
191
|
+
const requestContext = await createRequestContext(appContext, mockRequest)
|
|
192
|
+
|
|
193
|
+
const header = requestContext.scope.header
|
|
194
|
+
|
|
195
|
+
expect(header('non-existent-header' as any)).toBeUndefined()
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
it('should handle case-insensitive header names', async () => {
|
|
199
|
+
const requestContext = await createRequestContext(appContext, mockRequest)
|
|
200
|
+
|
|
201
|
+
const header = requestContext.scope.header
|
|
202
|
+
|
|
203
|
+
// HTTP headers are case-insensitive, Node.js typically lowercases them
|
|
204
|
+
expect(header('Content-Type' as any)).toBe('application/json')
|
|
205
|
+
})
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
describe('middleware arrays injection', () => {
|
|
209
|
+
it('should inject requestMiddleware array', async () => {
|
|
210
|
+
const requestContext = await createRequestContext(appContext, mockRequest)
|
|
211
|
+
|
|
212
|
+
const requestMiddleware = requestContext.resolve('requestMiddleware')
|
|
213
|
+
|
|
214
|
+
expect(Array.isArray(requestMiddleware)).toBe(true)
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('should inject nodeMiddleware array', async () => {
|
|
218
|
+
const requestContext = await createRequestContext(appContext, mockRequest)
|
|
219
|
+
|
|
220
|
+
const nodeMiddleware = requestContext.resolve('nodeMiddleware')
|
|
221
|
+
|
|
222
|
+
expect(Array.isArray(nodeMiddleware)).toBe(true)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('should provide middleware arrays via scope properties', async () => {
|
|
226
|
+
const requestContext = await createRequestContext(appContext, mockRequest)
|
|
227
|
+
|
|
228
|
+
expect(Array.isArray(requestContext.scope.requestMiddleware)).toBe(true)
|
|
229
|
+
expect(Array.isArray(requestContext.scope.nodeMiddleware)).toBe(true)
|
|
230
|
+
})
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
describe('type safety and scope validation', () => {
|
|
234
|
+
it('should satisfy RequestScope interface', async () => {
|
|
235
|
+
const requestContext = await createRequestContext(appContext, mockRequest)
|
|
236
|
+
|
|
237
|
+
// Test that all RequestScope properties are available
|
|
238
|
+
expect(requestContext.scope.request).toBeDefined()
|
|
239
|
+
expect(requestContext.scope.headers).toBeDefined()
|
|
240
|
+
expect(typeof requestContext.scope.header).toBe('function')
|
|
241
|
+
expect(Array.isArray(requestContext.scope.requestMiddleware)).toBe(true)
|
|
242
|
+
expect(Array.isArray(requestContext.scope.nodeMiddleware)).toBe(true)
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
it('should allow injection of all RequestScope dependencies', async () => {
|
|
246
|
+
const requestContext = await createRequestContext(appContext, mockRequest)
|
|
247
|
+
|
|
248
|
+
// Test direct resolution of all expected dependencies
|
|
249
|
+
expect(() => requestContext.resolve('request')).not.toThrow()
|
|
250
|
+
expect(() => requestContext.resolve('headers')).not.toThrow()
|
|
251
|
+
expect(() => requestContext.resolve('header')).not.toThrow()
|
|
252
|
+
expect(() => requestContext.resolve('requestMiddleware')).not.toThrow()
|
|
253
|
+
expect(() => requestContext.resolve('nodeMiddleware')).not.toThrow()
|
|
254
|
+
})
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
describe('dependency inheritance', () => {
|
|
258
|
+
it('should inherit all app dependencies', async () => {
|
|
259
|
+
// Register various types of dependencies in app context
|
|
260
|
+
appContext.registerValue('appValue', 'test-value')
|
|
261
|
+
appContext.registerFactory('appFactory', () => 'factory-result', [])
|
|
262
|
+
;(appContext as any).registerClass('appClass', TestClass, [])
|
|
263
|
+
|
|
264
|
+
const requestContext = (await createRequestContext(appContext, mockRequest)) as Context<TestRequestScope>
|
|
265
|
+
|
|
266
|
+
// All should be accessible in request context
|
|
267
|
+
expect((requestContext as any).resolve('appValue')).toBe('test-value')
|
|
268
|
+
expect((requestContext as any).resolve('appFactory')).toBe('factory-result')
|
|
269
|
+
expect(((requestContext as any).resolve('appClass') as TestClass).getValue()).toBe('class-result')
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
it('should allow request context to override app dependencies', async () => {
|
|
273
|
+
appContext.registerValue('sharedKey', 'app-value')
|
|
274
|
+
|
|
275
|
+
// Add middleware that overrides the value
|
|
276
|
+
appContext.scope.requestMiddleware.push((context) => {
|
|
277
|
+
;(context as any).registerValue('sharedKey', 'request-value')
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
const requestContext = await createRequestContext(appContext, mockRequest)
|
|
281
|
+
|
|
282
|
+
expect((requestContext as any).resolve('sharedKey')).toBe('request-value')
|
|
283
|
+
})
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
describe('error handling', () => {
|
|
287
|
+
it('should handle middleware errors gracefully', async () => {
|
|
288
|
+
// Add middleware that throws
|
|
289
|
+
appContext.scope.requestMiddleware.push(() => {
|
|
290
|
+
throw new Error('Middleware error')
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
await expect(createRequestContext(appContext, mockRequest)).rejects.toThrow('Middleware error')
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
it('should handle null/undefined request', async () => {
|
|
297
|
+
// The current implementation doesn't validate the request parameter
|
|
298
|
+
// It will set request to null and continue processing
|
|
299
|
+
const requestContext = await createRequestContext(appContext, null as any)
|
|
300
|
+
|
|
301
|
+
expect(requestContext.resolve('request')).toBeNull()
|
|
302
|
+
expect(requestContext.resolve('headers')).toEqual({})
|
|
303
|
+
})
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
describe('async middleware support', () => {
|
|
307
|
+
it('should handle async middleware', async () => {
|
|
308
|
+
let asyncMiddlewareRan = false
|
|
309
|
+
|
|
310
|
+
appContext.scope.requestMiddleware.push(async (context) => {
|
|
311
|
+
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
312
|
+
asyncMiddlewareRan = true
|
|
313
|
+
;(context as any).registerValue('asyncTest', 'async-result')
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
const requestContext = await createRequestContext(appContext, mockRequest)
|
|
317
|
+
|
|
318
|
+
expect(asyncMiddlewareRan).toBe(true)
|
|
319
|
+
expect((requestContext as any).resolve('asyncTest')).toBe('async-result')
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
it('should run multiple async middleware in sequence', async () => {
|
|
323
|
+
const executionOrder: number[] = []
|
|
324
|
+
|
|
325
|
+
appContext.scope.requestMiddleware.push(
|
|
326
|
+
async (context) => {
|
|
327
|
+
await new Promise((resolve) => setTimeout(resolve, 20))
|
|
328
|
+
executionOrder.push(1)
|
|
329
|
+
;(context as any).registerValue('middleware1', 'first')
|
|
330
|
+
},
|
|
331
|
+
async (context) => {
|
|
332
|
+
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
333
|
+
executionOrder.push(2)
|
|
334
|
+
;(context as any).registerValue('middleware2', 'second')
|
|
335
|
+
},
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
const requestContext = await createRequestContext(appContext, mockRequest)
|
|
339
|
+
|
|
340
|
+
expect(executionOrder).toEqual([1, 2])
|
|
341
|
+
expect((requestContext as any).resolve('middleware1')).toBe('first')
|
|
342
|
+
expect((requestContext as any).resolve('middleware2')).toBe('second')
|
|
343
|
+
})
|
|
344
|
+
})
|
|
345
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Context, type DeclaroRequestScope, type DeclaroScope } from '../context/context'
|
|
2
|
+
import { provideRequest, type Request } from '../http/request'
|
|
3
|
+
|
|
4
|
+
export async function createRequestContext<S extends DeclaroScope>(
|
|
5
|
+
appContext: Context<S>,
|
|
6
|
+
request: Request,
|
|
7
|
+
): Promise<Context<DeclaroRequestScope>> {
|
|
8
|
+
const context = new Context<DeclaroRequestScope>()
|
|
9
|
+
context.extend(appContext)
|
|
10
|
+
|
|
11
|
+
provideRequest(context, request)
|
|
12
|
+
|
|
13
|
+
const requestMiddleware = appContext.scope.requestMiddleware
|
|
14
|
+
await context.use(...requestMiddleware)
|
|
15
|
+
|
|
16
|
+
await context.initializeEagerDependencies()
|
|
17
|
+
|
|
18
|
+
return context
|
|
19
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { IncomingHttpHeaders } from 'http'
|
|
2
|
+
import type { Context, DeclaroScope } from '../context/context'
|
|
3
|
+
import { provideRequestMiddleware } from '../http/request-context'
|
|
4
|
+
|
|
5
|
+
export function useDeclaro() {
|
|
6
|
+
return async <S extends DeclaroScope>(context: Context<S>) => {
|
|
7
|
+
context.registerValue('requestMiddleware', [])
|
|
8
|
+
context.registerValue('nodeMiddleware', [])
|
|
9
|
+
|
|
10
|
+
provideRequestMiddleware(context, async (context) => {
|
|
11
|
+
// TODO: Support modern web Request type, and Headers instance for full fetch compatibility
|
|
12
|
+
|
|
13
|
+
context.registerFactory(
|
|
14
|
+
'headers',
|
|
15
|
+
(request: Request) => {
|
|
16
|
+
return request?.headers ?? {}
|
|
17
|
+
},
|
|
18
|
+
['request'],
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
context.registerValue('header', (header: keyof IncomingHttpHeaders) => {
|
|
22
|
+
const headers = context.resolve('headers')
|
|
23
|
+
return headers[header]
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -43,7 +43,7 @@ describe('Permission Builder', () => {
|
|
|
43
43
|
|
|
44
44
|
const validResult = validator.safeValidate(['look', 'we', 'have', 'some', 'permissions', 'and', 'some', 'more'])
|
|
45
45
|
|
|
46
|
-
expect(validResult).
|
|
46
|
+
expect(validResult).toEqual({
|
|
47
47
|
valid: true,
|
|
48
48
|
errorMessage: '',
|
|
49
49
|
errors: [],
|
|
@@ -51,7 +51,7 @@ describe('Permission Builder', () => {
|
|
|
51
51
|
|
|
52
52
|
expect(() => validator.safeValidate(['we', 'are', 'not', 'authorized'])).not.toThrowError()
|
|
53
53
|
const result = validator.safeValidate(['we', 'are', 'not', 'authorized'])
|
|
54
|
-
expect(result).
|
|
54
|
+
expect(result).toEqual({
|
|
55
55
|
valid: false,
|
|
56
56
|
errorMessage: 'Tisk tisk',
|
|
57
57
|
errors: [new PermissionError('Tisk tisk', validator.rules[0], ['we', 'are', 'not', 'authorized'])],
|
|
@@ -206,4 +206,240 @@ describe('Permission Builder', () => {
|
|
|
206
206
|
expect(validator3.safeValidate(['namespace::resource.action1:write']).valid).toBe(true)
|
|
207
207
|
expect(validator3.safeValidate(['namespace::resource.action2:delete']).valid).toBe(true)
|
|
208
208
|
})
|
|
209
|
+
|
|
210
|
+
describe('Nested Permission Validators', () => {
|
|
211
|
+
it('should support nesting validators with someOf for OR logic', () => {
|
|
212
|
+
// Create nested validator: permission1 OR (permission2 AND permission3)
|
|
213
|
+
const nestedValidator = PermissionValidator.create().allOf(['permission2', 'permission3'])
|
|
214
|
+
const validator = PermissionValidator.create().someOf(
|
|
215
|
+
['permission1', nestedValidator],
|
|
216
|
+
'Need permission1 OR (permission2 AND permission3)',
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
// Test with permission1 only - should pass
|
|
220
|
+
expect(validator.safeValidate(['permission1']).valid).toBe(true)
|
|
221
|
+
|
|
222
|
+
// Test with permission2 and permission3 - should pass
|
|
223
|
+
expect(validator.safeValidate(['permission2', 'permission3']).valid).toBe(true)
|
|
224
|
+
|
|
225
|
+
// Test with all permissions - should pass
|
|
226
|
+
expect(validator.safeValidate(['permission1', 'permission2', 'permission3']).valid).toBe(true)
|
|
227
|
+
|
|
228
|
+
// Test with permission2 only - should fail
|
|
229
|
+
expect(validator.safeValidate(['permission2']).valid).toBe(false)
|
|
230
|
+
|
|
231
|
+
// Test with permission3 only - should fail
|
|
232
|
+
expect(validator.safeValidate(['permission3']).valid).toBe(false)
|
|
233
|
+
|
|
234
|
+
// Test with no permissions - should fail
|
|
235
|
+
expect(validator.safeValidate([]).valid).toBe(false)
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it('should support nesting validators with allOf for AND logic', () => {
|
|
239
|
+
// Create nested validator: permission1 AND (permission2 OR permission3)
|
|
240
|
+
const nestedValidator = PermissionValidator.create().someOf(['permission2', 'permission3'])
|
|
241
|
+
const validator = PermissionValidator.create().allOf(
|
|
242
|
+
['permission1', nestedValidator],
|
|
243
|
+
'Need permission1 AND (permission2 OR permission3)',
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
// Test with permission1 and permission2 - should pass
|
|
247
|
+
expect(validator.safeValidate(['permission1', 'permission2']).valid).toBe(true)
|
|
248
|
+
|
|
249
|
+
// Test with permission1 and permission3 - should pass
|
|
250
|
+
expect(validator.safeValidate(['permission1', 'permission3']).valid).toBe(true)
|
|
251
|
+
|
|
252
|
+
// Test with all permissions - should pass
|
|
253
|
+
expect(validator.safeValidate(['permission1', 'permission2', 'permission3']).valid).toBe(true)
|
|
254
|
+
|
|
255
|
+
// Test with permission1 only - should fail
|
|
256
|
+
expect(validator.safeValidate(['permission1']).valid).toBe(false)
|
|
257
|
+
|
|
258
|
+
// Test with permission2 only - should fail
|
|
259
|
+
expect(validator.safeValidate(['permission2']).valid).toBe(false)
|
|
260
|
+
|
|
261
|
+
// Test with permission2 and permission3 only - should fail
|
|
262
|
+
expect(validator.safeValidate(['permission2', 'permission3']).valid).toBe(false)
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
it('should support multiple levels of nesting', () => {
|
|
266
|
+
// Create complex nested validator: permission1 OR (permission2 AND (permission3 OR permission4))
|
|
267
|
+
const deepNestedValidator = PermissionValidator.create().someOf(['permission3', 'permission4'])
|
|
268
|
+
const nestedValidator = PermissionValidator.create().allOf(['permission2', deepNestedValidator])
|
|
269
|
+
const validator = PermissionValidator.create().someOf(
|
|
270
|
+
['permission1', nestedValidator],
|
|
271
|
+
'Complex permission logic failed',
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
// Test with permission1 only - should pass
|
|
275
|
+
expect(validator.safeValidate(['permission1']).valid).toBe(true)
|
|
276
|
+
|
|
277
|
+
// Test with permission2 and permission3 - should pass
|
|
278
|
+
expect(validator.safeValidate(['permission2', 'permission3']).valid).toBe(true)
|
|
279
|
+
|
|
280
|
+
// Test with permission2 and permission4 - should pass
|
|
281
|
+
expect(validator.safeValidate(['permission2', 'permission4']).valid).toBe(true)
|
|
282
|
+
|
|
283
|
+
// Test with permission2, permission3, and permission4 - should pass
|
|
284
|
+
expect(validator.safeValidate(['permission2', 'permission3', 'permission4']).valid).toBe(true)
|
|
285
|
+
|
|
286
|
+
// Test with permission2 only - should fail
|
|
287
|
+
expect(validator.safeValidate(['permission2']).valid).toBe(false)
|
|
288
|
+
|
|
289
|
+
// Test with permission3 and permission4 only - should fail
|
|
290
|
+
expect(validator.safeValidate(['permission3', 'permission4']).valid).toBe(false)
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
it('should support nesting with noneOf for exclusion logic', () => {
|
|
294
|
+
// Create validator: permission1 AND NOT(permission2 OR permission3)
|
|
295
|
+
const excludeValidator = PermissionValidator.create().someOf(['permission2', 'permission3'])
|
|
296
|
+
const validator = PermissionValidator.create()
|
|
297
|
+
.allOf(['permission1'], 'Must have permission1')
|
|
298
|
+
.noneOf([excludeValidator], 'Cannot have permission2 or permission3')
|
|
299
|
+
|
|
300
|
+
// Test with permission1 only - should pass
|
|
301
|
+
expect(validator.safeValidate(['permission1']).valid).toBe(true)
|
|
302
|
+
|
|
303
|
+
// Test with permission1 and permission4 - should pass
|
|
304
|
+
expect(validator.safeValidate(['permission1', 'permission4']).valid).toBe(true)
|
|
305
|
+
|
|
306
|
+
// Test with permission1 and permission2 - should fail
|
|
307
|
+
expect(validator.safeValidate(['permission1', 'permission2']).valid).toBe(false)
|
|
308
|
+
|
|
309
|
+
// Test with permission1 and permission3 - should fail
|
|
310
|
+
expect(validator.safeValidate(['permission1', 'permission3']).valid).toBe(false)
|
|
311
|
+
|
|
312
|
+
// Test with permission1, permission2, and permission3 - should fail
|
|
313
|
+
expect(validator.safeValidate(['permission1', 'permission2', 'permission3']).valid).toBe(false)
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
it('should provide meaningful error messages for nested validators', () => {
|
|
317
|
+
const nestedValidator = PermissionValidator.create().allOf(
|
|
318
|
+
['permission2', 'permission3'],
|
|
319
|
+
'Missing required permissions 2 and 3',
|
|
320
|
+
)
|
|
321
|
+
const validator = PermissionValidator.create().someOf(
|
|
322
|
+
['permission1', nestedValidator],
|
|
323
|
+
'Need permission1 OR both permission2 and permission3',
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
const result = validator.safeValidate(['permission2'])
|
|
327
|
+
|
|
328
|
+
expect(result.valid).toBe(false)
|
|
329
|
+
expect(result.errorMessage).toBe('Need permission1 OR both permission2 and permission3')
|
|
330
|
+
expect(result.errors).toHaveLength(1)
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
it('should work with real-world permission patterns using nesting', () => {
|
|
334
|
+
// Create validator for: write:all OR (create:all AND update:all)
|
|
335
|
+
const createAndUpdate = PermissionValidator.create().allOf(['create:all', 'update:all'])
|
|
336
|
+
const validator = PermissionValidator.create().someOf(
|
|
337
|
+
['write:all', createAndUpdate],
|
|
338
|
+
'Need write:all OR (create:all AND update:all)',
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
// Test with write:all - should pass
|
|
342
|
+
expect(validator.safeValidate(['write:all']).valid).toBe(true)
|
|
343
|
+
|
|
344
|
+
// Test with create:all and update:all - should pass
|
|
345
|
+
expect(validator.safeValidate(['create:all', 'update:all']).valid).toBe(true)
|
|
346
|
+
|
|
347
|
+
// Test with all permissions - should pass
|
|
348
|
+
expect(validator.safeValidate(['write:all', 'create:all', 'update:all']).valid).toBe(true)
|
|
349
|
+
|
|
350
|
+
// Test with create:all only - should fail
|
|
351
|
+
expect(validator.safeValidate(['create:all']).valid).toBe(false)
|
|
352
|
+
|
|
353
|
+
// Test with update:all only - should fail
|
|
354
|
+
expect(validator.safeValidate(['update:all']).valid).toBe(false)
|
|
355
|
+
|
|
356
|
+
// Test with read:all - should fail
|
|
357
|
+
expect(validator.safeValidate(['read:all']).valid).toBe(false)
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
it('should support the exact syntax mentioned: someOf with nested allOf for OR-AND logic', () => {
|
|
361
|
+
// Test the exact pattern: baseValidator.someOf(['permission1', baseValidator.someOf(['permission2', 'permission3'])])
|
|
362
|
+
// This creates: permission1 OR (permission2 AND permission3)
|
|
363
|
+
const nestedValidator = PermissionValidator.create().allOf(['permission2', 'permission3'])
|
|
364
|
+
const validator = PermissionValidator.create().someOf(['permission1', nestedValidator])
|
|
365
|
+
|
|
366
|
+
// Test permission1 only - should pass
|
|
367
|
+
expect(validator.safeValidate(['permission1']).valid).toBe(true)
|
|
368
|
+
|
|
369
|
+
// Test permission2 and permission3 - should pass
|
|
370
|
+
expect(validator.safeValidate(['permission2', 'permission3']).valid).toBe(true)
|
|
371
|
+
|
|
372
|
+
// Test all permissions - should pass
|
|
373
|
+
expect(validator.safeValidate(['permission1', 'permission2', 'permission3']).valid).toBe(true)
|
|
374
|
+
|
|
375
|
+
// Test permission2 only - should fail
|
|
376
|
+
expect(validator.safeValidate(['permission2']).valid).toBe(false)
|
|
377
|
+
|
|
378
|
+
// Test permission3 only - should fail
|
|
379
|
+
expect(validator.safeValidate(['permission3']).valid).toBe(false)
|
|
380
|
+
|
|
381
|
+
// Test no permissions - should fail
|
|
382
|
+
expect(validator.safeValidate([]).valid).toBe(false)
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
it('should work with wildcards in nested validators', () => {
|
|
386
|
+
// Create validator: admin:* OR (books:read AND books:write)
|
|
387
|
+
const booksReadWrite = PermissionValidator.create().allOf(['books:read', 'books:write'])
|
|
388
|
+
const validator = PermissionValidator.create().someOf(
|
|
389
|
+
['admin:*', booksReadWrite],
|
|
390
|
+
'Need admin access OR books read/write',
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
// Test with admin wildcard - should pass
|
|
394
|
+
expect(validator.safeValidate(['admin:read']).valid).toBe(true)
|
|
395
|
+
expect(validator.safeValidate(['admin:write']).valid).toBe(true)
|
|
396
|
+
expect(validator.safeValidate(['admin:delete']).valid).toBe(true)
|
|
397
|
+
|
|
398
|
+
// Test with books read and write - should pass
|
|
399
|
+
expect(validator.safeValidate(['books:read', 'books:write']).valid).toBe(true)
|
|
400
|
+
|
|
401
|
+
// Test with books read only - should fail
|
|
402
|
+
expect(validator.safeValidate(['books:read']).valid).toBe(false)
|
|
403
|
+
|
|
404
|
+
// Test with books write only - should fail
|
|
405
|
+
expect(validator.safeValidate(['books:write']).valid).toBe(false)
|
|
406
|
+
|
|
407
|
+
// Test with other permissions - should fail
|
|
408
|
+
expect(validator.safeValidate(['users:read']).valid).toBe(false)
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
it('should demonstrate practical usage for upsert/bulkUpsert permissions', () => {
|
|
412
|
+
// Create validator for upsert/bulkUpsert: books:write:all OR (books:create:all AND books:update:all)
|
|
413
|
+
const createAndUpdate = PermissionValidator.create().allOf(['books:create:all', 'books:update:all'])
|
|
414
|
+
const upsertValidator = PermissionValidator.create().someOf(
|
|
415
|
+
['books:write:all', createAndUpdate],
|
|
416
|
+
'Need books:write:all OR (books:create:all AND books:update:all)',
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
// Test with write permission - should pass
|
|
420
|
+
expect(upsertValidator.safeValidate(['books:write:all']).valid).toBe(true)
|
|
421
|
+
|
|
422
|
+
// Test with create and update permissions - should pass
|
|
423
|
+
expect(upsertValidator.safeValidate(['books:create:all', 'books:update:all']).valid).toBe(true)
|
|
424
|
+
|
|
425
|
+
// Test with all permissions - should pass
|
|
426
|
+
expect(
|
|
427
|
+
upsertValidator.safeValidate(['books:write:all', 'books:create:all', 'books:update:all']).valid,
|
|
428
|
+
).toBe(true)
|
|
429
|
+
|
|
430
|
+
// Test with create only - should fail
|
|
431
|
+
expect(upsertValidator.safeValidate(['books:create:all']).valid).toBe(false)
|
|
432
|
+
|
|
433
|
+
// Test with update only - should fail
|
|
434
|
+
expect(upsertValidator.safeValidate(['books:update:all']).valid).toBe(false)
|
|
435
|
+
|
|
436
|
+
// Test with read only - should fail
|
|
437
|
+
expect(upsertValidator.safeValidate(['books:read:all']).valid).toBe(false)
|
|
438
|
+
|
|
439
|
+
// Test error message
|
|
440
|
+
const result = upsertValidator.safeValidate(['books:create:all'])
|
|
441
|
+
expect(result.valid).toBe(false)
|
|
442
|
+
expect(result.errorMessage).toBe('Need books:write:all OR (books:create:all AND books:update:all)')
|
|
443
|
+
})
|
|
444
|
+
})
|
|
209
445
|
})
|
|
@@ -44,7 +44,7 @@ export class PermissionValidator {
|
|
|
44
44
|
return this
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
allOf(permissions: RulePermission[], errorMessage
|
|
47
|
+
allOf(permissions: RulePermission[], errorMessage: string = 'You do not have the required permissions') {
|
|
48
48
|
this.addRule({
|
|
49
49
|
type: PermissionRuleType.ALL_OF,
|
|
50
50
|
permissions,
|
|
@@ -53,7 +53,7 @@ export class PermissionValidator {
|
|
|
53
53
|
return this
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
noneOf(permissions: RulePermission[], errorMessage
|
|
56
|
+
noneOf(permissions: RulePermission[], errorMessage: string = 'You have forbidden permissions') {
|
|
57
57
|
this.addRule({
|
|
58
58
|
type: PermissionRuleType.NONE_OF,
|
|
59
59
|
permissions,
|
|
@@ -62,7 +62,7 @@ export class PermissionValidator {
|
|
|
62
62
|
return this
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
someOf(permissions: RulePermission[], errorMessage
|
|
65
|
+
someOf(permissions: RulePermission[], errorMessage: string = 'You do not have any of the required permissions') {
|
|
66
66
|
this.addRule({
|
|
67
67
|
type: PermissionRuleType.SOME_OF,
|
|
68
68
|
permissions,
|