@ditojs/server 3.0.0 → 3.1.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ditojs/server",
3
- "version": "3.0.0",
3
+ "version": "3.1.1",
4
4
  "type": "module",
5
5
  "description": "Dito.js Server – Dito.js is a declarative and modern web framework, based on Objection.js, Koa.js and Vue.js",
6
6
  "repository": "https://github.com/ditojs/dito/tree/main/packages/server",
@@ -10,7 +10,9 @@
10
10
  "types": "./types/index.d.ts",
11
11
  "files": [
12
12
  "src/",
13
- "types/"
13
+ "types/",
14
+ "!src/**/*.test.js",
15
+ "!src/**/__snapshots__/"
14
16
  ],
15
17
  "bin": {
16
18
  "dito": "./src/cli/index.js"
@@ -22,10 +24,10 @@
22
24
  "node >= 18"
23
25
  ],
24
26
  "dependencies": {
25
- "@ditojs/admin": "^3.0.0",
26
- "@ditojs/build": "^3.0.0",
27
- "@ditojs/router": "^3.0.0",
28
- "@ditojs/utils": "^3.0.0",
27
+ "@ditojs/admin": "^3.1.1",
28
+ "@ditojs/build": "^3.1.1",
29
+ "@ditojs/router": "^3.1.1",
30
+ "@ditojs/utils": "^3.1.1",
29
31
  "@koa/cors": "^5.0.0",
30
32
  "@koa/etag": "^5.0.2",
31
33
  "@koa/multer": "^4.0.0",
@@ -87,5 +89,5 @@
87
89
  "objection": "^3.1.5",
88
90
  "typescript": "^6.0.3"
89
91
  },
90
- "gitHead": "399f683a6ca227a03cfc0978fee9055383e6b3d8"
92
+ "gitHead": "acce85bb38d817aab18f9d5d03b3aa9dff00c3f9"
91
93
  }
