@api-client/core 0.19.22 → 0.19.24

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 (98) hide show
  1. package/build/src/browser.d.ts +3 -0
  2. package/build/src/browser.d.ts.map +1 -1
  3. package/build/src/browser.js +2 -0
  4. package/build/src/browser.js.map +1 -1
  5. package/build/src/index.d.ts +3 -0
  6. package/build/src/index.d.ts.map +1 -1
  7. package/build/src/index.js +2 -0
  8. package/build/src/index.js.map +1 -1
  9. package/build/src/modeling/ApiModel.d.ts.map +1 -1
  10. package/build/src/modeling/ApiModel.js +37 -13
  11. package/build/src/modeling/ApiModel.js.map +1 -1
  12. package/build/src/modeling/ExposedEntity.d.ts.map +1 -1
  13. package/build/src/modeling/ExposedEntity.js +54 -15
  14. package/build/src/modeling/ExposedEntity.js.map +1 -1
  15. package/build/src/modeling/actions/Action.js +2 -2
  16. package/build/src/modeling/actions/Action.js.map +1 -1
  17. package/build/src/modeling/rules/AccessRule.d.ts +5 -1
  18. package/build/src/modeling/rules/AccessRule.d.ts.map +1 -1
  19. package/build/src/modeling/rules/AccessRule.js +4 -1
  20. package/build/src/modeling/rules/AccessRule.js.map +1 -1
  21. package/build/src/modeling/rules/AllowAuthenticated.d.ts +4 -1
  22. package/build/src/modeling/rules/AllowAuthenticated.d.ts.map +1 -1
  23. package/build/src/modeling/rules/AllowAuthenticated.js +2 -2
  24. package/build/src/modeling/rules/AllowAuthenticated.js.map +1 -1
  25. package/build/src/modeling/rules/AllowPublic.d.ts +4 -1
  26. package/build/src/modeling/rules/AllowPublic.d.ts.map +1 -1
  27. package/build/src/modeling/rules/AllowPublic.js +2 -2
  28. package/build/src/modeling/rules/AllowPublic.js.map +1 -1
  29. package/build/src/modeling/rules/MatchEmailDomain.d.ts +4 -1
  30. package/build/src/modeling/rules/MatchEmailDomain.d.ts.map +1 -1
  31. package/build/src/modeling/rules/MatchEmailDomain.js +2 -2
  32. package/build/src/modeling/rules/MatchEmailDomain.js.map +1 -1
  33. package/build/src/modeling/rules/MatchResourceOwner.d.ts +4 -1
  34. package/build/src/modeling/rules/MatchResourceOwner.d.ts.map +1 -1
  35. package/build/src/modeling/rules/MatchResourceOwner.js +2 -2
  36. package/build/src/modeling/rules/MatchResourceOwner.js.map +1 -1
  37. package/build/src/modeling/rules/MatchUserProperty.d.ts +4 -1
  38. package/build/src/modeling/rules/MatchUserProperty.d.ts.map +1 -1
  39. package/build/src/modeling/rules/MatchUserProperty.js +2 -2
  40. package/build/src/modeling/rules/MatchUserProperty.js.map +1 -1
  41. package/build/src/modeling/rules/MatchUserRole.d.ts +4 -1
  42. package/build/src/modeling/rules/MatchUserRole.d.ts.map +1 -1
  43. package/build/src/modeling/rules/MatchUserRole.js +2 -2
  44. package/build/src/modeling/rules/MatchUserRole.js.map +1 -1
  45. package/build/src/modeling/rules/index.d.ts +4 -1
  46. package/build/src/modeling/rules/index.d.ts.map +1 -1
  47. package/build/src/modeling/rules/index.js +7 -7
  48. package/build/src/modeling/rules/index.js.map +1 -1
  49. package/build/src/models/store/CustomDomain.d.ts +50 -0
  50. package/build/src/models/store/CustomDomain.d.ts.map +1 -0
  51. package/build/src/models/store/CustomDomain.js +79 -0
  52. package/build/src/models/store/CustomDomain.js.map +1 -0
  53. package/build/src/models/store/Deployment.d.ts +81 -0
  54. package/build/src/models/store/Deployment.d.ts.map +1 -0
  55. package/build/src/models/store/Deployment.js +124 -0
  56. package/build/src/models/store/Deployment.js.map +1 -0
  57. package/build/src/models/store/DeploymentCustomDomain.d.ts +52 -0
  58. package/build/src/models/store/DeploymentCustomDomain.d.ts.map +1 -0
  59. package/build/src/models/store/DeploymentCustomDomain.js +84 -0
  60. package/build/src/models/store/DeploymentCustomDomain.js.map +1 -0
  61. package/build/tsconfig.tsbuildinfo +1 -1
  62. package/package.json +1 -1
  63. package/src/modeling/ApiModel.ts +37 -13
  64. package/src/modeling/ExposedEntity.ts +62 -16
  65. package/src/modeling/actions/Action.ts +2 -2
  66. package/src/modeling/rules/AccessRule.ts +8 -1
  67. package/src/modeling/rules/AllowAuthenticated.ts +5 -2
  68. package/src/modeling/rules/AllowPublic.ts +5 -2
  69. package/src/modeling/rules/MatchEmailDomain.ts +5 -2
  70. package/src/modeling/rules/MatchResourceOwner.ts +5 -2
  71. package/src/modeling/rules/MatchUserProperty.ts +5 -2
  72. package/src/modeling/rules/MatchUserRole.ts +5 -2
  73. package/src/models/store/CustomDomain.ts +119 -0
  74. package/src/models/store/Deployment.ts +173 -0
  75. package/src/models/store/DeploymentCustomDomain.ts +120 -0
  76. package/tests/unit/modeling/actions/Action.spec.ts +13 -10
  77. package/tests/unit/modeling/actions/CreateAction.spec.ts +7 -6
  78. package/tests/unit/modeling/actions/DeleteAction.spec.ts +7 -6
  79. package/tests/unit/modeling/actions/ListAction.spec.ts +5 -4
  80. package/tests/unit/modeling/actions/ReadAction.spec.ts +9 -8
  81. package/tests/unit/modeling/actions/SearchAction.spec.ts +5 -4
  82. package/tests/unit/modeling/actions/UpdateAction.spec.ts +7 -6
  83. package/tests/unit/modeling/actions/helpers.ts +7 -0
  84. package/tests/unit/modeling/api_model.spec.ts +3 -1
  85. package/tests/unit/modeling/api_model_expose_entity.spec.ts +5 -17
  86. package/tests/unit/modeling/exposed_entity.spec.ts +6 -2
  87. package/tests/unit/modeling/exposed_entity_actions.spec.ts +0 -4
  88. package/tests/unit/modeling/rules/AccessRule.spec.ts +6 -5
  89. package/tests/unit/modeling/rules/AllowAuthenticated.spec.ts +4 -3
  90. package/tests/unit/modeling/rules/AllowPublic.spec.ts +4 -3
  91. package/tests/unit/modeling/rules/MatchEmailDomain.spec.ts +6 -5
  92. package/tests/unit/modeling/rules/MatchResourceOwner.spec.ts +7 -6
  93. package/tests/unit/modeling/rules/MatchUserProperty.spec.ts +6 -5
  94. package/tests/unit/modeling/rules/MatchUserRole.spec.ts +6 -5
  95. package/tests/unit/modeling/rules/restoring_rules.spec.ts +19 -21
  96. package/tests/unit/models/store/CustomDomain.spec.ts +111 -0
  97. package/tests/unit/models/store/Deployment.spec.ts +134 -0
  98. package/tests/unit/models/store/DeploymentCustomDomain.spec.ts +122 -0
