@appsemble/utils 0.30.14-test.5 → 0.32.1-test.14
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/README.md +3 -3
- package/allActions.js +5 -3
- package/api/components/parameters/$orderby.js +2 -2
- package/api/components/parameters/index.d.ts +2 -1
- package/api/components/parameters/index.js +2 -1
- package/api/components/parameters/webhookName.d.ts +2 -0
- package/api/components/parameters/webhookName.js +10 -0
- package/api/components/parameters/webhookSecretId.d.ts +2 -0
- package/api/components/parameters/webhookSecretId.js +8 -0
- package/api/components/schemas/ActionDefinition.js +3 -0
- package/api/components/schemas/App.js +8 -0
- package/api/components/schemas/AppDefinition.js +6 -0
- package/api/components/schemas/AppLayoutDefinition.js +8 -0
- package/api/components/schemas/AppServiceSecret.js +5 -0
- package/api/components/schemas/AppWebhookSecret.d.ts +2 -0
- package/api/components/schemas/AppWebhookSecret.js +25 -0
- package/api/components/schemas/DialogOkActionDefinition.js +1 -1
- package/api/components/schemas/FilterParametersDefinition.d.ts +2 -0
- package/api/components/schemas/FilterParametersDefinition.js +15 -0
- package/api/components/schemas/FlowCancelActionDefinition.js +1 -1
- package/api/components/schemas/FlowFinishActionDefinition.js +1 -1
- package/api/components/schemas/GroupMemberDeleteActionDefinition.js +1 -1
- package/api/components/schemas/ResourceDefinition.js +5 -0
- package/api/components/schemas/ResourceDeleteAllActionDefinition.d.ts +1 -0
- package/api/components/schemas/ResourceDeleteAllActionDefinition.js +21 -0
- package/api/components/schemas/ResourceDeleteBulkActionDefinition.d.ts +1 -0
- package/api/components/schemas/ResourceDeleteBulkActionDefinition.js +18 -0
- package/api/components/schemas/ResourceGetActionDefinition.js +3 -0
- package/api/components/schemas/ResourcePatchActionDefinition.js +3 -0
- package/api/components/schemas/ResourceSubscriptionStatusActionDefinition.js +1 -0
- package/api/components/schemas/ResourceSubscriptionSubscribeActionDefinition.js +1 -0
- package/api/components/schemas/ResourceSubscriptionToggleActionDefinition.js +1 -0
- package/api/components/schemas/ResourceSubscriptionUnsubscribeActionDefinition.js +1 -0
- package/api/components/schemas/ResourceUpdatePositionsActionDefinition.d.ts +1 -0
- package/api/components/schemas/ResourceUpdatePositionsActionDefinition.js +21 -0
- package/api/components/schemas/SecurityCronDefinition.d.ts +2 -0
- package/api/components/schemas/SecurityCronDefinition.js +26 -0
- package/api/components/schemas/SecurityDefinition.js +1 -0
- package/api/components/schemas/StorageSubtractActionDefinition.js +2 -2
- package/api/components/schemas/StorageWriteActionDefinition.js +4 -0
- package/api/components/schemas/Training.js +2 -25
- package/api/components/schemas/TrainingCompleted.d.ts +2 -0
- package/api/components/schemas/TrainingCompleted.js +17 -0
- package/api/components/schemas/WebhookDefinition.d.ts +2 -0
- package/api/components/schemas/WebhookDefinition.js +17 -0
- package/api/components/schemas/index.d.ts +8 -1
- package/api/components/schemas/index.js +8 -1
- package/api/components/schemas/utils.js +1 -1
- package/api/components/securitySchemes/index.d.ts +1 -0
- package/api/components/securitySchemes/index.js +1 -0
- package/api/components/securitySchemes/webhook.d.ts +2 -0
- package/api/components/securitySchemes/webhook.js +6 -0
- package/api/paths/apps/appId/resources/resourceType/resourceId/positions.js +54 -0
- package/api/paths/apps/appId/secrets/webhook/secretId.js +61 -0
- package/api/paths/apps/appId/secrets/webhook.js +44 -0
- package/api/paths/apps/appId/webhooks/webhookName.js +35 -0
- package/api/paths/apps/appId.js +6 -0
- package/api/paths/auth/email/patchPassword.js +30 -0
- package/api/paths/index.d.ts +7 -5
- package/api/paths/index.js +16 -12
- package/api/paths/trainings/completeTraining.js +18 -0
- package/api/paths/trainings/completedTrainings.d.ts +2 -0
- package/api/paths/trainings/completedTrainings.js +21 -0
- package/api/paths/trainings/trainingIds.d.ts +2 -0
- package/api/paths/trainings/trainingIds.js +20 -0
- package/api/tags/index.js +4 -4
- package/appMessages.js +1 -1
- package/assets.js +1 -1
- package/authorization.d.ts +1 -1
- package/authorization.js +1 -1
- package/blockUtils.d.ts +1 -1
- package/blockUtils.js +1 -0
- package/convertToCsv.js +2 -0
- package/examples.js +214 -4
- package/formatRequestAction.js +2 -2
- package/has.d.ts +1 -1
- package/has.js +1 -1
- package/i18n.js +6 -8
- package/ics.js +1 -1
- package/iterApp.js +3 -3
- package/jsonschema.js +16 -3
- package/package.json +15 -11
- package/reference-schemas/actions/appMember.d.ts +2 -0
- package/reference-schemas/actions/appMember.js +21 -0
- package/reference-schemas/actions/flow.d.ts +2 -0
- package/reference-schemas/actions/flow.js +13 -0
- package/reference-schemas/actions/group.d.ts +2 -0
- package/reference-schemas/actions/group.js +13 -0
- package/reference-schemas/actions/index.d.ts +7 -0
- package/reference-schemas/actions/index.js +8 -0
- package/reference-schemas/actions/link.d.ts +2 -0
- package/reference-schemas/actions/link.js +9 -0
- package/reference-schemas/actions/miscellaneous.d.ts +2 -0
- package/reference-schemas/actions/miscellaneous.js +41 -0
- package/reference-schemas/actions/resources.d.ts +2 -0
- package/reference-schemas/actions/resources.js +19 -0
- package/reference-schemas/actions/storage.d.ts +2 -0
- package/reference-schemas/actions/storage.js +15 -0
- package/reference-schemas/remappers/conditionals.js +39 -0
- package/reference-schemas/remappers/data.js +26 -2
- package/reference-schemas/remappers/index.d.ts +1 -0
- package/reference-schemas/remappers/index.js +1 -0
- package/reference-schemas/remappers/objects.js +32 -0
- package/reference-schemas/remappers/odata.d.ts +2 -0
- package/reference-schemas/remappers/odata.js +95 -0
- package/reference-schemas/remappers/strings.js +33 -0
- package/reference-schemas/remappers/unsorted.js +9 -0
- package/remap.d.ts +2 -0
- package/remap.js +192 -15
- package/serializeResource.d.ts +7 -0
- package/serializeResource.js +7 -0
- package/serverActions.d.ts +1 -1
- package/serverActions.js +2 -0
- package/theme.js +5 -1
- package/validateStyle.js +2 -0
- package/validation.js +52 -16
- package/api/components/parameters/trainingBlockId.d.ts +0 -2
- package/api/components/parameters/trainingBlockId.js +0 -8
- package/api/components/schemas/TrainingBlock.d.ts +0 -2
- package/api/components/schemas/TrainingBlock.js +0 -38
- package/api/components/securitySchemes/cli.test.d.ts +0 -1
- package/api/components/securitySchemes/cli.test.js +0 -7
- package/api/index.test.d.ts +0 -1
- package/api/index.test.js +0 -180
- package/api/paths/trainingBlocks/trainingBlockId.js +0 -41
- package/api/paths/trainings/trainingId/blocks.js +0 -51
- package/api/paths/trainings/trainingId/users/current.js +0 -62
- package/api/paths/trainings/trainingId/users.js +0 -25
- package/api/paths/trainings/trainingId.js +0 -85
- package/api/paths/trainings.js +0 -44
- package/appMessages.test.d.ts +0 -1
- package/appMessages.test.js +0 -409
- package/blockUtils.test.d.ts +0 -1
- package/blockUtils.test.js +0 -77
- package/constants/patterns.test.d.ts +0 -1
- package/constants/patterns.test.js +0 -83
- package/convertToCsv.test.d.ts +0 -1
- package/convertToCsv.test.js +0 -55
- package/has.test.d.ts +0 -1
- package/has.test.js +0 -17
- package/i18n.test.d.ts +0 -1
- package/i18n.test.js +0 -76
- package/iterApp.test.d.ts +0 -1
- package/iterApp.test.js +0 -439
- package/jsonschema.test.d.ts +0 -1
- package/jsonschema.test.js +0 -256
- package/mapValues.test.d.ts +0 -1
- package/mapValues.test.js +0 -16
- package/miscellaneous.test.d.ts +0 -1
- package/miscellaneous.test.js +0 -87
- package/normalize.test.d.ts +0 -1
- package/normalize.test.js +0 -23
- package/objectCache.test.d.ts +0 -1
- package/objectCache.test.js +0 -20
- package/prefix.test.d.ts +0 -1
- package/prefix.test.js +0 -11
- package/remap.test.d.ts +0 -1
- package/remap.test.js +0 -1387
- package/string.test.d.ts +0 -1
- package/string.test.js +0 -27
- package/theme.test.d.ts +0 -1
- package/theme.test.js +0 -91
- package/validateStyle.test.d.ts +0 -1
- package/validateStyle.test.js +0 -13
- package/validation.test.d.ts +0 -1
- package/validation.test.js +0 -3121
- /package/api/paths/{trainingBlocks/trainingBlockId.d.ts → apps/appId/resources/resourceType/resourceId/positions.d.ts} +0 -0
- /package/api/paths/{trainings/trainingId.d.ts → apps/appId/secrets/webhook/secretId.d.ts} +0 -0
- /package/api/paths/{trainings.d.ts → apps/appId/secrets/webhook.d.ts} +0 -0
- /package/api/paths/{trainings/trainingId/blocks.d.ts → apps/appId/webhooks/webhookName.d.ts} +0 -0
- /package/api/paths/{trainings/trainingId/users.d.ts → auth/email/patchPassword.d.ts} +0 -0
- /package/api/paths/trainings/{trainingId/users/current.d.ts → completeTraining.d.ts} +0 -0
package/validation.test.js
DELETED
|
@@ -1,3121 +0,0 @@
|
|
|
1
|
-
import { predefinedAppRoles, } from '@appsemble/types';
|
|
2
|
-
import { ValidationError } from 'jsonschema';
|
|
3
|
-
import { describe, expect, it } from 'vitest';
|
|
4
|
-
import { validateAppDefinition } from './validation.js';
|
|
5
|
-
function createTestApp() {
|
|
6
|
-
return {
|
|
7
|
-
name: 'Test app',
|
|
8
|
-
defaultPage: 'Test Page',
|
|
9
|
-
security: {
|
|
10
|
-
default: { role: 'User' },
|
|
11
|
-
roles: { User: {} },
|
|
12
|
-
},
|
|
13
|
-
resources: {
|
|
14
|
-
person: {
|
|
15
|
-
update: {},
|
|
16
|
-
schema: {
|
|
17
|
-
type: 'object',
|
|
18
|
-
properties: {
|
|
19
|
-
name: { type: 'string' },
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
pages: [
|
|
25
|
-
{
|
|
26
|
-
name: 'Test Page',
|
|
27
|
-
blocks: [],
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
name: 'Page with parameters',
|
|
31
|
-
parameters: [],
|
|
32
|
-
blocks: [],
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
name: 'Page with tabs',
|
|
36
|
-
type: 'tabs',
|
|
37
|
-
tabs: [{ name: 'Tab A', blocks: [] }],
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
name: 'Page with steps',
|
|
41
|
-
type: 'flow',
|
|
42
|
-
steps: [
|
|
43
|
-
{ name: 'Step A', blocks: [] },
|
|
44
|
-
{ name: 'Step B', blocks: [] },
|
|
45
|
-
],
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
name: 'Container Page 1',
|
|
49
|
-
type: 'container',
|
|
50
|
-
pages: [
|
|
51
|
-
{
|
|
52
|
-
name: 'Contained Page',
|
|
53
|
-
blocks: [],
|
|
54
|
-
},
|
|
55
|
-
],
|
|
56
|
-
},
|
|
57
|
-
],
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
describe('validateAppDefinition', () => {
|
|
61
|
-
it('should report unknown block types', async () => {
|
|
62
|
-
const app = createTestApp();
|
|
63
|
-
app.pages[0].blocks.push({
|
|
64
|
-
type: 'test',
|
|
65
|
-
version: '1.2.3',
|
|
66
|
-
});
|
|
67
|
-
const result = await validateAppDefinition(app, () => []);
|
|
68
|
-
expect(result.valid).toBe(false);
|
|
69
|
-
expect(result.errors).toStrictEqual([
|
|
70
|
-
new ValidationError('is not a known block type', 'test', undefined, [
|
|
71
|
-
'pages',
|
|
72
|
-
0,
|
|
73
|
-
'blocks',
|
|
74
|
-
0,
|
|
75
|
-
'type',
|
|
76
|
-
]),
|
|
77
|
-
]);
|
|
78
|
-
});
|
|
79
|
-
it('should report unknown block versions', async () => {
|
|
80
|
-
const app = createTestApp();
|
|
81
|
-
app.pages[0].blocks.push({
|
|
82
|
-
type: 'test',
|
|
83
|
-
version: '1.2.3',
|
|
84
|
-
});
|
|
85
|
-
const result = await validateAppDefinition(app, () => [
|
|
86
|
-
{ name: '@appsemble/test', version: '0.0.0', files: [], languages: [] },
|
|
87
|
-
]);
|
|
88
|
-
expect(result.valid).toBe(false);
|
|
89
|
-
expect(result.errors).toStrictEqual([
|
|
90
|
-
new ValidationError('is not a known version for this block type', '1.2.3', undefined, [
|
|
91
|
-
'pages',
|
|
92
|
-
0,
|
|
93
|
-
'blocks',
|
|
94
|
-
0,
|
|
95
|
-
'version',
|
|
96
|
-
]),
|
|
97
|
-
]);
|
|
98
|
-
});
|
|
99
|
-
it('should report duplicate page names', async () => {
|
|
100
|
-
const app = createTestApp();
|
|
101
|
-
app.pages.push({
|
|
102
|
-
name: 'Container Page',
|
|
103
|
-
type: 'container',
|
|
104
|
-
pages: [{ name: 'Test Page', blocks: [] }],
|
|
105
|
-
});
|
|
106
|
-
const result = await validateAppDefinition(app, () => [
|
|
107
|
-
{ name: '@appsemble/test', version: '0.0.0', files: [], languages: [] },
|
|
108
|
-
]);
|
|
109
|
-
expect(result.valid).toBe(false);
|
|
110
|
-
expect(result.errors).toStrictEqual([
|
|
111
|
-
new ValidationError('is a duplicate page name', 'Test Page', undefined, [
|
|
112
|
-
'Container Page',
|
|
113
|
-
'Test Page',
|
|
114
|
-
]),
|
|
115
|
-
]);
|
|
116
|
-
});
|
|
117
|
-
it('should validate block parameters', async () => {
|
|
118
|
-
const app = createTestApp();
|
|
119
|
-
app.pages[0].blocks.push({
|
|
120
|
-
type: 'test',
|
|
121
|
-
version: '1.2.3',
|
|
122
|
-
parameters: {},
|
|
123
|
-
});
|
|
124
|
-
const result = await validateAppDefinition(app, () => [
|
|
125
|
-
{
|
|
126
|
-
name: '@appsemble/test',
|
|
127
|
-
version: '1.2.3',
|
|
128
|
-
files: [],
|
|
129
|
-
languages: [],
|
|
130
|
-
parameters: {
|
|
131
|
-
type: 'object',
|
|
132
|
-
required: ['foo'],
|
|
133
|
-
properties: { foo: { type: 'string' } },
|
|
134
|
-
},
|
|
135
|
-
},
|
|
136
|
-
]);
|
|
137
|
-
expect(result.valid).toBe(false);
|
|
138
|
-
expect(result.errors).toStrictEqual([
|
|
139
|
-
new ValidationError('requires property "foo"', {}, undefined, [
|
|
140
|
-
'pages',
|
|
141
|
-
0,
|
|
142
|
-
'blocks',
|
|
143
|
-
0,
|
|
144
|
-
'parameters',
|
|
145
|
-
]),
|
|
146
|
-
]);
|
|
147
|
-
});
|
|
148
|
-
it('should validate missing block parameters', async () => {
|
|
149
|
-
const app = createTestApp();
|
|
150
|
-
app.pages[0].blocks.push({
|
|
151
|
-
type: 'test',
|
|
152
|
-
version: '1.2.3',
|
|
153
|
-
});
|
|
154
|
-
const result = await validateAppDefinition(app, () => [
|
|
155
|
-
{
|
|
156
|
-
name: '@appsemble/test',
|
|
157
|
-
version: '1.2.3',
|
|
158
|
-
files: [],
|
|
159
|
-
languages: [],
|
|
160
|
-
parameters: {
|
|
161
|
-
type: 'object',
|
|
162
|
-
required: ['foo'],
|
|
163
|
-
properties: { foo: { type: 'string' } },
|
|
164
|
-
},
|
|
165
|
-
},
|
|
166
|
-
]);
|
|
167
|
-
expect(result.valid).toBe(false);
|
|
168
|
-
expect(result.errors).toStrictEqual([
|
|
169
|
-
new ValidationError('requires property "parameters"', { type: 'test', version: '1.2.3' }, undefined, ['pages', 0, 'blocks', 0]),
|
|
170
|
-
]);
|
|
171
|
-
});
|
|
172
|
-
it('should validate block parameters using the action format', async () => {
|
|
173
|
-
const app = createTestApp();
|
|
174
|
-
app.pages[0].blocks.push({
|
|
175
|
-
type: 'test',
|
|
176
|
-
version: '1.2.3',
|
|
177
|
-
parameters: {
|
|
178
|
-
foo: 'invalid',
|
|
179
|
-
bar: 'onClick',
|
|
180
|
-
},
|
|
181
|
-
actions: {
|
|
182
|
-
onClick: { type: 'noop' },
|
|
183
|
-
},
|
|
184
|
-
});
|
|
185
|
-
const result = await validateAppDefinition(app, () => [
|
|
186
|
-
{
|
|
187
|
-
name: '@appsemble/test',
|
|
188
|
-
version: '1.2.3',
|
|
189
|
-
files: [],
|
|
190
|
-
languages: [],
|
|
191
|
-
parameters: {
|
|
192
|
-
type: 'object',
|
|
193
|
-
properties: {
|
|
194
|
-
foo: { type: 'string', format: 'action' },
|
|
195
|
-
bar: { type: 'string', format: 'action' },
|
|
196
|
-
},
|
|
197
|
-
},
|
|
198
|
-
actions: {
|
|
199
|
-
onClick: {},
|
|
200
|
-
},
|
|
201
|
-
},
|
|
202
|
-
]);
|
|
203
|
-
expect(result.valid).toBe(false);
|
|
204
|
-
expect(result.errors).toStrictEqual([
|
|
205
|
-
new ValidationError('does not conform to the "action" format', 'invalid', undefined, [
|
|
206
|
-
'pages',
|
|
207
|
-
0,
|
|
208
|
-
'blocks',
|
|
209
|
-
0,
|
|
210
|
-
'parameters',
|
|
211
|
-
'foo',
|
|
212
|
-
]),
|
|
213
|
-
]);
|
|
214
|
-
});
|
|
215
|
-
it('should validate block parameters using the event-emitter format', async () => {
|
|
216
|
-
const app = createTestApp();
|
|
217
|
-
app.pages[0].blocks.push({
|
|
218
|
-
type: 'test',
|
|
219
|
-
version: '1.2.3',
|
|
220
|
-
parameters: {
|
|
221
|
-
foo: 'invalid',
|
|
222
|
-
bar: 'myEvent',
|
|
223
|
-
},
|
|
224
|
-
events: {
|
|
225
|
-
emit: { myEvent: 'handleEvent' },
|
|
226
|
-
listen: { myEvent: 'handleEvent' },
|
|
227
|
-
},
|
|
228
|
-
});
|
|
229
|
-
const result = await validateAppDefinition(app, () => [
|
|
230
|
-
{
|
|
231
|
-
name: '@appsemble/test',
|
|
232
|
-
version: '1.2.3',
|
|
233
|
-
files: [],
|
|
234
|
-
languages: [],
|
|
235
|
-
parameters: {
|
|
236
|
-
type: 'object',
|
|
237
|
-
properties: {
|
|
238
|
-
foo: { type: 'string', format: 'event-emitter' },
|
|
239
|
-
bar: { type: 'string', format: 'event-emitter' },
|
|
240
|
-
},
|
|
241
|
-
},
|
|
242
|
-
events: {
|
|
243
|
-
emit: { myEvent: {} },
|
|
244
|
-
listen: { myEvent: {} },
|
|
245
|
-
},
|
|
246
|
-
},
|
|
247
|
-
]);
|
|
248
|
-
expect(result.valid).toBe(false);
|
|
249
|
-
expect(result.errors).toStrictEqual([
|
|
250
|
-
new ValidationError('does not conform to the "event-emitter" format', 'invalid', undefined, [
|
|
251
|
-
'pages',
|
|
252
|
-
0,
|
|
253
|
-
'blocks',
|
|
254
|
-
0,
|
|
255
|
-
'parameters',
|
|
256
|
-
'foo',
|
|
257
|
-
]),
|
|
258
|
-
]);
|
|
259
|
-
});
|
|
260
|
-
it('should validate block parameters using the event-listener format', async () => {
|
|
261
|
-
const app = createTestApp();
|
|
262
|
-
app.pages[0].blocks.push({
|
|
263
|
-
type: 'test',
|
|
264
|
-
version: '1.2.3',
|
|
265
|
-
parameters: {
|
|
266
|
-
foo: 'invalid',
|
|
267
|
-
bar: 'myEvent',
|
|
268
|
-
},
|
|
269
|
-
events: {
|
|
270
|
-
emit: { myEvent: 'handleEvent' },
|
|
271
|
-
listen: { myEvent: 'handleEvent' },
|
|
272
|
-
},
|
|
273
|
-
});
|
|
274
|
-
const result = await validateAppDefinition(app, () => [
|
|
275
|
-
{
|
|
276
|
-
name: '@appsemble/test',
|
|
277
|
-
version: '1.2.3',
|
|
278
|
-
files: [],
|
|
279
|
-
languages: [],
|
|
280
|
-
parameters: {
|
|
281
|
-
type: 'object',
|
|
282
|
-
properties: {
|
|
283
|
-
foo: { type: 'string', format: 'event-listener' },
|
|
284
|
-
bar: { type: 'string', format: 'event-listener' },
|
|
285
|
-
},
|
|
286
|
-
},
|
|
287
|
-
events: {
|
|
288
|
-
emit: { myEvent: {} },
|
|
289
|
-
listen: { myEvent: {} },
|
|
290
|
-
},
|
|
291
|
-
},
|
|
292
|
-
]);
|
|
293
|
-
expect(result.valid).toBe(false);
|
|
294
|
-
expect(result.errors).toStrictEqual([
|
|
295
|
-
new ValidationError('does not conform to the "event-listener" format', 'invalid', undefined, [
|
|
296
|
-
'pages',
|
|
297
|
-
0,
|
|
298
|
-
'blocks',
|
|
299
|
-
0,
|
|
300
|
-
'parameters',
|
|
301
|
-
'foo',
|
|
302
|
-
]),
|
|
303
|
-
]);
|
|
304
|
-
});
|
|
305
|
-
it('should not allow block parameters if the block manifest doesn’t specify them', async () => {
|
|
306
|
-
const app = createTestApp();
|
|
307
|
-
app.pages[0].blocks.push({
|
|
308
|
-
type: 'test',
|
|
309
|
-
version: '1.2.3',
|
|
310
|
-
parameters: {},
|
|
311
|
-
});
|
|
312
|
-
const result = await validateAppDefinition(app, () => [
|
|
313
|
-
{
|
|
314
|
-
name: '@appsemble/test',
|
|
315
|
-
version: '1.2.3',
|
|
316
|
-
files: [],
|
|
317
|
-
languages: [],
|
|
318
|
-
},
|
|
319
|
-
]);
|
|
320
|
-
expect(result.valid).toBe(false);
|
|
321
|
-
expect(result.errors).toStrictEqual([
|
|
322
|
-
new ValidationError('is not allowed on this block type', {}, undefined, [
|
|
323
|
-
'pages',
|
|
324
|
-
0,
|
|
325
|
-
'blocks',
|
|
326
|
-
0,
|
|
327
|
-
'parameters',
|
|
328
|
-
]),
|
|
329
|
-
]);
|
|
330
|
-
});
|
|
331
|
-
it('should validate block actions', async () => {
|
|
332
|
-
const app = createTestApp();
|
|
333
|
-
app.pages[0].blocks.push({
|
|
334
|
-
type: 'test',
|
|
335
|
-
version: '1.2.3',
|
|
336
|
-
actions: {
|
|
337
|
-
onClick: { type: 'noop' },
|
|
338
|
-
onSubmit: { type: 'noop' },
|
|
339
|
-
},
|
|
340
|
-
});
|
|
341
|
-
const result = await validateAppDefinition(app, () => [
|
|
342
|
-
{
|
|
343
|
-
name: '@appsemble/test',
|
|
344
|
-
version: '1.2.3',
|
|
345
|
-
files: [],
|
|
346
|
-
languages: [],
|
|
347
|
-
actions: {
|
|
348
|
-
onClick: {},
|
|
349
|
-
},
|
|
350
|
-
},
|
|
351
|
-
]);
|
|
352
|
-
expect(result.valid).toBe(false);
|
|
353
|
-
expect(result.errors).toStrictEqual([
|
|
354
|
-
new ValidationError('is an unknown action for this block', { type: 'noop' }, undefined, [
|
|
355
|
-
'pages',
|
|
356
|
-
0,
|
|
357
|
-
'blocks',
|
|
358
|
-
0,
|
|
359
|
-
'actions',
|
|
360
|
-
'onSubmit',
|
|
361
|
-
]),
|
|
362
|
-
]);
|
|
363
|
-
});
|
|
364
|
-
it('should validate controller actions', async () => {
|
|
365
|
-
const app = createTestApp();
|
|
366
|
-
app.controller = {
|
|
367
|
-
actions: {
|
|
368
|
-
onClick: { type: 'noop' },
|
|
369
|
-
onSubmit: { type: 'noop' },
|
|
370
|
-
},
|
|
371
|
-
};
|
|
372
|
-
const result = await validateAppDefinition(app, () => [], {
|
|
373
|
-
actions: {
|
|
374
|
-
onClick: {},
|
|
375
|
-
},
|
|
376
|
-
});
|
|
377
|
-
expect(result.valid).toBe(false);
|
|
378
|
-
expect(result.errors).toStrictEqual([
|
|
379
|
-
new ValidationError('is an unknown action for this controller', { type: 'noop' }, undefined, [
|
|
380
|
-
'controller',
|
|
381
|
-
'actions',
|
|
382
|
-
'onSubmit',
|
|
383
|
-
]),
|
|
384
|
-
]);
|
|
385
|
-
});
|
|
386
|
-
it('should report if a block doesn’t support actions', async () => {
|
|
387
|
-
const app = createTestApp();
|
|
388
|
-
app.pages[0].blocks.push({
|
|
389
|
-
type: 'test',
|
|
390
|
-
version: '1.2.3',
|
|
391
|
-
actions: {},
|
|
392
|
-
});
|
|
393
|
-
const result = await validateAppDefinition(app, () => [
|
|
394
|
-
{
|
|
395
|
-
name: '@appsemble/test',
|
|
396
|
-
version: '1.2.3',
|
|
397
|
-
files: [],
|
|
398
|
-
languages: [],
|
|
399
|
-
},
|
|
400
|
-
]);
|
|
401
|
-
expect(result.valid).toBe(false);
|
|
402
|
-
expect(result.errors).toStrictEqual([
|
|
403
|
-
new ValidationError('is not allowed on this block', {}, undefined, [
|
|
404
|
-
'pages',
|
|
405
|
-
0,
|
|
406
|
-
'blocks',
|
|
407
|
-
0,
|
|
408
|
-
'actions',
|
|
409
|
-
]),
|
|
410
|
-
]);
|
|
411
|
-
});
|
|
412
|
-
it('should report if a controller doesn’t support actions', async () => {
|
|
413
|
-
const app = createTestApp();
|
|
414
|
-
app.controller = {
|
|
415
|
-
actions: {},
|
|
416
|
-
};
|
|
417
|
-
const result = await validateAppDefinition(app, () => [], {});
|
|
418
|
-
expect(result.valid).toBe(false);
|
|
419
|
-
expect(result.errors).toStrictEqual([
|
|
420
|
-
new ValidationError('is not allowed on this controller', {}, undefined, [
|
|
421
|
-
'controller',
|
|
422
|
-
'actions',
|
|
423
|
-
]),
|
|
424
|
-
]);
|
|
425
|
-
});
|
|
426
|
-
it('should report unused block actions based on parameters', async () => {
|
|
427
|
-
const app = createTestApp();
|
|
428
|
-
app.pages[0].blocks.push({
|
|
429
|
-
type: 'test',
|
|
430
|
-
version: '1.2.3',
|
|
431
|
-
actions: {
|
|
432
|
-
foo: { type: 'noop' },
|
|
433
|
-
bar: { type: 'noop' },
|
|
434
|
-
},
|
|
435
|
-
parameters: {
|
|
436
|
-
onClick: 'foo',
|
|
437
|
-
},
|
|
438
|
-
});
|
|
439
|
-
const result = await validateAppDefinition(app, () => [
|
|
440
|
-
{
|
|
441
|
-
name: '@appsemble/test',
|
|
442
|
-
version: '1.2.3',
|
|
443
|
-
files: [],
|
|
444
|
-
languages: [],
|
|
445
|
-
actions: {
|
|
446
|
-
$any: {},
|
|
447
|
-
},
|
|
448
|
-
parameters: {
|
|
449
|
-
type: 'object',
|
|
450
|
-
properties: {
|
|
451
|
-
onClick: {
|
|
452
|
-
type: 'string',
|
|
453
|
-
format: 'action',
|
|
454
|
-
},
|
|
455
|
-
},
|
|
456
|
-
},
|
|
457
|
-
},
|
|
458
|
-
]);
|
|
459
|
-
expect(result.valid).toBe(false);
|
|
460
|
-
expect(result.errors).toStrictEqual([
|
|
461
|
-
new ValidationError('is unused', { type: 'noop' }, undefined, [
|
|
462
|
-
'pages',
|
|
463
|
-
0,
|
|
464
|
-
'blocks',
|
|
465
|
-
0,
|
|
466
|
-
'actions',
|
|
467
|
-
'bar',
|
|
468
|
-
]),
|
|
469
|
-
]);
|
|
470
|
-
});
|
|
471
|
-
it('should allow wildcard actions on blocks', async () => {
|
|
472
|
-
const app = createTestApp();
|
|
473
|
-
app.pages[0].blocks.push({
|
|
474
|
-
type: 'test',
|
|
475
|
-
version: '1.2.3',
|
|
476
|
-
actions: {
|
|
477
|
-
foo: { type: 'noop' },
|
|
478
|
-
bar: { type: 'noop' },
|
|
479
|
-
},
|
|
480
|
-
parameters: {
|
|
481
|
-
onClick: 'foo',
|
|
482
|
-
},
|
|
483
|
-
});
|
|
484
|
-
const result = await validateAppDefinition(app, () => [
|
|
485
|
-
{
|
|
486
|
-
name: '@appsemble/test',
|
|
487
|
-
version: '1.2.3',
|
|
488
|
-
files: [],
|
|
489
|
-
languages: [],
|
|
490
|
-
wildcardActions: true,
|
|
491
|
-
actions: {
|
|
492
|
-
$any: {},
|
|
493
|
-
},
|
|
494
|
-
parameters: {
|
|
495
|
-
type: 'object',
|
|
496
|
-
properties: {
|
|
497
|
-
onClick: {
|
|
498
|
-
type: 'string',
|
|
499
|
-
format: 'action',
|
|
500
|
-
},
|
|
501
|
-
},
|
|
502
|
-
},
|
|
503
|
-
},
|
|
504
|
-
]);
|
|
505
|
-
expect(result.valid).toBe(true);
|
|
506
|
-
});
|
|
507
|
-
it('should report unknown event emitters on blocks', async () => {
|
|
508
|
-
const app = createTestApp();
|
|
509
|
-
app.pages[0].blocks.push({
|
|
510
|
-
type: 'test',
|
|
511
|
-
version: '1.2.3',
|
|
512
|
-
events: {
|
|
513
|
-
emit: {
|
|
514
|
-
foo: 'bar',
|
|
515
|
-
},
|
|
516
|
-
listen: {
|
|
517
|
-
foo: 'bar',
|
|
518
|
-
},
|
|
519
|
-
},
|
|
520
|
-
});
|
|
521
|
-
const result = await validateAppDefinition(app, () => [
|
|
522
|
-
{
|
|
523
|
-
name: '@appsemble/test',
|
|
524
|
-
version: '1.2.3',
|
|
525
|
-
files: [],
|
|
526
|
-
languages: [],
|
|
527
|
-
wildcardActions: true,
|
|
528
|
-
events: {
|
|
529
|
-
emit: {},
|
|
530
|
-
listen: { $any: {} },
|
|
531
|
-
},
|
|
532
|
-
},
|
|
533
|
-
]);
|
|
534
|
-
expect(result.valid).toBe(false);
|
|
535
|
-
expect(result.errors).toStrictEqual([
|
|
536
|
-
new ValidationError('is an unknown event emitter', 'bar', undefined, [
|
|
537
|
-
'pages',
|
|
538
|
-
0,
|
|
539
|
-
'blocks',
|
|
540
|
-
0,
|
|
541
|
-
'events',
|
|
542
|
-
'emit',
|
|
543
|
-
'foo',
|
|
544
|
-
]),
|
|
545
|
-
]);
|
|
546
|
-
});
|
|
547
|
-
it('should report unknown event emitters on controller', async () => {
|
|
548
|
-
const app = createTestApp();
|
|
549
|
-
app.pages[0].blocks.push({
|
|
550
|
-
type: 'test',
|
|
551
|
-
version: '1.2.3',
|
|
552
|
-
events: {
|
|
553
|
-
listen: {
|
|
554
|
-
foo: 'bar',
|
|
555
|
-
},
|
|
556
|
-
},
|
|
557
|
-
});
|
|
558
|
-
app.controller = {
|
|
559
|
-
events: {
|
|
560
|
-
emit: {
|
|
561
|
-
foo: 'bar',
|
|
562
|
-
},
|
|
563
|
-
},
|
|
564
|
-
};
|
|
565
|
-
const result = await validateAppDefinition(app, () => [
|
|
566
|
-
{
|
|
567
|
-
name: '@appsemble/test',
|
|
568
|
-
version: '1.2.3',
|
|
569
|
-
files: [],
|
|
570
|
-
languages: [],
|
|
571
|
-
wildcardActions: true,
|
|
572
|
-
events: {
|
|
573
|
-
listen: { foo: {} },
|
|
574
|
-
},
|
|
575
|
-
},
|
|
576
|
-
], {
|
|
577
|
-
events: {
|
|
578
|
-
emit: {},
|
|
579
|
-
},
|
|
580
|
-
});
|
|
581
|
-
expect(result.valid).toBe(false);
|
|
582
|
-
expect(result.errors).toStrictEqual([
|
|
583
|
-
new ValidationError('is an unknown event emitter', 'bar', undefined, [
|
|
584
|
-
'controller',
|
|
585
|
-
'events',
|
|
586
|
-
'emit',
|
|
587
|
-
'foo',
|
|
588
|
-
]),
|
|
589
|
-
]);
|
|
590
|
-
});
|
|
591
|
-
it('should allow $any matching unknown event emitters on blocks', async () => {
|
|
592
|
-
const app = createTestApp();
|
|
593
|
-
app.pages[0].blocks.push({
|
|
594
|
-
type: 'test',
|
|
595
|
-
version: '1.2.3',
|
|
596
|
-
events: {
|
|
597
|
-
emit: {
|
|
598
|
-
foo: 'bar',
|
|
599
|
-
},
|
|
600
|
-
listen: {
|
|
601
|
-
foo: 'bar',
|
|
602
|
-
},
|
|
603
|
-
},
|
|
604
|
-
});
|
|
605
|
-
const result = await validateAppDefinition(app, () => [
|
|
606
|
-
{
|
|
607
|
-
name: '@appsemble/test',
|
|
608
|
-
version: '1.2.3',
|
|
609
|
-
files: [],
|
|
610
|
-
languages: [],
|
|
611
|
-
wildcardActions: true,
|
|
612
|
-
events: {
|
|
613
|
-
emit: { $any: {} },
|
|
614
|
-
listen: { $any: {} },
|
|
615
|
-
},
|
|
616
|
-
},
|
|
617
|
-
]);
|
|
618
|
-
expect(result.valid).toBe(true);
|
|
619
|
-
});
|
|
620
|
-
it('should report unknown event listeners on blocks', async () => {
|
|
621
|
-
const app = createTestApp();
|
|
622
|
-
app.pages[0].blocks.push({
|
|
623
|
-
type: 'test',
|
|
624
|
-
version: '1.2.3',
|
|
625
|
-
events: {
|
|
626
|
-
listen: {
|
|
627
|
-
foo: 'bar',
|
|
628
|
-
},
|
|
629
|
-
emit: {
|
|
630
|
-
foo: 'bar',
|
|
631
|
-
},
|
|
632
|
-
},
|
|
633
|
-
});
|
|
634
|
-
const result = await validateAppDefinition(app, () => [
|
|
635
|
-
{
|
|
636
|
-
name: '@appsemble/test',
|
|
637
|
-
version: '1.2.3',
|
|
638
|
-
files: [],
|
|
639
|
-
languages: [],
|
|
640
|
-
wildcardActions: true,
|
|
641
|
-
events: {
|
|
642
|
-
listen: {},
|
|
643
|
-
emit: { $any: {} },
|
|
644
|
-
},
|
|
645
|
-
},
|
|
646
|
-
]);
|
|
647
|
-
expect(result.valid).toBe(false);
|
|
648
|
-
expect(result.errors).toStrictEqual([
|
|
649
|
-
new ValidationError('is an unknown event listener', 'bar', undefined, [
|
|
650
|
-
'pages',
|
|
651
|
-
0,
|
|
652
|
-
'blocks',
|
|
653
|
-
0,
|
|
654
|
-
'events',
|
|
655
|
-
'listen',
|
|
656
|
-
'foo',
|
|
657
|
-
]),
|
|
658
|
-
]);
|
|
659
|
-
});
|
|
660
|
-
it('should report unknown event listeners on controller', async () => {
|
|
661
|
-
const app = createTestApp();
|
|
662
|
-
app.pages[0].blocks.push({
|
|
663
|
-
type: 'test',
|
|
664
|
-
version: '1.2.3',
|
|
665
|
-
events: {
|
|
666
|
-
emit: {
|
|
667
|
-
foo: 'bar',
|
|
668
|
-
},
|
|
669
|
-
},
|
|
670
|
-
});
|
|
671
|
-
app.controller = {
|
|
672
|
-
events: {
|
|
673
|
-
listen: {
|
|
674
|
-
foo: 'bar',
|
|
675
|
-
},
|
|
676
|
-
},
|
|
677
|
-
};
|
|
678
|
-
const result = await validateAppDefinition(app, () => [
|
|
679
|
-
{
|
|
680
|
-
name: '@appsemble/test',
|
|
681
|
-
version: '1.2.3',
|
|
682
|
-
files: [],
|
|
683
|
-
languages: [],
|
|
684
|
-
wildcardActions: true,
|
|
685
|
-
events: {
|
|
686
|
-
emit: { foo: {} },
|
|
687
|
-
},
|
|
688
|
-
},
|
|
689
|
-
], {
|
|
690
|
-
events: {
|
|
691
|
-
listen: {},
|
|
692
|
-
},
|
|
693
|
-
});
|
|
694
|
-
expect(result.valid).toBe(false);
|
|
695
|
-
expect(result.errors).toStrictEqual([
|
|
696
|
-
new ValidationError('is an unknown event listener', 'bar', undefined, [
|
|
697
|
-
'controller',
|
|
698
|
-
'events',
|
|
699
|
-
'listen',
|
|
700
|
-
'foo',
|
|
701
|
-
]),
|
|
702
|
-
]);
|
|
703
|
-
});
|
|
704
|
-
it('should allow $any matching unknown event listener', async () => {
|
|
705
|
-
const app = createTestApp();
|
|
706
|
-
app.pages[0].blocks.push({
|
|
707
|
-
type: 'test',
|
|
708
|
-
version: '1.2.3',
|
|
709
|
-
events: {
|
|
710
|
-
emit: {
|
|
711
|
-
foo: 'bar',
|
|
712
|
-
},
|
|
713
|
-
listen: {
|
|
714
|
-
foo: 'bar',
|
|
715
|
-
},
|
|
716
|
-
},
|
|
717
|
-
});
|
|
718
|
-
const result = await validateAppDefinition(app, () => [
|
|
719
|
-
{
|
|
720
|
-
name: '@appsemble/test',
|
|
721
|
-
version: '1.2.3',
|
|
722
|
-
files: [],
|
|
723
|
-
languages: [],
|
|
724
|
-
wildcardActions: true,
|
|
725
|
-
events: {
|
|
726
|
-
emit: { foo: {} },
|
|
727
|
-
listen: { $any: {} },
|
|
728
|
-
},
|
|
729
|
-
},
|
|
730
|
-
]);
|
|
731
|
-
expect(result.valid).toBe(true);
|
|
732
|
-
});
|
|
733
|
-
it('should report unmatched event listeners when there is no controller present', async () => {
|
|
734
|
-
const app = createTestApp();
|
|
735
|
-
app.pages[0].blocks.push({
|
|
736
|
-
type: 'test',
|
|
737
|
-
version: '1.2.3',
|
|
738
|
-
events: {
|
|
739
|
-
listen: {
|
|
740
|
-
foo: 'bar',
|
|
741
|
-
},
|
|
742
|
-
},
|
|
743
|
-
});
|
|
744
|
-
const result = await validateAppDefinition(app, () => [
|
|
745
|
-
{
|
|
746
|
-
name: '@appsemble/test',
|
|
747
|
-
version: '1.2.3',
|
|
748
|
-
files: [],
|
|
749
|
-
languages: [],
|
|
750
|
-
wildcardActions: true,
|
|
751
|
-
events: {
|
|
752
|
-
listen: { $any: {} },
|
|
753
|
-
},
|
|
754
|
-
},
|
|
755
|
-
]);
|
|
756
|
-
expect(result.valid).toBe(false);
|
|
757
|
-
expect(result.errors).toStrictEqual([
|
|
758
|
-
new ValidationError('does not match any event emitters', 'bar', undefined, [
|
|
759
|
-
'pages',
|
|
760
|
-
0,
|
|
761
|
-
'blocks',
|
|
762
|
-
0,
|
|
763
|
-
'events',
|
|
764
|
-
'listen',
|
|
765
|
-
'foo',
|
|
766
|
-
]),
|
|
767
|
-
]);
|
|
768
|
-
});
|
|
769
|
-
it('should report unmatched event emitters when there is no controller present', async () => {
|
|
770
|
-
const app = createTestApp();
|
|
771
|
-
app.pages[0].blocks.push({
|
|
772
|
-
type: 'test',
|
|
773
|
-
version: '1.2.3',
|
|
774
|
-
events: {
|
|
775
|
-
emit: {
|
|
776
|
-
foo: 'bar',
|
|
777
|
-
},
|
|
778
|
-
},
|
|
779
|
-
});
|
|
780
|
-
const result = await validateAppDefinition(app, () => [
|
|
781
|
-
{
|
|
782
|
-
name: '@appsemble/test',
|
|
783
|
-
version: '1.2.3',
|
|
784
|
-
files: [],
|
|
785
|
-
languages: [],
|
|
786
|
-
wildcardActions: true,
|
|
787
|
-
events: {
|
|
788
|
-
emit: { $any: {} },
|
|
789
|
-
},
|
|
790
|
-
},
|
|
791
|
-
]);
|
|
792
|
-
expect(result.valid).toBe(false);
|
|
793
|
-
expect(result.errors).toStrictEqual([
|
|
794
|
-
new ValidationError('does not match any event listeners', 'bar', undefined, [
|
|
795
|
-
'pages',
|
|
796
|
-
0,
|
|
797
|
-
'blocks',
|
|
798
|
-
0,
|
|
799
|
-
'events',
|
|
800
|
-
'emit',
|
|
801
|
-
'foo',
|
|
802
|
-
]),
|
|
803
|
-
]);
|
|
804
|
-
});
|
|
805
|
-
it('should report unmatched event from event actions when there is no controller present', async () => {
|
|
806
|
-
const app = createTestApp();
|
|
807
|
-
app.pages[0].blocks.push({
|
|
808
|
-
type: 'test',
|
|
809
|
-
version: '1.2.3',
|
|
810
|
-
actions: {
|
|
811
|
-
onClick: {
|
|
812
|
-
type: 'event',
|
|
813
|
-
event: 'sent',
|
|
814
|
-
waitFor: 'reply',
|
|
815
|
-
},
|
|
816
|
-
},
|
|
817
|
-
});
|
|
818
|
-
const result = await validateAppDefinition(app, () => [
|
|
819
|
-
{
|
|
820
|
-
name: '@appsemble/test',
|
|
821
|
-
version: '1.2.3',
|
|
822
|
-
files: [],
|
|
823
|
-
languages: [],
|
|
824
|
-
wildcardActions: true,
|
|
825
|
-
actions: {
|
|
826
|
-
onClick: {},
|
|
827
|
-
},
|
|
828
|
-
},
|
|
829
|
-
]);
|
|
830
|
-
expect(result.valid).toBe(false);
|
|
831
|
-
expect(result.errors).toStrictEqual([
|
|
832
|
-
new ValidationError('does not match any event emitters', 'reply', undefined, [
|
|
833
|
-
'pages',
|
|
834
|
-
0,
|
|
835
|
-
'blocks',
|
|
836
|
-
0,
|
|
837
|
-
'actions',
|
|
838
|
-
'onClick',
|
|
839
|
-
'waitFor',
|
|
840
|
-
]),
|
|
841
|
-
new ValidationError('does not match any event listeners', 'sent', undefined, [
|
|
842
|
-
'pages',
|
|
843
|
-
0,
|
|
844
|
-
'blocks',
|
|
845
|
-
0,
|
|
846
|
-
'actions',
|
|
847
|
-
'onClick',
|
|
848
|
-
'event',
|
|
849
|
-
]),
|
|
850
|
-
]);
|
|
851
|
-
});
|
|
852
|
-
it('should not crash if security is undefined', async () => {
|
|
853
|
-
const app = createTestApp();
|
|
854
|
-
delete app.security;
|
|
855
|
-
const result = await validateAppDefinition(app, () => []);
|
|
856
|
-
expect(result.valid).toBe(true);
|
|
857
|
-
});
|
|
858
|
-
it('should not crash if controller is undefined', async () => {
|
|
859
|
-
const app = createTestApp();
|
|
860
|
-
app.controller = undefined;
|
|
861
|
-
const result = await validateAppDefinition(app, () => [], {});
|
|
862
|
-
expect(result.valid).toBe(true);
|
|
863
|
-
});
|
|
864
|
-
it('should report if notifications is "login" without a security definition', async () => {
|
|
865
|
-
const app = createTestApp();
|
|
866
|
-
delete app.security;
|
|
867
|
-
app.notifications = 'login';
|
|
868
|
-
const result = await validateAppDefinition(app, () => []);
|
|
869
|
-
expect(result.valid).toBe(false);
|
|
870
|
-
expect(result.errors).toStrictEqual([
|
|
871
|
-
new ValidationError('only works if security is defined', 'login', undefined, [
|
|
872
|
-
'notifications',
|
|
873
|
-
]),
|
|
874
|
-
]);
|
|
875
|
-
});
|
|
876
|
-
it('should validate the default role exists', async () => {
|
|
877
|
-
const app = createTestApp();
|
|
878
|
-
app.security.default.role = 'Unknown';
|
|
879
|
-
const result = await validateAppDefinition(app, () => []);
|
|
880
|
-
expect(result.valid).toBe(false);
|
|
881
|
-
expect(result.errors).toStrictEqual([
|
|
882
|
-
new ValidationError('does not exist in this app’s roles', 'Unknown', undefined, [
|
|
883
|
-
'security',
|
|
884
|
-
'default',
|
|
885
|
-
'role',
|
|
886
|
-
]),
|
|
887
|
-
]);
|
|
888
|
-
});
|
|
889
|
-
it('should validate resource types against reserved keywords', async () => {
|
|
890
|
-
const app = {
|
|
891
|
-
name: 'Test app',
|
|
892
|
-
defaultPage: 'Test Page',
|
|
893
|
-
pages: [
|
|
894
|
-
{
|
|
895
|
-
name: 'Test Page',
|
|
896
|
-
blocks: [],
|
|
897
|
-
},
|
|
898
|
-
],
|
|
899
|
-
resources: {
|
|
900
|
-
created: {
|
|
901
|
-
schema: {
|
|
902
|
-
type: 'object',
|
|
903
|
-
properties: {
|
|
904
|
-
name: { type: 'string' },
|
|
905
|
-
},
|
|
906
|
-
},
|
|
907
|
-
},
|
|
908
|
-
updated: {
|
|
909
|
-
schema: {
|
|
910
|
-
type: 'object',
|
|
911
|
-
properties: {
|
|
912
|
-
name: { type: 'string' },
|
|
913
|
-
},
|
|
914
|
-
},
|
|
915
|
-
},
|
|
916
|
-
author: {
|
|
917
|
-
schema: {
|
|
918
|
-
type: 'object',
|
|
919
|
-
properties: {
|
|
920
|
-
name: { type: 'string' },
|
|
921
|
-
},
|
|
922
|
-
},
|
|
923
|
-
},
|
|
924
|
-
editor: {
|
|
925
|
-
schema: {
|
|
926
|
-
type: 'object',
|
|
927
|
-
properties: {
|
|
928
|
-
name: { type: 'string' },
|
|
929
|
-
},
|
|
930
|
-
},
|
|
931
|
-
},
|
|
932
|
-
seed: {
|
|
933
|
-
schema: {
|
|
934
|
-
type: 'object',
|
|
935
|
-
properties: {
|
|
936
|
-
name: { type: 'string' },
|
|
937
|
-
},
|
|
938
|
-
},
|
|
939
|
-
},
|
|
940
|
-
ephemeral: {
|
|
941
|
-
schema: {
|
|
942
|
-
type: 'object',
|
|
943
|
-
properties: {
|
|
944
|
-
name: { type: 'string' },
|
|
945
|
-
},
|
|
946
|
-
},
|
|
947
|
-
},
|
|
948
|
-
clonable: {
|
|
949
|
-
schema: {
|
|
950
|
-
type: 'object',
|
|
951
|
-
properties: {
|
|
952
|
-
name: { type: 'string' },
|
|
953
|
-
},
|
|
954
|
-
},
|
|
955
|
-
},
|
|
956
|
-
expires: {
|
|
957
|
-
schema: {
|
|
958
|
-
type: 'object',
|
|
959
|
-
properties: {
|
|
960
|
-
name: { type: 'string' },
|
|
961
|
-
},
|
|
962
|
-
},
|
|
963
|
-
},
|
|
964
|
-
},
|
|
965
|
-
};
|
|
966
|
-
const result = await validateAppDefinition(app, () => []);
|
|
967
|
-
expect(result.valid).toBe(false);
|
|
968
|
-
expect(result.errors).toStrictEqual([
|
|
969
|
-
new ValidationError('is a reserved keyword', { type: 'object', properties: { name: { type: 'string' } } }, undefined, ['resources', 'created']),
|
|
970
|
-
new ValidationError('is a reserved keyword', { type: 'object', properties: { name: { type: 'string' } } }, undefined, ['resources', 'updated']),
|
|
971
|
-
new ValidationError('is a reserved keyword', { type: 'object', properties: { name: { type: 'string' } } }, undefined, ['resources', 'author']),
|
|
972
|
-
new ValidationError('is a reserved keyword', { type: 'object', properties: { name: { type: 'string' } } }, undefined, ['resources', 'editor']),
|
|
973
|
-
new ValidationError('is a reserved keyword', { type: 'object', properties: { name: { type: 'string' } } }, undefined, ['resources', 'seed']),
|
|
974
|
-
new ValidationError('is a reserved keyword', { type: 'object', properties: { name: { type: 'string' } } }, undefined, ['resources', 'ephemeral']),
|
|
975
|
-
new ValidationError('is a reserved keyword', { type: 'object', properties: { name: { type: 'string' } } }, undefined, ['resources', 'clonable']),
|
|
976
|
-
new ValidationError('is a reserved keyword', { type: 'object', properties: { name: { type: 'string' } } }, undefined, ['resources', 'expires']),
|
|
977
|
-
]);
|
|
978
|
-
});
|
|
979
|
-
it('should validate app member properties for type or enum', async () => {
|
|
980
|
-
const app = { ...createTestApp(), members: { properties: { foo: { schema: {} } } } };
|
|
981
|
-
const result = await validateAppDefinition(app, () => []);
|
|
982
|
-
expect(result.valid).toBe(false);
|
|
983
|
-
expect(result.errors).toStrictEqual([
|
|
984
|
-
new ValidationError('must define type or enum', {}, undefined, [
|
|
985
|
-
'members',
|
|
986
|
-
'properties',
|
|
987
|
-
'foo',
|
|
988
|
-
'schema',
|
|
989
|
-
]),
|
|
990
|
-
]);
|
|
991
|
-
});
|
|
992
|
-
it('should validate app member properties for resource references', async () => {
|
|
993
|
-
const app = {
|
|
994
|
-
...createTestApp(),
|
|
995
|
-
members: {
|
|
996
|
-
properties: {
|
|
997
|
-
foo: {
|
|
998
|
-
schema: { type: 'integer' },
|
|
999
|
-
reference: {
|
|
1000
|
-
resource: 'tasks',
|
|
1001
|
-
},
|
|
1002
|
-
},
|
|
1003
|
-
},
|
|
1004
|
-
},
|
|
1005
|
-
};
|
|
1006
|
-
const result = await validateAppDefinition(app, () => []);
|
|
1007
|
-
expect(result.valid).toBe(false);
|
|
1008
|
-
expect(result.errors).toStrictEqual([
|
|
1009
|
-
new ValidationError('refers to a resource that doesn’t exist', 'tasks', undefined, [
|
|
1010
|
-
'members',
|
|
1011
|
-
'properties',
|
|
1012
|
-
'foo',
|
|
1013
|
-
'reference',
|
|
1014
|
-
'tasks',
|
|
1015
|
-
]),
|
|
1016
|
-
]);
|
|
1017
|
-
});
|
|
1018
|
-
it('should validate resources use schemas define a type', async () => {
|
|
1019
|
-
const app = createTestApp();
|
|
1020
|
-
app.resources.person.schema = { properties: {} };
|
|
1021
|
-
const result = await validateAppDefinition(app, () => []);
|
|
1022
|
-
expect(result.valid).toBe(false);
|
|
1023
|
-
expect(result.errors).toStrictEqual([
|
|
1024
|
-
new ValidationError('must define type object', { properties: {} }, undefined, [
|
|
1025
|
-
'resources',
|
|
1026
|
-
'person',
|
|
1027
|
-
'schema',
|
|
1028
|
-
]),
|
|
1029
|
-
]);
|
|
1030
|
-
});
|
|
1031
|
-
it('should validate resources use schemas define a type of object', async () => {
|
|
1032
|
-
const app = createTestApp();
|
|
1033
|
-
app.resources.person.schema = { type: 'string', properties: {} };
|
|
1034
|
-
const result = await validateAppDefinition(app, () => []);
|
|
1035
|
-
expect(result.valid).toBe(false);
|
|
1036
|
-
expect(result.errors).toStrictEqual([
|
|
1037
|
-
new ValidationError('must define type object', 'string', undefined, [
|
|
1038
|
-
'resources',
|
|
1039
|
-
'person',
|
|
1040
|
-
'schema',
|
|
1041
|
-
'type',
|
|
1042
|
-
]),
|
|
1043
|
-
]);
|
|
1044
|
-
});
|
|
1045
|
-
it('should validate the resource id schema is correct', async () => {
|
|
1046
|
-
const app = createTestApp();
|
|
1047
|
-
app.resources.person.schema = {
|
|
1048
|
-
type: 'object',
|
|
1049
|
-
properties: { id: { type: 'string', description: '', title: '', format: 'email' } },
|
|
1050
|
-
};
|
|
1051
|
-
const result = await validateAppDefinition(app, () => []);
|
|
1052
|
-
expect(result.valid).toBe(false);
|
|
1053
|
-
expect(result.errors).toStrictEqual([
|
|
1054
|
-
new ValidationError('must be integer', 'string', undefined, [
|
|
1055
|
-
'resources',
|
|
1056
|
-
'person',
|
|
1057
|
-
'schema',
|
|
1058
|
-
'properties',
|
|
1059
|
-
'id',
|
|
1060
|
-
'type',
|
|
1061
|
-
]),
|
|
1062
|
-
new ValidationError('does not support custom validators', 'email', undefined, [
|
|
1063
|
-
'resources',
|
|
1064
|
-
'person',
|
|
1065
|
-
'schema',
|
|
1066
|
-
'properties',
|
|
1067
|
-
'id',
|
|
1068
|
-
'format',
|
|
1069
|
-
]),
|
|
1070
|
-
]);
|
|
1071
|
-
});
|
|
1072
|
-
it('should validate the resource $created and $updated schemas are correct', async () => {
|
|
1073
|
-
const app = createTestApp();
|
|
1074
|
-
app.resources.person.schema = {
|
|
1075
|
-
type: 'object',
|
|
1076
|
-
properties: {
|
|
1077
|
-
$created: { type: 'number', description: '', title: '', format: 'email' },
|
|
1078
|
-
$updated: { type: 'boolean', description: '', title: '', format: 'uuid' },
|
|
1079
|
-
},
|
|
1080
|
-
};
|
|
1081
|
-
const result = await validateAppDefinition(app, () => []);
|
|
1082
|
-
expect(result.valid).toBe(false);
|
|
1083
|
-
expect(result.errors).toStrictEqual([
|
|
1084
|
-
new ValidationError('must be string', 'number', undefined, [
|
|
1085
|
-
'resources',
|
|
1086
|
-
'person',
|
|
1087
|
-
'schema',
|
|
1088
|
-
'properties',
|
|
1089
|
-
'$created',
|
|
1090
|
-
'type',
|
|
1091
|
-
]),
|
|
1092
|
-
new ValidationError('must be date-time', 'email', undefined, [
|
|
1093
|
-
'resources',
|
|
1094
|
-
'person',
|
|
1095
|
-
'schema',
|
|
1096
|
-
'properties',
|
|
1097
|
-
'$created',
|
|
1098
|
-
'format',
|
|
1099
|
-
]),
|
|
1100
|
-
new ValidationError('must be string', 'boolean', undefined, [
|
|
1101
|
-
'resources',
|
|
1102
|
-
'person',
|
|
1103
|
-
'schema',
|
|
1104
|
-
'properties',
|
|
1105
|
-
'$updated',
|
|
1106
|
-
'type',
|
|
1107
|
-
]),
|
|
1108
|
-
new ValidationError('must be date-time', 'uuid', undefined, [
|
|
1109
|
-
'resources',
|
|
1110
|
-
'person',
|
|
1111
|
-
'schema',
|
|
1112
|
-
'properties',
|
|
1113
|
-
'$updated',
|
|
1114
|
-
'format',
|
|
1115
|
-
]),
|
|
1116
|
-
]);
|
|
1117
|
-
});
|
|
1118
|
-
it('should report resource properties starting with $', async () => {
|
|
1119
|
-
const app = createTestApp();
|
|
1120
|
-
app.resources.person.schema = {
|
|
1121
|
-
type: 'object',
|
|
1122
|
-
properties: { $invalid: { type: 'string' } },
|
|
1123
|
-
};
|
|
1124
|
-
const result = await validateAppDefinition(app, () => []);
|
|
1125
|
-
expect(result.valid).toBe(false);
|
|
1126
|
-
expect(result.errors).toStrictEqual([
|
|
1127
|
-
new ValidationError('may not start with $', { type: 'string' }, undefined, [
|
|
1128
|
-
'resources',
|
|
1129
|
-
'person',
|
|
1130
|
-
'schema',
|
|
1131
|
-
'properties',
|
|
1132
|
-
'$invalid',
|
|
1133
|
-
]),
|
|
1134
|
-
]);
|
|
1135
|
-
});
|
|
1136
|
-
it('should report missing properties in JSON schemas', async () => {
|
|
1137
|
-
const app = createTestApp();
|
|
1138
|
-
app.resources.person.schema = { type: 'object' };
|
|
1139
|
-
const result = await validateAppDefinition(app, () => []);
|
|
1140
|
-
expect(result.valid).toBe(false);
|
|
1141
|
-
expect(result.errors).toStrictEqual([
|
|
1142
|
-
new ValidationError('is missing properties', { type: 'object' }, undefined, [
|
|
1143
|
-
'resources',
|
|
1144
|
-
'person',
|
|
1145
|
-
'schema',
|
|
1146
|
-
]),
|
|
1147
|
-
]);
|
|
1148
|
-
});
|
|
1149
|
-
it('should report missing properties in JSON schemas resursively', async () => {
|
|
1150
|
-
const app = createTestApp();
|
|
1151
|
-
app.resources.person.schema = {
|
|
1152
|
-
type: 'object',
|
|
1153
|
-
properties: { foo: { type: 'object' } },
|
|
1154
|
-
};
|
|
1155
|
-
const result = await validateAppDefinition(app, () => []);
|
|
1156
|
-
expect(result.valid).toBe(false);
|
|
1157
|
-
expect(result.errors).toStrictEqual([
|
|
1158
|
-
new ValidationError('is missing properties', { type: 'object' }, undefined, [
|
|
1159
|
-
'resources',
|
|
1160
|
-
'person',
|
|
1161
|
-
'schema',
|
|
1162
|
-
'properties',
|
|
1163
|
-
'foo',
|
|
1164
|
-
]),
|
|
1165
|
-
]);
|
|
1166
|
-
});
|
|
1167
|
-
it('should report unknown required properties in JSON schemas', async () => {
|
|
1168
|
-
const app = createTestApp();
|
|
1169
|
-
app.resources.person.schema = {
|
|
1170
|
-
type: 'object',
|
|
1171
|
-
required: ['bar'],
|
|
1172
|
-
properties: { foo: { type: 'object', properties: {}, required: ['baz'] } },
|
|
1173
|
-
};
|
|
1174
|
-
const result = await validateAppDefinition(app, () => []);
|
|
1175
|
-
expect(result.valid).toBe(false);
|
|
1176
|
-
expect(result.errors).toStrictEqual([
|
|
1177
|
-
new ValidationError('is not defined in properties', 'bar', undefined, [
|
|
1178
|
-
'resources',
|
|
1179
|
-
'person',
|
|
1180
|
-
'schema',
|
|
1181
|
-
'required',
|
|
1182
|
-
0,
|
|
1183
|
-
]),
|
|
1184
|
-
new ValidationError('is not defined in properties', 'baz', undefined, [
|
|
1185
|
-
'resources',
|
|
1186
|
-
'person',
|
|
1187
|
-
'schema',
|
|
1188
|
-
'properties',
|
|
1189
|
-
'foo',
|
|
1190
|
-
'required',
|
|
1191
|
-
0,
|
|
1192
|
-
]),
|
|
1193
|
-
]);
|
|
1194
|
-
});
|
|
1195
|
-
it('should validate page roles', async () => {
|
|
1196
|
-
const app = createTestApp();
|
|
1197
|
-
app.pages[0].roles = ['Unknown'];
|
|
1198
|
-
const result = await validateAppDefinition(app, () => []);
|
|
1199
|
-
expect(result.valid).toBe(false);
|
|
1200
|
-
expect(result.errors).toStrictEqual([
|
|
1201
|
-
new ValidationError('does not exist in this app’s roles', 'Unknown', undefined, [
|
|
1202
|
-
'pages',
|
|
1203
|
-
0,
|
|
1204
|
-
'roles',
|
|
1205
|
-
0,
|
|
1206
|
-
]),
|
|
1207
|
-
]);
|
|
1208
|
-
});
|
|
1209
|
-
it('should validate block roles', async () => {
|
|
1210
|
-
const app = createTestApp();
|
|
1211
|
-
app.pages[0].blocks.push({
|
|
1212
|
-
type: 'test',
|
|
1213
|
-
version: '1.2.3',
|
|
1214
|
-
roles: ['Unknown'],
|
|
1215
|
-
});
|
|
1216
|
-
const result = await validateAppDefinition(app, () => [
|
|
1217
|
-
{ name: '@appsemble/test', version: '1.2.3', files: [], languages: [] },
|
|
1218
|
-
]);
|
|
1219
|
-
expect(result.valid).toBe(false);
|
|
1220
|
-
expect(result.errors).toStrictEqual([
|
|
1221
|
-
new ValidationError('does not exist in this app’s roles', 'Unknown', undefined, [
|
|
1222
|
-
'pages',
|
|
1223
|
-
0,
|
|
1224
|
-
'blocks',
|
|
1225
|
-
0,
|
|
1226
|
-
'roles',
|
|
1227
|
-
0,
|
|
1228
|
-
]),
|
|
1229
|
-
]);
|
|
1230
|
-
});
|
|
1231
|
-
it('should validate inherited roles', async () => {
|
|
1232
|
-
const app = createTestApp();
|
|
1233
|
-
app.security.roles.User.inherits = ['Unknown'];
|
|
1234
|
-
const result = await validateAppDefinition(app, () => []);
|
|
1235
|
-
expect(result.valid).toBe(false);
|
|
1236
|
-
expect(result.errors).toStrictEqual([
|
|
1237
|
-
new ValidationError('does not exist in this app’s roles', 'Unknown', undefined, [
|
|
1238
|
-
'security',
|
|
1239
|
-
'roles',
|
|
1240
|
-
'User',
|
|
1241
|
-
'inherits',
|
|
1242
|
-
0,
|
|
1243
|
-
]),
|
|
1244
|
-
]);
|
|
1245
|
-
});
|
|
1246
|
-
it('should report cyclic role inheritance', async () => {
|
|
1247
|
-
const app = createTestApp();
|
|
1248
|
-
app.security.roles.A = { inherits: ['B'] };
|
|
1249
|
-
app.security.roles.B = { inherits: ['C'] };
|
|
1250
|
-
app.security.roles.C = { inherits: ['E', 'A'] };
|
|
1251
|
-
app.security.roles.D = { inherits: ['A'] };
|
|
1252
|
-
app.security.roles.E = {};
|
|
1253
|
-
const result = await validateAppDefinition(app, () => []);
|
|
1254
|
-
expect(result.valid).toBe(false);
|
|
1255
|
-
expect(result.errors).toStrictEqual([
|
|
1256
|
-
new ValidationError('cyclically inherits itself', { inherits: ['B'] }, undefined, [
|
|
1257
|
-
'security',
|
|
1258
|
-
'roles',
|
|
1259
|
-
'A',
|
|
1260
|
-
]),
|
|
1261
|
-
new ValidationError('cyclically inherits itself', { inherits: ['C'] }, undefined, [
|
|
1262
|
-
'security',
|
|
1263
|
-
'roles',
|
|
1264
|
-
'B',
|
|
1265
|
-
]),
|
|
1266
|
-
new ValidationError('cyclically inherits itself', { inherits: ['E', 'A'] }, undefined, [
|
|
1267
|
-
'security',
|
|
1268
|
-
'roles',
|
|
1269
|
-
'C',
|
|
1270
|
-
]),
|
|
1271
|
-
]);
|
|
1272
|
-
});
|
|
1273
|
-
it('should report unknown roles in resource notification hooks', async () => {
|
|
1274
|
-
const app = createTestApp();
|
|
1275
|
-
app.resources.person.update.hooks = {
|
|
1276
|
-
notification: {
|
|
1277
|
-
to: ['Unknown'],
|
|
1278
|
-
},
|
|
1279
|
-
};
|
|
1280
|
-
const result = await validateAppDefinition(app, () => []);
|
|
1281
|
-
expect(result.valid).toBe(false);
|
|
1282
|
-
expect(result.errors).toStrictEqual([
|
|
1283
|
-
new ValidationError('is an unknown role', 'Unknown', undefined, [
|
|
1284
|
-
'resources',
|
|
1285
|
-
'person',
|
|
1286
|
-
'update',
|
|
1287
|
-
'hooks',
|
|
1288
|
-
'notifications',
|
|
1289
|
-
'to',
|
|
1290
|
-
0,
|
|
1291
|
-
]),
|
|
1292
|
-
]);
|
|
1293
|
-
});
|
|
1294
|
-
it('should allow $author in resource notification hooks', async () => {
|
|
1295
|
-
const app = createTestApp();
|
|
1296
|
-
app.resources.person.update.hooks = {
|
|
1297
|
-
notification: {
|
|
1298
|
-
to: ['$author'],
|
|
1299
|
-
},
|
|
1300
|
-
};
|
|
1301
|
-
const result = await validateAppDefinition(app, () => []);
|
|
1302
|
-
expect(result.valid).toBe(true);
|
|
1303
|
-
});
|
|
1304
|
-
it('should report invalid resource references', async () => {
|
|
1305
|
-
const app = createTestApp();
|
|
1306
|
-
app.resources.person.references = {
|
|
1307
|
-
name: {
|
|
1308
|
-
resource: 'non-existent',
|
|
1309
|
-
},
|
|
1310
|
-
};
|
|
1311
|
-
const result = await validateAppDefinition(app, () => []);
|
|
1312
|
-
expect(result.valid).toBe(false);
|
|
1313
|
-
expect(result.errors).toStrictEqual([
|
|
1314
|
-
new ValidationError('is not an existing resource', 'non-existent', undefined, [
|
|
1315
|
-
'resources',
|
|
1316
|
-
'person',
|
|
1317
|
-
'references',
|
|
1318
|
-
'name',
|
|
1319
|
-
'resource',
|
|
1320
|
-
]),
|
|
1321
|
-
]);
|
|
1322
|
-
});
|
|
1323
|
-
it('should report invalid resource reference fields', async () => {
|
|
1324
|
-
const app = createTestApp();
|
|
1325
|
-
app.resources.person.references = {
|
|
1326
|
-
invalid: {
|
|
1327
|
-
resource: 'person',
|
|
1328
|
-
},
|
|
1329
|
-
};
|
|
1330
|
-
const result = await validateAppDefinition(app, () => []);
|
|
1331
|
-
expect(result.valid).toBe(false);
|
|
1332
|
-
expect(result.errors).toStrictEqual([
|
|
1333
|
-
new ValidationError('does not exist on this resource', 'invalid', undefined, [
|
|
1334
|
-
'resources',
|
|
1335
|
-
'person',
|
|
1336
|
-
'references',
|
|
1337
|
-
'invalid',
|
|
1338
|
-
]),
|
|
1339
|
-
]);
|
|
1340
|
-
});
|
|
1341
|
-
it('should not report valid resource references', async () => {
|
|
1342
|
-
const app = createTestApp();
|
|
1343
|
-
app.resources.person.references = {
|
|
1344
|
-
name: {
|
|
1345
|
-
resource: 'person',
|
|
1346
|
-
},
|
|
1347
|
-
};
|
|
1348
|
-
const result = await validateAppDefinition(app, () => []);
|
|
1349
|
-
expect(result.valid).toBe(true);
|
|
1350
|
-
});
|
|
1351
|
-
it('should not crash if not resources exist', async () => {
|
|
1352
|
-
const result = await validateAppDefinition({ ...createTestApp(), resources: undefined }, () => []);
|
|
1353
|
-
expect(result.valid).toBe(true);
|
|
1354
|
-
});
|
|
1355
|
-
it('should report an invalid default language', async () => {
|
|
1356
|
-
const result = await validateAppDefinition({ ...createTestApp(), defaultLanguage: 'Klingon' }, () => []);
|
|
1357
|
-
expect(result.valid).toBe(false);
|
|
1358
|
-
expect(result.errors).toStrictEqual([
|
|
1359
|
-
new ValidationError('is not a valid language code', 'Klingon', undefined, [
|
|
1360
|
-
'defaultLanguage',
|
|
1361
|
-
]),
|
|
1362
|
-
]);
|
|
1363
|
-
});
|
|
1364
|
-
it('should allow a valid default language', async () => {
|
|
1365
|
-
const result = await validateAppDefinition({ ...createTestApp(), defaultLanguage: 'kln' }, () => []);
|
|
1366
|
-
expect(result.valid).toBe(true);
|
|
1367
|
-
expect(result.errors).toStrictEqual([]);
|
|
1368
|
-
});
|
|
1369
|
-
it('should validate the default page exists', async () => {
|
|
1370
|
-
const result = await validateAppDefinition({ ...createTestApp(), defaultPage: 'Does not exist' }, () => []);
|
|
1371
|
-
expect(result.valid).toBe(false);
|
|
1372
|
-
expect(result.errors).toStrictEqual([
|
|
1373
|
-
new ValidationError('does not refer to an existing page', 'Does not exist', undefined, [
|
|
1374
|
-
'defaultPage',
|
|
1375
|
-
]),
|
|
1376
|
-
]);
|
|
1377
|
-
});
|
|
1378
|
-
it('should check if the default page exists inside contained page', async () => {
|
|
1379
|
-
const result = await validateAppDefinition({ ...createTestApp(), defaultPage: 'Contained Page' }, () => []);
|
|
1380
|
-
expect(result.valid).toBe(true);
|
|
1381
|
-
});
|
|
1382
|
-
it('should validate the default page doesn’t specify parameters', async () => {
|
|
1383
|
-
const result = await validateAppDefinition({ ...createTestApp(), defaultPage: 'Page with parameters' }, () => []);
|
|
1384
|
-
expect(result.valid).toBe(false);
|
|
1385
|
-
expect(result.errors).toStrictEqual([
|
|
1386
|
-
new ValidationError('may not specify parameters', 'Page with parameters', undefined, [
|
|
1387
|
-
'defaultPage',
|
|
1388
|
-
]),
|
|
1389
|
-
]);
|
|
1390
|
-
});
|
|
1391
|
-
it('should report invalid cronjob schedule syntax', async () => {
|
|
1392
|
-
const result = await validateAppDefinition({
|
|
1393
|
-
...createTestApp(),
|
|
1394
|
-
cron: { foo: { schedule: 'invalid cronjob test', action: { type: 'noop' } } },
|
|
1395
|
-
}, () => []);
|
|
1396
|
-
expect(result.valid).toBe(false);
|
|
1397
|
-
expect(result.errors).toStrictEqual([
|
|
1398
|
-
new ValidationError('contains an invalid expression', 'invalid cronjob test', undefined, [
|
|
1399
|
-
'cron',
|
|
1400
|
-
'foo',
|
|
1401
|
-
'schedule',
|
|
1402
|
-
]),
|
|
1403
|
-
]);
|
|
1404
|
-
});
|
|
1405
|
-
it('should allow valid cronjob schedule syntax', async () => {
|
|
1406
|
-
const result = await validateAppDefinition({ ...createTestApp(), cron: { foo: { schedule: '5 4 * * *', action: { type: 'noop' } } } }, () => []);
|
|
1407
|
-
expect(result.valid).toBe(true);
|
|
1408
|
-
expect(result.errors).toStrictEqual([]);
|
|
1409
|
-
});
|
|
1410
|
-
it('should not crash if cron is not a valid object', async () => {
|
|
1411
|
-
const result = await validateAppDefinition(
|
|
1412
|
-
// @ts-expect-error This tests invalid user input.
|
|
1413
|
-
{ ...createTestApp(), cron: { foo: null, bar: { schedule: 12 } } }, () => []);
|
|
1414
|
-
expect(result.valid).toBe(true);
|
|
1415
|
-
expect(result.errors).toStrictEqual([]);
|
|
1416
|
-
});
|
|
1417
|
-
it('should report an error if a link action contains a link to a page that doesn’t exist', async () => {
|
|
1418
|
-
const app = createTestApp();
|
|
1419
|
-
app.pages[0].blocks.push({
|
|
1420
|
-
type: 'test',
|
|
1421
|
-
version: '1.2.3',
|
|
1422
|
-
actions: {
|
|
1423
|
-
onWhatever: {
|
|
1424
|
-
type: 'link',
|
|
1425
|
-
to: 'Doesn’t exist',
|
|
1426
|
-
},
|
|
1427
|
-
},
|
|
1428
|
-
});
|
|
1429
|
-
const result = await validateAppDefinition(app, () => [
|
|
1430
|
-
{
|
|
1431
|
-
name: '@appsemble/test',
|
|
1432
|
-
version: '1.2.3',
|
|
1433
|
-
files: [],
|
|
1434
|
-
languages: [],
|
|
1435
|
-
actions: {
|
|
1436
|
-
onWhatever: {},
|
|
1437
|
-
},
|
|
1438
|
-
},
|
|
1439
|
-
]);
|
|
1440
|
-
expect(result.valid).toBe(false);
|
|
1441
|
-
expect(result.errors).toStrictEqual([
|
|
1442
|
-
new ValidationError('refers to a page that doesn’t exist', 'Doesn’t exist', undefined, [
|
|
1443
|
-
'pages',
|
|
1444
|
-
0,
|
|
1445
|
-
'blocks',
|
|
1446
|
-
0,
|
|
1447
|
-
'actions',
|
|
1448
|
-
'onWhatever',
|
|
1449
|
-
'to',
|
|
1450
|
-
]),
|
|
1451
|
-
]);
|
|
1452
|
-
});
|
|
1453
|
-
it('should report an error if a link action contains a link to a sub page for a page without sub pages', async () => {
|
|
1454
|
-
const app = createTestApp();
|
|
1455
|
-
app.pages[0].blocks.push({
|
|
1456
|
-
type: 'test',
|
|
1457
|
-
version: '1.2.3',
|
|
1458
|
-
actions: {
|
|
1459
|
-
onWhatever: {
|
|
1460
|
-
type: 'link',
|
|
1461
|
-
to: ['Test Page', 'Bla'],
|
|
1462
|
-
},
|
|
1463
|
-
},
|
|
1464
|
-
});
|
|
1465
|
-
const result = await validateAppDefinition(app, () => [
|
|
1466
|
-
{
|
|
1467
|
-
name: '@appsemble/test',
|
|
1468
|
-
version: '1.2.3',
|
|
1469
|
-
files: [],
|
|
1470
|
-
languages: [],
|
|
1471
|
-
actions: {
|
|
1472
|
-
onWhatever: {},
|
|
1473
|
-
},
|
|
1474
|
-
},
|
|
1475
|
-
]);
|
|
1476
|
-
expect(result.valid).toBe(false);
|
|
1477
|
-
expect(result.errors).toStrictEqual([
|
|
1478
|
-
new ValidationError('refers to a sub page on a page that isn’t of type ‘tabs’ or ‘flow’', 'Bla', undefined, ['pages', 0, 'blocks', 0, 'actions', 'onWhatever', 'to', 1]),
|
|
1479
|
-
]);
|
|
1480
|
-
});
|
|
1481
|
-
it('should report an error if a link action contains a link to a tab that doesn’t exist', async () => {
|
|
1482
|
-
const app = createTestApp();
|
|
1483
|
-
app.pages[0].blocks.push({
|
|
1484
|
-
type: 'test',
|
|
1485
|
-
version: '1.2.3',
|
|
1486
|
-
actions: {
|
|
1487
|
-
onWhatever: {
|
|
1488
|
-
type: 'link',
|
|
1489
|
-
to: ['Page with tabs', 'Bla'],
|
|
1490
|
-
},
|
|
1491
|
-
},
|
|
1492
|
-
});
|
|
1493
|
-
const result = await validateAppDefinition(app, () => [
|
|
1494
|
-
{
|
|
1495
|
-
name: '@appsemble/test',
|
|
1496
|
-
version: '1.2.3',
|
|
1497
|
-
files: [],
|
|
1498
|
-
languages: [],
|
|
1499
|
-
actions: {
|
|
1500
|
-
onWhatever: {},
|
|
1501
|
-
},
|
|
1502
|
-
},
|
|
1503
|
-
]);
|
|
1504
|
-
expect(result.valid).toBe(false);
|
|
1505
|
-
expect(result.errors).toStrictEqual([
|
|
1506
|
-
new ValidationError('refers to a tab that doesn’t exist', 'Bla', undefined, [
|
|
1507
|
-
'pages',
|
|
1508
|
-
0,
|
|
1509
|
-
'blocks',
|
|
1510
|
-
0,
|
|
1511
|
-
'actions',
|
|
1512
|
-
'onWhatever',
|
|
1513
|
-
'to',
|
|
1514
|
-
1,
|
|
1515
|
-
]),
|
|
1516
|
-
]);
|
|
1517
|
-
});
|
|
1518
|
-
it('should be valid if to is (remapper) object; considered as dynamic link', async () => {
|
|
1519
|
-
const app = createTestApp();
|
|
1520
|
-
app.pages[0].blocks.push({
|
|
1521
|
-
type: 'test',
|
|
1522
|
-
version: '1.2.3',
|
|
1523
|
-
actions: {
|
|
1524
|
-
onWhatever: {
|
|
1525
|
-
type: 'link',
|
|
1526
|
-
to: { static: 'test' },
|
|
1527
|
-
},
|
|
1528
|
-
},
|
|
1529
|
-
});
|
|
1530
|
-
const result = await validateAppDefinition(app, () => [
|
|
1531
|
-
{
|
|
1532
|
-
name: '@appsemble/test',
|
|
1533
|
-
version: '1.2.3',
|
|
1534
|
-
files: [],
|
|
1535
|
-
languages: [],
|
|
1536
|
-
actions: {
|
|
1537
|
-
onWhatever: {},
|
|
1538
|
-
},
|
|
1539
|
-
},
|
|
1540
|
-
]);
|
|
1541
|
-
expect(result.valid).toBe(true);
|
|
1542
|
-
expect(result.errors).toStrictEqual([]);
|
|
1543
|
-
});
|
|
1544
|
-
it('should be valid if to is array of (remapper) objects; considered as dynamic link', async () => {
|
|
1545
|
-
const app = createTestApp();
|
|
1546
|
-
app.pages[0].blocks.push({
|
|
1547
|
-
type: 'test',
|
|
1548
|
-
version: '1.2.3',
|
|
1549
|
-
actions: {
|
|
1550
|
-
onWhatever: {
|
|
1551
|
-
type: 'link',
|
|
1552
|
-
to: [{ static: 'test' }],
|
|
1553
|
-
},
|
|
1554
|
-
},
|
|
1555
|
-
});
|
|
1556
|
-
const result = await validateAppDefinition(app, () => [
|
|
1557
|
-
{
|
|
1558
|
-
name: '@appsemble/test',
|
|
1559
|
-
version: '1.2.3',
|
|
1560
|
-
files: [],
|
|
1561
|
-
languages: [],
|
|
1562
|
-
actions: {
|
|
1563
|
-
onWhatever: {},
|
|
1564
|
-
},
|
|
1565
|
-
},
|
|
1566
|
-
]);
|
|
1567
|
-
expect(result.valid).toBe(true);
|
|
1568
|
-
expect(result.errors).toStrictEqual([]);
|
|
1569
|
-
});
|
|
1570
|
-
it('should report an error if app member actions are used without a security definition', async () => {
|
|
1571
|
-
const { security, ...app } = createTestApp();
|
|
1572
|
-
app.pages[0].blocks.push({
|
|
1573
|
-
type: 'test',
|
|
1574
|
-
version: '1.2.3',
|
|
1575
|
-
actions: {
|
|
1576
|
-
onWhatever: {
|
|
1577
|
-
type: 'app.member.login',
|
|
1578
|
-
email: 'example@example.com',
|
|
1579
|
-
password: 'password',
|
|
1580
|
-
},
|
|
1581
|
-
},
|
|
1582
|
-
});
|
|
1583
|
-
app.pages[0].blocks.push({
|
|
1584
|
-
type: 'test',
|
|
1585
|
-
version: '1.2.3',
|
|
1586
|
-
actions: {
|
|
1587
|
-
onWhatever: {
|
|
1588
|
-
type: 'app.member.register',
|
|
1589
|
-
email: 'example@example.com',
|
|
1590
|
-
password: 'password',
|
|
1591
|
-
name: 'Test User',
|
|
1592
|
-
},
|
|
1593
|
-
},
|
|
1594
|
-
});
|
|
1595
|
-
app.pages[0].blocks.push({
|
|
1596
|
-
type: 'test',
|
|
1597
|
-
version: '1.2.3',
|
|
1598
|
-
actions: {
|
|
1599
|
-
onWhatever: {
|
|
1600
|
-
type: 'app.member.current.patch',
|
|
1601
|
-
},
|
|
1602
|
-
},
|
|
1603
|
-
});
|
|
1604
|
-
const result = await validateAppDefinition(app, () => [
|
|
1605
|
-
{
|
|
1606
|
-
name: '@appsemble/test',
|
|
1607
|
-
version: '1.2.3',
|
|
1608
|
-
files: [],
|
|
1609
|
-
languages: [],
|
|
1610
|
-
actions: {
|
|
1611
|
-
onWhatever: {},
|
|
1612
|
-
},
|
|
1613
|
-
},
|
|
1614
|
-
]);
|
|
1615
|
-
expect(result.valid).toBe(false);
|
|
1616
|
-
expect(result.errors).toStrictEqual([
|
|
1617
|
-
new ValidationError('refers to an app member action but the app doesn’t have a security definition', 'app.member.login', undefined, ['pages', 0, 'blocks', 0, 'actions', 'onWhatever', 'type']),
|
|
1618
|
-
new ValidationError('refers to an app member action but the app doesn’t have a security definition', 'app.member.register', undefined, ['pages', 0, 'blocks', 1, 'actions', 'onWhatever', 'type']),
|
|
1619
|
-
new ValidationError('refers to an app member action but the app doesn’t have a security definition', 'app.member.current.patch', undefined, ['pages', 0, 'blocks', 2, 'actions', 'onWhatever', 'type']),
|
|
1620
|
-
]);
|
|
1621
|
-
});
|
|
1622
|
-
it('should report an error if flow actions are used on a non-flow page', async () => {
|
|
1623
|
-
const app = createTestApp();
|
|
1624
|
-
app.pages[0].blocks.push({
|
|
1625
|
-
type: 'test',
|
|
1626
|
-
version: '1.2.3',
|
|
1627
|
-
actions: {
|
|
1628
|
-
onWhatever: {
|
|
1629
|
-
type: 'flow.next',
|
|
1630
|
-
},
|
|
1631
|
-
},
|
|
1632
|
-
});
|
|
1633
|
-
const result = await validateAppDefinition(app, () => [
|
|
1634
|
-
{
|
|
1635
|
-
name: '@appsemble/test',
|
|
1636
|
-
version: '1.2.3',
|
|
1637
|
-
files: [],
|
|
1638
|
-
languages: [],
|
|
1639
|
-
actions: {
|
|
1640
|
-
onWhatever: {},
|
|
1641
|
-
},
|
|
1642
|
-
},
|
|
1643
|
-
]);
|
|
1644
|
-
expect(result.valid).toBe(false);
|
|
1645
|
-
expect(result.errors).toStrictEqual([
|
|
1646
|
-
new ValidationError('flow actions can only be used on pages with the type ‘flow’ or ‘loop’', 'flow.next', undefined, ['pages', 0, 'blocks', 0, 'actions', 'onWhatever', 'type']),
|
|
1647
|
-
]);
|
|
1648
|
-
});
|
|
1649
|
-
it('should report an error if flow.back is used on the first step', async () => {
|
|
1650
|
-
const app = createTestApp();
|
|
1651
|
-
app.pages[3].steps[0].blocks.push({
|
|
1652
|
-
type: 'test',
|
|
1653
|
-
version: '1.2.3',
|
|
1654
|
-
actions: {
|
|
1655
|
-
onWhatever: {
|
|
1656
|
-
type: 'flow.back',
|
|
1657
|
-
},
|
|
1658
|
-
},
|
|
1659
|
-
});
|
|
1660
|
-
const result = await validateAppDefinition(app, () => [
|
|
1661
|
-
{
|
|
1662
|
-
name: '@appsemble/test',
|
|
1663
|
-
version: '1.2.3',
|
|
1664
|
-
files: [],
|
|
1665
|
-
languages: [],
|
|
1666
|
-
actions: {
|
|
1667
|
-
onWhatever: {},
|
|
1668
|
-
},
|
|
1669
|
-
},
|
|
1670
|
-
]);
|
|
1671
|
-
expect(result.valid).toBe(false);
|
|
1672
|
-
expect(result.errors).toStrictEqual([
|
|
1673
|
-
new ValidationError('is not allowed on the first step in the flow', 'flow.back', undefined, [
|
|
1674
|
-
'pages',
|
|
1675
|
-
3,
|
|
1676
|
-
'steps',
|
|
1677
|
-
0,
|
|
1678
|
-
'blocks',
|
|
1679
|
-
0,
|
|
1680
|
-
'actions',
|
|
1681
|
-
'onWhatever',
|
|
1682
|
-
'type',
|
|
1683
|
-
]),
|
|
1684
|
-
]);
|
|
1685
|
-
});
|
|
1686
|
-
it('should report an error if flow.to refers to a step that doesn’t exist', async () => {
|
|
1687
|
-
const app = createTestApp();
|
|
1688
|
-
app.pages[3].steps[0].blocks.push({
|
|
1689
|
-
type: 'test',
|
|
1690
|
-
version: '1.2.3',
|
|
1691
|
-
actions: {
|
|
1692
|
-
onWhatever: {
|
|
1693
|
-
type: 'flow.to',
|
|
1694
|
-
step: 'Some Step',
|
|
1695
|
-
},
|
|
1696
|
-
},
|
|
1697
|
-
});
|
|
1698
|
-
const result = await validateAppDefinition(app, () => [
|
|
1699
|
-
{
|
|
1700
|
-
name: '@appsemble/test',
|
|
1701
|
-
version: '1.2.3',
|
|
1702
|
-
files: [],
|
|
1703
|
-
languages: [],
|
|
1704
|
-
actions: {
|
|
1705
|
-
onWhatever: {},
|
|
1706
|
-
},
|
|
1707
|
-
},
|
|
1708
|
-
]);
|
|
1709
|
-
expect(result.valid).toBe(false);
|
|
1710
|
-
expect(result.errors).toStrictEqual([
|
|
1711
|
-
new ValidationError('refers to a step that doesn’t exist', 'flow.to', undefined, [
|
|
1712
|
-
'pages',
|
|
1713
|
-
3,
|
|
1714
|
-
'steps',
|
|
1715
|
-
0,
|
|
1716
|
-
'blocks',
|
|
1717
|
-
0,
|
|
1718
|
-
'actions',
|
|
1719
|
-
'onWhatever',
|
|
1720
|
-
'step',
|
|
1721
|
-
]),
|
|
1722
|
-
]);
|
|
1723
|
-
});
|
|
1724
|
-
it('should report an error if flow.next is called on the last step without onFlowFinish', async () => {
|
|
1725
|
-
const app = createTestApp();
|
|
1726
|
-
app.pages[3].steps[1].blocks.push({
|
|
1727
|
-
type: 'test',
|
|
1728
|
-
version: '1.2.3',
|
|
1729
|
-
actions: {
|
|
1730
|
-
onWhatever: {
|
|
1731
|
-
type: 'flow.next',
|
|
1732
|
-
},
|
|
1733
|
-
},
|
|
1734
|
-
});
|
|
1735
|
-
const result = await validateAppDefinition(app, () => [
|
|
1736
|
-
{
|
|
1737
|
-
name: '@appsemble/test',
|
|
1738
|
-
version: '1.2.3',
|
|
1739
|
-
files: [],
|
|
1740
|
-
languages: [],
|
|
1741
|
-
actions: {
|
|
1742
|
-
onWhatever: {},
|
|
1743
|
-
},
|
|
1744
|
-
},
|
|
1745
|
-
]);
|
|
1746
|
-
expect(result.valid).toBe(false);
|
|
1747
|
-
expect(result.errors).toStrictEqual([
|
|
1748
|
-
new ValidationError('was defined on the last step but ‘onFlowFinish’ page action wasn’t defined', 'flow.next', undefined, ['pages', 3, 'steps', 1, 'blocks', 0, 'actions', 'onWhatever', 'type']),
|
|
1749
|
-
]);
|
|
1750
|
-
});
|
|
1751
|
-
it('should report an error if flow.finish is called without onFlowFinish', async () => {
|
|
1752
|
-
const app = createTestApp();
|
|
1753
|
-
app.pages[3].steps[1].blocks.push({
|
|
1754
|
-
type: 'test',
|
|
1755
|
-
version: '1.2.3',
|
|
1756
|
-
actions: {
|
|
1757
|
-
onWhatever: {
|
|
1758
|
-
type: 'flow.finish',
|
|
1759
|
-
},
|
|
1760
|
-
},
|
|
1761
|
-
});
|
|
1762
|
-
const result = await validateAppDefinition(app, () => [
|
|
1763
|
-
{
|
|
1764
|
-
name: '@appsemble/test',
|
|
1765
|
-
version: '1.2.3',
|
|
1766
|
-
files: [],
|
|
1767
|
-
languages: [],
|
|
1768
|
-
actions: {
|
|
1769
|
-
onWhatever: {},
|
|
1770
|
-
},
|
|
1771
|
-
},
|
|
1772
|
-
]);
|
|
1773
|
-
expect(result.valid).toBe(false);
|
|
1774
|
-
expect(result.errors).toStrictEqual([
|
|
1775
|
-
new ValidationError('was defined but ‘onFlowFinish’ page action wasn’t defined', 'flow.finish', undefined, ['pages', 3, 'steps', 1, 'blocks', 0, 'actions', 'onWhatever', 'type']),
|
|
1776
|
-
]);
|
|
1777
|
-
});
|
|
1778
|
-
it('should report an error if flow.cancel is called without onFlowCancel', async () => {
|
|
1779
|
-
const app = createTestApp();
|
|
1780
|
-
app.pages[3].steps[1].blocks.push({
|
|
1781
|
-
type: 'test',
|
|
1782
|
-
version: '1.2.3',
|
|
1783
|
-
actions: {
|
|
1784
|
-
onWhatever: {
|
|
1785
|
-
type: 'flow.cancel',
|
|
1786
|
-
},
|
|
1787
|
-
},
|
|
1788
|
-
});
|
|
1789
|
-
const result = await validateAppDefinition(app, () => [
|
|
1790
|
-
{
|
|
1791
|
-
name: '@appsemble/test',
|
|
1792
|
-
version: '1.2.3',
|
|
1793
|
-
files: [],
|
|
1794
|
-
languages: [],
|
|
1795
|
-
actions: {
|
|
1796
|
-
onWhatever: {},
|
|
1797
|
-
},
|
|
1798
|
-
},
|
|
1799
|
-
]);
|
|
1800
|
-
expect(result.valid).toBe(false);
|
|
1801
|
-
expect(result.errors).toStrictEqual([
|
|
1802
|
-
new ValidationError('was defined but ‘onFlowCancel’ page action wasn’t defined', 'flow.cancel', undefined, ['pages', 3, 'steps', 1, 'blocks', 0, 'actions', 'onWhatever', 'type']),
|
|
1803
|
-
]);
|
|
1804
|
-
});
|
|
1805
|
-
it('should report an error if a user register action on a block adds unsupported user properties', async () => {
|
|
1806
|
-
const app = {
|
|
1807
|
-
...createTestApp(),
|
|
1808
|
-
members: {
|
|
1809
|
-
properties: {
|
|
1810
|
-
foo: {
|
|
1811
|
-
schema: {
|
|
1812
|
-
type: 'string',
|
|
1813
|
-
},
|
|
1814
|
-
},
|
|
1815
|
-
},
|
|
1816
|
-
},
|
|
1817
|
-
};
|
|
1818
|
-
app.pages[0].blocks.push({
|
|
1819
|
-
type: 'test',
|
|
1820
|
-
version: '1.2.3',
|
|
1821
|
-
actions: {
|
|
1822
|
-
onWhatever: {
|
|
1823
|
-
type: 'app.member.register',
|
|
1824
|
-
name: 'name',
|
|
1825
|
-
email: 'email@example.com',
|
|
1826
|
-
password: 'password',
|
|
1827
|
-
properties: {
|
|
1828
|
-
'object.from': {
|
|
1829
|
-
bar: 'baz',
|
|
1830
|
-
},
|
|
1831
|
-
},
|
|
1832
|
-
},
|
|
1833
|
-
},
|
|
1834
|
-
});
|
|
1835
|
-
const result = await validateAppDefinition(app, () => [
|
|
1836
|
-
{
|
|
1837
|
-
name: '@appsemble/test',
|
|
1838
|
-
version: '1.2.3',
|
|
1839
|
-
files: [],
|
|
1840
|
-
languages: [],
|
|
1841
|
-
actions: {
|
|
1842
|
-
onWhatever: {},
|
|
1843
|
-
},
|
|
1844
|
-
},
|
|
1845
|
-
]);
|
|
1846
|
-
expect(result.valid).toBe(false);
|
|
1847
|
-
expect(result.errors).toStrictEqual([
|
|
1848
|
-
new ValidationError('contains a property that doesn’t exist in app member properties', 'app.member.register', undefined, ['pages', 0, 'blocks', 0, 'actions', 'onWhatever', 'properties']),
|
|
1849
|
-
]);
|
|
1850
|
-
});
|
|
1851
|
-
it('should report an error if a app member update action on a block adds unsupported app member properties', async () => {
|
|
1852
|
-
const app = {
|
|
1853
|
-
...createTestApp(),
|
|
1854
|
-
members: {
|
|
1855
|
-
properties: {
|
|
1856
|
-
foo: {
|
|
1857
|
-
schema: {
|
|
1858
|
-
type: 'string',
|
|
1859
|
-
},
|
|
1860
|
-
},
|
|
1861
|
-
},
|
|
1862
|
-
},
|
|
1863
|
-
};
|
|
1864
|
-
app.pages[0].blocks.push({
|
|
1865
|
-
type: 'test',
|
|
1866
|
-
version: '1.2.3',
|
|
1867
|
-
actions: {
|
|
1868
|
-
onWhatever: {
|
|
1869
|
-
type: 'app.member.current.patch',
|
|
1870
|
-
name: 'name',
|
|
1871
|
-
properties: {
|
|
1872
|
-
'object.from': {
|
|
1873
|
-
bar: 'baz',
|
|
1874
|
-
},
|
|
1875
|
-
},
|
|
1876
|
-
},
|
|
1877
|
-
},
|
|
1878
|
-
});
|
|
1879
|
-
const result = await validateAppDefinition(app, () => [
|
|
1880
|
-
{
|
|
1881
|
-
name: '@appsemble/test',
|
|
1882
|
-
version: '1.2.3',
|
|
1883
|
-
files: [],
|
|
1884
|
-
languages: [],
|
|
1885
|
-
actions: {
|
|
1886
|
-
onWhatever: {},
|
|
1887
|
-
},
|
|
1888
|
-
},
|
|
1889
|
-
]);
|
|
1890
|
-
expect(result.valid).toBe(false);
|
|
1891
|
-
expect(result.errors).toStrictEqual([
|
|
1892
|
-
new ValidationError('contains a property that doesn’t exist in app member properties', 'app.member.current.patch', undefined, ['pages', 0, 'blocks', 0, 'actions', 'onWhatever', 'properties']),
|
|
1893
|
-
]);
|
|
1894
|
-
});
|
|
1895
|
-
it('should report an error if a resource action on a block refers to a non-existent resource', async () => {
|
|
1896
|
-
const app = createTestApp();
|
|
1897
|
-
app.pages[0].blocks.push({
|
|
1898
|
-
type: 'test',
|
|
1899
|
-
version: '1.2.3',
|
|
1900
|
-
actions: {
|
|
1901
|
-
onWhatever: {
|
|
1902
|
-
type: 'resource.get',
|
|
1903
|
-
resource: 'Nonexistent',
|
|
1904
|
-
},
|
|
1905
|
-
},
|
|
1906
|
-
});
|
|
1907
|
-
const result = await validateAppDefinition(app, () => [
|
|
1908
|
-
{
|
|
1909
|
-
name: '@appsemble/test',
|
|
1910
|
-
version: '1.2.3',
|
|
1911
|
-
files: [],
|
|
1912
|
-
languages: [],
|
|
1913
|
-
actions: {
|
|
1914
|
-
onWhatever: {},
|
|
1915
|
-
},
|
|
1916
|
-
},
|
|
1917
|
-
]);
|
|
1918
|
-
expect(result.valid).toBe(false);
|
|
1919
|
-
expect(result.errors).toStrictEqual([
|
|
1920
|
-
new ValidationError('refers to a resource that doesn’t exist', 'resource.get', undefined, [
|
|
1921
|
-
'pages',
|
|
1922
|
-
0,
|
|
1923
|
-
'blocks',
|
|
1924
|
-
0,
|
|
1925
|
-
'actions',
|
|
1926
|
-
'onWhatever',
|
|
1927
|
-
'resource',
|
|
1928
|
-
]),
|
|
1929
|
-
]);
|
|
1930
|
-
});
|
|
1931
|
-
it('should report an error if a resource action on the controller refers to a non-existent resource', async () => {
|
|
1932
|
-
const app = createTestApp();
|
|
1933
|
-
app.controller = {
|
|
1934
|
-
actions: {
|
|
1935
|
-
onWhatever: {
|
|
1936
|
-
type: 'resource.get',
|
|
1937
|
-
resource: 'Nonexistent',
|
|
1938
|
-
},
|
|
1939
|
-
},
|
|
1940
|
-
};
|
|
1941
|
-
const result = await validateAppDefinition(app, () => [], {
|
|
1942
|
-
actions: {
|
|
1943
|
-
onWhatever: {},
|
|
1944
|
-
},
|
|
1945
|
-
});
|
|
1946
|
-
expect(result.valid).toBe(false);
|
|
1947
|
-
expect(result.errors).toStrictEqual([
|
|
1948
|
-
new ValidationError('refers to a resource that doesn’t exist', 'resource.get', undefined, [
|
|
1949
|
-
'controller',
|
|
1950
|
-
'actions',
|
|
1951
|
-
'onWhatever',
|
|
1952
|
-
'resource',
|
|
1953
|
-
]),
|
|
1954
|
-
]);
|
|
1955
|
-
});
|
|
1956
|
-
it('should report an error if a resource action on a block is accessible by no roles in the app', async () => {
|
|
1957
|
-
const app = createTestApp();
|
|
1958
|
-
app.pages[0].blocks.push({
|
|
1959
|
-
type: 'test',
|
|
1960
|
-
version: '1.2.3',
|
|
1961
|
-
actions: {
|
|
1962
|
-
onWhatever: {
|
|
1963
|
-
type: 'resource.get',
|
|
1964
|
-
resource: 'person',
|
|
1965
|
-
},
|
|
1966
|
-
},
|
|
1967
|
-
});
|
|
1968
|
-
const result = await validateAppDefinition(app, () => [
|
|
1969
|
-
{
|
|
1970
|
-
name: '@appsemble/test',
|
|
1971
|
-
version: '1.2.3',
|
|
1972
|
-
files: [],
|
|
1973
|
-
languages: [],
|
|
1974
|
-
actions: {
|
|
1975
|
-
onWhatever: {},
|
|
1976
|
-
},
|
|
1977
|
-
},
|
|
1978
|
-
]);
|
|
1979
|
-
expect(result.valid).toBe(false);
|
|
1980
|
-
expect(result.errors).toStrictEqual([
|
|
1981
|
-
new ValidationError('there is no-one in the app, who has permissions to use this action', 'resource.get', undefined, ['pages', 0, 'blocks', 0, 'actions', 'onWhatever', 'resource']),
|
|
1982
|
-
]);
|
|
1983
|
-
});
|
|
1984
|
-
it('should report an error if a resource action on the controller is accessible by no roles in the app', async () => {
|
|
1985
|
-
const app = createTestApp();
|
|
1986
|
-
app.controller = {
|
|
1987
|
-
actions: {
|
|
1988
|
-
onWhatever: {
|
|
1989
|
-
type: 'resource.get',
|
|
1990
|
-
resource: 'person',
|
|
1991
|
-
},
|
|
1992
|
-
},
|
|
1993
|
-
};
|
|
1994
|
-
const result = await validateAppDefinition(app, () => [], {
|
|
1995
|
-
actions: {
|
|
1996
|
-
onWhatever: {},
|
|
1997
|
-
},
|
|
1998
|
-
});
|
|
1999
|
-
expect(result.valid).toBe(false);
|
|
2000
|
-
expect(result.errors).toStrictEqual([
|
|
2001
|
-
new ValidationError('there is no-one in the app, who has permissions to use this action', 'resource.get', undefined, ['controller', 'actions', 'onWhatever', 'resource']),
|
|
2002
|
-
]);
|
|
2003
|
-
});
|
|
2004
|
-
it('should throw if an app is null', async () => {
|
|
2005
|
-
const result = await validateAppDefinition(null, () => []);
|
|
2006
|
-
expect(result.valid).toBe(false);
|
|
2007
|
-
expect(result.errors).toStrictEqual([
|
|
2008
|
-
expect.objectContaining({
|
|
2009
|
-
message: 'App definition can not be null',
|
|
2010
|
-
instance: null,
|
|
2011
|
-
schema: {},
|
|
2012
|
-
}),
|
|
2013
|
-
]);
|
|
2014
|
-
});
|
|
2015
|
-
it('should report an error if the defaultPage does not exist', async () => {
|
|
2016
|
-
const result = await validateAppDefinition({ name: 'Test App', pages: [], defaultPage: 'Test Page' }, () => []);
|
|
2017
|
-
expect(result.valid).toBe(false);
|
|
2018
|
-
expect(result.errors).toStrictEqual([
|
|
2019
|
-
expect.objectContaining({
|
|
2020
|
-
instance: 'Test Page',
|
|
2021
|
-
message: 'does not refer to an existing page',
|
|
2022
|
-
}),
|
|
2023
|
-
]);
|
|
2024
|
-
});
|
|
2025
|
-
it('should handle if an unexpected error occurs', async () => {
|
|
2026
|
-
const result = await validateAppDefinition({
|
|
2027
|
-
get defaultPage() {
|
|
2028
|
-
throw new Error('Boom!');
|
|
2029
|
-
},
|
|
2030
|
-
pages: [],
|
|
2031
|
-
}, () => []);
|
|
2032
|
-
expect(result.valid).toBe(false);
|
|
2033
|
-
expect(result.errors).toStrictEqual([
|
|
2034
|
-
new ValidationError('Unexpected error: Boom!', null, undefined, []),
|
|
2035
|
-
]);
|
|
2036
|
-
});
|
|
2037
|
-
it('should prevent block with layout float to be used in a dialog action', async () => {
|
|
2038
|
-
const app = createTestApp();
|
|
2039
|
-
app.pages[0].blocks.push({
|
|
2040
|
-
type: 'test',
|
|
2041
|
-
version: '1.2.3',
|
|
2042
|
-
actions: {
|
|
2043
|
-
onClick: {
|
|
2044
|
-
type: 'dialog',
|
|
2045
|
-
blocks: [
|
|
2046
|
-
{
|
|
2047
|
-
type: 'test',
|
|
2048
|
-
version: '1.2.3',
|
|
2049
|
-
},
|
|
2050
|
-
],
|
|
2051
|
-
},
|
|
2052
|
-
},
|
|
2053
|
-
});
|
|
2054
|
-
const result = await validateAppDefinition(app, () => [
|
|
2055
|
-
{
|
|
2056
|
-
name: '@appsemble/test',
|
|
2057
|
-
version: '1.2.3',
|
|
2058
|
-
files: [],
|
|
2059
|
-
languages: [],
|
|
2060
|
-
layout: 'float',
|
|
2061
|
-
actions: {
|
|
2062
|
-
onClick: {},
|
|
2063
|
-
},
|
|
2064
|
-
},
|
|
2065
|
-
]);
|
|
2066
|
-
expect(result.valid).toBe(false);
|
|
2067
|
-
expect(result.errors).toStrictEqual([
|
|
2068
|
-
new ValidationError('block with layout type: "float" is not allowed in a dialog action', '1.2.3', undefined, ['pages', 0, 'blocks', 0, 'actions', 'onClick', 'type']),
|
|
2069
|
-
]);
|
|
2070
|
-
});
|
|
2071
|
-
it('should check app definition for blocks that have their layout manually set to float', async () => {
|
|
2072
|
-
const app = createTestApp();
|
|
2073
|
-
app.pages[0].blocks.push({
|
|
2074
|
-
type: 'test',
|
|
2075
|
-
version: '1.2.3',
|
|
2076
|
-
actions: {
|
|
2077
|
-
onClick: {
|
|
2078
|
-
type: 'dialog',
|
|
2079
|
-
blocks: [
|
|
2080
|
-
{
|
|
2081
|
-
type: 'test',
|
|
2082
|
-
version: '1.2.3',
|
|
2083
|
-
layout: 'float',
|
|
2084
|
-
},
|
|
2085
|
-
],
|
|
2086
|
-
},
|
|
2087
|
-
},
|
|
2088
|
-
});
|
|
2089
|
-
const result = await validateAppDefinition(app, () => [
|
|
2090
|
-
{
|
|
2091
|
-
name: '@appsemble/test',
|
|
2092
|
-
version: '1.2.3',
|
|
2093
|
-
files: [],
|
|
2094
|
-
languages: [],
|
|
2095
|
-
layout: 'hidden',
|
|
2096
|
-
actions: {
|
|
2097
|
-
onClick: {},
|
|
2098
|
-
},
|
|
2099
|
-
},
|
|
2100
|
-
]);
|
|
2101
|
-
expect(result.valid).toBe(false);
|
|
2102
|
-
expect(result.errors).toStrictEqual([
|
|
2103
|
-
new ValidationError('block with layout type: "float" is not allowed in a dialog action', {
|
|
2104
|
-
layout: 'float',
|
|
2105
|
-
type: 'test',
|
|
2106
|
-
version: '1.2.3',
|
|
2107
|
-
}, undefined, ['pages', 0, 'blocks', 0, 'actions', 'onClick', 'type']),
|
|
2108
|
-
]);
|
|
2109
|
-
});
|
|
2110
|
-
it('should validate security definition', async () => {
|
|
2111
|
-
const { security, ...app } = createTestApp();
|
|
2112
|
-
app.security = {};
|
|
2113
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2114
|
-
expect(result.valid).toBe(false);
|
|
2115
|
-
expect(result.errors).toStrictEqual([
|
|
2116
|
-
new ValidationError('invalid security definition. Must define either guest or roles and default', app, undefined, ['security']),
|
|
2117
|
-
]);
|
|
2118
|
-
});
|
|
2119
|
-
it('should report an error on duplicate guest permissions', async () => {
|
|
2120
|
-
const app = createTestApp();
|
|
2121
|
-
app.security = {
|
|
2122
|
-
guest: {
|
|
2123
|
-
permissions: ['$group:query', '$group:query'],
|
|
2124
|
-
},
|
|
2125
|
-
};
|
|
2126
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2127
|
-
expect(result.valid).toBe(false);
|
|
2128
|
-
expect(result.errors).toStrictEqual([
|
|
2129
|
-
new ValidationError('duplicate permission declaration', app, undefined, [
|
|
2130
|
-
'security',
|
|
2131
|
-
'guest',
|
|
2132
|
-
'permissions',
|
|
2133
|
-
1,
|
|
2134
|
-
]),
|
|
2135
|
-
]);
|
|
2136
|
-
});
|
|
2137
|
-
it('should report an error when a guest resource permission references a non existing resource', async () => {
|
|
2138
|
-
const app = createTestApp();
|
|
2139
|
-
app.security = {
|
|
2140
|
-
guest: {
|
|
2141
|
-
permissions: ['$resource:unknown:query'],
|
|
2142
|
-
},
|
|
2143
|
-
};
|
|
2144
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2145
|
-
expect(result.valid).toBe(false);
|
|
2146
|
-
expect(result.errors).toStrictEqual([
|
|
2147
|
-
new ValidationError("resource unknown does not exist in the app's resources definition", app, undefined, ['security', 'guest', 'permissions', 0]),
|
|
2148
|
-
]);
|
|
2149
|
-
});
|
|
2150
|
-
it('should report an error when a guest resource permission for a specific resource is declared and there is already a resource permission with scope all declared', async () => {
|
|
2151
|
-
const app = createTestApp();
|
|
2152
|
-
app.security = {
|
|
2153
|
-
guest: {
|
|
2154
|
-
permissions: ['$resource:person:query', '$resource:all:query'],
|
|
2155
|
-
},
|
|
2156
|
-
};
|
|
2157
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2158
|
-
expect(result.valid).toBe(false);
|
|
2159
|
-
expect(result.errors).toStrictEqual([
|
|
2160
|
-
new ValidationError('redundant permission. A permission for the query resource action with scope all is already declared', app, undefined, ['security', 'guest', 'permissions', 0]),
|
|
2161
|
-
]);
|
|
2162
|
-
});
|
|
2163
|
-
it('should report an error when a guest resource view permission with scope all is declared and there is a resource that does not define the view', async () => {
|
|
2164
|
-
const app = createTestApp();
|
|
2165
|
-
app.security = {
|
|
2166
|
-
guest: {
|
|
2167
|
-
permissions: ['$resource:all:query:public'],
|
|
2168
|
-
},
|
|
2169
|
-
};
|
|
2170
|
-
app.resources = {
|
|
2171
|
-
person: {
|
|
2172
|
-
schema: {
|
|
2173
|
-
type: 'object',
|
|
2174
|
-
properties: {
|
|
2175
|
-
name: { type: 'string' },
|
|
2176
|
-
},
|
|
2177
|
-
},
|
|
2178
|
-
views: {
|
|
2179
|
-
public: {
|
|
2180
|
-
remap: 'log.info',
|
|
2181
|
-
},
|
|
2182
|
-
},
|
|
2183
|
-
},
|
|
2184
|
-
note: {
|
|
2185
|
-
schema: {
|
|
2186
|
-
type: 'object',
|
|
2187
|
-
properties: {
|
|
2188
|
-
name: { type: 'string' },
|
|
2189
|
-
},
|
|
2190
|
-
},
|
|
2191
|
-
},
|
|
2192
|
-
};
|
|
2193
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2194
|
-
expect(result.valid).toBe(false);
|
|
2195
|
-
expect(result.errors).toStrictEqual([
|
|
2196
|
-
new ValidationError('resource note is missing a definition for the public view', app, undefined, ['security', 'guest', 'permissions', 0]),
|
|
2197
|
-
]);
|
|
2198
|
-
});
|
|
2199
|
-
it('should report an error when a guest resource view permission is declared for a resource that does not define the view', async () => {
|
|
2200
|
-
const app = createTestApp();
|
|
2201
|
-
app.security = {
|
|
2202
|
-
guest: {
|
|
2203
|
-
permissions: ['$resource:person:query:public'],
|
|
2204
|
-
},
|
|
2205
|
-
};
|
|
2206
|
-
app.resources = {
|
|
2207
|
-
person: {
|
|
2208
|
-
schema: {
|
|
2209
|
-
type: 'object',
|
|
2210
|
-
properties: {
|
|
2211
|
-
name: { type: 'string' },
|
|
2212
|
-
},
|
|
2213
|
-
},
|
|
2214
|
-
},
|
|
2215
|
-
};
|
|
2216
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2217
|
-
expect(result.valid).toBe(false);
|
|
2218
|
-
expect(result.errors).toStrictEqual([
|
|
2219
|
-
new ValidationError('resource person is missing a definition for the public view', app, undefined, ['security', 'guest', 'permissions', 0]),
|
|
2220
|
-
]);
|
|
2221
|
-
});
|
|
2222
|
-
it('should report an error when a guest resource view permission redeclares a view permission for an already declared resource action view permission', async () => {
|
|
2223
|
-
const app = createTestApp();
|
|
2224
|
-
app.security = {
|
|
2225
|
-
guest: {
|
|
2226
|
-
permissions: ['$resource:person:query:public', '$resource:person:query:private'],
|
|
2227
|
-
},
|
|
2228
|
-
};
|
|
2229
|
-
app.resources = {
|
|
2230
|
-
person: {
|
|
2231
|
-
schema: {
|
|
2232
|
-
type: 'object',
|
|
2233
|
-
properties: {
|
|
2234
|
-
name: { type: 'string' },
|
|
2235
|
-
},
|
|
2236
|
-
},
|
|
2237
|
-
views: {
|
|
2238
|
-
public: {
|
|
2239
|
-
remap: 'log.info',
|
|
2240
|
-
},
|
|
2241
|
-
private: {
|
|
2242
|
-
remap: 'log.info',
|
|
2243
|
-
},
|
|
2244
|
-
},
|
|
2245
|
-
},
|
|
2246
|
-
};
|
|
2247
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2248
|
-
expect(result.valid).toBe(false);
|
|
2249
|
-
expect(result.errors).toStrictEqual([
|
|
2250
|
-
new ValidationError('a view permission for the query action on resource person is already declared', app, undefined, ['security', 'guest', 'permissions', 0]),
|
|
2251
|
-
]);
|
|
2252
|
-
});
|
|
2253
|
-
it('should report an error when a guest resource view permission redeclares a view permission for an already declared resource action view permission with scope all', async () => {
|
|
2254
|
-
const app = createTestApp();
|
|
2255
|
-
app.security = {
|
|
2256
|
-
guest: {
|
|
2257
|
-
permissions: ['$resource:person:query:public', '$resource:all:query:private'],
|
|
2258
|
-
},
|
|
2259
|
-
};
|
|
2260
|
-
app.resources = {
|
|
2261
|
-
person: {
|
|
2262
|
-
schema: {
|
|
2263
|
-
type: 'object',
|
|
2264
|
-
properties: {
|
|
2265
|
-
name: { type: 'string' },
|
|
2266
|
-
},
|
|
2267
|
-
},
|
|
2268
|
-
views: {
|
|
2269
|
-
public: {
|
|
2270
|
-
remap: 'log.info',
|
|
2271
|
-
},
|
|
2272
|
-
private: {
|
|
2273
|
-
remap: 'log.info',
|
|
2274
|
-
},
|
|
2275
|
-
},
|
|
2276
|
-
},
|
|
2277
|
-
};
|
|
2278
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2279
|
-
expect(result.valid).toBe(false);
|
|
2280
|
-
expect(result.errors).toStrictEqual([
|
|
2281
|
-
new ValidationError('a view permission for the query action with scope all is already declared', app, undefined, ['security', 'guest', 'permissions', 0]),
|
|
2282
|
-
]);
|
|
2283
|
-
});
|
|
2284
|
-
it('should report an error when a guest resource view permission is declared for a specific view and there is already a permission for the same resource action without a specific view', async () => {
|
|
2285
|
-
const app = createTestApp();
|
|
2286
|
-
app.security = {
|
|
2287
|
-
guest: {
|
|
2288
|
-
permissions: ['$resource:person:query:public', '$resource:person:query'],
|
|
2289
|
-
},
|
|
2290
|
-
};
|
|
2291
|
-
app.resources = {
|
|
2292
|
-
person: {
|
|
2293
|
-
schema: {
|
|
2294
|
-
type: 'object',
|
|
2295
|
-
properties: {
|
|
2296
|
-
name: { type: 'string' },
|
|
2297
|
-
},
|
|
2298
|
-
},
|
|
2299
|
-
views: {
|
|
2300
|
-
public: {
|
|
2301
|
-
remap: 'log.info',
|
|
2302
|
-
},
|
|
2303
|
-
},
|
|
2304
|
-
},
|
|
2305
|
-
};
|
|
2306
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2307
|
-
expect(result.valid).toBe(false);
|
|
2308
|
-
expect(result.errors).toStrictEqual([
|
|
2309
|
-
new ValidationError('redundant permission. A permission for the query action on resource person without a specific view is already declared', app, undefined, ['security', 'guest', 'permissions', 0]),
|
|
2310
|
-
]);
|
|
2311
|
-
});
|
|
2312
|
-
it('should report an error when a guest resource view permission is declared for a specific view and there is already a permission for the same resource action with scope all without a specific view', async () => {
|
|
2313
|
-
const app = createTestApp();
|
|
2314
|
-
app.security = {
|
|
2315
|
-
guest: {
|
|
2316
|
-
permissions: ['$resource:person:query:public', '$resource:all:query'],
|
|
2317
|
-
},
|
|
2318
|
-
};
|
|
2319
|
-
app.resources = {
|
|
2320
|
-
person: {
|
|
2321
|
-
schema: {
|
|
2322
|
-
type: 'object',
|
|
2323
|
-
properties: {
|
|
2324
|
-
name: { type: 'string' },
|
|
2325
|
-
},
|
|
2326
|
-
},
|
|
2327
|
-
views: {
|
|
2328
|
-
public: {
|
|
2329
|
-
remap: 'log.info',
|
|
2330
|
-
},
|
|
2331
|
-
},
|
|
2332
|
-
},
|
|
2333
|
-
};
|
|
2334
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2335
|
-
expect(result.valid).toBe(false);
|
|
2336
|
-
expect(result.errors).toStrictEqual([
|
|
2337
|
-
new ValidationError('redundant permission. A permission for the query resource action with scope all without a specific view is already declared', app, undefined, ['security', 'guest', 'permissions', 0]),
|
|
2338
|
-
]);
|
|
2339
|
-
});
|
|
2340
|
-
it('should report an error when a guest resource view permission is declared for a specific view and there is already a permission for the same resource action with scope all with the same view', async () => {
|
|
2341
|
-
const app = createTestApp();
|
|
2342
|
-
app.security = {
|
|
2343
|
-
guest: {
|
|
2344
|
-
permissions: ['$resource:person:query:public', '$resource:all:query:public'],
|
|
2345
|
-
},
|
|
2346
|
-
};
|
|
2347
|
-
app.resources = {
|
|
2348
|
-
person: {
|
|
2349
|
-
schema: {
|
|
2350
|
-
type: 'object',
|
|
2351
|
-
properties: {
|
|
2352
|
-
name: { type: 'string' },
|
|
2353
|
-
},
|
|
2354
|
-
},
|
|
2355
|
-
views: {
|
|
2356
|
-
public: {
|
|
2357
|
-
remap: 'log.info',
|
|
2358
|
-
},
|
|
2359
|
-
},
|
|
2360
|
-
},
|
|
2361
|
-
};
|
|
2362
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2363
|
-
expect(result.valid).toBe(false);
|
|
2364
|
-
expect(result.errors).toStrictEqual([
|
|
2365
|
-
new ValidationError('redundant permission. A permission for the query resource action with scope all for this view is already declared', app, undefined, ['security', 'guest', 'permissions', 0]),
|
|
2366
|
-
]);
|
|
2367
|
-
});
|
|
2368
|
-
it('should report an error on duplicate role permissions', async () => {
|
|
2369
|
-
const app = createTestApp();
|
|
2370
|
-
app.security = {
|
|
2371
|
-
default: {
|
|
2372
|
-
role: 'test',
|
|
2373
|
-
},
|
|
2374
|
-
roles: {
|
|
2375
|
-
test: {
|
|
2376
|
-
permissions: ['$group:query', '$group:query'],
|
|
2377
|
-
},
|
|
2378
|
-
},
|
|
2379
|
-
};
|
|
2380
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2381
|
-
expect(result.valid).toBe(false);
|
|
2382
|
-
expect(result.errors).toStrictEqual([
|
|
2383
|
-
new ValidationError('duplicate permission declaration', app, undefined, [
|
|
2384
|
-
'security',
|
|
2385
|
-
'roles',
|
|
2386
|
-
'test',
|
|
2387
|
-
'permissions',
|
|
2388
|
-
1,
|
|
2389
|
-
]),
|
|
2390
|
-
]);
|
|
2391
|
-
});
|
|
2392
|
-
it('should report an error if a role redeclares an inherited permission', async () => {
|
|
2393
|
-
const app = createTestApp();
|
|
2394
|
-
app.security = {
|
|
2395
|
-
default: {
|
|
2396
|
-
role: 'test',
|
|
2397
|
-
},
|
|
2398
|
-
roles: {
|
|
2399
|
-
inherited: {
|
|
2400
|
-
permissions: ['$group:query'],
|
|
2401
|
-
},
|
|
2402
|
-
test: {
|
|
2403
|
-
permissions: ['$group:query'],
|
|
2404
|
-
inherits: ['inherited'],
|
|
2405
|
-
},
|
|
2406
|
-
},
|
|
2407
|
-
};
|
|
2408
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2409
|
-
expect(result.valid).toBe(false);
|
|
2410
|
-
expect(result.errors).toStrictEqual([
|
|
2411
|
-
new ValidationError('permission is already inherited from another role', app, undefined, [
|
|
2412
|
-
'security',
|
|
2413
|
-
'roles',
|
|
2414
|
-
'test',
|
|
2415
|
-
'permissions',
|
|
2416
|
-
0,
|
|
2417
|
-
]),
|
|
2418
|
-
]);
|
|
2419
|
-
});
|
|
2420
|
-
it('should report an error when a role resource permission references a non existing resource', async () => {
|
|
2421
|
-
const app = createTestApp();
|
|
2422
|
-
app.security = {
|
|
2423
|
-
default: {
|
|
2424
|
-
role: 'test',
|
|
2425
|
-
},
|
|
2426
|
-
roles: {
|
|
2427
|
-
test: {
|
|
2428
|
-
permissions: ['$resource:unknown:query'],
|
|
2429
|
-
},
|
|
2430
|
-
},
|
|
2431
|
-
};
|
|
2432
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2433
|
-
expect(result.valid).toBe(false);
|
|
2434
|
-
expect(result.errors).toStrictEqual([
|
|
2435
|
-
new ValidationError("resource unknown does not exist in the app's resources definition", app, undefined, ['security', 'roles', 'test', 'permissions', 0]),
|
|
2436
|
-
]);
|
|
2437
|
-
});
|
|
2438
|
-
it('should report an error when a role resource permission for a specific resource is declared and there is already a resource permission with scope all declared', async () => {
|
|
2439
|
-
const app = createTestApp();
|
|
2440
|
-
app.security = {
|
|
2441
|
-
default: {
|
|
2442
|
-
role: 'test',
|
|
2443
|
-
},
|
|
2444
|
-
roles: {
|
|
2445
|
-
test: {
|
|
2446
|
-
permissions: ['$resource:person:query', '$resource:all:query'],
|
|
2447
|
-
},
|
|
2448
|
-
},
|
|
2449
|
-
};
|
|
2450
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2451
|
-
expect(result.valid).toBe(false);
|
|
2452
|
-
expect(result.errors).toStrictEqual([
|
|
2453
|
-
new ValidationError('redundant permission. A permission for the query resource action with scope all is already declared', app, undefined, ['security', 'roles', 'test', 'permissions', 0]),
|
|
2454
|
-
]);
|
|
2455
|
-
});
|
|
2456
|
-
it('should report an error when a role resource permission for a specific resource is declared and there is already an inherited resource permission with scope all', async () => {
|
|
2457
|
-
const app = createTestApp();
|
|
2458
|
-
app.security = {
|
|
2459
|
-
default: {
|
|
2460
|
-
role: 'test',
|
|
2461
|
-
},
|
|
2462
|
-
roles: {
|
|
2463
|
-
inherited: {
|
|
2464
|
-
permissions: ['$resource:all:query'],
|
|
2465
|
-
},
|
|
2466
|
-
test: {
|
|
2467
|
-
permissions: ['$resource:person:query'],
|
|
2468
|
-
inherits: ['inherited'],
|
|
2469
|
-
},
|
|
2470
|
-
},
|
|
2471
|
-
};
|
|
2472
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2473
|
-
expect(result.valid).toBe(false);
|
|
2474
|
-
expect(result.errors).toStrictEqual([
|
|
2475
|
-
new ValidationError('redundant permission. A permission for the query resource action with scope all is already inherited from another role', app, undefined, ['security', 'roles', 'test', 'permissions', 0]),
|
|
2476
|
-
]);
|
|
2477
|
-
});
|
|
2478
|
-
it('should report an error when a role resource view permission with scope all is declared and there is a resource that does not define the view', async () => {
|
|
2479
|
-
const app = createTestApp();
|
|
2480
|
-
app.security = {
|
|
2481
|
-
default: {
|
|
2482
|
-
role: 'test',
|
|
2483
|
-
},
|
|
2484
|
-
roles: {
|
|
2485
|
-
test: {
|
|
2486
|
-
permissions: ['$resource:all:query:public'],
|
|
2487
|
-
},
|
|
2488
|
-
},
|
|
2489
|
-
};
|
|
2490
|
-
app.resources = {
|
|
2491
|
-
person: {
|
|
2492
|
-
schema: {
|
|
2493
|
-
type: 'object',
|
|
2494
|
-
properties: {
|
|
2495
|
-
name: { type: 'string' },
|
|
2496
|
-
},
|
|
2497
|
-
},
|
|
2498
|
-
},
|
|
2499
|
-
};
|
|
2500
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2501
|
-
expect(result.valid).toBe(false);
|
|
2502
|
-
expect(result.errors).toStrictEqual([
|
|
2503
|
-
new ValidationError('resource person is missing a definition for the public view', app, undefined, ['security', 'roles', 'test', 'permissions', 0]),
|
|
2504
|
-
]);
|
|
2505
|
-
});
|
|
2506
|
-
it('should report an error when a role resource view permission is declared for a resource that does not define the view', async () => {
|
|
2507
|
-
const app = createTestApp();
|
|
2508
|
-
app.security = {
|
|
2509
|
-
default: {
|
|
2510
|
-
role: 'test',
|
|
2511
|
-
},
|
|
2512
|
-
roles: {
|
|
2513
|
-
test: {
|
|
2514
|
-
permissions: ['$resource:person:query:public'],
|
|
2515
|
-
},
|
|
2516
|
-
},
|
|
2517
|
-
};
|
|
2518
|
-
app.resources = {
|
|
2519
|
-
person: {
|
|
2520
|
-
schema: {
|
|
2521
|
-
type: 'object',
|
|
2522
|
-
properties: {
|
|
2523
|
-
name: { type: 'string' },
|
|
2524
|
-
},
|
|
2525
|
-
},
|
|
2526
|
-
},
|
|
2527
|
-
};
|
|
2528
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2529
|
-
expect(result.valid).toBe(false);
|
|
2530
|
-
expect(result.errors).toStrictEqual([
|
|
2531
|
-
new ValidationError('resource person is missing a definition for the public view', app, undefined, ['security', 'roles', 'test', 'permissions', 0]),
|
|
2532
|
-
]);
|
|
2533
|
-
});
|
|
2534
|
-
it('should report an error when a role resource view permission redeclares a view permission for an already declared resource action view permission', async () => {
|
|
2535
|
-
const app = createTestApp();
|
|
2536
|
-
app.security = {
|
|
2537
|
-
default: {
|
|
2538
|
-
role: 'test',
|
|
2539
|
-
},
|
|
2540
|
-
roles: {
|
|
2541
|
-
test: {
|
|
2542
|
-
permissions: ['$resource:person:query:public', '$resource:person:query:private'],
|
|
2543
|
-
},
|
|
2544
|
-
},
|
|
2545
|
-
};
|
|
2546
|
-
app.resources = {
|
|
2547
|
-
person: {
|
|
2548
|
-
schema: {
|
|
2549
|
-
type: 'object',
|
|
2550
|
-
properties: {
|
|
2551
|
-
name: { type: 'string' },
|
|
2552
|
-
},
|
|
2553
|
-
},
|
|
2554
|
-
views: {
|
|
2555
|
-
public: {
|
|
2556
|
-
remap: 'log.info',
|
|
2557
|
-
},
|
|
2558
|
-
private: {
|
|
2559
|
-
remap: 'log.info',
|
|
2560
|
-
},
|
|
2561
|
-
},
|
|
2562
|
-
},
|
|
2563
|
-
};
|
|
2564
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2565
|
-
expect(result.valid).toBe(false);
|
|
2566
|
-
expect(result.errors).toStrictEqual([
|
|
2567
|
-
new ValidationError('a view permission for the query action on resource person is already declared', app, undefined, ['security', 'roles', 'test', 'permissions', 0]),
|
|
2568
|
-
]);
|
|
2569
|
-
});
|
|
2570
|
-
it('should report an error when a role resource view permission redeclares a view permission for an already declared resource action view permission with scope all', async () => {
|
|
2571
|
-
const app = createTestApp();
|
|
2572
|
-
app.security = {
|
|
2573
|
-
default: {
|
|
2574
|
-
role: 'test',
|
|
2575
|
-
},
|
|
2576
|
-
roles: {
|
|
2577
|
-
test: {
|
|
2578
|
-
permissions: ['$resource:person:query:public', '$resource:all:query:private'],
|
|
2579
|
-
},
|
|
2580
|
-
},
|
|
2581
|
-
};
|
|
2582
|
-
app.resources = {
|
|
2583
|
-
person: {
|
|
2584
|
-
schema: {
|
|
2585
|
-
type: 'object',
|
|
2586
|
-
properties: {
|
|
2587
|
-
name: { type: 'string' },
|
|
2588
|
-
},
|
|
2589
|
-
},
|
|
2590
|
-
views: {
|
|
2591
|
-
public: {
|
|
2592
|
-
remap: 'log.info',
|
|
2593
|
-
},
|
|
2594
|
-
private: {
|
|
2595
|
-
remap: 'log.info',
|
|
2596
|
-
},
|
|
2597
|
-
},
|
|
2598
|
-
},
|
|
2599
|
-
};
|
|
2600
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2601
|
-
expect(result.valid).toBe(false);
|
|
2602
|
-
expect(result.errors).toStrictEqual([
|
|
2603
|
-
new ValidationError('a view permission for the query action with scope all is already declared', app, undefined, ['security', 'roles', 'test', 'permissions', 0]),
|
|
2604
|
-
]);
|
|
2605
|
-
});
|
|
2606
|
-
it('should report an error when a role resource view permission is declared for a specific view and there is already a permission for the same resource action without a specific view', async () => {
|
|
2607
|
-
const app = createTestApp();
|
|
2608
|
-
app.security = {
|
|
2609
|
-
default: {
|
|
2610
|
-
role: 'test',
|
|
2611
|
-
},
|
|
2612
|
-
roles: {
|
|
2613
|
-
test: {
|
|
2614
|
-
permissions: ['$resource:person:query:public', '$resource:person:query'],
|
|
2615
|
-
},
|
|
2616
|
-
},
|
|
2617
|
-
};
|
|
2618
|
-
app.resources = {
|
|
2619
|
-
person: {
|
|
2620
|
-
schema: {
|
|
2621
|
-
type: 'object',
|
|
2622
|
-
properties: {
|
|
2623
|
-
name: { type: 'string' },
|
|
2624
|
-
},
|
|
2625
|
-
},
|
|
2626
|
-
views: {
|
|
2627
|
-
public: {
|
|
2628
|
-
remap: 'log.info',
|
|
2629
|
-
},
|
|
2630
|
-
},
|
|
2631
|
-
},
|
|
2632
|
-
};
|
|
2633
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2634
|
-
expect(result.valid).toBe(false);
|
|
2635
|
-
expect(result.errors).toStrictEqual([
|
|
2636
|
-
new ValidationError('redundant permission. A permission for the query action on resource person without a specific view is already declared', app, undefined, ['security', 'roles', 'test', 'permissions', 0]),
|
|
2637
|
-
]);
|
|
2638
|
-
});
|
|
2639
|
-
it('should report an error when a role resource view permission is declared for a specific view and there is already a permission for the same resource action with scope all without a specific view', async () => {
|
|
2640
|
-
const app = createTestApp();
|
|
2641
|
-
app.security = {
|
|
2642
|
-
default: {
|
|
2643
|
-
role: 'test',
|
|
2644
|
-
},
|
|
2645
|
-
roles: {
|
|
2646
|
-
test: {
|
|
2647
|
-
permissions: ['$resource:person:query:public', '$resource:all:query'],
|
|
2648
|
-
},
|
|
2649
|
-
},
|
|
2650
|
-
};
|
|
2651
|
-
app.resources = {
|
|
2652
|
-
person: {
|
|
2653
|
-
schema: {
|
|
2654
|
-
type: 'object',
|
|
2655
|
-
properties: {
|
|
2656
|
-
name: { type: 'string' },
|
|
2657
|
-
},
|
|
2658
|
-
},
|
|
2659
|
-
views: {
|
|
2660
|
-
public: {
|
|
2661
|
-
remap: 'log.info',
|
|
2662
|
-
},
|
|
2663
|
-
},
|
|
2664
|
-
},
|
|
2665
|
-
};
|
|
2666
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2667
|
-
expect(result.valid).toBe(false);
|
|
2668
|
-
expect(result.errors).toStrictEqual([
|
|
2669
|
-
new ValidationError('redundant permission. A permission for the query resource action with scope all without a specific view is already declared', app, undefined, ['security', 'roles', 'test', 'permissions', 0]),
|
|
2670
|
-
]);
|
|
2671
|
-
});
|
|
2672
|
-
it('should report an error when a role resource view permission is declared for a specific view and there is already a permission for the same resource action with scope all with the same view', async () => {
|
|
2673
|
-
const app = createTestApp();
|
|
2674
|
-
app.security = {
|
|
2675
|
-
default: {
|
|
2676
|
-
role: 'test',
|
|
2677
|
-
},
|
|
2678
|
-
roles: {
|
|
2679
|
-
test: {
|
|
2680
|
-
permissions: ['$resource:person:query:public', '$resource:all:query:public'],
|
|
2681
|
-
},
|
|
2682
|
-
},
|
|
2683
|
-
};
|
|
2684
|
-
app.resources = {
|
|
2685
|
-
person: {
|
|
2686
|
-
schema: {
|
|
2687
|
-
type: 'object',
|
|
2688
|
-
properties: {
|
|
2689
|
-
name: { type: 'string' },
|
|
2690
|
-
},
|
|
2691
|
-
},
|
|
2692
|
-
views: {
|
|
2693
|
-
public: {
|
|
2694
|
-
remap: 'log.info',
|
|
2695
|
-
},
|
|
2696
|
-
},
|
|
2697
|
-
},
|
|
2698
|
-
};
|
|
2699
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2700
|
-
expect(result.valid).toBe(false);
|
|
2701
|
-
expect(result.errors).toStrictEqual([
|
|
2702
|
-
new ValidationError('redundant permission. A permission for the query resource action with scope all for this view is already declared', app, undefined, ['security', 'roles', 'test', 'permissions', 0]),
|
|
2703
|
-
]);
|
|
2704
|
-
});
|
|
2705
|
-
it('should report an error when a role resource view permission redeclares a view permission for an already inherited resource action view permission', async () => {
|
|
2706
|
-
const app = createTestApp();
|
|
2707
|
-
app.security = {
|
|
2708
|
-
default: {
|
|
2709
|
-
role: 'test',
|
|
2710
|
-
},
|
|
2711
|
-
roles: {
|
|
2712
|
-
inherited: {
|
|
2713
|
-
permissions: ['$resource:person:query:private'],
|
|
2714
|
-
},
|
|
2715
|
-
test: {
|
|
2716
|
-
permissions: ['$resource:person:query:public'],
|
|
2717
|
-
inherits: ['inherited'],
|
|
2718
|
-
},
|
|
2719
|
-
},
|
|
2720
|
-
};
|
|
2721
|
-
app.resources = {
|
|
2722
|
-
person: {
|
|
2723
|
-
schema: {
|
|
2724
|
-
type: 'object',
|
|
2725
|
-
properties: {
|
|
2726
|
-
name: { type: 'string' },
|
|
2727
|
-
},
|
|
2728
|
-
},
|
|
2729
|
-
views: {
|
|
2730
|
-
public: {
|
|
2731
|
-
remap: 'log.info',
|
|
2732
|
-
},
|
|
2733
|
-
private: {
|
|
2734
|
-
remap: 'log.info',
|
|
2735
|
-
},
|
|
2736
|
-
},
|
|
2737
|
-
},
|
|
2738
|
-
};
|
|
2739
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2740
|
-
expect(result.valid).toBe(false);
|
|
2741
|
-
expect(result.errors).toStrictEqual([
|
|
2742
|
-
new ValidationError('a view permission for the query action on resource person is already inherited from another role', app, undefined, ['security', 'roles', 'test', 'permissions', 0]),
|
|
2743
|
-
]);
|
|
2744
|
-
});
|
|
2745
|
-
it('should report an error when a role resource view permission redeclares a view permission for an already inherited resource action view permission with scope all', async () => {
|
|
2746
|
-
const app = createTestApp();
|
|
2747
|
-
app.security = {
|
|
2748
|
-
default: {
|
|
2749
|
-
role: 'test',
|
|
2750
|
-
},
|
|
2751
|
-
roles: {
|
|
2752
|
-
inherited: {
|
|
2753
|
-
permissions: ['$resource:all:query:private'],
|
|
2754
|
-
},
|
|
2755
|
-
test: {
|
|
2756
|
-
permissions: ['$resource:person:query:public'],
|
|
2757
|
-
inherits: ['inherited'],
|
|
2758
|
-
},
|
|
2759
|
-
},
|
|
2760
|
-
};
|
|
2761
|
-
app.resources = {
|
|
2762
|
-
person: {
|
|
2763
|
-
schema: {
|
|
2764
|
-
type: 'object',
|
|
2765
|
-
properties: {
|
|
2766
|
-
name: { type: 'string' },
|
|
2767
|
-
},
|
|
2768
|
-
},
|
|
2769
|
-
views: {
|
|
2770
|
-
public: {
|
|
2771
|
-
remap: 'log.info',
|
|
2772
|
-
},
|
|
2773
|
-
private: {
|
|
2774
|
-
remap: 'log.info',
|
|
2775
|
-
},
|
|
2776
|
-
},
|
|
2777
|
-
},
|
|
2778
|
-
};
|
|
2779
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2780
|
-
expect(result.valid).toBe(false);
|
|
2781
|
-
expect(result.errors).toStrictEqual([
|
|
2782
|
-
new ValidationError('a view permission for the query action with scope all is already inherited from another role', app, undefined, ['security', 'roles', 'test', 'permissions', 0]),
|
|
2783
|
-
]);
|
|
2784
|
-
});
|
|
2785
|
-
it('should report an error when a role resource view permission is declared for a specific view and there is already an inherited permission for the same resource action without a specific view', async () => {
|
|
2786
|
-
const app = createTestApp();
|
|
2787
|
-
app.security = {
|
|
2788
|
-
default: {
|
|
2789
|
-
role: 'test',
|
|
2790
|
-
},
|
|
2791
|
-
roles: {
|
|
2792
|
-
inherited: {
|
|
2793
|
-
permissions: ['$resource:person:query'],
|
|
2794
|
-
},
|
|
2795
|
-
test: {
|
|
2796
|
-
permissions: ['$resource:person:query:public'],
|
|
2797
|
-
inherits: ['inherited'],
|
|
2798
|
-
},
|
|
2799
|
-
},
|
|
2800
|
-
};
|
|
2801
|
-
app.resources = {
|
|
2802
|
-
person: {
|
|
2803
|
-
schema: {
|
|
2804
|
-
type: 'object',
|
|
2805
|
-
properties: {
|
|
2806
|
-
name: { type: 'string' },
|
|
2807
|
-
},
|
|
2808
|
-
},
|
|
2809
|
-
views: {
|
|
2810
|
-
public: {
|
|
2811
|
-
remap: 'log.info',
|
|
2812
|
-
},
|
|
2813
|
-
},
|
|
2814
|
-
},
|
|
2815
|
-
};
|
|
2816
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2817
|
-
expect(result.valid).toBe(false);
|
|
2818
|
-
expect(result.errors).toStrictEqual([
|
|
2819
|
-
new ValidationError('redundant permission. A permission for the query action on resource person without a specific view is already inherited from another role', app, undefined, ['security', 'roles', 'test', 'permissions', 0]),
|
|
2820
|
-
]);
|
|
2821
|
-
});
|
|
2822
|
-
it('should report an error when a role resource view permission is declared for a specific view and there is already an inherited permission for the same resource action with scope all without a specific view', async () => {
|
|
2823
|
-
const app = createTestApp();
|
|
2824
|
-
app.security = {
|
|
2825
|
-
default: {
|
|
2826
|
-
role: 'test',
|
|
2827
|
-
},
|
|
2828
|
-
roles: {
|
|
2829
|
-
inherited: {
|
|
2830
|
-
permissions: ['$resource:all:query'],
|
|
2831
|
-
},
|
|
2832
|
-
test: {
|
|
2833
|
-
permissions: ['$resource:person:query:public'],
|
|
2834
|
-
inherits: ['inherited'],
|
|
2835
|
-
},
|
|
2836
|
-
},
|
|
2837
|
-
};
|
|
2838
|
-
app.resources = {
|
|
2839
|
-
person: {
|
|
2840
|
-
schema: {
|
|
2841
|
-
type: 'object',
|
|
2842
|
-
properties: {
|
|
2843
|
-
name: { type: 'string' },
|
|
2844
|
-
},
|
|
2845
|
-
},
|
|
2846
|
-
views: {
|
|
2847
|
-
public: {
|
|
2848
|
-
remap: 'log.info',
|
|
2849
|
-
},
|
|
2850
|
-
},
|
|
2851
|
-
},
|
|
2852
|
-
};
|
|
2853
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2854
|
-
expect(result.valid).toBe(false);
|
|
2855
|
-
expect(result.errors).toStrictEqual([
|
|
2856
|
-
new ValidationError('redundant permission. A permission for the query resource action with scope all without a specific view is already inherited from another role', app, undefined, ['security', 'roles', 'test', 'permissions', 0]),
|
|
2857
|
-
]);
|
|
2858
|
-
});
|
|
2859
|
-
it('a should report an error when a role resource view permission is declared for a specific view and there is already a permission for the same resource action with scope all with the same view', async () => {
|
|
2860
|
-
const app = createTestApp();
|
|
2861
|
-
app.security = {
|
|
2862
|
-
default: {
|
|
2863
|
-
role: 'test',
|
|
2864
|
-
},
|
|
2865
|
-
roles: {
|
|
2866
|
-
inherited: {
|
|
2867
|
-
permissions: ['$resource:all:query:public'],
|
|
2868
|
-
},
|
|
2869
|
-
test: {
|
|
2870
|
-
permissions: ['$resource:person:query:public'],
|
|
2871
|
-
inherits: ['inherited'],
|
|
2872
|
-
},
|
|
2873
|
-
},
|
|
2874
|
-
};
|
|
2875
|
-
app.resources = {
|
|
2876
|
-
person: {
|
|
2877
|
-
schema: {
|
|
2878
|
-
type: 'object',
|
|
2879
|
-
properties: {
|
|
2880
|
-
name: { type: 'string' },
|
|
2881
|
-
},
|
|
2882
|
-
},
|
|
2883
|
-
views: {
|
|
2884
|
-
public: {
|
|
2885
|
-
remap: 'log.info',
|
|
2886
|
-
},
|
|
2887
|
-
},
|
|
2888
|
-
},
|
|
2889
|
-
};
|
|
2890
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2891
|
-
expect(result.valid).toBe(false);
|
|
2892
|
-
expect(result.errors).toStrictEqual([
|
|
2893
|
-
new ValidationError('redundant permission. A permission for the query resource action with scope all for this view is already inherited from another role', app, undefined, ['security', 'roles', 'test', 'permissions', 0]),
|
|
2894
|
-
]);
|
|
2895
|
-
});
|
|
2896
|
-
it('should report an error when an own resource permission references a resource that does not exist', async () => {
|
|
2897
|
-
const app = createTestApp();
|
|
2898
|
-
app.security = {
|
|
2899
|
-
default: {
|
|
2900
|
-
role: 'test',
|
|
2901
|
-
},
|
|
2902
|
-
roles: {
|
|
2903
|
-
test: {
|
|
2904
|
-
permissions: ['$resource:unknown:own:get'],
|
|
2905
|
-
},
|
|
2906
|
-
},
|
|
2907
|
-
};
|
|
2908
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2909
|
-
expect(result.valid).toBe(false);
|
|
2910
|
-
expect(result.errors).toStrictEqual([
|
|
2911
|
-
new ValidationError("resource unknown does not exist in the app's resources definition", app, undefined, ['security', 'roles', 'test', 'permissions', 0]),
|
|
2912
|
-
]);
|
|
2913
|
-
});
|
|
2914
|
-
it('should report an error when an own resource permission is declared and there is already a generic resource permission for that action on that resource', async () => {
|
|
2915
|
-
const app = createTestApp();
|
|
2916
|
-
app.security = {
|
|
2917
|
-
default: {
|
|
2918
|
-
role: 'test',
|
|
2919
|
-
},
|
|
2920
|
-
roles: {
|
|
2921
|
-
test: {
|
|
2922
|
-
permissions: ['$resource:person:own:get', '$resource:person:get'],
|
|
2923
|
-
},
|
|
2924
|
-
},
|
|
2925
|
-
};
|
|
2926
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2927
|
-
expect(result.valid).toBe(false);
|
|
2928
|
-
expect(result.errors).toStrictEqual([
|
|
2929
|
-
new ValidationError('redundant permission. A permission for the get resource action on resource person is already declared', app, undefined, ['security', 'roles', 'test', 'permissions', 0]),
|
|
2930
|
-
]);
|
|
2931
|
-
});
|
|
2932
|
-
it('should report an error when an own resource permission is declared and there is already a generic resource permission with scope all for that action', async () => {
|
|
2933
|
-
const app = createTestApp();
|
|
2934
|
-
app.security = {
|
|
2935
|
-
default: {
|
|
2936
|
-
role: 'test',
|
|
2937
|
-
},
|
|
2938
|
-
roles: {
|
|
2939
|
-
test: {
|
|
2940
|
-
permissions: ['$resource:person:own:get', '$resource:all:get'],
|
|
2941
|
-
},
|
|
2942
|
-
},
|
|
2943
|
-
};
|
|
2944
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2945
|
-
expect(result.valid).toBe(false);
|
|
2946
|
-
expect(result.errors).toStrictEqual([
|
|
2947
|
-
new ValidationError('redundant permission. A permission for the get resource action with scope all is already declared', app, undefined, ['security', 'roles', 'test', 'permissions', 0]),
|
|
2948
|
-
]);
|
|
2949
|
-
});
|
|
2950
|
-
it('should report an error when an own resource permission is declared and there is already an own resource permission with scope all for that action', async () => {
|
|
2951
|
-
const app = createTestApp();
|
|
2952
|
-
app.security = {
|
|
2953
|
-
default: {
|
|
2954
|
-
role: 'test',
|
|
2955
|
-
},
|
|
2956
|
-
roles: {
|
|
2957
|
-
test: {
|
|
2958
|
-
permissions: ['$resource:person:own:get', '$resource:all:own:get'],
|
|
2959
|
-
},
|
|
2960
|
-
},
|
|
2961
|
-
};
|
|
2962
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2963
|
-
expect(result.valid).toBe(false);
|
|
2964
|
-
expect(result.errors).toStrictEqual([
|
|
2965
|
-
new ValidationError('redundant permission. An own permission for the get resource action with scope all is already declared', app, undefined, ['security', 'roles', 'test', 'permissions', 0]),
|
|
2966
|
-
]);
|
|
2967
|
-
});
|
|
2968
|
-
it('should report an error when an own resource permission is declared and there is already an inherited generic resource permission for that action on that resource', async () => {
|
|
2969
|
-
const app = createTestApp();
|
|
2970
|
-
app.security = {
|
|
2971
|
-
default: {
|
|
2972
|
-
role: 'test',
|
|
2973
|
-
},
|
|
2974
|
-
roles: {
|
|
2975
|
-
inherited: {
|
|
2976
|
-
permissions: ['$resource:person:get'],
|
|
2977
|
-
},
|
|
2978
|
-
test: {
|
|
2979
|
-
permissions: ['$resource:person:own:get'],
|
|
2980
|
-
inherits: ['inherited'],
|
|
2981
|
-
},
|
|
2982
|
-
},
|
|
2983
|
-
};
|
|
2984
|
-
const result = await validateAppDefinition(app, () => []);
|
|
2985
|
-
expect(result.valid).toBe(false);
|
|
2986
|
-
expect(result.errors).toStrictEqual([
|
|
2987
|
-
new ValidationError('redundant permission. A permission for the get resource action on resource person is already inherited from another role', app, undefined, ['security', 'roles', 'test', 'permissions', 0]),
|
|
2988
|
-
]);
|
|
2989
|
-
});
|
|
2990
|
-
it('should report an error when an own resource permission is declared and there is already an inherited generic resource permission with scope all for that action', async () => {
|
|
2991
|
-
const app = createTestApp();
|
|
2992
|
-
app.security = {
|
|
2993
|
-
default: {
|
|
2994
|
-
role: 'test',
|
|
2995
|
-
},
|
|
2996
|
-
roles: {
|
|
2997
|
-
inherited: {
|
|
2998
|
-
permissions: ['$resource:all:get'],
|
|
2999
|
-
},
|
|
3000
|
-
test: {
|
|
3001
|
-
permissions: ['$resource:person:own:get'],
|
|
3002
|
-
inherits: ['inherited'],
|
|
3003
|
-
},
|
|
3004
|
-
},
|
|
3005
|
-
};
|
|
3006
|
-
const result = await validateAppDefinition(app, () => []);
|
|
3007
|
-
expect(result.valid).toBe(false);
|
|
3008
|
-
expect(result.errors).toStrictEqual([
|
|
3009
|
-
new ValidationError('redundant permission. A permission for the get resource action with scope all is already inherited from another role', app, undefined, ['security', 'roles', 'test', 'permissions', 0]),
|
|
3010
|
-
]);
|
|
3011
|
-
});
|
|
3012
|
-
it('should report an error when an own resource permission is declared and there is already an inherited own resource permission with scope all for that action', async () => {
|
|
3013
|
-
const app = createTestApp();
|
|
3014
|
-
app.security = {
|
|
3015
|
-
default: {
|
|
3016
|
-
role: 'test',
|
|
3017
|
-
},
|
|
3018
|
-
roles: {
|
|
3019
|
-
inherited: {
|
|
3020
|
-
permissions: ['$resource:all:own:get'],
|
|
3021
|
-
},
|
|
3022
|
-
test: {
|
|
3023
|
-
permissions: ['$resource:person:own:get'],
|
|
3024
|
-
inherits: ['inherited'],
|
|
3025
|
-
},
|
|
3026
|
-
},
|
|
3027
|
-
};
|
|
3028
|
-
const result = await validateAppDefinition(app, () => []);
|
|
3029
|
-
expect(result.valid).toBe(false);
|
|
3030
|
-
expect(result.errors).toStrictEqual([
|
|
3031
|
-
new ValidationError('redundant permission. An own permission for the get resource action with scope all is already inherited from another role', app, undefined, ['security', 'roles', 'test', 'permissions', 0]),
|
|
3032
|
-
]);
|
|
3033
|
-
});
|
|
3034
|
-
it('should not allow overwriting predefined roles', async () => {
|
|
3035
|
-
const app = createTestApp();
|
|
3036
|
-
app.security = {
|
|
3037
|
-
default: {
|
|
3038
|
-
role: 'Member',
|
|
3039
|
-
},
|
|
3040
|
-
roles: {
|
|
3041
|
-
Member: {},
|
|
3042
|
-
MembersManager: {},
|
|
3043
|
-
GroupMembersManager: {},
|
|
3044
|
-
GroupsManager: {},
|
|
3045
|
-
ResourcesManager: {},
|
|
3046
|
-
Owner: {},
|
|
3047
|
-
},
|
|
3048
|
-
};
|
|
3049
|
-
const result = await validateAppDefinition(app, () => []);
|
|
3050
|
-
expect(result.valid).toBe(false);
|
|
3051
|
-
expect(result.errors).toStrictEqual(predefinedAppRoles.map((role) => new ValidationError(`not allowed to overwrite role ${role}`, app, undefined, [
|
|
3052
|
-
'security',
|
|
3053
|
-
'roles',
|
|
3054
|
-
role,
|
|
3055
|
-
])));
|
|
3056
|
-
});
|
|
3057
|
-
it('should throw an error on invalid role permissions', async () => {
|
|
3058
|
-
const app = createTestApp();
|
|
3059
|
-
app.security = {
|
|
3060
|
-
default: {
|
|
3061
|
-
role: 'test',
|
|
3062
|
-
},
|
|
3063
|
-
roles: {
|
|
3064
|
-
test: {
|
|
3065
|
-
permissions: ['$resource:person:modify'],
|
|
3066
|
-
},
|
|
3067
|
-
},
|
|
3068
|
-
};
|
|
3069
|
-
const result = await validateAppDefinition(app, () => []);
|
|
3070
|
-
expect(result.valid).toBe(false);
|
|
3071
|
-
expect(result.errors).toStrictEqual([
|
|
3072
|
-
new ValidationError('invalid permission', app, undefined, [
|
|
3073
|
-
'security',
|
|
3074
|
-
'roles',
|
|
3075
|
-
'test',
|
|
3076
|
-
'permissions',
|
|
3077
|
-
0,
|
|
3078
|
-
]),
|
|
3079
|
-
]);
|
|
3080
|
-
});
|
|
3081
|
-
it('should throw an error on invalid guest permissions', async () => {
|
|
3082
|
-
const app = createTestApp();
|
|
3083
|
-
app.security = {
|
|
3084
|
-
guest: {
|
|
3085
|
-
permissions: ['$resource:person:own:modify'],
|
|
3086
|
-
},
|
|
3087
|
-
};
|
|
3088
|
-
const result = await validateAppDefinition(app, () => []);
|
|
3089
|
-
expect(result.valid).toBe(false);
|
|
3090
|
-
expect(result.errors).toStrictEqual([
|
|
3091
|
-
new ValidationError('invalid permission', app, undefined, [
|
|
3092
|
-
'security',
|
|
3093
|
-
'guest',
|
|
3094
|
-
'permissions',
|
|
3095
|
-
0,
|
|
3096
|
-
]),
|
|
3097
|
-
]);
|
|
3098
|
-
});
|
|
3099
|
-
it('should throw an error on invalid guest inherited permissions', async () => {
|
|
3100
|
-
const app = createTestApp();
|
|
3101
|
-
app.security = {
|
|
3102
|
-
guest: {
|
|
3103
|
-
inherits: ['test'],
|
|
3104
|
-
},
|
|
3105
|
-
default: {
|
|
3106
|
-
role: 'test',
|
|
3107
|
-
},
|
|
3108
|
-
roles: {
|
|
3109
|
-
test: {
|
|
3110
|
-
permissions: ['$resource:person:own:get'],
|
|
3111
|
-
},
|
|
3112
|
-
},
|
|
3113
|
-
};
|
|
3114
|
-
const result = await validateAppDefinition(app, () => []);
|
|
3115
|
-
expect(result.valid).toBe(false);
|
|
3116
|
-
expect(result.errors).toStrictEqual([
|
|
3117
|
-
new ValidationError('invalid security definition. Guest cannot inherit roles that contain own resource permissions', app, undefined, ['security', 'guest', 'inherits']),
|
|
3118
|
-
]);
|
|
3119
|
-
});
|
|
3120
|
-
});
|
|
3121
|
-
//# sourceMappingURL=validation.test.js.map
|