@takeshape/schema 11.72.0 → 11.74.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/dist/models/runtime-schema.js +1 -0
- package/dist/project-schema/latest.d.ts +6 -1
- package/dist/project-schema/v3.48.0.d.ts +6 -1
- package/dist/project-schema/v3.49.0.d.ts +6 -1
- package/dist/project-schema/v3.50.0.d.ts +6 -1
- package/dist/project-schema/v3.51.0.d.ts +6 -1
- package/dist/project-schema/v3.52.0.d.ts +6 -1
- package/dist/project-schema/v3.53.0.d.ts +6 -1
- package/dist/project-schema/v3.54.0.d.ts +6 -1
- package/dist/schemas/project-schema/experimental.json +20 -0
- package/dist/util/expressions.js +2 -2
- package/package.json +6 -6
- package/dist/__tests__/_examples.test.d.ts +0 -1
- package/dist/__tests__/_examples.test.js +0 -77
- package/dist/__tests__/agents.test.d.ts +0 -1
- package/dist/__tests__/agents.test.js +0 -163
- package/dist/__tests__/api-version.test.d.ts +0 -1
- package/dist/__tests__/api-version.test.js +0 -24
- package/dist/__tests__/builtin-schema.test.d.ts +0 -1
- package/dist/__tests__/builtin-schema.test.js +0 -18
- package/dist/__tests__/content-schema-transform.test.d.ts +0 -1
- package/dist/__tests__/content-schema-transform.test.js +0 -522
- package/dist/__tests__/create-input-schema.test.d.ts +0 -1
- package/dist/__tests__/create-input-schema.test.js +0 -184
- package/dist/__tests__/enum.test.d.ts +0 -1
- package/dist/__tests__/enum.test.js +0 -36
- package/dist/__tests__/flatten-templates.test.d.ts +0 -1
- package/dist/__tests__/flatten-templates.test.js +0 -40
- package/dist/__tests__/get-is-leaf.test.d.ts +0 -1
- package/dist/__tests__/get-is-leaf.test.js +0 -50
- package/dist/__tests__/interfaces.test.d.ts +0 -1
- package/dist/__tests__/interfaces.test.js +0 -219
- package/dist/__tests__/migration.test.d.ts +0 -1
- package/dist/__tests__/migration.test.js +0 -34
- package/dist/__tests__/mocks.test.d.ts +0 -1
- package/dist/__tests__/mocks.test.js +0 -24
- package/dist/__tests__/refs.test.d.ts +0 -1
- package/dist/__tests__/refs.test.js +0 -813
- package/dist/__tests__/relationships.test.d.ts +0 -1
- package/dist/__tests__/relationships.test.js +0 -638
- package/dist/__tests__/schema-transform.test.d.ts +0 -1
- package/dist/__tests__/schema-transform.test.js +0 -205
- package/dist/__tests__/schema-util.test.d.ts +0 -1
- package/dist/__tests__/schema-util.test.js +0 -1731
- package/dist/__tests__/service-dependencies.test.d.ts +0 -1
- package/dist/__tests__/service-dependencies.test.js +0 -360
- package/dist/__tests__/unions.test.d.ts +0 -1
- package/dist/__tests__/unions.test.js +0 -44
- package/dist/__tests__/validate.test.d.ts +0 -1
- package/dist/__tests__/validate.test.js +0 -1980
- package/dist/__tests__/workflows.test.d.ts +0 -3
- package/dist/__tests__/workflows.test.js +0 -149
- package/dist/layers/__tests__/layers.test.d.ts +0 -1
- package/dist/layers/__tests__/layers.test.js +0 -38
- package/dist/migration/to/__tests__/v3.10.0.test.d.ts +0 -1
- package/dist/migration/to/__tests__/v3.10.0.test.js +0 -90
- package/dist/migration/to/__tests__/v3.12.3.test.d.ts +0 -1
- package/dist/migration/to/__tests__/v3.12.3.test.js +0 -103
- package/dist/migration/to/__tests__/v3.17.test.d.ts +0 -1
- package/dist/migration/to/__tests__/v3.17.test.js +0 -71
- package/dist/migration/to/__tests__/v3.34.0.test.d.ts +0 -1
- package/dist/migration/to/__tests__/v3.34.0.test.js +0 -240
- package/dist/migration/to/__tests__/v3.39.0.test.d.ts +0 -1
- package/dist/migration/to/__tests__/v3.39.0.test.js +0 -116
- package/dist/migration/to/__tests__/v3.46.0.test.d.ts +0 -1
- package/dist/migration/to/__tests__/v3.46.0.test.js +0 -59
- package/dist/models/__tests__/fixtures.d.ts +0 -15
- package/dist/models/__tests__/fixtures.js +0 -56
- package/dist/models/__tests__/query.test.d.ts +0 -1
- package/dist/models/__tests__/query.test.js +0 -19
- package/dist/models/__tests__/runtime-schema.test.d.ts +0 -1
- package/dist/models/__tests__/runtime-schema.test.js +0 -43
- package/dist/models/__tests__/shape.test.d.ts +0 -1
- package/dist/models/__tests__/shape.test.js +0 -24
- package/dist/resolvers/ai/__tests__/rag-query.test.d.ts +0 -1
- package/dist/resolvers/ai/__tests__/rag-query.test.js +0 -49
- package/dist/services/__tests__/services.test.d.ts +0 -1
- package/dist/services/__tests__/services.test.js +0 -113
- package/dist/template-shapes/__tests__/index.test.d.ts +0 -1
- package/dist/template-shapes/__tests__/index.test.js +0 -40
- package/dist/template-shapes/__tests__/names.test.d.ts +0 -1
- package/dist/template-shapes/__tests__/names.test.js +0 -19
- package/dist/template-shapes/__tests__/templates.test.d.ts +0 -1
- package/dist/template-shapes/__tests__/templates.test.js +0 -80
- package/dist/template-shapes/__tests__/where.test.d.ts +0 -1
- package/dist/template-shapes/__tests__/where.test.js +0 -218
- package/dist/types/__tests__/utils.test.d.ts +0 -1
- package/dist/types/__tests__/utils.test.js +0 -159
- package/dist/util/__tests__/api-indexing.test.d.ts +0 -1
- package/dist/util/__tests__/api-indexing.test.js +0 -129
- package/dist/util/__tests__/detect-cycles.test.d.ts +0 -1
- package/dist/util/__tests__/detect-cycles.test.js +0 -193
- package/dist/util/__tests__/expressions.test.d.ts +0 -1
- package/dist/util/__tests__/expressions.test.js +0 -172
- package/dist/util/__tests__/find-shape-at-path.test.d.ts +0 -1
- package/dist/util/__tests__/find-shape-at-path.test.js +0 -41
- package/dist/util/__tests__/form-config.test.d.ts +0 -1
- package/dist/util/__tests__/form-config.test.js +0 -196
- package/dist/util/__tests__/get-return-shape.test.d.ts +0 -1
- package/dist/util/__tests__/get-return-shape.test.js +0 -27
- package/dist/util/__tests__/has-args.test.d.ts +0 -1
- package/dist/util/__tests__/has-args.test.js +0 -46
- package/dist/util/__tests__/merge.test.d.ts +0 -1
- package/dist/util/__tests__/merge.test.js +0 -1074
- package/dist/util/__tests__/patch-schema.test.d.ts +0 -1
- package/dist/util/__tests__/patch-schema.test.js +0 -82
- package/dist/util/__tests__/shapes.test.d.ts +0 -1
- package/dist/util/__tests__/shapes.test.js +0 -30
|
@@ -1,1980 +0,0 @@
|
|
|
1
|
-
import { assert, DEFAULT_ENTITLEMENTS, deepClone } from '@takeshape/util';
|
|
2
|
-
import get from 'lodash/get.js';
|
|
3
|
-
import omit from 'lodash/omit.js';
|
|
4
|
-
import set from 'lodash/set.js';
|
|
5
|
-
import shortid from 'shortid';
|
|
6
|
-
import { describe, expect, it, test } from 'vitest';
|
|
7
|
-
import rickAndMortyLayer from '@/examples/latest/layers/rick-and-morty-layer.json';
|
|
8
|
-
import shopifyLayer from '@/examples/latest/layers/shopify-layer-2023-01.json';
|
|
9
|
-
import agentSchemaJson from '../../examples/latest/agent-schema.json';
|
|
10
|
-
import shapeBooksLatestJson from '../../examples/latest/shape-books.json';
|
|
11
|
-
import shopifySchemaWithInterfacesJson from '../../examples/latest/shopify-product-2022-07.json';
|
|
12
|
-
import shopifySchemaLatestJson from '../../examples/latest/shopify-store-with-widget.json';
|
|
13
|
-
import { createMockSchema, createMockStoredServiceConfig } from "../mocks.js";
|
|
14
|
-
import { applyDefaultsToSchema, createShape } from "../schema-util.js";
|
|
15
|
-
import { ensureValidProjectSchemaImport, ensureValidRoleImport, validateRoleInput, validateSchema } from "../validate/validate.js";
|
|
16
|
-
const layers = {
|
|
17
|
-
shopify: shopifyLayer,
|
|
18
|
-
rick: rickAndMortyLayer,
|
|
19
|
-
'gregs-takeshape-store': shopifyLayer
|
|
20
|
-
};
|
|
21
|
-
const openaiService = {
|
|
22
|
-
id: 'openai',
|
|
23
|
-
provider: 'openai',
|
|
24
|
-
serviceType: 'openapi',
|
|
25
|
-
namespace: 'OpenAI',
|
|
26
|
-
title: 'OpenAI',
|
|
27
|
-
authenticationType: 'none'
|
|
28
|
-
};
|
|
29
|
-
const validateContext = {
|
|
30
|
-
entitlements: DEFAULT_ENTITLEMENTS,
|
|
31
|
-
async resolveLayer(layerId) {
|
|
32
|
-
if (layers[layerId]) {
|
|
33
|
-
return { id: layerId, status: 'ok', schema: layers[layerId] };
|
|
34
|
-
}
|
|
35
|
-
return { id: layerId, status: 'error', schema: undefined };
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
/**
|
|
39
|
-
* Tests that run against the latest schema version only, can use migrated schemas.
|
|
40
|
-
*/
|
|
41
|
-
describe('latest', () => {
|
|
42
|
-
function shapeBooksSchemaLatest() {
|
|
43
|
-
return deepClone(applyDefaultsToSchema(shapeBooksLatestJson));
|
|
44
|
-
}
|
|
45
|
-
function shopifySchemaLatest() {
|
|
46
|
-
return deepClone(applyDefaultsToSchema(shopifySchemaLatestJson));
|
|
47
|
-
}
|
|
48
|
-
function shopifySchemaLatestWithInterfaces() {
|
|
49
|
-
return deepClone(applyDefaultsToSchema(shopifySchemaWithInterfacesJson));
|
|
50
|
-
}
|
|
51
|
-
function agentSchema() {
|
|
52
|
-
return deepClone(applyDefaultsToSchema(agentSchemaJson));
|
|
53
|
-
}
|
|
54
|
-
test('validateSchema - shopify valid', async () => {
|
|
55
|
-
const validSchema = shopifySchemaLatestWithInterfaces();
|
|
56
|
-
const { valid, schema, errors } = await validateSchema(validateContext, validSchema);
|
|
57
|
-
expect(errors).toEqual(undefined);
|
|
58
|
-
expect(valid).toBe(true);
|
|
59
|
-
expect(schema).toEqual(validSchema);
|
|
60
|
-
expect(errors).toEqual(undefined);
|
|
61
|
-
});
|
|
62
|
-
test('validateSchema - validates with missing property errors suppressed', async () => {
|
|
63
|
-
const mockProjectSchema = omit(shapeBooksSchemaLatest(), [
|
|
64
|
-
'projectId',
|
|
65
|
-
'locales',
|
|
66
|
-
'defaultLocale',
|
|
67
|
-
'author'
|
|
68
|
-
]);
|
|
69
|
-
const { valid, schema, errors } = await validateSchema({
|
|
70
|
-
...validateContext,
|
|
71
|
-
suppressErrorPaths: ['projectId', 'locales', 'defaultLocale', 'author']
|
|
72
|
-
}, mockProjectSchema);
|
|
73
|
-
expect(errors).toEqual(undefined);
|
|
74
|
-
expect(valid).toBe(true);
|
|
75
|
-
expect(schema).toEqual(mockProjectSchema);
|
|
76
|
-
});
|
|
77
|
-
test('validateSchema - conflicting shape ids', async () => {
|
|
78
|
-
const validSchema = shapeBooksSchemaLatest();
|
|
79
|
-
const invalidSchema = set(validSchema, ['shapes', 'Post', 'id'], 'rJBYXVyuQ');
|
|
80
|
-
const { valid, schema, errors } = await validateSchema(validateContext, invalidSchema);
|
|
81
|
-
expect(valid).toBe(false);
|
|
82
|
-
expect(schema).toEqual(undefined);
|
|
83
|
-
expect(errors).toEqual([
|
|
84
|
-
{
|
|
85
|
-
type: 'conflict',
|
|
86
|
-
message: 'Shapes must have unique ids. "rJBYXVyuQ" is not unique.',
|
|
87
|
-
path: ['shapes', 'Tag', 'id']
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
type: 'conflict',
|
|
91
|
-
message: 'Shapes must have unique ids. "rJBYXVyuQ" is not unique.',
|
|
92
|
-
path: ['shapes', 'Post', 'id']
|
|
93
|
-
}
|
|
94
|
-
]);
|
|
95
|
-
});
|
|
96
|
-
test('validateSchema - invalid workflow', async () => {
|
|
97
|
-
const validSchema = shapeBooksSchemaLatest();
|
|
98
|
-
const invalidSchema = set(validSchema, ['shapes', 'Book', 'workflow'], 'bogus');
|
|
99
|
-
const { valid, schema, errors } = await validateSchema(validateContext, invalidSchema);
|
|
100
|
-
expect(valid).toBe(false);
|
|
101
|
-
expect(schema).toEqual(undefined);
|
|
102
|
-
expect(errors).toEqual([
|
|
103
|
-
{
|
|
104
|
-
type: 'notFound',
|
|
105
|
-
message: 'Invalid Workflow "bogus" for shape "Book"',
|
|
106
|
-
path: ['shapes', 'Book', 'workflow']
|
|
107
|
-
}
|
|
108
|
-
]);
|
|
109
|
-
});
|
|
110
|
-
test('validateSchema - invalid ref due to invalid $ref path', async () => {
|
|
111
|
-
const validSchema = shapeBooksSchemaLatest();
|
|
112
|
-
validSchema.shapes.Book.schema.properties.author['@ref'] = undefined;
|
|
113
|
-
const invalidSchema = set(validSchema, ['shapes', 'Book', 'schema', 'properties', 'author', '$ref'], '#/shapes/bogus/schema');
|
|
114
|
-
const { valid, schema, errors } = await validateSchema(validateContext, invalidSchema);
|
|
115
|
-
expect(valid).toBe(false);
|
|
116
|
-
expect(schema).toEqual(undefined);
|
|
117
|
-
expect(errors).toEqual([
|
|
118
|
-
{
|
|
119
|
-
type: 'notFound',
|
|
120
|
-
message: 'Invalid ref "#/shapes/bogus/schema"',
|
|
121
|
-
path: ['shapes', 'Book', 'schema', 'properties', 'author', '$ref']
|
|
122
|
-
}
|
|
123
|
-
]);
|
|
124
|
-
});
|
|
125
|
-
test('validateSchema - invalid ref due to unknown service id', async () => {
|
|
126
|
-
const validSchema = shapeBooksSchemaLatest();
|
|
127
|
-
validSchema.shapes.Book.schema.properties.author['@ref'] = undefined;
|
|
128
|
-
const invalidSchema = set(validSchema, ['shapes', 'Book', 'schema', 'properties', 'author', '@ref'], 'bogus:Book');
|
|
129
|
-
const { valid, schema, errors } = await validateSchema(validateContext, invalidSchema);
|
|
130
|
-
expect(valid).toBe(false);
|
|
131
|
-
expect(schema).toEqual(undefined);
|
|
132
|
-
expect(errors).toEqual([
|
|
133
|
-
{
|
|
134
|
-
type: 'notFound',
|
|
135
|
-
message: 'Invalid ref "bogus:Book"',
|
|
136
|
-
path: ['shapes', 'Book', 'schema', 'properties', 'author', '@ref']
|
|
137
|
-
}
|
|
138
|
-
]);
|
|
139
|
-
});
|
|
140
|
-
test('validateSchema - invalid ref for array type', async () => {
|
|
141
|
-
const invalidSchema = shapeBooksSchemaLatest();
|
|
142
|
-
invalidSchema.shapes.Book.schema.properties.author['@ref'] = undefined;
|
|
143
|
-
set(invalidSchema, ['shapes', 'Book', 'schema', 'properties', 'author', 'type'], 'array');
|
|
144
|
-
set(invalidSchema, ['shapes', 'Book', 'schema', 'properties', 'author', 'items', '@ref'], 'local:Bogus');
|
|
145
|
-
const { valid, schema, errors } = await validateSchema(validateContext, invalidSchema);
|
|
146
|
-
expect(valid).toBe(false);
|
|
147
|
-
expect(schema).toEqual(undefined);
|
|
148
|
-
expect(errors).toEqual([
|
|
149
|
-
{
|
|
150
|
-
type: 'notFound',
|
|
151
|
-
message: 'Invalid ref "local:Bogus"',
|
|
152
|
-
path: ['shapes', 'Book', 'schema', 'properties', 'author', 'items', '@ref']
|
|
153
|
-
}
|
|
154
|
-
]);
|
|
155
|
-
});
|
|
156
|
-
test('validateSchema - allows built-in shapes to be used in resolvers', async () => {
|
|
157
|
-
const validSchema = createMockSchema('project-id', {
|
|
158
|
-
queries: {
|
|
159
|
-
listAssets: {
|
|
160
|
-
shape: 'PaginatedList<Asset>',
|
|
161
|
-
resolver: {
|
|
162
|
-
name: 'shapedb:list',
|
|
163
|
-
service: 'shapedb',
|
|
164
|
-
shapeName: 'Asset'
|
|
165
|
-
},
|
|
166
|
-
args: 'TSListArgs<Asset>'
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
const { valid } = await validateSchema(validateContext, validSchema);
|
|
171
|
-
expect(valid).toBe(true);
|
|
172
|
-
});
|
|
173
|
-
test('validateSchema - broken query shape', async () => {
|
|
174
|
-
const validSchema = shapeBooksSchemaLatest();
|
|
175
|
-
const invalidSchema = set(validSchema, ['queries', 'getBook', 'shape'], 'bogus');
|
|
176
|
-
const { valid, schema, errors } = await validateSchema(validateContext, invalidSchema);
|
|
177
|
-
expect(valid).toBe(false);
|
|
178
|
-
expect(schema).toEqual(undefined);
|
|
179
|
-
expect(errors).toEqual([
|
|
180
|
-
{
|
|
181
|
-
type: 'notFound',
|
|
182
|
-
message: 'Invalid ref "bogus"',
|
|
183
|
-
path: ['queries', 'getBook', 'shape']
|
|
184
|
-
}
|
|
185
|
-
]);
|
|
186
|
-
});
|
|
187
|
-
test('validateSchema - broken mutation shape', async () => {
|
|
188
|
-
const validSchema = shapeBooksSchemaLatest();
|
|
189
|
-
const invalidSchema = set(validSchema, ['mutations', 'createBook', 'shape'], 'bogus');
|
|
190
|
-
const { valid, schema, errors } = await validateSchema(validateContext, invalidSchema);
|
|
191
|
-
expect(valid).toBe(false);
|
|
192
|
-
expect(schema).toEqual(undefined);
|
|
193
|
-
expect(errors).toEqual([
|
|
194
|
-
{
|
|
195
|
-
type: 'notFound',
|
|
196
|
-
message: 'Invalid ref "bogus"',
|
|
197
|
-
path: ['mutations', 'createBook', 'shape']
|
|
198
|
-
}
|
|
199
|
-
]);
|
|
200
|
-
});
|
|
201
|
-
test('validateSchema - broken relationship', async () => {
|
|
202
|
-
const validSchema = shapeBooksSchemaLatest();
|
|
203
|
-
const invalidSchema = set(validSchema, ['shapes', 'Book', 'schema', 'properties', 'author', 'oneOf'], [{ '@ref': 'local:bogus' }]);
|
|
204
|
-
const { valid, errors } = await validateSchema(validateContext, invalidSchema);
|
|
205
|
-
expect(valid).toBe(false);
|
|
206
|
-
expect(errors).toEqual([
|
|
207
|
-
{
|
|
208
|
-
type: 'notFound',
|
|
209
|
-
message: 'Invalid ref "local:bogus"',
|
|
210
|
-
path: ['shapes', 'Book', 'schema', 'properties', 'author', 'oneOf', '0', '@ref']
|
|
211
|
-
}
|
|
212
|
-
]);
|
|
213
|
-
});
|
|
214
|
-
test('validateSchema - invalid resolver.name', async () => {
|
|
215
|
-
const validSchema = shapeBooksSchemaLatest();
|
|
216
|
-
const invalidSchema = set(validSchema, ['queries', 'getBook', 'resolver', 'name'], 'bogus');
|
|
217
|
-
const { valid, schema, errors } = await validateSchema(validateContext, invalidSchema);
|
|
218
|
-
expect(valid).toBe(false);
|
|
219
|
-
expect(schema).toEqual(undefined);
|
|
220
|
-
expect(errors).toMatchSnapshot();
|
|
221
|
-
});
|
|
222
|
-
test('validateSchema - invalid takeshape resolver.service', async () => {
|
|
223
|
-
const validSchema = shapeBooksSchemaLatest();
|
|
224
|
-
const invalidSchema = set(validSchema, ['queries', 'getBook', 'resolver', 'service'], 'bogus');
|
|
225
|
-
const { valid, schema, errors } = await validateSchema(validateContext, invalidSchema);
|
|
226
|
-
expect(valid).toBe(false);
|
|
227
|
-
expect(schema).toEqual(undefined);
|
|
228
|
-
expect(errors).toMatchSnapshot();
|
|
229
|
-
});
|
|
230
|
-
test('validateSchema - invalid regular resolver.service', async () => {
|
|
231
|
-
const validSchema = shopifySchemaLatest();
|
|
232
|
-
const invalidSchema = set(validSchema, ['shapes', 'Product', 'schema', 'properties', 'gregsTakeshapeStore', '@resolver', 'service'], 'bogus');
|
|
233
|
-
const { valid, schema, errors } = await validateSchema(validateContext, invalidSchema);
|
|
234
|
-
expect(valid).toBe(false);
|
|
235
|
-
expect(schema).toEqual(undefined);
|
|
236
|
-
expect(errors).toEqual([
|
|
237
|
-
{
|
|
238
|
-
type: 'notFound',
|
|
239
|
-
message: 'Invalid service "bogus"',
|
|
240
|
-
path: ['shapes', 'Product', 'schema', 'properties', 'gregsTakeshapeStore', '@resolver', 'service']
|
|
241
|
-
}
|
|
242
|
-
]);
|
|
243
|
-
});
|
|
244
|
-
test('validateSchema - invalid resolver.service in compose', async () => {
|
|
245
|
-
const validSchema = shopifySchemaLatest();
|
|
246
|
-
const resolverPath = ['shapes', 'Product', 'schema', 'properties', 'gregsTakeshapeStore', '@resolver'];
|
|
247
|
-
const resolver = get(validSchema, resolverPath);
|
|
248
|
-
const invalidComposedResolver = {
|
|
249
|
-
compose: [
|
|
250
|
-
{
|
|
251
|
-
...resolver,
|
|
252
|
-
service: 'bogus'
|
|
253
|
-
}
|
|
254
|
-
]
|
|
255
|
-
};
|
|
256
|
-
const invalidSchema = set(validSchema, resolverPath, invalidComposedResolver);
|
|
257
|
-
const { valid, schema, errors } = await validateSchema(validateContext, invalidSchema);
|
|
258
|
-
expect(valid).toBe(false);
|
|
259
|
-
expect(schema).toEqual(undefined);
|
|
260
|
-
expect(errors).toEqual([
|
|
261
|
-
{
|
|
262
|
-
type: 'notFound',
|
|
263
|
-
message: 'Invalid service "bogus"',
|
|
264
|
-
path: [...resolverPath, 'compose', 0, 'service']
|
|
265
|
-
}
|
|
266
|
-
]);
|
|
267
|
-
});
|
|
268
|
-
test('validateSchema - invalid Shape.name', async () => {
|
|
269
|
-
const schema = shapeBooksSchemaLatest();
|
|
270
|
-
schema.shapes.Book.name = 'NotBook';
|
|
271
|
-
// Avoid the noisy extra error generated by a bad relationship
|
|
272
|
-
set(schema, ['shapes', 'Homepage', 'schema', 'properties', 'featuredBook'], { type: 'string' });
|
|
273
|
-
expect(await validateSchema(validateContext, schema)).toEqual({
|
|
274
|
-
errors: [
|
|
275
|
-
{
|
|
276
|
-
message: 'Shape.name "NotBook" must match key "Book".',
|
|
277
|
-
path: ['shapes', 'Book', 'name'],
|
|
278
|
-
type: 'conflict'
|
|
279
|
-
}
|
|
280
|
-
],
|
|
281
|
-
valid: false
|
|
282
|
-
});
|
|
283
|
-
});
|
|
284
|
-
test('validateSchema - invalid @resolver.name', async () => {
|
|
285
|
-
const validSchema = shapeBooksSchemaLatest();
|
|
286
|
-
const invalidSchema = set(validSchema, ['shapes', 'Product', 'schema', 'properties', 'gregsTakeshapeStore', '@resolver', 'name'], 'bogus');
|
|
287
|
-
const { valid, schema, errors } = await validateSchema(validateContext, invalidSchema);
|
|
288
|
-
expect(valid).toBe(false);
|
|
289
|
-
expect(schema).toEqual(undefined);
|
|
290
|
-
expect(errors).toMatchSnapshot();
|
|
291
|
-
});
|
|
292
|
-
test('validateSchema - defaultLocale missing from locales', async () => {
|
|
293
|
-
const validSchema = shapeBooksSchemaLatest();
|
|
294
|
-
const invalidSchema = set(validSchema, ['locales'], ['de-de', 'es-es']);
|
|
295
|
-
const { valid, schema, errors } = await validateSchema(validateContext, invalidSchema);
|
|
296
|
-
expect(valid).toBe(false);
|
|
297
|
-
expect(schema).toEqual(undefined);
|
|
298
|
-
expect(errors).toEqual([
|
|
299
|
-
{
|
|
300
|
-
message: 'Default locale "en-us" missing from locales',
|
|
301
|
-
path: ['locales'],
|
|
302
|
-
type: 'notFound'
|
|
303
|
-
}
|
|
304
|
-
]);
|
|
305
|
-
});
|
|
306
|
-
test('validateSchema - valid delegate resolver', async () => {
|
|
307
|
-
const query = {
|
|
308
|
-
shape: 'Shopify_Product',
|
|
309
|
-
resolver: { name: 'delegate', to: 'shopify:Query.products' }
|
|
310
|
-
};
|
|
311
|
-
const projectSchema = set(shopifySchemaLatestWithInterfaces(), 'queries.delegateProducts', query);
|
|
312
|
-
const { errors } = await validateSchema(validateContext, projectSchema);
|
|
313
|
-
expect(errors).toEqual(undefined);
|
|
314
|
-
});
|
|
315
|
-
test('validateSchema - invalid delegate resolver property ref', async () => {
|
|
316
|
-
const query = {
|
|
317
|
-
shape: 'Shopify_Product',
|
|
318
|
-
resolver: { name: 'delegate', to: 'mr.super.bogus' }
|
|
319
|
-
};
|
|
320
|
-
const projectSchema = set(shopifySchemaLatestWithInterfaces(), 'queries.delegateProducts', query);
|
|
321
|
-
const { errors } = await validateSchema(validateContext, projectSchema);
|
|
322
|
-
expect(errors).toEqual([
|
|
323
|
-
{
|
|
324
|
-
message: 'Unable to parse property ref "mr.super.bogus"',
|
|
325
|
-
path: ['queries', 'delegateProducts', 'resolver', 'to'],
|
|
326
|
-
type: 'conflict'
|
|
327
|
-
}
|
|
328
|
-
]);
|
|
329
|
-
});
|
|
330
|
-
test('validateSchema - invalid delegate resolver missing resolver config', async () => {
|
|
331
|
-
const query = {
|
|
332
|
-
shape: 'Shopify_Product',
|
|
333
|
-
resolver: { name: 'delegate', to: 'Query.bogusProducts' }
|
|
334
|
-
};
|
|
335
|
-
const projectSchema = set(shopifySchemaLatestWithInterfaces(), 'queries.delegateProducts', query);
|
|
336
|
-
const { errors } = await validateSchema(validateContext, projectSchema);
|
|
337
|
-
expect(errors).toEqual([
|
|
338
|
-
{
|
|
339
|
-
message: 'Missing resolver config at property ref "Query.bogusProducts"',
|
|
340
|
-
path: ['queries', 'delegateProducts', 'resolver', 'to'],
|
|
341
|
-
type: 'notFound'
|
|
342
|
-
}
|
|
343
|
-
]);
|
|
344
|
-
});
|
|
345
|
-
test('validateSchema - invalid delegate resolver - missing remote property', async () => {
|
|
346
|
-
const query = {
|
|
347
|
-
shape: 'Shopify_Product',
|
|
348
|
-
resolver: { name: 'delegate', to: 'shopify:Query.bogusProducts' }
|
|
349
|
-
};
|
|
350
|
-
const projectSchema = set(shopifySchemaLatestWithInterfaces(), 'queries.delegateProducts', query);
|
|
351
|
-
const { errors } = await validateSchema(validateContext, projectSchema);
|
|
352
|
-
expect(errors).toEqual([
|
|
353
|
-
{
|
|
354
|
-
message: 'Missing resolver config at property ref "shopify:Query.bogusProducts"',
|
|
355
|
-
path: ['queries', 'delegateProducts', 'resolver', 'to'],
|
|
356
|
-
type: 'notFound'
|
|
357
|
-
}
|
|
358
|
-
]);
|
|
359
|
-
});
|
|
360
|
-
test('validateSchema - valid ai:generateText resolver', async () => {
|
|
361
|
-
const query = {
|
|
362
|
-
shape: 'TSChatResponse',
|
|
363
|
-
resolver: {
|
|
364
|
-
name: 'ai:generateText',
|
|
365
|
-
service: 'openai',
|
|
366
|
-
model: 'gpt-4o',
|
|
367
|
-
systemPrompt: 'You are a helpful assistant'
|
|
368
|
-
}
|
|
369
|
-
};
|
|
370
|
-
let projectSchema = shopifySchemaLatestWithInterfaces();
|
|
371
|
-
projectSchema = set(projectSchema, 'mutations.chat', query);
|
|
372
|
-
projectSchema = set(projectSchema, 'services.openai', openaiService);
|
|
373
|
-
const { errors } = await validateSchema(validateContext, projectSchema);
|
|
374
|
-
expect(errors).toEqual(undefined);
|
|
375
|
-
});
|
|
376
|
-
test('validateSchema - ai:generateText invalid tools', async () => {
|
|
377
|
-
const query = {
|
|
378
|
-
shape: 'TSChatResponse',
|
|
379
|
-
resolver: {
|
|
380
|
-
name: 'ai:generateText',
|
|
381
|
-
service: 'openai',
|
|
382
|
-
model: 'gpt-4o',
|
|
383
|
-
systemPrompt: 'You are a helpful assistant',
|
|
384
|
-
tools: [
|
|
385
|
-
'bogus',
|
|
386
|
-
{
|
|
387
|
-
ref: 'shopify:customer'
|
|
388
|
-
},
|
|
389
|
-
{
|
|
390
|
-
ref: 'anotherBogus'
|
|
391
|
-
}
|
|
392
|
-
]
|
|
393
|
-
}
|
|
394
|
-
};
|
|
395
|
-
let projectSchema = shopifySchemaLatestWithInterfaces();
|
|
396
|
-
projectSchema = set(projectSchema, 'mutations.chat', query);
|
|
397
|
-
projectSchema = set(projectSchema, 'services.openai', openaiService);
|
|
398
|
-
const { errors } = await validateSchema(validateContext, projectSchema);
|
|
399
|
-
expect(errors).toEqual([
|
|
400
|
-
{
|
|
401
|
-
message: 'Missing tool query "bogus"',
|
|
402
|
-
path: ['mutations', 'chat', 'resolver', 'tools', 0],
|
|
403
|
-
type: 'notFound'
|
|
404
|
-
},
|
|
405
|
-
{
|
|
406
|
-
message: 'Missing tool query "anotherBogus"',
|
|
407
|
-
path: ['mutations', 'chat', 'resolver', 'tools', 2, 'ref'],
|
|
408
|
-
type: 'notFound'
|
|
409
|
-
}
|
|
410
|
-
]);
|
|
411
|
-
});
|
|
412
|
-
test('validateSchema - ai:generateText invalid remote tools', async () => {
|
|
413
|
-
const query = {
|
|
414
|
-
shape: 'TSChatResponse',
|
|
415
|
-
resolver: {
|
|
416
|
-
name: 'ai:generateText',
|
|
417
|
-
service: 'openai',
|
|
418
|
-
model: 'gpt-4o',
|
|
419
|
-
systemPrompt: 'You are a helpful assistant',
|
|
420
|
-
tools: [
|
|
421
|
-
'shopify:customer',
|
|
422
|
-
{
|
|
423
|
-
ref: 'shopify:bogus'
|
|
424
|
-
}
|
|
425
|
-
]
|
|
426
|
-
}
|
|
427
|
-
};
|
|
428
|
-
let projectSchema = shopifySchemaLatestWithInterfaces();
|
|
429
|
-
projectSchema = set(projectSchema, 'mutations.chat', query);
|
|
430
|
-
projectSchema = set(projectSchema, 'services.openai', openaiService);
|
|
431
|
-
const { errors } = await validateSchema(validateContext, projectSchema);
|
|
432
|
-
expect(errors).toEqual([
|
|
433
|
-
{
|
|
434
|
-
message: 'Invalid ref "shopify:bogus"',
|
|
435
|
-
path: ['mutations', 'chat', 'resolver', 'tools', 1, 'ref'],
|
|
436
|
-
type: 'notFound'
|
|
437
|
-
}
|
|
438
|
-
]);
|
|
439
|
-
});
|
|
440
|
-
test('validateSchema - ai:generateText invalid options.toolChoice "required"', async () => {
|
|
441
|
-
const query = {
|
|
442
|
-
shape: 'TSChatResponse',
|
|
443
|
-
resolver: {
|
|
444
|
-
name: 'ai:generateText',
|
|
445
|
-
service: 'openai',
|
|
446
|
-
model: 'gpt-4o',
|
|
447
|
-
systemPrompt: 'You are a helpful assistant',
|
|
448
|
-
options: {
|
|
449
|
-
toolChoice: 'required'
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
};
|
|
453
|
-
let projectSchema = shopifySchemaLatestWithInterfaces();
|
|
454
|
-
projectSchema = set(projectSchema, 'mutations.chat', query);
|
|
455
|
-
projectSchema = set(projectSchema, 'services.openai', openaiService);
|
|
456
|
-
const { errors } = await validateSchema(validateContext, projectSchema);
|
|
457
|
-
expect(errors).toEqual([
|
|
458
|
-
{
|
|
459
|
-
message: 'Must have at least one tool configured to specify "required"',
|
|
460
|
-
path: ['mutations', 'chat', 'resolver', 'options', 'toolChoice'],
|
|
461
|
-
type: 'notFound'
|
|
462
|
-
}
|
|
463
|
-
]);
|
|
464
|
-
});
|
|
465
|
-
test('validateSchema - ai:generateText valid options.toolChoice', async () => {
|
|
466
|
-
const query = {
|
|
467
|
-
shape: 'TSChatResponse',
|
|
468
|
-
resolver: {
|
|
469
|
-
name: 'ai:generateText',
|
|
470
|
-
service: 'openai',
|
|
471
|
-
model: 'gpt-4o',
|
|
472
|
-
systemPrompt: 'You are a helpful assistant',
|
|
473
|
-
tools: [{ ref: 'shopify:customer' }],
|
|
474
|
-
options: {
|
|
475
|
-
toolChoice: 'shopify:Query.customer'
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
};
|
|
479
|
-
let projectSchema = shopifySchemaLatestWithInterfaces();
|
|
480
|
-
projectSchema = set(projectSchema, 'mutations.chat', query);
|
|
481
|
-
projectSchema = set(projectSchema, 'services.openai', openaiService);
|
|
482
|
-
const { errors } = await validateSchema(validateContext, projectSchema);
|
|
483
|
-
expect(errors).toEqual(undefined);
|
|
484
|
-
});
|
|
485
|
-
test('validateSchema - ai:generateText invalid options.toolChoice missing', async () => {
|
|
486
|
-
const query = {
|
|
487
|
-
shape: 'TSChatResponse',
|
|
488
|
-
resolver: {
|
|
489
|
-
name: 'ai:generateText',
|
|
490
|
-
service: 'openai',
|
|
491
|
-
model: 'gpt-4o',
|
|
492
|
-
systemPrompt: 'You are a helpful assistant',
|
|
493
|
-
tools: ['shopify:customer'],
|
|
494
|
-
options: {
|
|
495
|
-
toolChoice: 'bogus'
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
};
|
|
499
|
-
let projectSchema = shopifySchemaLatestWithInterfaces();
|
|
500
|
-
projectSchema = set(projectSchema, 'mutations.chat', query);
|
|
501
|
-
projectSchema = set(projectSchema, 'services.openai', openaiService);
|
|
502
|
-
const { errors } = await validateSchema(validateContext, projectSchema);
|
|
503
|
-
expect(errors).toEqual([
|
|
504
|
-
{
|
|
505
|
-
message: 'Missing tool "bogus" must be specified in "tools" array',
|
|
506
|
-
path: ['mutations', 'chat', 'resolver', 'options', 'toolChoice'],
|
|
507
|
-
type: 'notFound'
|
|
508
|
-
}
|
|
509
|
-
]);
|
|
510
|
-
});
|
|
511
|
-
test('validateSchema - joins - invalid local shape', async () => {
|
|
512
|
-
const join = {
|
|
513
|
-
resolver: {
|
|
514
|
-
name: 'shapedb:get',
|
|
515
|
-
service: 'shapedb',
|
|
516
|
-
shapeName: 'BOGUS'
|
|
517
|
-
}
|
|
518
|
-
};
|
|
519
|
-
const projectSchema = set(shopifySchemaLatestWithInterfaces(), ['shapes', 'Shopify_Product', 'joins', 'local:Bogus'], join);
|
|
520
|
-
const { errors } = await validateSchema(validateContext, projectSchema);
|
|
521
|
-
expect(errors).toEqual([
|
|
522
|
-
{
|
|
523
|
-
message: 'Invalid join "local:Bogus" not found',
|
|
524
|
-
path: ['shapes', 'Shopify_Product', 'joins', 'local:Bogus'],
|
|
525
|
-
type: 'notFound'
|
|
526
|
-
},
|
|
527
|
-
{
|
|
528
|
-
message: 'Invalid Model Shape "BOGUS"',
|
|
529
|
-
path: ['shapes', 'Shopify_Product', 'joins', 'local:Bogus', 'resolver', 'shapeName'],
|
|
530
|
-
type: 'notFound'
|
|
531
|
-
}
|
|
532
|
-
]);
|
|
533
|
-
});
|
|
534
|
-
test('validateSchema - joins - invalid shapedb join', async () => {
|
|
535
|
-
const join = {
|
|
536
|
-
resolver: {
|
|
537
|
-
name: 'shapedb:get',
|
|
538
|
-
service: 'shapedb',
|
|
539
|
-
shapeName: 'BOGUS'
|
|
540
|
-
}
|
|
541
|
-
};
|
|
542
|
-
const projectSchema = set(shopifySchemaLatestWithInterfaces(), ['shapes', 'Shopify_Product', 'joins', 'local:Bogus'], join);
|
|
543
|
-
set(projectSchema, ['shapes', 'Bogus'], createShape('Bogus', { type: 'object', properties: {} }));
|
|
544
|
-
const { errors } = await validateSchema(validateContext, projectSchema);
|
|
545
|
-
expect(errors).toEqual([
|
|
546
|
-
{
|
|
547
|
-
message: 'Invalid Model Shape "BOGUS"',
|
|
548
|
-
path: ['shapes', 'Shopify_Product', 'joins', 'local:Bogus', 'resolver', 'shapeName'],
|
|
549
|
-
type: 'notFound'
|
|
550
|
-
}
|
|
551
|
-
]);
|
|
552
|
-
});
|
|
553
|
-
test('validateSchema - joins - valid delegate join', async () => {
|
|
554
|
-
const shape = createShape('Product', {
|
|
555
|
-
type: 'object',
|
|
556
|
-
properties: {
|
|
557
|
-
shopifyProductId: { type: 'string' }
|
|
558
|
-
}
|
|
559
|
-
}, {
|
|
560
|
-
joins: {
|
|
561
|
-
'shopify:Product': {
|
|
562
|
-
resolver: {
|
|
563
|
-
name: 'delegate',
|
|
564
|
-
to: 'shopify:Query.product',
|
|
565
|
-
args: {
|
|
566
|
-
ops: [{ path: 'id', mapping: '$source.shopifyProductId' }]
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
});
|
|
572
|
-
const projectSchema = set(shopifySchemaLatestWithInterfaces(), ['shapes', 'Product'], shape);
|
|
573
|
-
const { errors } = await validateSchema(validateContext, projectSchema);
|
|
574
|
-
expect(errors).toEqual(undefined);
|
|
575
|
-
});
|
|
576
|
-
function createPetSchema() {
|
|
577
|
-
return {
|
|
578
|
-
...createMockSchema('project-id'),
|
|
579
|
-
shapes: {
|
|
580
|
-
Petstore: {
|
|
581
|
-
id: 'i8tv9QutR',
|
|
582
|
-
name: 'Petstore',
|
|
583
|
-
title: 'Petstore',
|
|
584
|
-
workflow: 'default',
|
|
585
|
-
model: { type: 'multiple' },
|
|
586
|
-
schema: {
|
|
587
|
-
type: 'object',
|
|
588
|
-
properties: {
|
|
589
|
-
pets: {
|
|
590
|
-
type: 'array',
|
|
591
|
-
'@mapping': 'shapedb:Petstore.aaa3LRPxC',
|
|
592
|
-
items: {
|
|
593
|
-
oneOf: [{ '@ref': 'local:Dog' }, { '@ref': 'local:Cat' }]
|
|
594
|
-
},
|
|
595
|
-
title: 'Pets'
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
},
|
|
600
|
-
Dog: {
|
|
601
|
-
id: '7PSFIZU-N',
|
|
602
|
-
name: 'Dog',
|
|
603
|
-
title: 'Dog',
|
|
604
|
-
schema: {
|
|
605
|
-
type: 'object',
|
|
606
|
-
properties: {
|
|
607
|
-
name: {
|
|
608
|
-
type: 'string',
|
|
609
|
-
title: 'Name',
|
|
610
|
-
'@mapping': 'shapedb:Dog.i7S3LRPxC'
|
|
611
|
-
},
|
|
612
|
-
rank: {
|
|
613
|
-
type: 'string',
|
|
614
|
-
title: 'Rank',
|
|
615
|
-
'@mapping': 'shapedb:Dog.yOvol-ZPg'
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
},
|
|
620
|
-
Cat: {
|
|
621
|
-
id: 'k49WhBWPs',
|
|
622
|
-
name: 'Cat',
|
|
623
|
-
title: 'Cat',
|
|
624
|
-
schema: {
|
|
625
|
-
type: 'object',
|
|
626
|
-
properties: {
|
|
627
|
-
name: {
|
|
628
|
-
type: 'string',
|
|
629
|
-
title: 'Name',
|
|
630
|
-
'@mapping': 'shapedb:Cat.tbWPT2kvU'
|
|
631
|
-
},
|
|
632
|
-
breed: {
|
|
633
|
-
type: 'string',
|
|
634
|
-
title: 'Breed',
|
|
635
|
-
'@mapping': 'shapedb:Cat.XuyPh7IHw',
|
|
636
|
-
oneOf: [
|
|
637
|
-
{
|
|
638
|
-
title: 'Maine Coon',
|
|
639
|
-
enum: ['stuart']
|
|
640
|
-
},
|
|
641
|
-
{
|
|
642
|
-
title: 'Tabby',
|
|
643
|
-
enum: ['tabby']
|
|
644
|
-
},
|
|
645
|
-
{
|
|
646
|
-
title: 'Russian Blue',
|
|
647
|
-
enum: ['pelmeni']
|
|
648
|
-
}
|
|
649
|
-
]
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
};
|
|
656
|
-
}
|
|
657
|
-
test('validateSchema - mixed oneOf', async () => {
|
|
658
|
-
const invalidSchema = set(createPetSchema(), ['shapes', 'Petstore', 'schema', 'properties', 'pets', 'items'], {
|
|
659
|
-
oneOf: [{ '@ref': 'local:Dog' }, { type: 'string' }]
|
|
660
|
-
});
|
|
661
|
-
const { valid, schema, errors } = await validateSchema(validateContext, invalidSchema);
|
|
662
|
-
expect(valid).toBe(false);
|
|
663
|
-
expect(schema).toEqual(undefined);
|
|
664
|
-
expect(errors).toEqual([
|
|
665
|
-
{
|
|
666
|
-
message: 'Invalid oneOf must contain only @ref or title + enum/const schemas',
|
|
667
|
-
path: ['shapes', 'Petstore', 'schema', 'properties', 'pets', 'items', 'oneOf'],
|
|
668
|
-
type: 'conflict'
|
|
669
|
-
}
|
|
670
|
-
]);
|
|
671
|
-
});
|
|
672
|
-
test('validateSchema - ref with @ref and $ref', async () => {
|
|
673
|
-
const invalidSchema = set(createPetSchema(), ['shapes', 'Petstore', 'schema', 'properties', 'pets', 'items'], {
|
|
674
|
-
oneOf: [{ '@ref': 'local:Dog', $ref: '#/shapes/Cat/schema' }]
|
|
675
|
-
});
|
|
676
|
-
const { valid, schema, errors } = await validateSchema(validateContext, invalidSchema);
|
|
677
|
-
expect(valid).toBe(false);
|
|
678
|
-
expect(schema).toEqual(undefined);
|
|
679
|
-
expect(errors).toEqual([
|
|
680
|
-
{
|
|
681
|
-
message: 'Ref cannot have both @ref and $ref',
|
|
682
|
-
path: ['shapes', 'Petstore', 'schema', 'properties', 'pets', 'items', 'oneOf', '0'],
|
|
683
|
-
type: 'conflict'
|
|
684
|
-
}
|
|
685
|
-
]);
|
|
686
|
-
});
|
|
687
|
-
test('validateSchema - oneOf with duplicate', async () => {
|
|
688
|
-
const invalidSchema = set(createPetSchema(), ['shapes', 'Petstore', 'schema', 'properties', 'pets', 'items'], {
|
|
689
|
-
oneOf: [{ '@ref': 'local:Dog' }, { '@ref': 'local:Dog' }]
|
|
690
|
-
});
|
|
691
|
-
const { valid, schema, errors } = await validateSchema(validateContext, invalidSchema);
|
|
692
|
-
expect(valid).toBe(false);
|
|
693
|
-
expect(schema).toEqual(undefined);
|
|
694
|
-
expect(errors).toEqual([
|
|
695
|
-
{
|
|
696
|
-
message: 'Invalid oneOf must not contain duplicate references',
|
|
697
|
-
path: ['shapes', 'Petstore', 'schema', 'properties', 'pets', 'items', 'oneOf'],
|
|
698
|
-
type: 'conflict'
|
|
699
|
-
}
|
|
700
|
-
]);
|
|
701
|
-
});
|
|
702
|
-
test('validateSchema - self-referential allOf', async () => {
|
|
703
|
-
const invalidSchema = {
|
|
704
|
-
...createMockSchema('project-id'),
|
|
705
|
-
shapes: {
|
|
706
|
-
BadShape: {
|
|
707
|
-
name: 'BadShape',
|
|
708
|
-
title: 'BadShape',
|
|
709
|
-
id: 'BadShape',
|
|
710
|
-
schema: {
|
|
711
|
-
allOf: [{ '@ref': 'local:BadShape' }]
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
};
|
|
716
|
-
const { valid, schema, errors } = await validateSchema(validateContext, invalidSchema);
|
|
717
|
-
expect(valid).toBe(false);
|
|
718
|
-
expect(schema).toEqual(undefined);
|
|
719
|
-
expect(errors).toEqual([
|
|
720
|
-
{
|
|
721
|
-
message: 'allOf cannot be self-referential',
|
|
722
|
-
path: ['shapes', 'BadShape', 'schema', 'allOf', '0', '@ref'],
|
|
723
|
-
type: 'conflict'
|
|
724
|
-
}
|
|
725
|
-
]);
|
|
726
|
-
});
|
|
727
|
-
test('validateSchema - cannot be both model and interface', async () => {
|
|
728
|
-
const invalidSchema = createMockSchema('projectId', {
|
|
729
|
-
shapes: {
|
|
730
|
-
FooBar: {
|
|
731
|
-
name: 'FooBar',
|
|
732
|
-
title: 'FooBar',
|
|
733
|
-
id: 'FooBar',
|
|
734
|
-
type: 'interface',
|
|
735
|
-
model: { type: 'multiple' },
|
|
736
|
-
schema: {
|
|
737
|
-
enum: ['foo', 'bar', 'baz']
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
});
|
|
742
|
-
const { valid, schema, errors } = await validateSchema(validateContext, invalidSchema);
|
|
743
|
-
expect(valid).toBe(false);
|
|
744
|
-
expect(schema).toEqual(undefined);
|
|
745
|
-
expect(errors).toEqual([
|
|
746
|
-
{
|
|
747
|
-
message: '"FooBar" may not be both an interface and model. Create a separate model shape which implements this interface.',
|
|
748
|
-
path: ['shapes', 'FooBar', 'model'],
|
|
749
|
-
type: 'conflict'
|
|
750
|
-
}
|
|
751
|
-
]);
|
|
752
|
-
});
|
|
753
|
-
test('validateSchema - invalid non-object interface', async () => {
|
|
754
|
-
const invalidSchema = createMockSchema('projectId', {
|
|
755
|
-
shapes: {
|
|
756
|
-
FooBar: {
|
|
757
|
-
name: 'FooBar',
|
|
758
|
-
title: 'FooBar',
|
|
759
|
-
id: 'FooBar',
|
|
760
|
-
type: 'interface',
|
|
761
|
-
schema: {
|
|
762
|
-
enum: ['foo', 'bar', 'baz']
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
});
|
|
767
|
-
const { valid, schema, errors } = await validateSchema(validateContext, invalidSchema);
|
|
768
|
-
expect(valid).toBe(false);
|
|
769
|
-
expect(schema).toEqual(undefined);
|
|
770
|
-
expect(errors).toEqual([
|
|
771
|
-
{
|
|
772
|
-
message: 'Interface "FooBar" must define an object schema',
|
|
773
|
-
path: ['shapes', 'FooBar', 'schema'],
|
|
774
|
-
type: 'conflict'
|
|
775
|
-
}
|
|
776
|
-
]);
|
|
777
|
-
});
|
|
778
|
-
test('validateSchema - invalid empty object interface', async () => {
|
|
779
|
-
const invalidSchema = createMockSchema('projectId', {
|
|
780
|
-
shapes: {
|
|
781
|
-
FooBar: {
|
|
782
|
-
name: 'FooBar',
|
|
783
|
-
title: 'FooBar',
|
|
784
|
-
id: 'FooBar',
|
|
785
|
-
type: 'interface',
|
|
786
|
-
schema: {
|
|
787
|
-
type: 'object',
|
|
788
|
-
properties: {}
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
});
|
|
793
|
-
const { valid, schema, errors } = await validateSchema(validateContext, invalidSchema);
|
|
794
|
-
expect(valid).toBe(false);
|
|
795
|
-
expect(schema).toEqual(undefined);
|
|
796
|
-
expect(errors).toEqual([
|
|
797
|
-
{
|
|
798
|
-
message: 'Interface "FooBar" must define at least one property',
|
|
799
|
-
path: ['shapes', 'FooBar', 'schema', 'properties'],
|
|
800
|
-
type: 'conflict'
|
|
801
|
-
}
|
|
802
|
-
]);
|
|
803
|
-
});
|
|
804
|
-
test('validateSchema - missing interface', async () => {
|
|
805
|
-
const invalidSchema = createMockSchema('projectId', {
|
|
806
|
-
shapes: {
|
|
807
|
-
Book: {
|
|
808
|
-
name: 'Book',
|
|
809
|
-
title: 'Book',
|
|
810
|
-
id: 'Book',
|
|
811
|
-
interfaces: ['Node'],
|
|
812
|
-
schema: {
|
|
813
|
-
type: 'object',
|
|
814
|
-
properties: {
|
|
815
|
-
title: {
|
|
816
|
-
type: 'string'
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
});
|
|
823
|
-
const { valid, schema, errors } = await validateSchema(validateContext, invalidSchema);
|
|
824
|
-
expect(valid).toBe(false);
|
|
825
|
-
expect(schema).toEqual(undefined);
|
|
826
|
-
expect(errors).toEqual([
|
|
827
|
-
{
|
|
828
|
-
message: 'Invalid ref "Node"',
|
|
829
|
-
path: ['shapes', 'Book', 'interfaces', 0],
|
|
830
|
-
type: 'notFound'
|
|
831
|
-
}
|
|
832
|
-
]);
|
|
833
|
-
});
|
|
834
|
-
test('validateSchema - interface ref to disconnected layer', async () => {
|
|
835
|
-
const invalidSchema = createMockSchema('projectId', {
|
|
836
|
-
shapes: {
|
|
837
|
-
Book: {
|
|
838
|
-
name: 'Book',
|
|
839
|
-
title: 'Book',
|
|
840
|
-
id: 'Book',
|
|
841
|
-
interfaces: ['disconnected:Node'],
|
|
842
|
-
schema: {
|
|
843
|
-
type: 'object',
|
|
844
|
-
properties: {
|
|
845
|
-
title: {
|
|
846
|
-
type: 'string'
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
},
|
|
852
|
-
services: {
|
|
853
|
-
disconnected: {
|
|
854
|
-
id: 'disconnected',
|
|
855
|
-
provider: 'shopify',
|
|
856
|
-
title: 'Shopify',
|
|
857
|
-
namespace: 'Shopify',
|
|
858
|
-
serviceType: 'graphql',
|
|
859
|
-
authenticationType: 'custom'
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
});
|
|
863
|
-
expect((await validateSchema({ ...validateContext, allowDisconnectedLayers: true }, invalidSchema)).valid).toBe(true);
|
|
864
|
-
const { valid, schema, errors } = await validateSchema({ ...validateContext, allowDisconnectedLayers: false }, invalidSchema);
|
|
865
|
-
expect(valid).toBe(false);
|
|
866
|
-
expect(schema).toEqual(undefined);
|
|
867
|
-
expect(errors).toEqual([
|
|
868
|
-
{
|
|
869
|
-
message: 'Invalid ref "disconnected:Node"',
|
|
870
|
-
path: ['shapes', 'Book', 'interfaces', 0],
|
|
871
|
-
type: 'notFound'
|
|
872
|
-
}
|
|
873
|
-
]);
|
|
874
|
-
});
|
|
875
|
-
test('validateSchema - implement non-interface', async () => {
|
|
876
|
-
const objectSchema = {
|
|
877
|
-
type: 'object',
|
|
878
|
-
properties: {
|
|
879
|
-
title: {
|
|
880
|
-
type: 'string'
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
};
|
|
884
|
-
const invalidSchema = createMockSchema('projectId', {
|
|
885
|
-
shapes: {
|
|
886
|
-
Foo: createShape('Foo', objectSchema, { interfaces: ['Bar'] }),
|
|
887
|
-
Bar: createShape('Bar', objectSchema)
|
|
888
|
-
}
|
|
889
|
-
});
|
|
890
|
-
const { valid, schema, errors } = await validateSchema(validateContext, invalidSchema);
|
|
891
|
-
expect(valid).toBe(false);
|
|
892
|
-
expect(schema).toEqual(undefined);
|
|
893
|
-
expect(errors).toEqual([
|
|
894
|
-
{
|
|
895
|
-
message: '"Foo" cannot implement non-interface "Bar"',
|
|
896
|
-
path: ['shapes', 'Foo', 'interfaces', 0],
|
|
897
|
-
type: 'conflict'
|
|
898
|
-
}
|
|
899
|
-
]);
|
|
900
|
-
});
|
|
901
|
-
const Node = {
|
|
902
|
-
name: 'Node',
|
|
903
|
-
title: 'Node',
|
|
904
|
-
id: 'Node',
|
|
905
|
-
type: 'interface',
|
|
906
|
-
schema: {
|
|
907
|
-
type: 'object',
|
|
908
|
-
properties: {
|
|
909
|
-
id: {
|
|
910
|
-
'@tag': 'id',
|
|
911
|
-
type: 'string'
|
|
912
|
-
}
|
|
913
|
-
},
|
|
914
|
-
required: ['id']
|
|
915
|
-
}
|
|
916
|
-
};
|
|
917
|
-
test('validateSchema - invalid implementation non object', async () => {
|
|
918
|
-
const invalidSchema = createMockSchema('projectId', {
|
|
919
|
-
shapes: {
|
|
920
|
-
Node,
|
|
921
|
-
Book: {
|
|
922
|
-
name: 'Book',
|
|
923
|
-
title: 'Book',
|
|
924
|
-
id: 'Book',
|
|
925
|
-
interfaces: ['Node'],
|
|
926
|
-
schema: {
|
|
927
|
-
enum: ['lol', 'it', 'might', 'work']
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
});
|
|
932
|
-
const { valid, schema, errors } = await validateSchema(validateContext, invalidSchema);
|
|
933
|
-
expect(valid).toBe(false);
|
|
934
|
-
expect(schema).toEqual(undefined);
|
|
935
|
-
expect(errors).toEqual([
|
|
936
|
-
{
|
|
937
|
-
message: '"Book" must have an object or extends schema to implement interfaces',
|
|
938
|
-
path: ['shapes', 'Book', 'schema'],
|
|
939
|
-
type: 'conflict'
|
|
940
|
-
}
|
|
941
|
-
]);
|
|
942
|
-
});
|
|
943
|
-
test('validateSchema - invalid implementation missing property', async () => {
|
|
944
|
-
const invalidSchema = createMockSchema('projectId', {
|
|
945
|
-
shapes: {
|
|
946
|
-
Node,
|
|
947
|
-
Book: {
|
|
948
|
-
name: 'Book',
|
|
949
|
-
title: 'Book',
|
|
950
|
-
id: 'Book',
|
|
951
|
-
interfaces: ['Node'],
|
|
952
|
-
schema: {
|
|
953
|
-
type: 'object',
|
|
954
|
-
properties: {
|
|
955
|
-
title: {
|
|
956
|
-
type: 'string'
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
});
|
|
963
|
-
const { valid, schema, errors } = await validateSchema(validateContext, invalidSchema);
|
|
964
|
-
expect(valid).toBe(false);
|
|
965
|
-
expect(schema).toEqual(undefined);
|
|
966
|
-
expect(errors).toEqual([
|
|
967
|
-
{
|
|
968
|
-
message: '"Book" is missing property "id" required by interface "Node"',
|
|
969
|
-
path: ['shapes', 'Book', 'schema', 'properties'],
|
|
970
|
-
type: 'notFound'
|
|
971
|
-
}
|
|
972
|
-
]);
|
|
973
|
-
});
|
|
974
|
-
test('validateSchema - invalid implementation non-matching property', async () => {
|
|
975
|
-
const invalidSchema = createMockSchema('projectId', {
|
|
976
|
-
shapes: {
|
|
977
|
-
Node,
|
|
978
|
-
Book: {
|
|
979
|
-
name: 'Book',
|
|
980
|
-
title: 'Book',
|
|
981
|
-
id: 'Book',
|
|
982
|
-
interfaces: ['Node'],
|
|
983
|
-
schema: {
|
|
984
|
-
type: 'object',
|
|
985
|
-
properties: {
|
|
986
|
-
id: {
|
|
987
|
-
type: 'string'
|
|
988
|
-
// missing @tag
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
});
|
|
995
|
-
const { valid, schema, errors } = await validateSchema(validateContext, invalidSchema);
|
|
996
|
-
expect(valid).toBe(false);
|
|
997
|
-
expect(schema).toEqual(undefined);
|
|
998
|
-
expect(errors).toEqual([
|
|
999
|
-
{
|
|
1000
|
-
message: '"Book.id" must match "Node.id"',
|
|
1001
|
-
path: ['shapes', 'Book', 'schema', 'properties', 'id'],
|
|
1002
|
-
type: 'conflict'
|
|
1003
|
-
}
|
|
1004
|
-
]);
|
|
1005
|
-
});
|
|
1006
|
-
test('validateSchema - invalid implementation non-matching property required', async () => {
|
|
1007
|
-
const invalidSchema = createMockSchema('projectId', {
|
|
1008
|
-
shapes: {
|
|
1009
|
-
Node,
|
|
1010
|
-
Book: {
|
|
1011
|
-
name: 'Book',
|
|
1012
|
-
title: 'Book',
|
|
1013
|
-
id: 'Book',
|
|
1014
|
-
interfaces: ['Node'],
|
|
1015
|
-
schema: {
|
|
1016
|
-
type: 'object',
|
|
1017
|
-
properties: {
|
|
1018
|
-
id: {
|
|
1019
|
-
type: 'string',
|
|
1020
|
-
'@tag': 'id'
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
});
|
|
1027
|
-
const { valid, schema, errors } = await validateSchema(validateContext, invalidSchema);
|
|
1028
|
-
expect(valid).toBe(false);
|
|
1029
|
-
expect(schema).toEqual(undefined);
|
|
1030
|
-
expect(errors).toEqual([
|
|
1031
|
-
{
|
|
1032
|
-
message: '"Book.id" must be required to match "Node.id"',
|
|
1033
|
-
path: ['shapes', 'Book', 'schema', 'properties', 'id'],
|
|
1034
|
-
type: 'conflict'
|
|
1035
|
-
}
|
|
1036
|
-
]);
|
|
1037
|
-
});
|
|
1038
|
-
const createMockRoleInput = () => ({
|
|
1039
|
-
name: 'mockRole',
|
|
1040
|
-
conditions: {
|
|
1041
|
-
'ForAllValues:StringEquals': {
|
|
1042
|
-
'claims:provider': 'mockService'
|
|
1043
|
-
}
|
|
1044
|
-
},
|
|
1045
|
-
permissions: {
|
|
1046
|
-
Version: '2012-10-17',
|
|
1047
|
-
Statement: [
|
|
1048
|
-
{
|
|
1049
|
-
Resource: ['ts:workflow-step:4a0614fc-9519-4dbe-976a-99622dc4fe37:*'],
|
|
1050
|
-
Effect: 'Allow',
|
|
1051
|
-
Action: ['workflow-step:*']
|
|
1052
|
-
},
|
|
1053
|
-
{
|
|
1054
|
-
Resource: ['ts:graphql:4a0614fc-9519-4dbe-976a-99622dc4fe37:Query.getAsset'],
|
|
1055
|
-
Effect: 'Allow',
|
|
1056
|
-
Action: ['graphql:execute']
|
|
1057
|
-
}
|
|
1058
|
-
]
|
|
1059
|
-
}
|
|
1060
|
-
});
|
|
1061
|
-
const createMockRoleImport = () => [createMockRoleInput()];
|
|
1062
|
-
test('validateRoleInput - successfully validates a valid role input', async () => {
|
|
1063
|
-
const mockRoleInput = createMockRoleInput();
|
|
1064
|
-
const roleInput = validateRoleInput(mockRoleInput);
|
|
1065
|
-
expect(roleInput).toEqual(mockRoleInput);
|
|
1066
|
-
});
|
|
1067
|
-
test('validateRoleInput - successfully validates a valid role input with additional properties', async () => {
|
|
1068
|
-
const mockRoleInput = createMockRoleInput();
|
|
1069
|
-
mockRoleInput.__updatedRegion = 'us-east-1';
|
|
1070
|
-
mockRoleInput.__createdRegion = 'us-east-1';
|
|
1071
|
-
const roleInput = validateRoleInput(mockRoleInput);
|
|
1072
|
-
expect(roleInput).toEqual(mockRoleInput);
|
|
1073
|
-
});
|
|
1074
|
-
test('validateRoleInput - throw when an input is invalid', async () => {
|
|
1075
|
-
const mockRoleInput = createMockRoleInput();
|
|
1076
|
-
expect(() => validateRoleInput({ ...mockRoleInput, name: undefined })).toThrowErrorMatchingInlineSnapshot('[SchemaValidationError: RoleInput was invalid]');
|
|
1077
|
-
});
|
|
1078
|
-
test('validateRoleImport - successfully validates a valid role import', async () => {
|
|
1079
|
-
const mockRoleImport = createMockRoleImport();
|
|
1080
|
-
const roleImport = ensureValidRoleImport(mockRoleImport);
|
|
1081
|
-
expect(roleImport).toEqual(mockRoleImport);
|
|
1082
|
-
});
|
|
1083
|
-
test('validateRoleImport - successfully validates a valid role import with additional properties', async () => {
|
|
1084
|
-
const mockRoleImport = createMockRoleImport();
|
|
1085
|
-
mockRoleImport.__updatedRegion = 'us-east-1';
|
|
1086
|
-
mockRoleImport.__createdRegion = 'us-east-1';
|
|
1087
|
-
const roleImport = ensureValidRoleImport(mockRoleImport);
|
|
1088
|
-
expect(roleImport).toEqual(mockRoleImport);
|
|
1089
|
-
});
|
|
1090
|
-
test('validateRoleImport - throw when an import is invalid', async () => {
|
|
1091
|
-
const mockRoleImport = createMockRoleImport();
|
|
1092
|
-
mockRoleImport[0].name = undefined;
|
|
1093
|
-
expect(() => ensureValidRoleImport(mockRoleImport)).toThrowErrorMatchingInlineSnapshot('[SchemaValidationError: Roles were invalid]');
|
|
1094
|
-
});
|
|
1095
|
-
test('validateProjectSchemaImport - successfully validates a valid project schema import', () => {
|
|
1096
|
-
const mockProjectSchema = shapeBooksSchemaLatest();
|
|
1097
|
-
const projectSchema = ensureValidProjectSchemaImport(mockProjectSchema);
|
|
1098
|
-
expect(projectSchema).toEqual(mockProjectSchema);
|
|
1099
|
-
});
|
|
1100
|
-
test('ensureValidProjectSchemaImport - validates a minimal import schema', () => {
|
|
1101
|
-
const mockProjectSchema = omit(shapeBooksSchemaLatest(), [
|
|
1102
|
-
'projectId',
|
|
1103
|
-
'locales',
|
|
1104
|
-
'defaultLocale',
|
|
1105
|
-
'author'
|
|
1106
|
-
]);
|
|
1107
|
-
const result = ensureValidProjectSchemaImport(mockProjectSchema);
|
|
1108
|
-
expect(result).toBeDefined();
|
|
1109
|
-
});
|
|
1110
|
-
test('ensureValidProjectSchemaImport - throw when an import is invalid', async () => {
|
|
1111
|
-
const mockProjectSchema = shapeBooksSchemaLatest();
|
|
1112
|
-
// @ts-expect-error Intentionally invalid
|
|
1113
|
-
mockProjectSchema.shapes = undefined;
|
|
1114
|
-
expect(() => ensureValidProjectSchemaImport(mockProjectSchema)).toThrowErrorMatchingInlineSnapshot('[SchemaValidationError: ProjectSchema could not be validated]');
|
|
1115
|
-
});
|
|
1116
|
-
test('ensureValidProjectSchemaImport - successfully validates a valid project schema import', () => {
|
|
1117
|
-
const mockProjectSchema = shapeBooksSchemaLatest();
|
|
1118
|
-
const projectSchema = ensureValidProjectSchemaImport(mockProjectSchema);
|
|
1119
|
-
expect(projectSchema).toEqual(mockProjectSchema);
|
|
1120
|
-
});
|
|
1121
|
-
it('should validate with a maximal example filling in every optional field', async () => {
|
|
1122
|
-
const mockSchema = createMockSchema('<project-id>', {
|
|
1123
|
-
services: {
|
|
1124
|
-
rick: createMockStoredServiceConfig('rick')
|
|
1125
|
-
},
|
|
1126
|
-
queries: {
|
|
1127
|
-
Rick_characters: {
|
|
1128
|
-
shape: {
|
|
1129
|
-
type: 'array',
|
|
1130
|
-
items: {
|
|
1131
|
-
'@ref': 'rick:Character'
|
|
1132
|
-
}
|
|
1133
|
-
},
|
|
1134
|
-
resolver: {
|
|
1135
|
-
name: 'rest:get',
|
|
1136
|
-
service: 'rick',
|
|
1137
|
-
searchParams: {
|
|
1138
|
-
ops: [
|
|
1139
|
-
{
|
|
1140
|
-
path: '$',
|
|
1141
|
-
mapping: '$args'
|
|
1142
|
-
},
|
|
1143
|
-
{
|
|
1144
|
-
path: 'foo',
|
|
1145
|
-
op: 'set',
|
|
1146
|
-
value: 'FOO'
|
|
1147
|
-
},
|
|
1148
|
-
{
|
|
1149
|
-
path: 'bar',
|
|
1150
|
-
op: 'extend',
|
|
1151
|
-
value: { foo: 'FOO' }
|
|
1152
|
-
},
|
|
1153
|
-
{
|
|
1154
|
-
path: 'bar',
|
|
1155
|
-
op: 'remove'
|
|
1156
|
-
},
|
|
1157
|
-
{
|
|
1158
|
-
path: 'bar',
|
|
1159
|
-
op: 'concat',
|
|
1160
|
-
value: ['FOO']
|
|
1161
|
-
}
|
|
1162
|
-
],
|
|
1163
|
-
serialize: {
|
|
1164
|
-
defaults: {
|
|
1165
|
-
style: 'form',
|
|
1166
|
-
explode: true,
|
|
1167
|
-
allowEmptyValue: true
|
|
1168
|
-
},
|
|
1169
|
-
paths: {
|
|
1170
|
-
foo: {
|
|
1171
|
-
style: 'deepObject',
|
|
1172
|
-
explode: true,
|
|
1173
|
-
allowReserved: true
|
|
1174
|
-
},
|
|
1175
|
-
bar: {
|
|
1176
|
-
style: 'pipeDelimited',
|
|
1177
|
-
explode: false,
|
|
1178
|
-
skipEncoding: true
|
|
1179
|
-
},
|
|
1180
|
-
baz: {
|
|
1181
|
-
style: 'spaceDelimited'
|
|
1182
|
-
},
|
|
1183
|
-
fuzz: {
|
|
1184
|
-
style: 'none'
|
|
1185
|
-
},
|
|
1186
|
-
kermit: {
|
|
1187
|
-
contentType: 'application/x-www-form-urlencoded'
|
|
1188
|
-
},
|
|
1189
|
-
frog: {
|
|
1190
|
-
contentType: 'application/json'
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
}
|
|
1194
|
-
},
|
|
1195
|
-
path: {
|
|
1196
|
-
ops: [
|
|
1197
|
-
{
|
|
1198
|
-
path: '$',
|
|
1199
|
-
mapping: '$args'
|
|
1200
|
-
}
|
|
1201
|
-
],
|
|
1202
|
-
serialize: {
|
|
1203
|
-
defaults: {
|
|
1204
|
-
style: 'label',
|
|
1205
|
-
explode: true,
|
|
1206
|
-
allowEmptyValue: true
|
|
1207
|
-
},
|
|
1208
|
-
paths: {
|
|
1209
|
-
chunky: {
|
|
1210
|
-
style: 'matrix',
|
|
1211
|
-
explode: false,
|
|
1212
|
-
allowReserved: true
|
|
1213
|
-
},
|
|
1214
|
-
monkey: {
|
|
1215
|
-
style: 'label',
|
|
1216
|
-
skipEncoding: true
|
|
1217
|
-
},
|
|
1218
|
-
cherry: {
|
|
1219
|
-
style: 'none',
|
|
1220
|
-
explode: true
|
|
1221
|
-
},
|
|
1222
|
-
kermit: {
|
|
1223
|
-
contentType: 'application/x-www-form-urlencoded'
|
|
1224
|
-
},
|
|
1225
|
-
frog: {
|
|
1226
|
-
contentType: 'application/json'
|
|
1227
|
-
}
|
|
1228
|
-
},
|
|
1229
|
-
template: '/:foo'
|
|
1230
|
-
}
|
|
1231
|
-
},
|
|
1232
|
-
form: {
|
|
1233
|
-
ops: [
|
|
1234
|
-
{
|
|
1235
|
-
path: '$',
|
|
1236
|
-
mapping: '$args'
|
|
1237
|
-
}
|
|
1238
|
-
],
|
|
1239
|
-
serialize: {
|
|
1240
|
-
defaults: {
|
|
1241
|
-
style: 'deepObject',
|
|
1242
|
-
explode: true
|
|
1243
|
-
},
|
|
1244
|
-
paths: {
|
|
1245
|
-
chunky: {
|
|
1246
|
-
style: 'form',
|
|
1247
|
-
explode: false
|
|
1248
|
-
},
|
|
1249
|
-
monkey: {
|
|
1250
|
-
style: 'pipeDelimited'
|
|
1251
|
-
},
|
|
1252
|
-
cherry: {
|
|
1253
|
-
style: 'spaceDelimited',
|
|
1254
|
-
explode: true
|
|
1255
|
-
},
|
|
1256
|
-
kermit: {
|
|
1257
|
-
contentType: 'application/x-www-form-urlencoded'
|
|
1258
|
-
},
|
|
1259
|
-
frog: {
|
|
1260
|
-
contentType: 'application/json'
|
|
1261
|
-
}
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1264
|
-
},
|
|
1265
|
-
json: {
|
|
1266
|
-
ops: [
|
|
1267
|
-
{
|
|
1268
|
-
path: '$',
|
|
1269
|
-
mapping: '$args'
|
|
1270
|
-
}
|
|
1271
|
-
]
|
|
1272
|
-
},
|
|
1273
|
-
body: {
|
|
1274
|
-
ops: [
|
|
1275
|
-
{
|
|
1276
|
-
path: '$',
|
|
1277
|
-
mapping: '$args'
|
|
1278
|
-
}
|
|
1279
|
-
],
|
|
1280
|
-
serialize: {
|
|
1281
|
-
content: {
|
|
1282
|
-
contentType: 'application/x-www-form-urlencoded'
|
|
1283
|
-
}
|
|
1284
|
-
}
|
|
1285
|
-
},
|
|
1286
|
-
headers: {
|
|
1287
|
-
ops: [
|
|
1288
|
-
{
|
|
1289
|
-
path: 'x-user-id',
|
|
1290
|
-
mapping: '$args'
|
|
1291
|
-
}
|
|
1292
|
-
],
|
|
1293
|
-
serialize: {
|
|
1294
|
-
defaults: {
|
|
1295
|
-
style: 'simple',
|
|
1296
|
-
explode: true
|
|
1297
|
-
},
|
|
1298
|
-
paths: {
|
|
1299
|
-
'x-user-id': {
|
|
1300
|
-
style: 'simple',
|
|
1301
|
-
explode: false
|
|
1302
|
-
},
|
|
1303
|
-
'y-user-id': {
|
|
1304
|
-
style: 'none'
|
|
1305
|
-
}
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
},
|
|
1309
|
-
args: {
|
|
1310
|
-
ops: [
|
|
1311
|
-
{
|
|
1312
|
-
path: '$',
|
|
1313
|
-
mapping: '$args'
|
|
1314
|
-
}
|
|
1315
|
-
]
|
|
1316
|
-
},
|
|
1317
|
-
results: {
|
|
1318
|
-
ops: [
|
|
1319
|
-
{
|
|
1320
|
-
path: '$',
|
|
1321
|
-
mapping: '$finalStep.results'
|
|
1322
|
-
},
|
|
1323
|
-
{
|
|
1324
|
-
path: '[*].firstName',
|
|
1325
|
-
mapping: [
|
|
1326
|
-
[
|
|
1327
|
-
'jsonPath',
|
|
1328
|
-
{
|
|
1329
|
-
path: '$loop.item.name'
|
|
1330
|
-
}
|
|
1331
|
-
],
|
|
1332
|
-
[
|
|
1333
|
-
'replace',
|
|
1334
|
-
{
|
|
1335
|
-
regexp: '[^\\s]+$',
|
|
1336
|
-
replacement: ''
|
|
1337
|
-
}
|
|
1338
|
-
],
|
|
1339
|
-
['trim', {}]
|
|
1340
|
-
]
|
|
1341
|
-
}
|
|
1342
|
-
]
|
|
1343
|
-
}
|
|
1344
|
-
}
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
|
-
});
|
|
1348
|
-
const { valid, errors } = await validateSchema(validateContext, mockSchema);
|
|
1349
|
-
expect(errors).toEqual(undefined);
|
|
1350
|
-
expect(valid).toBe(true);
|
|
1351
|
-
});
|
|
1352
|
-
it('should validate with a minimal example where optional props are not present', async () => {
|
|
1353
|
-
const mockSchema = createMockSchema('<project-id>', {
|
|
1354
|
-
services: {
|
|
1355
|
-
rick: createMockStoredServiceConfig('rick')
|
|
1356
|
-
},
|
|
1357
|
-
queries: {
|
|
1358
|
-
Rick_characters: {
|
|
1359
|
-
shape: {
|
|
1360
|
-
type: 'array',
|
|
1361
|
-
items: {
|
|
1362
|
-
'@ref': 'rick:Character'
|
|
1363
|
-
}
|
|
1364
|
-
},
|
|
1365
|
-
resolver: {
|
|
1366
|
-
name: 'rest:get',
|
|
1367
|
-
service: 'rick',
|
|
1368
|
-
searchParams: {
|
|
1369
|
-
ops: [
|
|
1370
|
-
{
|
|
1371
|
-
path: '$',
|
|
1372
|
-
op: 'remove'
|
|
1373
|
-
}
|
|
1374
|
-
]
|
|
1375
|
-
},
|
|
1376
|
-
path: {
|
|
1377
|
-
ops: [
|
|
1378
|
-
{
|
|
1379
|
-
path: '$',
|
|
1380
|
-
value: 'foo'
|
|
1381
|
-
}
|
|
1382
|
-
],
|
|
1383
|
-
serialize: {
|
|
1384
|
-
template: '/:foo'
|
|
1385
|
-
}
|
|
1386
|
-
},
|
|
1387
|
-
form: {
|
|
1388
|
-
ops: [
|
|
1389
|
-
{
|
|
1390
|
-
path: '$',
|
|
1391
|
-
mapping: 'bar'
|
|
1392
|
-
}
|
|
1393
|
-
]
|
|
1394
|
-
},
|
|
1395
|
-
json: {
|
|
1396
|
-
ops: [
|
|
1397
|
-
{
|
|
1398
|
-
path: '$',
|
|
1399
|
-
ops: [
|
|
1400
|
-
{
|
|
1401
|
-
path: '$',
|
|
1402
|
-
value: 'nested'
|
|
1403
|
-
}
|
|
1404
|
-
]
|
|
1405
|
-
}
|
|
1406
|
-
]
|
|
1407
|
-
},
|
|
1408
|
-
body: {
|
|
1409
|
-
ops: [
|
|
1410
|
-
{
|
|
1411
|
-
path: '$',
|
|
1412
|
-
op: 'remove'
|
|
1413
|
-
}
|
|
1414
|
-
]
|
|
1415
|
-
},
|
|
1416
|
-
headers: {
|
|
1417
|
-
ops: [
|
|
1418
|
-
{
|
|
1419
|
-
path: 'x-user-id',
|
|
1420
|
-
value: 'userid'
|
|
1421
|
-
}
|
|
1422
|
-
]
|
|
1423
|
-
},
|
|
1424
|
-
args: {
|
|
1425
|
-
ops: [
|
|
1426
|
-
{
|
|
1427
|
-
path: '$',
|
|
1428
|
-
mapping: 'bar'
|
|
1429
|
-
}
|
|
1430
|
-
]
|
|
1431
|
-
},
|
|
1432
|
-
results: {
|
|
1433
|
-
ops: [
|
|
1434
|
-
{
|
|
1435
|
-
path: '$',
|
|
1436
|
-
ops: [
|
|
1437
|
-
{
|
|
1438
|
-
path: '$',
|
|
1439
|
-
value: 'nested'
|
|
1440
|
-
}
|
|
1441
|
-
]
|
|
1442
|
-
}
|
|
1443
|
-
]
|
|
1444
|
-
}
|
|
1445
|
-
}
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
});
|
|
1449
|
-
const { valid, errors } = await validateSchema(validateContext, mockSchema);
|
|
1450
|
-
expect(errors).toEqual(undefined);
|
|
1451
|
-
expect(valid).toBe(true);
|
|
1452
|
-
});
|
|
1453
|
-
it('should fail to validate when required props are not present or have bad values', async () => {
|
|
1454
|
-
const bogus = {
|
|
1455
|
-
services: {
|
|
1456
|
-
rick: createMockStoredServiceConfig('rick')
|
|
1457
|
-
},
|
|
1458
|
-
queries: {
|
|
1459
|
-
Rick_characters: {
|
|
1460
|
-
shape: {
|
|
1461
|
-
type: 'array',
|
|
1462
|
-
items: {
|
|
1463
|
-
'@ref': 'rick:Character'
|
|
1464
|
-
}
|
|
1465
|
-
},
|
|
1466
|
-
resolver: {
|
|
1467
|
-
name: 'rest:get',
|
|
1468
|
-
service: 'rick',
|
|
1469
|
-
searchParams: {
|
|
1470
|
-
serialize: {
|
|
1471
|
-
defaults: {
|
|
1472
|
-
explode: true,
|
|
1473
|
-
allowEmptyValue: true
|
|
1474
|
-
},
|
|
1475
|
-
paths: {
|
|
1476
|
-
fuzz: {}
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
},
|
|
1480
|
-
pathParams: {},
|
|
1481
|
-
form: {
|
|
1482
|
-
ops: [
|
|
1483
|
-
{
|
|
1484
|
-
op: 'nah'
|
|
1485
|
-
}
|
|
1486
|
-
],
|
|
1487
|
-
serialize: {
|
|
1488
|
-
defaults: {
|
|
1489
|
-
contentType: 'application/nah'
|
|
1490
|
-
}
|
|
1491
|
-
}
|
|
1492
|
-
},
|
|
1493
|
-
json: {
|
|
1494
|
-
ops: [
|
|
1495
|
-
{
|
|
1496
|
-
path: '$',
|
|
1497
|
-
nah: 'nope'
|
|
1498
|
-
}
|
|
1499
|
-
]
|
|
1500
|
-
},
|
|
1501
|
-
headers: {
|
|
1502
|
-
ops: [
|
|
1503
|
-
{
|
|
1504
|
-
path: 'x-user-id'
|
|
1505
|
-
}
|
|
1506
|
-
],
|
|
1507
|
-
serialize: {
|
|
1508
|
-
defaults: {
|
|
1509
|
-
style: 'form'
|
|
1510
|
-
}
|
|
1511
|
-
}
|
|
1512
|
-
},
|
|
1513
|
-
args: {
|
|
1514
|
-
ops: [
|
|
1515
|
-
{
|
|
1516
|
-
path: '$'
|
|
1517
|
-
}
|
|
1518
|
-
],
|
|
1519
|
-
serialize: {
|
|
1520
|
-
style: 'form'
|
|
1521
|
-
}
|
|
1522
|
-
}
|
|
1523
|
-
}
|
|
1524
|
-
}
|
|
1525
|
-
}
|
|
1526
|
-
};
|
|
1527
|
-
// @ts-expect-error
|
|
1528
|
-
const mockSchema = createMockSchema('<project-id>', bogus);
|
|
1529
|
-
const { valid, errors } = await validateSchema(validateContext, mockSchema);
|
|
1530
|
-
expect(valid).toBe(false);
|
|
1531
|
-
expect(errors).toBeDefined();
|
|
1532
|
-
});
|
|
1533
|
-
it('should fail to validate ops are confused', async () => {
|
|
1534
|
-
const bogus = {
|
|
1535
|
-
schemaVersion: '3.7.0',
|
|
1536
|
-
services: {
|
|
1537
|
-
rick: createMockStoredServiceConfig('rick')
|
|
1538
|
-
},
|
|
1539
|
-
queries: {
|
|
1540
|
-
Rick_characters: {
|
|
1541
|
-
shape: {
|
|
1542
|
-
type: 'array',
|
|
1543
|
-
items: {
|
|
1544
|
-
'@ref': 'rick:Character'
|
|
1545
|
-
}
|
|
1546
|
-
},
|
|
1547
|
-
resolver: {
|
|
1548
|
-
name: 'rest:get',
|
|
1549
|
-
service: 'rick',
|
|
1550
|
-
searchParams: {
|
|
1551
|
-
ops: [
|
|
1552
|
-
{
|
|
1553
|
-
path: '$',
|
|
1554
|
-
value: 'foo',
|
|
1555
|
-
mapping: '$args.foo'
|
|
1556
|
-
}
|
|
1557
|
-
]
|
|
1558
|
-
}
|
|
1559
|
-
}
|
|
1560
|
-
}
|
|
1561
|
-
}
|
|
1562
|
-
};
|
|
1563
|
-
// @ts-expect-error
|
|
1564
|
-
const mockSchema = createMockSchema('<project-id>', bogus);
|
|
1565
|
-
const { valid, errors } = await validateSchema(validateContext, mockSchema);
|
|
1566
|
-
expect(valid).toBe(false);
|
|
1567
|
-
expect(errors).toBeDefined();
|
|
1568
|
-
});
|
|
1569
|
-
const characterSchema = createMockSchema('<project-id>', {
|
|
1570
|
-
services: {
|
|
1571
|
-
rick: createMockStoredServiceConfig('rick')
|
|
1572
|
-
},
|
|
1573
|
-
shapes: {
|
|
1574
|
-
Character: {
|
|
1575
|
-
id: 'Character',
|
|
1576
|
-
name: 'Character',
|
|
1577
|
-
title: 'Character',
|
|
1578
|
-
model: {
|
|
1579
|
-
type: 'single'
|
|
1580
|
-
},
|
|
1581
|
-
schema: {
|
|
1582
|
-
type: 'object',
|
|
1583
|
-
properties: {
|
|
1584
|
-
name: {
|
|
1585
|
-
type: 'string'
|
|
1586
|
-
}
|
|
1587
|
-
}
|
|
1588
|
-
}
|
|
1589
|
-
}
|
|
1590
|
-
},
|
|
1591
|
-
queries: {
|
|
1592
|
-
getCharacter: {
|
|
1593
|
-
shape: 'Character',
|
|
1594
|
-
resolver: {
|
|
1595
|
-
name: 'shapedb:get',
|
|
1596
|
-
service: 'shapedb',
|
|
1597
|
-
shapeName: 'Character'
|
|
1598
|
-
}
|
|
1599
|
-
}
|
|
1600
|
-
}
|
|
1601
|
-
});
|
|
1602
|
-
it('should validate with valid resolver config', async () => {
|
|
1603
|
-
const schema = {
|
|
1604
|
-
...characterSchema,
|
|
1605
|
-
queries: {
|
|
1606
|
-
a: {
|
|
1607
|
-
shape: 'Character',
|
|
1608
|
-
resolver: {
|
|
1609
|
-
name: 'rest:get',
|
|
1610
|
-
service: 'rick',
|
|
1611
|
-
path: 'characters'
|
|
1612
|
-
}
|
|
1613
|
-
},
|
|
1614
|
-
b: {
|
|
1615
|
-
shape: 'Character',
|
|
1616
|
-
resolver: {
|
|
1617
|
-
name: 'graphql:query',
|
|
1618
|
-
service: 'rick',
|
|
1619
|
-
fieldName: 'characters'
|
|
1620
|
-
}
|
|
1621
|
-
},
|
|
1622
|
-
c: {
|
|
1623
|
-
shape: 'Character',
|
|
1624
|
-
resolver: {
|
|
1625
|
-
name: 'shapedb:find',
|
|
1626
|
-
service: 'shapedb',
|
|
1627
|
-
shapeName: 'Character'
|
|
1628
|
-
}
|
|
1629
|
-
},
|
|
1630
|
-
d: {
|
|
1631
|
-
shape: 'Character',
|
|
1632
|
-
resolver: {
|
|
1633
|
-
name: 'awsLambda:invoke',
|
|
1634
|
-
service: 'rick',
|
|
1635
|
-
functionName: 'my-lambda'
|
|
1636
|
-
}
|
|
1637
|
-
},
|
|
1638
|
-
e: {
|
|
1639
|
-
shape: 'Character',
|
|
1640
|
-
resolver: {
|
|
1641
|
-
name: 'util:wrap'
|
|
1642
|
-
}
|
|
1643
|
-
},
|
|
1644
|
-
f: {
|
|
1645
|
-
shape: 'Character',
|
|
1646
|
-
resolver: {
|
|
1647
|
-
name: 'rest:get',
|
|
1648
|
-
service: 'rick',
|
|
1649
|
-
path: {
|
|
1650
|
-
ops: [
|
|
1651
|
-
{
|
|
1652
|
-
path: 'foo',
|
|
1653
|
-
value: 'FOO'
|
|
1654
|
-
}
|
|
1655
|
-
],
|
|
1656
|
-
serialize: {
|
|
1657
|
-
template: '/bar/{foo}'
|
|
1658
|
-
}
|
|
1659
|
-
}
|
|
1660
|
-
}
|
|
1661
|
-
},
|
|
1662
|
-
g: {
|
|
1663
|
-
shape: 'Character',
|
|
1664
|
-
resolver: {
|
|
1665
|
-
name: 'awsLambda:invoke',
|
|
1666
|
-
service: 'rick',
|
|
1667
|
-
functionName: {
|
|
1668
|
-
ops: [
|
|
1669
|
-
{
|
|
1670
|
-
path: 'foo',
|
|
1671
|
-
value: 'FOO'
|
|
1672
|
-
}
|
|
1673
|
-
],
|
|
1674
|
-
serialize: {
|
|
1675
|
-
template: 'us-east-1:{foo}'
|
|
1676
|
-
}
|
|
1677
|
-
}
|
|
1678
|
-
}
|
|
1679
|
-
}
|
|
1680
|
-
}
|
|
1681
|
-
};
|
|
1682
|
-
const { valid, errors } = await validateSchema(validateContext, schema);
|
|
1683
|
-
expect(errors).toEqual(undefined);
|
|
1684
|
-
expect(valid).toBe(true);
|
|
1685
|
-
});
|
|
1686
|
-
it('should fail to validate an invalid resolver.shapeName', async () => {
|
|
1687
|
-
const invalidSchema = set(characterSchema, ['queries', 'getCharacter', 'resolver', 'shapeName'], 'bogus');
|
|
1688
|
-
const { valid, errors } = await validateSchema(validateContext, invalidSchema);
|
|
1689
|
-
expect(valid).toBe(false);
|
|
1690
|
-
expect(errors).toEqual([
|
|
1691
|
-
{
|
|
1692
|
-
type: 'notFound',
|
|
1693
|
-
message: 'Invalid Model Shape "bogus"',
|
|
1694
|
-
path: ['queries', 'getCharacter', 'resolver', 'shapeName']
|
|
1695
|
-
}
|
|
1696
|
-
]);
|
|
1697
|
-
});
|
|
1698
|
-
it('should fail to validate resolvers without required props', async () => {
|
|
1699
|
-
const schema = {
|
|
1700
|
-
...characterSchema,
|
|
1701
|
-
queries: {
|
|
1702
|
-
a: {
|
|
1703
|
-
shape: {
|
|
1704
|
-
type: 'array',
|
|
1705
|
-
items: {
|
|
1706
|
-
'@ref': 'rick:Character'
|
|
1707
|
-
}
|
|
1708
|
-
},
|
|
1709
|
-
resolver: {
|
|
1710
|
-
name: 'rest:get',
|
|
1711
|
-
service: 'rick',
|
|
1712
|
-
notwhatyouneed: ''
|
|
1713
|
-
}
|
|
1714
|
-
}
|
|
1715
|
-
}
|
|
1716
|
-
};
|
|
1717
|
-
const { valid, errors } = await validateSchema(validateContext, schema);
|
|
1718
|
-
expect(valid).toBe(false);
|
|
1719
|
-
expect(errors).toBeDefined();
|
|
1720
|
-
});
|
|
1721
|
-
it('should fail to validate resolvers without required props', async () => {
|
|
1722
|
-
const schema = {
|
|
1723
|
-
...characterSchema,
|
|
1724
|
-
queries: {
|
|
1725
|
-
a: {
|
|
1726
|
-
shape: {
|
|
1727
|
-
type: 'array',
|
|
1728
|
-
items: {
|
|
1729
|
-
'@ref': 'rick:Character'
|
|
1730
|
-
}
|
|
1731
|
-
},
|
|
1732
|
-
resolver: {
|
|
1733
|
-
name: 'graphql:query',
|
|
1734
|
-
service: 'rick'
|
|
1735
|
-
}
|
|
1736
|
-
}
|
|
1737
|
-
}
|
|
1738
|
-
};
|
|
1739
|
-
const { valid, errors } = await validateSchema(validateContext, schema);
|
|
1740
|
-
expect(valid).toBe(false);
|
|
1741
|
-
expect(errors).toBeDefined();
|
|
1742
|
-
});
|
|
1743
|
-
function mockSchemaWithCachedShapes(nestedCount = 1) {
|
|
1744
|
-
const definition = {
|
|
1745
|
-
type: 'array',
|
|
1746
|
-
items: { type: 'string' },
|
|
1747
|
-
'@indexed': { nested: true }
|
|
1748
|
-
};
|
|
1749
|
-
const properties = new Array(nestedCount).fill(definition).reduce((prev, propDef) => ({
|
|
1750
|
-
...prev,
|
|
1751
|
-
[shortid()]: propDef
|
|
1752
|
-
}), {});
|
|
1753
|
-
const shapeName = 'ShapeA';
|
|
1754
|
-
const shape = createShape(shapeName, {
|
|
1755
|
-
type: 'object',
|
|
1756
|
-
properties
|
|
1757
|
-
}, {
|
|
1758
|
-
loaders: {
|
|
1759
|
-
list: {
|
|
1760
|
-
query: 'getList'
|
|
1761
|
-
}
|
|
1762
|
-
},
|
|
1763
|
-
cache: {
|
|
1764
|
-
enabled: true
|
|
1765
|
-
}
|
|
1766
|
-
});
|
|
1767
|
-
const shapeBName = 'ShapeB';
|
|
1768
|
-
const shapeB = createShape(shapeBName, {
|
|
1769
|
-
extends: [
|
|
1770
|
-
{
|
|
1771
|
-
type: 'object',
|
|
1772
|
-
properties
|
|
1773
|
-
}
|
|
1774
|
-
]
|
|
1775
|
-
}, {
|
|
1776
|
-
loaders: {
|
|
1777
|
-
list: {
|
|
1778
|
-
query: 'getList'
|
|
1779
|
-
}
|
|
1780
|
-
},
|
|
1781
|
-
cache: { enabled: true }
|
|
1782
|
-
});
|
|
1783
|
-
return createMockSchema('<project-id>', {
|
|
1784
|
-
shapes: {
|
|
1785
|
-
[shapeName]: shape,
|
|
1786
|
-
[shapeBName]: shapeB
|
|
1787
|
-
},
|
|
1788
|
-
queries: {
|
|
1789
|
-
getList: {
|
|
1790
|
-
shape: 'PaginatedList<ShapeA>',
|
|
1791
|
-
resolver: {
|
|
1792
|
-
name: 'shapedb:list',
|
|
1793
|
-
service: 'shapedb',
|
|
1794
|
-
shapeName
|
|
1795
|
-
}
|
|
1796
|
-
}
|
|
1797
|
-
}
|
|
1798
|
-
});
|
|
1799
|
-
}
|
|
1800
|
-
it('should validate with @indexed.nested set', async () => {
|
|
1801
|
-
const { valid, errors } = await validateSchema(validateContext, mockSchemaWithCachedShapes());
|
|
1802
|
-
expect(valid).toBe(true);
|
|
1803
|
-
expect(errors).toEqual(undefined);
|
|
1804
|
-
});
|
|
1805
|
-
it('should fail to validate with more than 50 @indexed.nested values set', async () => {
|
|
1806
|
-
const { valid, errors } = await validateSchema(validateContext, mockSchemaWithCachedShapes(51));
|
|
1807
|
-
expect(valid).toBe(false);
|
|
1808
|
-
expect(errors?.[0]).toMatchInlineSnapshot(`
|
|
1809
|
-
{
|
|
1810
|
-
"message": "102 nested indexes found",
|
|
1811
|
-
"path": [
|
|
1812
|
-
"shapes",
|
|
1813
|
-
],
|
|
1814
|
-
"type": "tooMany",
|
|
1815
|
-
}
|
|
1816
|
-
`);
|
|
1817
|
-
});
|
|
1818
|
-
it('should fail to validate if @indexed is not on an array', async () => {
|
|
1819
|
-
const schema = mockSchemaWithCachedShapes();
|
|
1820
|
-
const shapePropertyName = Object.keys(schema.shapes.ShapeA.schema.properties)[0];
|
|
1821
|
-
const invalidSchema = set(schema, `shapes.ShapeA.schema.properties.${shapePropertyName}.type`, 'string');
|
|
1822
|
-
const { valid, errors } = await validateSchema(validateContext, invalidSchema);
|
|
1823
|
-
expect(valid).toBe(false);
|
|
1824
|
-
expect(errors?.[0].message).toMatchInlineSnapshot(`"Nested indexes may only appear next to fields of type: "array""`);
|
|
1825
|
-
});
|
|
1826
|
-
it('should fail to validate if @indexed is not on an array inside an extends', async () => {
|
|
1827
|
-
const schema = mockSchemaWithCachedShapes();
|
|
1828
|
-
const shapePropertyName = Object.keys(schema.shapes.ShapeB.schema.extends[0].properties)[0];
|
|
1829
|
-
const invalidSchema = set(schema, `shapes.ShapeB.schema.extends[0].properties.${shapePropertyName}.type`, 'string');
|
|
1830
|
-
const { valid, errors } = await validateSchema(validateContext, invalidSchema);
|
|
1831
|
-
expect(valid).toBe(false);
|
|
1832
|
-
expect(errors?.[0].message).toMatchInlineSnapshot(`"Nested indexes may only appear next to fields of type: "array""`);
|
|
1833
|
-
});
|
|
1834
|
-
test('validateSchema - invalid tool in agent', async () => {
|
|
1835
|
-
const invalidSchema = agentSchema();
|
|
1836
|
-
invalidSchema['ai-experimental'].agents.findDogColor.states.ccc.execution.tools = [
|
|
1837
|
-
{ ref: 'local:Query.fake' }
|
|
1838
|
-
];
|
|
1839
|
-
const { valid, errors } = await validateSchema(validateContext, invalidSchema);
|
|
1840
|
-
expect(valid).toEqual(false);
|
|
1841
|
-
expect(errors).toEqual([
|
|
1842
|
-
{
|
|
1843
|
-
message: 'Missing tool query "local:Query.fake"',
|
|
1844
|
-
path: ['ai-experimental', 'agents', 'findDogColor', 'states', 'ccc', 'execution', 'tools', 0, 'ref', 'ref'],
|
|
1845
|
-
type: 'notFound'
|
|
1846
|
-
}
|
|
1847
|
-
]);
|
|
1848
|
-
});
|
|
1849
|
-
test('validateSchema - duplicate state names', async () => {
|
|
1850
|
-
const invalidSchema = agentSchema();
|
|
1851
|
-
const { findDogColor } = invalidSchema['ai-experimental'].agents;
|
|
1852
|
-
findDogColor.states.bbb.name = 'dupe';
|
|
1853
|
-
findDogColor.states.ccc.name = 'dupe';
|
|
1854
|
-
const { valid, errors } = await validateSchema(validateContext, invalidSchema);
|
|
1855
|
-
expect(valid).toEqual(false);
|
|
1856
|
-
expect(errors).toEqual([
|
|
1857
|
-
{
|
|
1858
|
-
message: 'Duplicate state name "dupe"',
|
|
1859
|
-
path: ['ai-experimental', 'agents', 'findDogColor', 'states', 'ccc', 'name'],
|
|
1860
|
-
type: 'conflict'
|
|
1861
|
-
}
|
|
1862
|
-
]);
|
|
1863
|
-
});
|
|
1864
|
-
test('validateSchema - an agent mutation can be a tool call', async () => {
|
|
1865
|
-
const invalidSchema = agentSchema();
|
|
1866
|
-
const { chat } = invalidSchema['ai-experimental'].agents;
|
|
1867
|
-
const exec = chat.states['3pjuyB47X'].execution;
|
|
1868
|
-
assert(exec.type === 'chat', 'Expected chat execution');
|
|
1869
|
-
exec.tools = [
|
|
1870
|
-
{
|
|
1871
|
-
ref: 'Mutation.findDogColor'
|
|
1872
|
-
}
|
|
1873
|
-
];
|
|
1874
|
-
const { valid } = await validateSchema(validateContext, invalidSchema);
|
|
1875
|
-
expect(valid).toEqual(true);
|
|
1876
|
-
});
|
|
1877
|
-
test('validateSchema - guards entitlement fails with 0 guards', async () => {
|
|
1878
|
-
const invalidSchema = agentSchema();
|
|
1879
|
-
set(invalidSchema, ['ai-experimental', 'guards', 'abc123'], {
|
|
1880
|
-
name: 'foo'
|
|
1881
|
-
});
|
|
1882
|
-
const { valid, errors } = await validateSchema(validateContext, invalidSchema);
|
|
1883
|
-
expect(valid).toEqual(false);
|
|
1884
|
-
expect(errors).toEqual([
|
|
1885
|
-
{
|
|
1886
|
-
message: 'Number of guards exceeds your entitled limit of 0',
|
|
1887
|
-
path: ['ai-experimental', 'guards'],
|
|
1888
|
-
type: 'entitlement'
|
|
1889
|
-
}
|
|
1890
|
-
]);
|
|
1891
|
-
});
|
|
1892
|
-
test('validateSchema - guards entitlement succeeds with 1 guard', async () => {
|
|
1893
|
-
const validSchema = agentSchema();
|
|
1894
|
-
set(validSchema, ['ai-experimental', 'guards', 'abc123'], { name: 'foo' });
|
|
1895
|
-
const context = {
|
|
1896
|
-
...validateContext,
|
|
1897
|
-
entitlements: { ...validateContext.entitlements, guards: 1 }
|
|
1898
|
-
};
|
|
1899
|
-
const { valid } = await validateSchema(context, validSchema);
|
|
1900
|
-
expect(valid).toEqual(true);
|
|
1901
|
-
});
|
|
1902
|
-
test('validateSchema - no guard for an agent guardId fails', async () => {
|
|
1903
|
-
const invalidSchema = agentSchema();
|
|
1904
|
-
set(invalidSchema, ['ai-experimental', 'agents', 'findDogColor', 'guards'], [{ guardId: 'abc123' }]);
|
|
1905
|
-
const { valid, errors } = await validateSchema(validateContext, invalidSchema);
|
|
1906
|
-
expect(valid).toEqual(false);
|
|
1907
|
-
expect(errors).toEqual([
|
|
1908
|
-
{
|
|
1909
|
-
message: `Invalid guardId "abc123"`,
|
|
1910
|
-
path: ['ai-experimental', 'agents', 'findDogColor', 'guards', 0, 'guardId'],
|
|
1911
|
-
type: 'notFound'
|
|
1912
|
-
}
|
|
1913
|
-
]);
|
|
1914
|
-
});
|
|
1915
|
-
test('validateSchema - existing guard for an agent guardId succeeds', async () => {
|
|
1916
|
-
const validSchema = agentSchema();
|
|
1917
|
-
set(validSchema, ['ai-experimental', 'agents', 'findDogColor', 'guards'], [{ guardId: 'abc123' }]);
|
|
1918
|
-
set(validSchema, ['ai-experimental', 'guards', 'abc123'], { name: 'foo' });
|
|
1919
|
-
const context = {
|
|
1920
|
-
...validateContext,
|
|
1921
|
-
entitlements: { ...validateContext.entitlements, guards: 1 }
|
|
1922
|
-
};
|
|
1923
|
-
const { valid } = await validateSchema(context, validSchema);
|
|
1924
|
-
expect(valid).toEqual(true);
|
|
1925
|
-
});
|
|
1926
|
-
test('validateSchema - invalid service in semantic chunker', async () => {
|
|
1927
|
-
const invalidSchema = agentSchema();
|
|
1928
|
-
invalidSchema.queries.semantic = {
|
|
1929
|
-
shape: { type: 'array', items: { type: 'string' } },
|
|
1930
|
-
args: {
|
|
1931
|
-
type: 'object',
|
|
1932
|
-
properties: { input: { type: 'string' } },
|
|
1933
|
-
required: ['input']
|
|
1934
|
-
},
|
|
1935
|
-
resolver: {
|
|
1936
|
-
name: 'util:chunk',
|
|
1937
|
-
options: {
|
|
1938
|
-
type: 'semantic',
|
|
1939
|
-
serviceId: 'INVALID SERVICE OH NO',
|
|
1940
|
-
modelId: 'text-embedding-3-small'
|
|
1941
|
-
}
|
|
1942
|
-
}
|
|
1943
|
-
};
|
|
1944
|
-
const { valid, errors } = await validateSchema(validateContext, invalidSchema);
|
|
1945
|
-
expect(valid).toEqual(false);
|
|
1946
|
-
expect(errors).toEqual([
|
|
1947
|
-
{
|
|
1948
|
-
message: 'Invalid service \"INVALID SERVICE OH NO\"',
|
|
1949
|
-
path: ['queries', 'semantic', 'resolver', 'options', 'serviceId'],
|
|
1950
|
-
type: 'notFound'
|
|
1951
|
-
}
|
|
1952
|
-
]);
|
|
1953
|
-
});
|
|
1954
|
-
test('validateSchema - invalid expressions', async () => {
|
|
1955
|
-
const invalidSchema = agentSchema();
|
|
1956
|
-
const { bbb, ccc, ddd } = invalidSchema['ai-experimental'].agents.findDogColor.states;
|
|
1957
|
-
bbb.variables[0].steps[0].expression = ')';
|
|
1958
|
-
ccc.transition[0].condition = '(';
|
|
1959
|
-
ddd.variables[0].steps[0].condition = 'foo';
|
|
1960
|
-
const { valid, errors } = await validateSchema(validateContext, invalidSchema);
|
|
1961
|
-
expect(valid).toEqual(false);
|
|
1962
|
-
expect(errors).toEqual([
|
|
1963
|
-
{
|
|
1964
|
-
message: 'Unexpected ")" at character 0',
|
|
1965
|
-
path: ['ai-experimental', 'agents', 'findDogColor', 'states', 'bbb', 'variables', 0, 'steps', 0, 'expression'],
|
|
1966
|
-
type: 'conflict'
|
|
1967
|
-
},
|
|
1968
|
-
{
|
|
1969
|
-
message: 'Unclosed ( at character 1',
|
|
1970
|
-
path: ['ai-experimental', 'agents', 'findDogColor', 'states', 'ccc', 'transition', 0, 'expression'],
|
|
1971
|
-
type: 'conflict'
|
|
1972
|
-
},
|
|
1973
|
-
{
|
|
1974
|
-
message: 'Unknown variable "foo"',
|
|
1975
|
-
path: ['ai-experimental', 'agents', 'findDogColor', 'states', 'ddd', 'variables', 0, 'steps', 0, 'condition'],
|
|
1976
|
-
type: 'conflict'
|
|
1977
|
-
}
|
|
1978
|
-
]);
|
|
1979
|
-
});
|
|
1980
|
-
});
|