@@ -1,180 +0,0 @@
1
- import { Model } from './Model.js'
2
-
3
- function makeFakeModel(assets) {
4
- const storage = {
5
- signAssetFile(file) {
6
- file.signature = `sig-of-${file.key}`
7
- }
8
- }
9
- return {
10
- app: {
11
- getStorage(name) {
12
- return name === 'test' ? storage : null
13
- }
14
- },
15
- definition: { assets }
16
- }
17
- }
18
-
19
- function signCallback(file, storage) {
20
- const signed = { ...file }
21
- storage.signAssetFile(signed)
22
- return signed
23
- }
24
-
25
- describe('Model._mapAssetFiles: asset signing across data paths', () => {
26
- it('writes the signature back to a literal data path', () => {
27
- const model = makeFakeModel({ 'image.file': { storage: 'test' } })
28
- const json = { image: { file: { key: 'foo.png' } } }
29
- Model._mapAssetFiles.call(model, json, signCallback)
30
- expect(json.image.file.signature).toBe('sig-of-foo.png')
31
- })
32
-
33
- it('writes the signature back to every match of a deep wildcard', () => {
34
- const model = makeFakeModel({ '**.file': { storage: 'test' } })
35
- const json = {
36
- content: {
37
- file: { key: 'a.png' },
38
- sections: [
39
- { file: { key: 'b.png' } },
40
- { file: { key: 'c.png' } }
41
- ]
42
- }
43
- }
44
- Model._mapAssetFiles.call(model, json, signCallback)
45
- expect(json.content.file.signature).toBe('sig-of-a.png')
46
- expect(json.content.sections[0].file.signature).toBe('sig-of-b.png')
47
- expect(json.content.sections[1].file.signature).toBe('sig-of-c.png')
48
- })
49
-
50
- it('writes the signature back into wildcard array values', () => {
51
- const model = makeFakeModel({ '**.files': { storage: 'test' } })
52
- const json = {
53
- content: {
54
- files: [{ key: 'x.png' }, { key: 'y.png' }]
55
- }
56
- }
57
- Model._mapAssetFiles.call(model, json, signCallback)
58
- expect(json.content.files[0].signature).toBe('sig-of-x.png')
59
- expect(json.content.files[1].signature).toBe('sig-of-y.png')
60
- })
61
-
62
- it('is a no-op when the wildcard matches nothing', () => {
63
- const model = makeFakeModel({ '**.file': { storage: 'test' } })
64
- const json = { name: 'no-files-here' }
65
- Model._mapAssetFiles.call(model, json, signCallback)
66
- expect(json).toEqual({ name: 'no-files-here' })
67
- })
68
- })
69
-
70
- describe('Model.getProperty: type inference for $ref/discriminator schemas', () => {
71
- function getProperty({ name, properties, definitions = {} }) {
72
- return Model.getProperty.call(
73
- { definition: { properties }, jsonSchema: { definitions } },
74
- name
75
- )
76
- }
77
-
78
- it('returns the property unchanged when it has an explicit type', () => {
79
- expect(
80
- getProperty({
81
- name: 'name',
82
- properties: { name: { type: 'string' } }
83
- })
84
- ).toEqual({ type: 'string' })
85
- })
86
-
87
- it('infers type: object from a discriminator on a $ref schema', () => {
88
- const property = getProperty({
89
- name: 'content',
90
- properties: { content: { $ref: '#StoryContent' } },
91
- definitions: {
92
- '#StoryContent': {
93
- discriminator: { propertyName: 'type' },
94
- oneOf: [
95
- { type: 'object', properties: { type: { const: 'a' } } },
96
- { type: 'object', properties: { type: { const: 'b' } } }
97
- ]
98
- }
99
- }
100
- })
101
- expect(property.type).toBe('object')
102
- expect(property.discriminator).toEqual({ propertyName: 'type' })
103
- })
104
-
105
- it('infers a shared type from a homogeneous oneOf', () => {
106
- const property = getProperty({
107
- name: 'content',
108
- properties: {
109
- content: { oneOf: [{ type: 'object' }, { type: 'object' }] }
110
- }
111
- })
112
- expect(property.type).toBe('object')
113
- })
114
-
115
- it('infers a shared type from a homogeneous anyOf', () => {
116
- const property = getProperty({
117
- name: 'content',
118
- properties: {
119
- content: { anyOf: [{ type: 'array' }, { type: 'array' }] }
120
- }
121
- })
122
- expect(property.type).toBe('array')
123
- })
124
-
125
- it('leaves type undefined when oneOf branches disagree', () => {
126
- const property = getProperty({
127
- name: 'content',
128
- properties: {
129
- content: { oneOf: [{ type: 'object' }, { type: 'string' }] }
130
- }
131
- })
132
- expect(property.type).toBeUndefined()
133
- })
134
-
135
- it('returns null for an unknown property', () => {
136
- expect(getProperty({ name: 'missing', properties: {} })).toBeNull()
137
- })
138
- })
139
-
140
- describe('Model.getPropertyOrRelationAtDataPath: traversal through inferred types', () => {
141
- function lookup({ dataPath, properties, definitions = {} }) {
142
- const host = {
143
- definition: { properties },
144
- jsonSchema: { definitions },
145
- getRelations: () => ({}),
146
- getProperty(name) {
147
- return Model.getProperty.call(this, name)
148
- }
149
- }
150
- return Model.getPropertyOrRelationAtDataPath.call(host, dataPath)
151
- }
152
-
153
- it('exposes object type for a discriminator-rooted nested wildcard path', () => {
154
- // Reproduces the original loadDataPath() crash on
155
- // `content.article.items[**].file`: before the fix, `content` had
156
- // no resolved `type`, so loadDataPath rejected the nested path.
157
- const result = lookup({
158
- dataPath: 'content.article.items[**].file',
159
- properties: { content: { $ref: '#StoryContent' } },
160
- definitions: {
161
- '#StoryContent': {
162
- discriminator: { propertyName: 'type' },
163
- oneOf: [{ type: 'object' }]
164
- }
165
- }
166
- })
167
- expect(result.property.type).toBe('object')
168
- expect(result.nestedDataPath).toBe('article/items/**/file')
169
- expect(result.dataPath).toBe('content')
170
- })
171
-
172
- it('returns no nestedDataPath for a leaf property lookup', () => {
173
- const result = lookup({
174
- dataPath: 'name',
175
- properties: { name: { type: 'string' } }
176
- })
177
- expect(result.property).toEqual({ type: 'string' })
178
- expect(result.nestedDataPath).toBe('')
179
- })
180
- })
@@ -1,177 +0,0 @@
1
- import {
2
- BelongsToOneRelation,
3
- HasOneRelation,
4
- HasOneThroughRelation,
5
- HasManyRelation,
6
- ManyToManyRelation
7
- } from 'objection'
8
- import { Model } from '../models/index.js'
9
- import {
10
- getRelationClass,
11
- convertRelation,
12
- addRelationSchemas
13
- } from './relations.js'
14
-
15
- describe('getRelationClass()', () => {
16
- it('returns the corresponding relation classes', () => {
17
- expect(getRelationClass('belongs-to')).toBe(BelongsToOneRelation)
18
- expect(getRelationClass('has-one')).toBe(HasOneRelation)
19
- expect(getRelationClass('has-one-through')).toBe(HasOneThroughRelation)
20
- expect(getRelationClass('has-many')).toBe(HasManyRelation)
21
- expect(getRelationClass('many-to-many')).toBe(ManyToManyRelation)
22
-
23
- expect(getRelationClass('belongsTo')).toBe(BelongsToOneRelation)
24
- expect(getRelationClass('hasOne')).toBe(HasOneRelation)
25
- expect(getRelationClass('hasOneThrough')).toBe(HasOneThroughRelation)
26
- expect(getRelationClass('hasMany')).toBe(HasManyRelation)
27
- expect(getRelationClass('manyToMany')).toBe(ManyToManyRelation)
28
-
29
- expect(getRelationClass('BelongsToOneRelation')).toBe(BelongsToOneRelation)
30
- expect(getRelationClass('HasOneRelation')).toBe(HasOneRelation)
31
- expect(getRelationClass('HasOneThroughRelation'))
32
- .toBe(HasOneThroughRelation)
33
- expect(getRelationClass('HasManyRelation')).toBe(HasManyRelation)
34
- expect(getRelationClass('ManyToManyRelation')).toBe(ManyToManyRelation)
35
- })
36
- })
37
-
38
- describe('convertRelation()', () => {
39
- class ModelOne extends Model {}
40
- class ModelTwo extends Model {}
41
-
42
- const models = { ModelOne, ModelTwo }
43
-
44
- it('converts a belongs-to relation to Objection.js format', () => {
45
- expect(
46
- convertRelation(
47
- {
48
- relation: 'belongs-to',
49
- from: 'ModelOne.modelTwoId',
50
- to: 'ModelTwo.id',
51
- modify: 'myScope'
52
- },
53
- models
54
- )
55
- ).toEqual({
56
- relation: BelongsToOneRelation,
57
- modelClass: ModelTwo,
58
- join: {
59
- from: 'ModelOne.modelTwoId',
60
- to: 'ModelTwo.id'
61
- },
62
- modify: 'myScope'
63
- })
64
- })
65
-
66
- it('converts a many-to-many relation to Objection.js format', () => {
67
- expect(
68
- convertRelation(
69
- {
70
- relation: 'many-to-many',
71
- from: 'ModelOne.id',
72
- to: 'ModelTwo.id',
73
- inverse: false
74
- },
75
- models
76
- )
77
- ).toEqual({
78
- relation: ManyToManyRelation,
79
- modelClass: ModelTwo,
80
- join: {
81
- from: 'ModelOne.id',
82
- through: {
83
- from: 'ModelOneModelTwo.modelOneId',
84
- to: 'ModelOneModelTwo.modelTwoId'
85
- },
86
- to: 'ModelTwo.id'
87
- }
88
- })
89
- })
90
-
91
- it('converts an inverse many-to-many relation to Objection.js format', () => {
92
- expect(
93
- convertRelation(
94
- {
95
- relation: 'many-to-many',
96
- from: 'ModelTwo.id',
97
- to: 'ModelOne.id',
98
- inverse: true
99
- },
100
- models
101
- )
102
- ).toEqual({
103
- relation: ManyToManyRelation,
104
- modelClass: ModelOne,
105
- join: {
106
- from: 'ModelTwo.id',
107
- through: {
108
- from: 'ModelOneModelTwo.modelTwoId',
109
- to: 'ModelOneModelTwo.modelOneId'
110
- },
111
- to: 'ModelOne.id'
112
- }
113
- })
114
- })
115
-
116
- it('preserves Objection.js relation format', () => {
117
- const relation = {
118
- relation: BelongsToOneRelation,
119
- modelClass: ModelTwo,
120
- join: {
121
- from: 'ModelOne.modelTwoId',
122
- to: 'ModelTwo.id'
123
- },
124
- modify: 'myScope'
125
- }
126
- expect(convertRelation(relation, models)).toEqual(relation)
127
- })
128
- })
129
-
130
- describe('addRelationSchemas()', () => {
131
- class ModelOne extends Model {
132
- static relations = {
133
- modelTwo: {
134
- relation: 'belongs-to',
135
- from: 'ModelOne.modelTwoId',
136
- to: 'ModelTwo.id'
137
- }
138
- }
139
- }
140
-
141
- class ModelTwo extends Model {
142
- static relations = {
143
- modelOnes: {
144
- relation: 'has-many',
145
- from: 'ModelTwo.id',
146
- to: 'ModelOne.modelTwoId',
147
- owner: true
148
- }
149
- }
150
- }
151
-
152
- // Add a mock app to both, to expose models:
153
- ModelOne.app = ModelTwo.app = {
154
- models: { ModelOne, ModelTwo }
155
- }
156
-
157
- it('adds correct property schema for a belongs-to relation', () => {
158
- expect(addRelationSchemas(ModelOne, {})).toEqual({
159
- modelTwo: {
160
- anyOf: [
161
- { type: 'null' },
162
- { relate: 'ModelTwo' },
163
- { $ref: 'ModelTwo' }
164
- ]
165
- }
166
- })
167
- })
168
-
169
- it('adds correct property schema for a has-many owner relation', () => {
170
- expect(addRelationSchemas(ModelTwo, {})).toEqual({
171
- modelOnes: {
172
- type: 'array',
173
- items: { $ref: 'ModelOne' }
174
- }
175
- })
176
- })
177
- })
@@ -1,720 +0,0 @@
1
- import { convertSchema } from './schema.js'
2
-
3
- describe('convertSchema()', () => {
4
- it('expands objects with properties to full JSON schemas', () => {
5
- const properties = {
6
- myString: {
7
- type: 'string'
8
- },
9
- myNumber: {
10
- type: 'number'
11
- },
12
- myInteger: {
13
- type: 'integer'
14
- },
15
- myBoolean: {
16
- type: 'boolean'
17
- },
18
- myObject: {
19
- type: 'object'
20
- },
21
- myArray: {
22
- type: 'array'
23
- }
24
- }
25
- expect(
26
- convertSchema({
27
- type: 'object',
28
- properties
29
- })
30
- ).toEqual({
31
- type: 'object',
32
- properties,
33
- unevaluatedProperties: false
34
- })
35
- })
36
-
37
- it('supports falsy default values', () => {
38
- const properties = {
39
- myString: {
40
- type: 'string',
41
- default: ''
42
- },
43
- myNumber: {
44
- type: 'number',
45
- default: 0
46
- },
47
- myInteger: {
48
- type: 'integer',
49
- default: 0
50
- },
51
- myBoolean: {
52
- type: 'boolean',
53
- default: false
54
- },
55
- myObject: {
56
- type: 'object',
57
- default: null
58
- },
59
- myArray: {
60
- type: 'array',
61
- default: null
62
- }
63
- }
64
- expect(
65
- convertSchema({
66
- type: 'object',
67
- properties
68
- })
69
- ).toEqual({
70
- type: 'object',
71
- properties,
72
- unevaluatedProperties: false
73
- })
74
- })
75
-
76
- it(`expands 'text' types to 'string' JSON schema types`, () => {
77
- expect(
78
- convertSchema({
79
- type: 'object',
80
- properties: {
81
- myText: {
82
- type: 'text'
83
- }
84
- }
85
- })
86
- ).toEqual({
87
- type: 'object',
88
- properties: {
89
- myText: {
90
- type: 'string'
91
- }
92
- },
93
- unevaluatedProperties: false
94
- })
95
- })
96
-
97
- it('adds `required` arrays and formats for required properties', () => {
98
- expect(
99
- convertSchema({
100
- type: 'object',
101
- properties: {
102
- myString: {
103
- type: 'string',
104
- required: true
105
- },
106
- myNumber: {
107
- type: 'number',
108
- required: true
109
- }
110
- }
111
- })
112
- ).toEqual({
113
- type: 'object',
114
- properties: {
115
- myString: {
116
- type: 'string',
117
- format: 'required'
118
- },
119
- myNumber: {
120
- type: 'number',
121
- format: 'required'
122
- }
123
- },
124
- unevaluatedProperties: false,
125
- required: ['myString', 'myNumber']
126
- })
127
- })
128
-
129
- it('preserves JSON schema-style `required` arrays', () => {
130
- expect(
131
- convertSchema({
132
- type: 'object',
133
- required: ['myString', 'myNumber'],
134
- properties: {
135
- myString: { type: 'string' },
136
- myNumber: { type: 'number' }
137
- }
138
- })
139
- ).toEqual({
140
- type: 'object',
141
- properties: {
142
- myString: { type: 'string' },
143
- myNumber: { type: 'number' }
144
- },
145
- unevaluatedProperties: false,
146
- required: ['myString', 'myNumber']
147
- })
148
- })
149
-
150
- it(`expands 'object' schemas with properties to JSON schemas allowing no unevaluated properties`, () => {
151
- expect(
152
- convertSchema({
153
- type: 'object',
154
- properties: {
155
- myText: {
156
- type: 'object',
157
- properties: {}
158
- }
159
- }
160
- })
161
- ).toEqual({
162
- type: 'object',
163
- properties: {
164
- myText: {
165
- type: 'object',
166
- unevaluatedProperties: false,
167
- properties: {}
168
- }
169
- },
170
- unevaluatedProperties: false
171
- })
172
- })
173
-
174
- it('preserves pre-existing settings for no unevaluated properties', () => {
175
- expect(
176
- convertSchema({
177
- type: 'object',
178
- properties: {
179
- myText: {
180
- type: 'object',
181
- unevaluatedProperties: true,
182
- properties: {}
183
- }
184
- }
185
- })
186
- ).toEqual({
187
- type: 'object',
188
- properties: {
189
- myText: {
190
- type: 'object',
191
- unevaluatedProperties: true,
192
- properties: {}
193
- }
194
- },
195
- unevaluatedProperties: false
196
- })
197
- })
198
-
199
- it('expands nested object schemas with required properties', () => {
200
- expect(
201
- convertSchema({
202
- type: 'object',
203
- properties: {
204
- myText: {
205
- type: 'object',
206
- properties: {
207
- myProperty: {
208
- type: 'text',
209
- required: true
210
- }
211
- }
212
- }
213
- }
214
- })
215
- ).toEqual({
216
- type: 'object',
217
- properties: {
218
- myText: {
219
- type: 'object',
220
- properties: {
221
- myProperty: {
222
- type: 'string',
223
- format: 'required'
224
- }
225
- },
226
- unevaluatedProperties: false,
227
- required: ['myProperty']
228
- }
229
- },
230
- unevaluatedProperties: false
231
- })
232
- })
233
-
234
- it(`expands 'object' schemas with patternProperties`, () => {
235
- expect(
236
- convertSchema({
237
- type: 'object',
238
- properties: {
239
- myText: {
240
- type: 'object',
241
- patternProperties: {
242
- '^.*$': {
243
- type: 'text'
244
- }
245
- }
246
- }
247
- }
248
- })
249
- ).toEqual({
250
- type: 'object',
251
- properties: {
252
- myText: {
253
- type: 'object',
254
- patternProperties: {
255
- '^.*$': {
256
- type: 'string'
257
- }
258
- },
259
- unevaluatedProperties: false
260
- }
261
- },
262
- unevaluatedProperties: false
263
- })
264
- })
265
-
266
- it('expands datetime types to their JSON schema representation', () => {
267
- expect(
268
- convertSchema({
269
- type: 'object',
270
- properties: {
271
- myDate: {
272
- type: 'date'
273
- },
274
- myDateTime: {
275
- type: 'datetime'
276
- },
277
- myTimeStamp: {
278
- type: 'timestamp'
279
- }
280
- }
281
- })
282
- ).toEqual({
283
- type: 'object',
284
- properties: {
285
- myDate: {
286
- type: ['string', 'object'],
287
- format: 'date-time'
288
- },
289
- myDateTime: {
290
- type: ['string', 'object'],
291
- format: 'date-time'
292
- },
293
- myTimeStamp: {
294
- type: ['string', 'object'],
295
- format: 'date-time'
296
- }
297
- },
298
- unevaluatedProperties: false
299
- })
300
- })
301
-
302
- it('expands unrecognized types to `$ref` references', () => {
303
- expect(
304
- convertSchema({
305
- type: 'object',
306
- properties: {
307
- myModel: {
308
- type: 'MyModel'
309
- }
310
- }
311
- })
312
- ).toEqual({
313
- type: 'object',
314
- properties: {
315
- myModel: {
316
- $ref: 'MyModel'
317
- }
318
- },
319
- unevaluatedProperties: false
320
- })
321
- })
322
-
323
- it(`expands unrecognized types to \`instanceof\` keywords when the \`useInstanceOf\` option is provided`, () => {
324
- expect(
325
- convertSchema(
326
- {
327
- type: 'object',
328
- properties: {
329
- myModel: {
330
- type: 'MyModel'
331
- }
332
- }
333
- },
334
- {
335
- useInstanceOf: true
336
- }
337
- )
338
- ).toEqual({
339
- type: 'object',
340
- properties: {
341
- myModel: {
342
- type: 'object',
343
- instanceof: 'MyModel'
344
- }
345
- },
346
- unevaluatedProperties: false
347
- })
348
- })
349
-
350
- it('handles `nullable: true` correctly (now natively supported)', () => {
351
- expect(
352
- convertSchema({
353
- type: 'object',
354
- properties: {
355
- myString: {
356
- type: 'string',
357
- nullable: true
358
- }
359
- }
360
- })
361
- ).toEqual({
362
- type: 'object',
363
- properties: {
364
- myString: {
365
- type: 'string',
366
- nullable: true
367
- }
368
- },
369
- unevaluatedProperties: false
370
- })
371
- })
372
-
373
- it(`handles \`nullable: true\` references correctly`, () => {
374
- expect(
375
- convertSchema({
376
- type: 'object',
377
- properties: {
378
- myModel: {
379
- type: 'MyModel',
380
- nullable: true
381
- }
382
- }
383
- })
384
- ).toEqual({
385
- type: 'object',
386
- properties: {
387
- myModel: {
388
- oneOf: [
389
- { type: 'null' },
390
- { $ref: 'MyModel' }
391
- ]
392
- }
393
- },
394
- unevaluatedProperties: false
395
- })
396
- })
397
-
398
- it(`handles \`nullable: true\` dates correctly (now natively supported)`, () => {
399
- expect(
400
- convertSchema({
401
- type: 'object',
402
- properties: {
403
- myDate: {
404
- type: 'date',
405
- nullable: true
406
- }
407
- }
408
- })
409
- ).toEqual({
410
- type: 'object',
411
- properties: {
412
- myDate: {
413
- type: ['string', 'object'],
414
- format: 'date-time',
415
- nullable: true
416
- }
417
- },
418
- unevaluatedProperties: false
419
- })
420
- })
421
-
422
- it(`handles \`nullable: true\` enums correctly`, () => {
423
- expect(
424
- convertSchema({
425
- type: 'object',
426
- properties: {
427
- myEnum: {
428
- type: 'string',
429
- enum: ['one', 'two', 'three'],
430
- nullable: true
431
- }
432
- }
433
- })
434
- ).toEqual({
435
- type: 'object',
436
- properties: {
437
- myEnum: {
438
- type: 'string',
439
- enum: ['one', 'two', 'three', null],
440
- nullable: true
441
- }
442
- },
443
- unevaluatedProperties: false
444
- })
445
- })
446
-
447
- it('converts schemas within oneOf properties', () => {
448
- expect(
449
- convertSchema({
450
- type: 'object',
451
- properties: {
452
- myList: {
453
- type: 'array',
454
- items: {
455
- oneOf: [
456
- {
457
- type: 'object',
458
- properties: {
459
- prop1: {
460
- type: 'string',
461
- required: true
462
- },
463
- prop2: {
464
- type: 'number',
465
- required: true
466
- }
467
- }
468
- },
469
- {
470
- type: 'object',
471
- properties: {
472
- prop3: {
473
- type: 'string',
474
- required: true
475
- },
476
- prop4: {
477
- type: 'number',
478
- required: true
479
- }
480
- }
481
- }
482
- ]
483
- }
484
- }
485
- }
486
- })
487
- ).toEqual({
488
- type: 'object',
489
- properties: {
490
- myList: {
491
- type: 'array',
492
- items: {
493
- oneOf: [
494
- {
495
- type: 'object',
496
- properties: {
497
- prop1: {
498
- type: 'string',
499
- format: 'required'
500
- },
501
- prop2: {
502
- type: 'number',
503
- format: 'required'
504
- }
505
- },
506
- required: ['prop1', 'prop2'],
507
- unevaluatedProperties: false
508
- },
509
- {
510
- type: 'object',
511
- properties: {
512
- prop3: {
513
- type: 'string',
514
- format: 'required'
515
- },
516
- prop4: {
517
- type: 'number',
518
- format: 'required'
519
- }
520
- },
521
- required: ['prop3', 'prop4'],
522
- unevaluatedProperties: false
523
- }
524
- ]
525
- }
526
- }
527
- },
528
- unevaluatedProperties: false
529
- })
530
- })
531
-
532
- it('supports `required: true` on object', () => {
533
- expect(
534
- convertSchema({
535
- type: 'object',
536
- properties: {
537
- myObject: {
538
- type: 'object',
539
- required: true,
540
- properties: {
541
- prop1: {
542
- type: 'string',
543
- required: true
544
- },
545
- prop2: {
546
- type: 'number',
547
- required: true
548
- }
549
- }
550
- }
551
- }
552
- })
553
- ).toEqual({
554
- type: 'object',
555
- properties: {
556
- myObject: {
557
- type: 'object',
558
- format: 'required',
559
- properties: {
560
- prop1: {
561
- format: 'required',
562
- type: 'string'
563
- },
564
- prop2: {
565
- format: 'required',
566
- type: 'number'
567
- }
568
- },
569
- unevaluatedProperties: false,
570
- required: ['prop1', 'prop2']
571
- }
572
- },
573
- unevaluatedProperties: false,
574
- required: ['myObject']
575
- })
576
- })
577
-
578
- it('processes discriminator schemas correctly', () => {
579
- expect(
580
- convertSchema({
581
- type: 'object',
582
- discriminator: { propertyName: 'foo' },
583
- required: ['foo'],
584
- oneOf: [
585
- {
586
- properties: {
587
- foo: { const: 'x' },
588
- a: {
589
- type: 'string',
590
- required: true
591
- }
592
- }
593
- },
594
- {
595
- properties: {
596
- foo: { enum: ['y', 'z'] },
597
- b: {
598
- type: 'string',
599
- required: true
600
- }
601
- }
602
- }
603
- ]
604
- })
605
- ).toEqual({
606
- type: 'object',
607
- discriminator: { propertyName: 'foo' },
608
- unevaluatedProperties: false,
609
- required: ['foo'],
610
- oneOf: [
611
- {
612
- properties: {
613
- foo: { const: 'x' },
614
- a: {
615
- type: 'string',
616
- format: 'required'
617
- }
618
- },
619
- required: ['a']
620
- },
621
- {
622
- properties: {
623
- foo: { enum: ['y', 'z'] },
624
- b: {
625
- type: 'string',
626
- format: 'required'
627
- }
628
- },
629
- required: ['b']
630
- }
631
- ]
632
- })
633
- })
634
-
635
- it('supports nested Dito.js definitions', () => {
636
- expect(
637
- convertSchema(
638
- {
639
- type: 'object',
640
- properties: {
641
- prop1: {
642
- $ref: '#type1'
643
- },
644
- prop2: {
645
- type: 'object',
646
- properties: {
647
- prop3: {
648
- $ref: '#type2'
649
- }
650
- },
651
- definitions: {
652
- '#type2': {
653
- type: 'object',
654
- properties: {
655
- prop4: {
656
- type: 'string'
657
- },
658
-
659
- prop5: {
660
- $ref: '#type3'
661
- }
662
- },
663
-
664
- definitions: {
665
- '#type3': {
666
- type: 'boolean'
667
- }
668
- }
669
- }
670
- }
671
- }
672
- },
673
-
674
- definitions: {
675
- '#type1': {
676
- type: 'integer'
677
- }
678
- }
679
- }
680
- )
681
- ).toEqual({
682
- type: 'object',
683
- unevaluatedProperties: false,
684
- properties: {
685
- prop1: {
686
- $ref: '#/definitions/#type1'
687
- },
688
- prop2: {
689
- type: 'object',
690
- unevaluatedProperties: false,
691
- properties: {
692
- prop3: {
693
- $ref: '#/definitions/#type2'
694
- }
695
- }
696
- }
697
- },
698
- definitions: {
699
- '#type1': {
700
- type: 'integer'
701
- },
702
- '#type2': {
703
- type: 'object',
704
- unevaluatedProperties: false,
705
- properties: {
706
- prop4: {
707
- type: 'string'
708
- },
709
- prop5: {
710
- $ref: '#/definitions/#type3'
711
- }
712
- }
713
- },
714
- '#type3': {
715
- type: 'boolean'
716
- }
717
- }
718
- })
719
- })
720
- })
@@ -1,58 +0,0 @@
1
- import { Storage } from './Storage.js'
2
-
3
- function createStorage(keys = ['test-secret']) {
4
- const app = { keys }
5
- return new Storage(app, { name: 'test' })
6
- }
7
-
8
- function simulateUpload(storage, key, originalname) {
9
- return storage.convertStorageFile({
10
- key,
11
- originalname,
12
- mimetype: 'image/png',
13
- size: 2048
14
- })
15
- }
16
-
17
- describe('Storage: asset key signature verification', () => {
18
- it('preserves the key through a full upload-then-save cycle', () => {
19
- const storage = createStorage()
20
- const uploaded = simulateUpload(storage, 'real-key.png', 'photo.png')
21
- const fromClient = { ...uploaded }
22
- storage.convertAssetFile(fromClient, { trusted: false })
23
- expect(fromClient.key).toBe('real-key.png')
24
- })
25
-
26
- it('throws when a forged key is submitted', () => {
27
- const storage = createStorage()
28
- const file = { key: 'victim-key.png', name: 'photo.png' }
29
- expect(() => {
30
- storage.convertAssetFile(file, { trusted: false })
31
- }).toThrow('Invalid asset signature')
32
- })
33
-
34
- it('throws when a valid signature is paired with a swapped key', () => {
35
- const storage = createStorage()
36
- const uploaded = simulateUpload(storage, 'legit.png', 'legit.png')
37
- const forged = { ...uploaded, key: 'victim-file.png' }
38
- expect(() => {
39
- storage.convertAssetFile(forged, { trusted: false })
40
- }).toThrow('Invalid asset signature')
41
- })
42
-
43
- it('does not throw when addFile() converts a server-created file', async () => {
44
- const storage = createStorage()
45
- const file = { key: 'imported.png', name: 'import.png' }
46
- const data = Buffer.from('fake-image-data')
47
- await storage.addFile(file, data)
48
- expect(file.key).toBe('imported.png')
49
- })
50
-
51
- it('strips signature after conversion', () => {
52
- const storage = createStorage()
53
- const uploaded = simulateUpload(storage, 'upload.png', 'photo.png')
54
- const fromClient = { ...uploaded }
55
- storage.convertAssetFile(fromClient, { trusted: false })
56
- expect('signature' in fromClient).toBe(false)
57
- })
58
- })
@@ -1,77 +0,0 @@
1
- import { describeFunction } from './function.js'
2
-
3
- describe('describeFunction()', () => {
4
- it('describes normal functions', () => {
5
- expect(
6
- describeFunction(function (a, b, c) {
7
- return a + b + c
8
- })
9
- )
10
- .toBe('function (a, b, c) { ... }')
11
- })
12
-
13
- it('describes lambdas with one param and a body', () => {
14
- expect(
15
- describeFunction(a => {
16
- return a
17
- })
18
- )
19
- .toBe('a => { ... }')
20
- })
21
-
22
- it('describes lambdas with one param and no body', () => {
23
- expect(describeFunction(a => a))
24
- .toBe('a => ...')
25
- })
26
-
27
- it('describes lambdas with multiple params and a body', () => {
28
- expect(
29
- describeFunction((a, b, c) => {
30
- return a + b + c
31
- })
32
- )
33
- .toBe('(a, b, c) => { ... }')
34
- })
35
-
36
- it('describes lambdas with multiple params and no body', () => {
37
- expect(describeFunction((a, b, c) => a + b + c))
38
- .toBe('(a, b, c) => ...')
39
- })
40
-
41
- it('describes async functions', () => {
42
- expect(
43
- describeFunction(async function (a, b, c) {
44
- return a + b + c
45
- })
46
- )
47
- .toBe('async function (a, b, c) { ... }')
48
- })
49
-
50
- it('describes async lambdas with one param and a body', () => {
51
- expect(
52
- describeFunction(async a => {
53
- return a
54
- })
55
- )
56
- .toBe('async a => { ... }')
57
- })
58
-
59
- it('describes async lambdas with one param and no body', () => {
60
- expect(describeFunction(async a => a))
61
- .toBe('async a => ...')
62
- })
63
-
64
- it('describes async lambdas with multiple params and a body', () => {
65
- expect(
66
- describeFunction(async (a, b, c) => {
67
- return a + b + c
68
- })
69
- )
70
- .toBe('async (a, b, c) => { ... }')
71
- })
72
-
73
- it('describes async lambdas with multiple params and no body', () => {
74
- expect(describeFunction(async (a, b, c) => a + b + c))
75
- .toBe('async (a, b, c) => ...')
76
- })
77
- })
@@ -1,86 +0,0 @@
1
- import {
2
- getAllKeys,
3
- getOwnProperty,
4
- createLookup,
5
- mergeReversed,
6
- mergeAsReversedArrays
7
- } from './object.js'
8
-
9
- const object = Object.create({ a: 1 })
10
- object.b = 2
11
-
12
- describe('getAllKeys()', () => {
13
- it('returns all keys, not just own keys', () => {
14
- expect(getAllKeys(object)).toEqual(['b', 'a'])
15
- })
16
- })
17
-
18
- describe('getOwnProperty()', () => {
19
- it('only returns own properties', () => {
20
- expect(getOwnProperty(object, 'a')).toBeUndefined()
21
- expect(getOwnProperty(object, 'b')).toBe(2)
22
- })
23
- })
24
-
25
- describe('createLookup()', () => {
26
- it('creates lookup objects out of arrays of strings', () => {
27
- expect(createLookup(['a', 'b'])).toEqual({
28
- a: true,
29
- b: true
30
- })
31
- })
32
- })
33
-
34
- describe('mergeReversed()', () => {
35
- it('merges property values in reversed order', () => {
36
- expect(
37
- mergeReversed([
38
- {
39
- a: 3
40
- },
41
- {
42
- a: 2,
43
- b: 2
44
- },
45
- {
46
- a: 1,
47
- b: 1,
48
- c: 1
49
- }
50
- ])
51
- ).toEqual({
52
- a: 3,
53
- b: 2,
54
- c: 1
55
- })
56
- })
57
- })
58
-
59
- describe('mergeAsReversedArrays()', () => {
60
- it('merges property values into arrays', () => {
61
- expect(
62
- mergeAsReversedArrays([
63
- {
64
- a: 1,
65
- b: 1,
66
- c: 1
67
- },
68
- {
69
- a: 2,
70
- b: 2
71
- },
72
- {
73
- a: 3
74
- }
75
- ])
76
- ).toEqual({
77
- a: [3, 2, 1],
78
- b: [2, 1],
79
- c: [1]
80
- })
81
- })
82
- })
83
-
84
- describe.skip('setupPropertyInheritance()', () => {
85
- // TODO: Add unit tests for this!
86
- })