@@ -1,9 +1,10 @@
1
1
  import { test } from '@japa/runner'
2
2
  import { UpdateAction } from '../../../../src/modeling/actions/UpdateAction.js'
3
+ import { mockExposedEntity } from './helpers.js'
3
4
 
4
5
  test.group('UpdateAction', () => {
5
6
  test('initializes with default values', ({ assert }) => {
6
- const action = new UpdateAction({} as any)
7
+ const action = new UpdateAction(mockExposedEntity())
7
8
  assert.equal(action.kind, 'update')
8
9
  assert.deepEqual(action.allowedMethods, ['PATCH'])
9
10
  assert.isEmpty(action.accessRule)
@@ -11,7 +12,7 @@ test.group('UpdateAction', () => {
11
12
 
12
13
  test('initializes with provided values', ({ assert }) => {
13
14
  const methods: ('PUT' | 'PATCH')[] = ['PUT', 'PATCH']
14
- const action = new UpdateAction({} as any, {
15
+ const action = new UpdateAction(mockExposedEntity(), {
15
16
  allowedMethods: methods,
16
17
  })
17
18
 
@@ -22,7 +23,7 @@ test.group('UpdateAction', () => {
22
23
  test('constructor copies arrays (immutability)', ({ assert }) => {
23
24
  const methods: ('PUT' | 'PATCH')[] = ['PUT']
24
25
 
25
- const action = new UpdateAction({} as any, {
26
+ const action = new UpdateAction(mockExposedEntity(), {
26
27
  allowedMethods: methods,
27
28
  })
28
29
 
@@ -33,7 +34,7 @@ test.group('UpdateAction', () => {
33
34
  }).tags(['@modeling', '@action', '@update-action', '@immutability'])
34
35
 
35
36
  test('toJSON returns valid schema', ({ assert }) => {
36
- const action = new UpdateAction({} as any, {
37
+ const action = new UpdateAction(mockExposedEntity(), {
37
38
  allowedMethods: ['PUT'],
38
39
  })
39
40
 
@@ -48,7 +49,7 @@ test.group('UpdateAction', () => {
48
49
  }).tags(['@modeling', '@action', '@update-action', '@serialization', '@immutability'])
49
50
 
50
51
  test('notifies change when allowedMethods changes', async ({ assert }) => {
51
- const action = new UpdateAction({} as any)
52
+ const action = new UpdateAction(mockExposedEntity())
52
53
  let notified = false
53
54
  action.addEventListener('change', () => {
54
55
  notified = true
@@ -60,7 +61,7 @@ test.group('UpdateAction', () => {
60
61
  }).tags(['@modeling', '@action', '@update-action', '@observed'])
61
62
 
62
63
  test('notifies change when allowedMethods value change', async ({ assert }) => {
63
- const action = new UpdateAction({} as any)
64
+ const action = new UpdateAction(mockExposedEntity())
64
65
  let notified = false
65
66
  action.addEventListener('change', () => {
66
67
  notified = true
@@ -0,0 +1,7 @@
1
+ import { type ExposedEntity } from '../../../../src/modeling/ExposedEntity.js'
2
+
3
+ export function mockExposedEntity(): ExposedEntity {
4
+ return {
5
+ notifyChange: () => {},
6
+ } as unknown as ExposedEntity
7
+ }
@@ -152,7 +152,7 @@ test.group('ApiModel.constructor()', () => {
152
152
  assert.deepEqual(model.authentication, { strategy: 'UsernamePassword' })
153
153
  assert.deepEqual(model.authorization, { strategy: 'RBAC', roleKey: 'role' })
154
154
  assert.deepEqual(model.session, { secret: 'secret', properties: ['email'] })
155
- assert.deepEqual(model.accessRule, [new AllowPublicAccessRule()])
155
+ assert.deepEqual(model.accessRule, [new AllowPublicAccessRule(model)])
156
156
  assert.deepEqual(model.rateLimiting, new RateLimitingConfiguration())
157
157
  assert.equal(model.termsOfService, 'https://example.com/terms')
158
158
  assert.deepEqual(model.contact, { name: 'John Doe', email: 'john.doe@example.com' })
@@ -223,6 +223,8 @@ test.group('ApiModel.constructor()', () => {
223
223
  })
224
224
 
225
225
  Array.from(model.exposes.values())[0]!.actions[0]!.kind = 'write'
226
+ // there are two microtasks on the notification path.
227
+ await Promise.resolve()
226
228
  await Promise.resolve()
227
229
  assert.isTrue(notified)
228
230
  }).tags(['@modeling', '@api', '@observed'])
@@ -35,20 +35,6 @@ test.group('ApiModel.exposeEntity()', () => {
35
35
  assert.deepEqual(exposedEntity.actions, [])
36
36
  }).tags(['@modeling', '@api'])
37
37
 
38
- test('returns an existing entity if already exposed', ({ assert }) => {
39
- const domain = new DataDomain()
40
- domain.info.version = '1.0.0'
41
- const dm = domain.addModel()
42
- const e1 = dm.addEntity()
43
- const model = new ApiModel()
44
- model.attachDataDomain(domain)
45
- const initialExposedEntity = model.exposeEntity({ key: e1.key })
46
- const retrievedExposedEntity = model.exposeEntity({ key: e1.key })
47
-
48
- assert.deepEqual(retrievedExposedEntity.toJSON(), initialExposedEntity.toJSON())
49
- assert.equal(model.exposes.size, 1)
50
- }).tags(['@modeling', '@api'])
51
-
52
38
  test('notifies change when a new entity is exposed', async ({ assert }) => {
53
39
  const domain = new DataDomain()
54
40
  domain.info.version = '1.0.0'
@@ -65,7 +51,7 @@ test.group('ApiModel.exposeEntity()', () => {
65
51
  assert.isTrue(notified)
66
52
  }).tags(['@modeling', '@api'])
67
53
 
68
- test('does not notify change if entity already exposed', async ({ assert }) => {
54
+ test('throws if entity already exposed', async ({ assert }) => {
69
55
  const domain = new DataDomain()
70
56
  domain.info.version = '1.0.0'
71
57
  const dm = domain.addModel()
@@ -78,9 +64,11 @@ test.group('ApiModel.exposeEntity()', () => {
78
64
  model.addEventListener('change', () => {
79
65
  notified = true
80
66
  })
81
- model.exposeEntity({ key: e1.key }) // Second exposure
67
+ await assert.rejects(async () => {
68
+ model.exposeEntity({ key: e1.key }) // Second exposure
69
+ }, `Entity ${e1.key} is already exposed.`)
82
70
  await Promise.resolve() // Allow microtask to run
83
- assert.isFalse(notified)
71
+ assert.isFalse(notified, 'should not notify change if entity already exposed')
84
72
  }).tags(['@modeling', '@api'])
85
73
 
86
74
  test('exposes nested entities through associations', ({ assert }) => {
@@ -127,7 +127,9 @@ test.group('ExposedEntity', () => {
127
127
 
128
128
  const ex = Array.from(model.exposes.values())[0]
129
129
  ex.setCollectionPath('items')
130
- await Promise.resolve() // allow ApiModel.notifyChange microtask to run
130
+ // there are two microtasks on the notification path.
131
+ await Promise.resolve()
132
+ await Promise.resolve()
131
133
  assert.isAtLeast(notified, 1)
132
134
  }).tags(['@modeling', '@exposed-entity', '@observed'])
133
135
 
@@ -154,6 +156,8 @@ test.group('ExposedEntity', () => {
154
156
 
155
157
  const ex = Array.from(model.exposes.values())[0]
156
158
  ex.setResourcePath('/products/{productId}')
159
+ // there are two microtasks on the notification path.
160
+ await Promise.resolve()
157
161
  await Promise.resolve()
158
162
  assert.isAtLeast(notified, 1)
159
163
  }).tags(['@modeling', '@exposed-entity', '@observed'])
@@ -304,7 +308,7 @@ test.group('ExposedEntity', () => {
304
308
 
305
309
  test('getAllRules() aggregates rules from entity, parent, and API', ({ assert }) => {
306
310
  const model = new ApiModel()
307
- model.accessRule = [new AccessRule({ type: 'allowPublic' })]
311
+ model.accessRule = [new AccessRule(model, { type: 'allowPublic' })]
308
312
 
309
313
  const rootEx = new ExposedEntity(model, {
310
314
  key: 'root',
@@ -33,9 +33,6 @@ test.group('ExposedEntity::actions', (group) => {
33
33
  test('restores a list acton', ({ assert }) => {
34
34
  const action: ListActionSchema = {
35
35
  kind: 'list',
36
- pagination: { kind: '' },
37
- sortableFields: [],
38
- filterableFields: [],
39
36
  }
40
37
  const model = new ApiModel(
41
38
  {
@@ -177,7 +174,6 @@ test.group('ExposedEntity::actions', (group) => {
177
174
  test('restores a search acton', ({ assert }) => {
178
175
  const action: SearchActionSchema = {
179
176
  kind: 'search',
180
- fields: [],
181
177
  }
182
178
  const model = new ApiModel(
183
179
  {
@@ -1,26 +1,27 @@
1
1
  import { test } from '@japa/runner'
2
2
  import { AccessRule, type AccessRuleSchema } from '../../../../src/modeling/index.js'
3
+ import { mockExposedEntity } from '../actions/helpers.js'
3
4
 
4
5
  test.group('AccessRule', () => {
5
6
  test('initializes with default values', ({ assert }) => {
6
- const rule = new AccessRule()
7
+ const rule = new AccessRule(mockExposedEntity())
7
8
  assert.equal(rule.type, '')
8
9
  }).tags(['@modeling', '@access-rule'])
9
10
 
10
11
  test('initializes with provided values', ({ assert }) => {
11
12
  const schema: AccessRuleSchema = { type: 'public' }
12
- const rule = new AccessRule(schema)
13
+ const rule = new AccessRule(mockExposedEntity(), schema)
13
14
  assert.equal(rule.type, 'public')
14
15
  }).tags(['@modeling', '@access-rule'])
15
16
 
16
17
  test('serializes to JSON', ({ assert }) => {
17
- const rule = new AccessRule({ type: 'authenticated' })
18
+ const rule = new AccessRule(mockExposedEntity(), { type: 'authenticated' })
18
19
  const json = rule.toJSON()
19
20
  assert.deepEqual(json, { type: 'authenticated' })
20
21
  }).tags(['@modeling', '@access-rule'])
21
22
 
22
23
  test('notifies change', async ({ assert }) => {
23
- const rule = new AccessRule({ type: 'public' })
24
+ const rule = new AccessRule(mockExposedEntity(), { type: 'public' })
24
25
  let notified = false
25
26
  rule.addEventListener('change', () => {
26
27
  notified = true
@@ -31,7 +32,7 @@ test.group('AccessRule', () => {
31
32
  }).tags(['@modeling', '@access-rule'])
32
33
 
33
34
  test('toJSON returns safe copy (immutability)', ({ assert }) => {
34
- const rule = new AccessRule({ type: 'public' })
35
+ const rule = new AccessRule(mockExposedEntity(), { type: 'public' })
35
36
  const json = rule.toJSON()
36
37
 
37
38
  // Modify JSON (simulate runtime mutation)
@@ -1,21 +1,22 @@
1
1
  import { test } from '@japa/runner'
2
2
  import { AllowAuthenticatedAccessRule } from '../../../../src/modeling/rules/AllowAuthenticated.js'
3
+ import { mockExposedEntity } from '../actions/helpers.js'
3
4
 
4
5
  test.group('AllowAuthenticatedAccessRule', () => {
5
6
  test('initializes with correct type', ({ assert }) => {
6
- const rule = new AllowAuthenticatedAccessRule()
7
+ const rule = new AllowAuthenticatedAccessRule(mockExposedEntity())
7
8
  assert.equal(rule.type, 'allowAuthenticated')
8
9
  }).tags(['@modeling', '@rule', '@allow-authenticated'])
9
10
 
10
11
  test('toJSON returns valid schema', ({ assert }) => {
11
- const rule = new AllowAuthenticatedAccessRule()
12
+ const rule = new AllowAuthenticatedAccessRule(mockExposedEntity())
12
13
  const json = rule.toJSON()
13
14
 
14
15
  assert.equal(json.type, 'allowAuthenticated')
15
16
  }).tags(['@modeling', '@rule', '@allow-authenticated', '@serialization'])
16
17
 
17
18
  test('notifies change', async ({ assert }) => {
18
- const rule = new AllowAuthenticatedAccessRule()
19
+ const rule = new AllowAuthenticatedAccessRule(mockExposedEntity())
19
20
  let notified = false
20
21
  rule.addEventListener('change', () => {
21
22
  notified = true
@@ -1,21 +1,22 @@
1
1
  import { test } from '@japa/runner'
2
2
  import { AllowPublicAccessRule } from '../../../../src/modeling/rules/AllowPublic.js'
3
+ import { mockExposedEntity } from '../actions/helpers.js'
3
4
 
4
5
  test.group('AllowPublicAccessRule', () => {
5
6
  test('initializes with correct type', ({ assert }) => {
6
- const rule = new AllowPublicAccessRule()
7
+ const rule = new AllowPublicAccessRule(mockExposedEntity())
7
8
  assert.equal(rule.type, 'allowPublic')
8
9
  }).tags(['@modeling', '@rule', '@allow-public'])
9
10
 
10
11
  test('toJSON returns valid schema', ({ assert }) => {
11
- const rule = new AllowPublicAccessRule()
12
+ const rule = new AllowPublicAccessRule(mockExposedEntity())
12
13
  const json = rule.toJSON()
13
14
 
14
15
  assert.equal(json.type, 'allowPublic')
15
16
  }).tags(['@modeling', '@rule', '@allow-public', '@serialization'])
16
17
 
17
18
  test('notifies change', async ({ assert }) => {
18
- const rule = new AllowPublicAccessRule()
19
+ const rule = new AllowPublicAccessRule(mockExposedEntity())
19
20
  let notified = false
20
21
  rule.addEventListener('change', () => {
21
22
  notified = true
@@ -1,16 +1,17 @@
1
1
  import { test } from '@japa/runner'
2
2
  import { MatchEmailDomainAccessRule } from '../../../../src/modeling/rules/MatchEmailDomain.js'
3
+ import { mockExposedEntity } from '../actions/helpers.js'
3
4
 
4
5
  test.group('MatchEmailDomainAccessRule', () => {
5
6
  test('initializes with default values', ({ assert }) => {
6
- const rule = new MatchEmailDomainAccessRule()
7
+ const rule = new MatchEmailDomainAccessRule(mockExposedEntity())
7
8
  assert.equal(rule.type, 'matchEmailDomain')
8
9
  assert.deepEqual(rule.domains, [])
9
10
  }).tags(['@modeling', '@rule', '@match-email-domain'])
10
11
 
11
12
  test('initializes with provided values', ({ assert }) => {
12
13
  const domains = ['example.com', 'test.org']
13
- const rule = new MatchEmailDomainAccessRule({ domains })
14
+ const rule = new MatchEmailDomainAccessRule(mockExposedEntity(), { domains })
14
15
 
15
16
  assert.equal(rule.type, 'matchEmailDomain')
16
17
  assert.deepEqual(rule.domains, domains)
@@ -18,7 +19,7 @@ test.group('MatchEmailDomainAccessRule', () => {
18
19
 
19
20
  test('constructor copies arrays (immutability)', ({ assert }) => {
20
21
  const domains = ['example.com']
21
- const rule = new MatchEmailDomainAccessRule({ domains })
22
+ const rule = new MatchEmailDomainAccessRule(mockExposedEntity(), { domains })
22
23
 
23
24
  // Modify original source
24
25
  domains.push('hacker.com')
@@ -27,7 +28,7 @@ test.group('MatchEmailDomainAccessRule', () => {
27
28
  }).tags(['@modeling', '@rule', '@match-email-domain', '@immutability'])
28
29
 
29
30
  test('toJSON returns valid schema', ({ assert }) => {
30
- const rule = new MatchEmailDomainAccessRule({ domains: ['example.com'] })
31
+ const rule = new MatchEmailDomainAccessRule(mockExposedEntity(), { domains: ['example.com'] })
31
32
  const json = rule.toJSON()
32
33
 
33
34
  // Modify JSON
@@ -39,7 +40,7 @@ test.group('MatchEmailDomainAccessRule', () => {
39
40
  }).tags(['@modeling', '@rule', '@match-email-domain', '@serialization', '@immutability'])
40
41
 
41
42
  test('notifies change when domains changes', async ({ assert }) => {
42
- const rule = new MatchEmailDomainAccessRule()
43
+ const rule = new MatchEmailDomainAccessRule(mockExposedEntity())
43
44
  let notified = false
44
45
  rule.addEventListener('change', () => {
45
46
  notified = true
@@ -1,16 +1,17 @@
1
1
  import { test } from '@japa/runner'
2
2
  import { MatchResourceOwnerAccessRule } from '../../../../src/modeling/rules/MatchResourceOwner.js'
3
+ import { mockExposedEntity } from '../actions/helpers.js'
3
4
 
4
5
  test.group('MatchResourceOwnerAccessRule', () => {
5
6
  test('initializes with default values', ({ assert }) => {
6
- const rule = new MatchResourceOwnerAccessRule()
7
+ const rule = new MatchResourceOwnerAccessRule(mockExposedEntity())
7
8
  assert.equal(rule.type, 'matchResourceOwner')
8
9
  assert.isUndefined(rule.property)
9
10
  assert.equal(rule.target, 'property')
10
11
  }).tags(['@modeling', '@rule', '@match-resource-owner'])
11
12
 
12
13
  test('initializes with provided values', ({ assert }) => {
13
- const rule = new MatchResourceOwnerAccessRule({ property: 'creatorId' })
14
+ const rule = new MatchResourceOwnerAccessRule(mockExposedEntity(), { property: 'creatorId' })
14
15
 
15
16
  assert.equal(rule.type, 'matchResourceOwner')
16
17
  assert.equal(rule.property, 'creatorId')
@@ -18,7 +19,7 @@ test.group('MatchResourceOwnerAccessRule', () => {
18
19
  }).tags(['@modeling', '@rule', '@match-resource-owner'])
19
20
 
20
21
  test('initializes with target user-entity', ({ assert }) => {
21
- const rule = new MatchResourceOwnerAccessRule({ target: 'user-entity' })
22
+ const rule = new MatchResourceOwnerAccessRule(mockExposedEntity(), { target: 'user-entity' })
22
23
 
23
24
  assert.equal(rule.type, 'matchResourceOwner')
24
25
  assert.isUndefined(rule.property)
@@ -26,7 +27,7 @@ test.group('MatchResourceOwnerAccessRule', () => {
26
27
  }).tags(['@modeling', '@rule', '@match-resource-owner'])
27
28
 
28
29
  test('toJSON returns valid schema', ({ assert }) => {
29
- const rule = new MatchResourceOwnerAccessRule({ property: 'creatorId' })
30
+ const rule = new MatchResourceOwnerAccessRule(mockExposedEntity(), { property: 'creatorId' })
30
31
  const json = rule.toJSON()
31
32
 
32
33
  assert.equal(json.type, 'matchResourceOwner')
@@ -35,7 +36,7 @@ test.group('MatchResourceOwnerAccessRule', () => {
35
36
  }).tags(['@modeling', '@rule', '@match-resource-owner', '@serialization'])
36
37
 
37
38
  test('toJSON omits property when undefined', ({ assert }) => {
38
- const rule = new MatchResourceOwnerAccessRule({ target: 'user-entity' })
39
+ const rule = new MatchResourceOwnerAccessRule(mockExposedEntity(), { target: 'user-entity' })
39
40
  const json = rule.toJSON()
40
41
 
41
42
  assert.equal(json.type, 'matchResourceOwner')
@@ -44,7 +45,7 @@ test.group('MatchResourceOwnerAccessRule', () => {
44
45
  }).tags(['@modeling', '@rule', '@match-resource-owner', '@serialization'])
45
46
 
46
47
  test('notifies change when property changes', async ({ assert }) => {
47
- const rule = new MatchResourceOwnerAccessRule()
48
+ const rule = new MatchResourceOwnerAccessRule(mockExposedEntity())
48
49
  let notified = false
49
50
  rule.addEventListener('change', () => {
50
51
  notified = true
@@ -1,16 +1,17 @@
1
1
  import { test } from '@japa/runner'
2
2
  import { MatchUserPropertyAccessRule } from '../../../../src/modeling/rules/MatchUserProperty.js'
3
+ import { mockExposedEntity } from '../actions/helpers.js'
3
4
 
4
5
  test.group('MatchUserPropertyAccessRule', () => {
5
6
  test('initializes with default values', ({ assert }) => {
6
- const rule = new MatchUserPropertyAccessRule()
7
+ const rule = new MatchUserPropertyAccessRule(mockExposedEntity())
7
8
  assert.equal(rule.type, 'matchUserProperty')
8
9
  assert.equal(rule.property, '')
9
10
  assert.equal(rule.value, '')
10
11
  }).tags(['@modeling', '@rule', '@match-user-property'])
11
12
 
12
13
  test('initializes with provided values', ({ assert }) => {
13
- const rule = new MatchUserPropertyAccessRule({
14
+ const rule = new MatchUserPropertyAccessRule(mockExposedEntity(), {
14
15
  property: 'department',
15
16
  value: 'engineering',
16
17
  })
@@ -21,7 +22,7 @@ test.group('MatchUserPropertyAccessRule', () => {
21
22
  }).tags(['@modeling', '@rule', '@match-user-property'])
22
23
 
23
24
  test('toJSON returns valid schema', ({ assert }) => {
24
- const rule = new MatchUserPropertyAccessRule({
25
+ const rule = new MatchUserPropertyAccessRule(mockExposedEntity(), {
25
26
  property: 'department',
26
27
  value: 'engineering',
27
28
  })
@@ -33,7 +34,7 @@ test.group('MatchUserPropertyAccessRule', () => {
33
34
  }).tags(['@modeling', '@rule', '@match-user-property', '@serialization'])
34
35
 
35
36
  test('notifies change when property changes', async ({ assert }) => {
36
- const rule = new MatchUserPropertyAccessRule()
37
+ const rule = new MatchUserPropertyAccessRule(mockExposedEntity())
37
38
  let notified = false
38
39
  rule.addEventListener('change', () => {
39
40
  notified = true
@@ -45,7 +46,7 @@ test.group('MatchUserPropertyAccessRule', () => {
45
46
  }).tags(['@modeling', '@rule', '@match-user-property', '@observed'])
46
47
 
47
48
  test('notifies change when value changes', async ({ assert }) => {
48
- const rule = new MatchUserPropertyAccessRule()
49
+ const rule = new MatchUserPropertyAccessRule(mockExposedEntity())
49
50
  let notified = false
50
51
  rule.addEventListener('change', () => {
51
52
  notified = true
@@ -1,16 +1,17 @@
1
1
  import { test } from '@japa/runner'
2
2
  import { MatchUserRoleAccessRule } from '../../../../src/modeling/rules/MatchUserRole.js'
3
+ import { mockExposedEntity } from '../actions/helpers.js'
3
4
 
4
5
  test.group('MatchUserRoleAccessRule', () => {
5
6
  test('initializes with default values', ({ assert }) => {
6
- const rule = new MatchUserRoleAccessRule()
7
+ const rule = new MatchUserRoleAccessRule(mockExposedEntity())
7
8
  assert.equal(rule.type, 'matchUserRole')
8
9
  assert.deepEqual(rule.role, [])
9
10
  }).tags(['@modeling', '@rule', '@match-user-role'])
10
11
 
11
12
  test('initializes with provided values', ({ assert }) => {
12
13
  const role = ['admin', 'manager']
13
- const rule = new MatchUserRoleAccessRule({ role })
14
+ const rule = new MatchUserRoleAccessRule(mockExposedEntity(), { role })
14
15
 
15
16
  assert.equal(rule.type, 'matchUserRole')
16
17
  assert.deepEqual(rule.role, role)
@@ -18,7 +19,7 @@ test.group('MatchUserRoleAccessRule', () => {
18
19
 
19
20
  test('constructor copies arrays (immutability)', ({ assert }) => {
20
21
  const role = ['admin']
21
- const rule = new MatchUserRoleAccessRule({ role })
22
+ const rule = new MatchUserRoleAccessRule(mockExposedEntity(), { role })
22
23
 
23
24
  // Modify original source
24
25
  role.push('guest')
@@ -27,7 +28,7 @@ test.group('MatchUserRoleAccessRule', () => {
27
28
  }).tags(['@modeling', '@rule', '@match-user-role', '@immutability'])
28
29
 
29
30
  test('toJSON returns valid schema', ({ assert }) => {
30
- const rule = new MatchUserRoleAccessRule({ role: ['admin'] })
31
+ const rule = new MatchUserRoleAccessRule(mockExposedEntity(), { role: ['admin'] })
31
32
  const json = rule.toJSON()
32
33
 
33
34
  // Modify JSON
@@ -39,7 +40,7 @@ test.group('MatchUserRoleAccessRule', () => {
39
40
  }).tags(['@modeling', '@rule', '@match-user-role', '@serialization', '@immutability'])
40
41
 
41
42
  test('notifies change when role changes', async ({ assert }) => {
42
- const rule = new MatchUserRoleAccessRule()
43
+ const rule = new MatchUserRoleAccessRule(mockExposedEntity())
43
44
  let notified = false
44
45
  rule.addEventListener('change', () => {
45
46
  notified = true
@@ -10,6 +10,7 @@ import {
10
10
  } from '../../../../src/modeling/index.js'
11
11
  import { nanoid } from '../../../../src/nanoid.js'
12
12
  import { ExposedEntityKind } from '../../../../src/models/kinds.js'
13
+ import { mockExposedEntity } from '../actions/helpers.js'
13
14
 
14
15
  test.group('restoring actions', (group) => {
15
16
  let domain: DataDomain
@@ -23,12 +24,12 @@ test.group('restoring actions', (group) => {
23
24
  })
24
25
 
25
26
  test('restores rules on the API model', ({ assert }) => {
26
- const r1 = new AllowAuthenticatedAccessRule()
27
- const r2 = new AllowPublicAccessRule()
28
- const r3 = new MatchEmailDomainAccessRule()
29
- const r4 = new MatchResourceOwnerAccessRule()
30
- const r5 = new MatchUserPropertyAccessRule()
31
- const r6 = new MatchUserRoleAccessRule()
27
+ const r1 = new AllowAuthenticatedAccessRule(mockExposedEntity())
28
+ const r2 = new AllowPublicAccessRule(mockExposedEntity())
29
+ const r3 = new MatchEmailDomainAccessRule(mockExposedEntity())
30
+ const r4 = new MatchResourceOwnerAccessRule(mockExposedEntity())
31
+ const r5 = new MatchUserPropertyAccessRule(mockExposedEntity())
32
+ const r6 = new MatchUserRoleAccessRule(mockExposedEntity())
32
33
  const model = new ApiModel(
33
34
  {
34
35
  accessRule: [r1.toJSON(), r2.toJSON(), r3.toJSON(), r4.toJSON(), r5.toJSON(), r6.toJSON()],
@@ -46,12 +47,12 @@ test.group('restoring actions', (group) => {
46
47
  }).tags(['@modeling', '@rule', '@restoring'])
47
48
 
48
49
  test('restores rules on the exposed entity', ({ assert }) => {
49
- const r1 = new AllowAuthenticatedAccessRule()
50
- const r2 = new AllowPublicAccessRule()
51
- const r3 = new MatchEmailDomainAccessRule()
52
- const r4 = new MatchResourceOwnerAccessRule()
53
- const r5 = new MatchUserPropertyAccessRule()
54
- const r6 = new MatchUserRoleAccessRule()
50
+ const r1 = new AllowAuthenticatedAccessRule(mockExposedEntity())
51
+ const r2 = new AllowPublicAccessRule(mockExposedEntity())
52
+ const r3 = new MatchEmailDomainAccessRule(mockExposedEntity())
53
+ const r4 = new MatchResourceOwnerAccessRule(mockExposedEntity())
54
+ const r5 = new MatchUserPropertyAccessRule(mockExposedEntity())
55
+ const r6 = new MatchUserRoleAccessRule(mockExposedEntity())
55
56
  const model = new ApiModel(
56
57
  {
57
58
  exposes: [
@@ -82,17 +83,14 @@ test.group('restoring actions', (group) => {
82
83
  }).tags(['@modeling', '@rule', '@restoring'])
83
84
 
84
85
  test('restores rules on the action', ({ assert }) => {
85
- const r1 = new AllowAuthenticatedAccessRule()
86
- const r2 = new AllowPublicAccessRule()
87
- const r3 = new MatchEmailDomainAccessRule()
88
- const r4 = new MatchResourceOwnerAccessRule()
89
- const r5 = new MatchUserPropertyAccessRule()
90
- const r6 = new MatchUserRoleAccessRule()
86
+ const r1 = new AllowAuthenticatedAccessRule(mockExposedEntity())
87
+ const r2 = new AllowPublicAccessRule(mockExposedEntity())
88
+ const r3 = new MatchEmailDomainAccessRule(mockExposedEntity())
89
+ const r4 = new MatchResourceOwnerAccessRule(mockExposedEntity())
90
+ const r5 = new MatchUserPropertyAccessRule(mockExposedEntity())
91
+ const r6 = new MatchUserRoleAccessRule(mockExposedEntity())
91
92
  const action: ListActionSchema = {
92
93
  kind: 'list',
93
- pagination: { kind: '' },
94
- sortableFields: [],
95
- filterableFields: [],
96
94
  accessRule: [r1.toJSON(), r2.toJSON(), r3.toJSON(), r4.toJSON(), r5.toJSON(), r6.toJSON()],
97
95
  }
98
96
  const model = new ApiModel(
@@ -0,0 +1,111 @@
1
+ import { test } from '@japa/runner'
2
+ import { CustomDomainModel } from '../../../../src/models/store/CustomDomain.js'
3
+ import type { CustomDomainSchema } from '../../../../src/models/store/CustomDomain.js'
4
+
5
+ test.group('CustomDomain model', () => {
6
+ test('createSchema() returns default values', ({ assert }) => {
7
+ const result = CustomDomainModel.createSchema()
8
+ assert.isString(result.id)
9
+ assert.equal(result.orgId, '')
10
+ assert.equal(result.domain, '')
11
+ assert.equal(result.dnsTarget, '')
12
+ assert.isFalse(result.dnsVerified)
13
+ assert.isUndefined(result.lastVerifiedAt)
14
+ assert.isNumber(result.createdAt)
15
+ assert.isNumber(result.updatedAt)
16
+ })
17
+
18
+ test('createSchema(init) assigns values', ({ assert }) => {
19
+ const init: Partial<CustomDomainSchema> = {
20
+ id: 'test-id',
21
+ orgId: 'org-1',
22
+ domain: 'api.example.com',
23
+ dnsTarget: 'target.example.com',
24
+ dnsVerified: true,
25
+ lastVerifiedAt: 11111,
26
+ createdAt: 12345,
27
+ updatedAt: 67890,
28
+ }
29
+ const result = CustomDomainModel.createSchema(init)
30
+ assert.equal(result.id, 'test-id')
31
+ assert.equal(result.orgId, 'org-1')
32
+ assert.equal(result.domain, 'api.example.com')
33
+ assert.equal(result.dnsTarget, 'target.example.com')
34
+ assert.isTrue(result.dnsVerified)
35
+ assert.equal(result.lastVerifiedAt, 11111)
36
+ assert.equal(result.createdAt, 12345)
37
+ assert.equal(result.updatedAt, 67890)
38
+ })
39
+
40
+ test('constructor() initializes with default values', ({ assert }) => {
41
+ const model = new CustomDomainModel()
42
+ assert.isString(model.id)
43
+ assert.equal(model.orgId, '')
44
+ assert.equal(model.domain, '')
45
+ assert.equal(model.dnsTarget, '')
46
+ assert.isFalse(model.dnsVerified)
47
+ assert.isUndefined(model.lastVerifiedAt)
48
+ assert.isNumber(model.createdAt)
49
+ assert.isNumber(model.updatedAt)
50
+ })
51
+
52
+ test('constructor(init) assigns values', ({ assert }) => {
53
+ const init: Partial<CustomDomainSchema> = {
54
+ orgId: 'org-1',
55
+ domain: 'api.example.com',
56
+ dnsVerified: true,
57
+ }
58
+ const model = new CustomDomainModel(init)
59
+ assert.equal(model.orgId, 'org-1')
60
+ assert.equal(model.domain, 'api.example.com')
61
+ assert.isTrue(model.dnsVerified)
62
+ })
63
+
64
+ test('toJSON() returns the schema representation', ({ assert }) => {
65
+ const init: Partial<CustomDomainSchema> = {
66
+ id: 'test-id',
67
+ orgId: 'org-1',
68
+ domain: 'api.example.com',
69
+ dnsTarget: 'target.example.com',
70
+ dnsVerified: true,
71
+ lastVerifiedAt: 11111,
72
+ createdAt: 12345,
73
+ updatedAt: 67890,
74
+ }
75
+ const model = new CustomDomainModel(init)
76
+ const result = model.toJSON()
77
+ assert.deepEqual(result, {
78
+ id: 'test-id',
79
+ orgId: 'org-1',
80
+ domain: 'api.example.com',
81
+ dnsTarget: 'target.example.com',
82
+ dnsVerified: true,
83
+ lastVerifiedAt: 11111,
84
+ createdAt: 12345,
85
+ updatedAt: 67890,
86
+ })
87
+ })
88
+
89
+ test('validate() returns errors for empty fields', ({ assert }) => {
90
+ const model = new CustomDomainModel({
91
+ domain: '',
92
+ orgId: '',
93
+ dnsTarget: '',
94
+ })
95
+ const errors = model.validate()
96
+ assert.lengthOf(errors, 3)
97
+ assert.deepEqual(errors[0], { field: 'domain', message: 'Domain must not be empty', rule: 'notEmpty' })
98
+ assert.deepEqual(errors[1], { field: 'orgId', message: 'Org ID must not be empty', rule: 'notEmpty' })
99
+ assert.deepEqual(errors[2], { field: 'dnsTarget', message: 'DNS target must not be empty', rule: 'notEmpty' })
100
+ })
101
+
102
+ test('validate() returns no errors for valid model', ({ assert }) => {
103
+ const model = new CustomDomainModel({
104
+ domain: 'api.example.com',
105
+ orgId: 'org-1',
106
+ dnsTarget: 'target.example.com',
107
+ })
108
+ const errors = model.validate()
109
+ assert.lengthOf(errors, 0)
110
+ })
111
+ })