@platformatic/sql-graphql 0.6.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/entity-to-type.js +82 -23
- package/lib/relationship.js +23 -16
- package/lib/subscriptions.js +10 -5
- package/lib/utils.js +2 -0
- package/package.json +6 -6
- package/test/composite.test.js +331 -0
- package/test/datatypes.test.js +25 -9
- package/test/helper.js +10 -0
- package/test/simple.test.js +57 -1
- package/test/subscription.test.js +1 -1
- package/test/where.test.js +288 -0
package/lib/entity-to-type.js
CHANGED
|
@@ -16,7 +16,7 @@ const ascDesc = new graphql.GraphQLEnumType({
|
|
|
16
16
|
})
|
|
17
17
|
|
|
18
18
|
function constructGraph (app, entity, opts) {
|
|
19
|
-
const
|
|
19
|
+
const primaryKeys = Array.from(entity.primaryKeys).map((key) => camelcase(key))
|
|
20
20
|
const relationalFields = entity.relations
|
|
21
21
|
.map((relation) => relation.column_name)
|
|
22
22
|
const entityName = entity.name
|
|
@@ -36,7 +36,18 @@ function constructGraph (app, entity, opts) {
|
|
|
36
36
|
for (const key of Object.keys(entity.fields)) {
|
|
37
37
|
const field = entity.fields[key]
|
|
38
38
|
const meta = { field }
|
|
39
|
-
|
|
39
|
+
|
|
40
|
+
// sqlite doesn't support enums
|
|
41
|
+
/* istanbul ignore next */
|
|
42
|
+
if (field.enum) {
|
|
43
|
+
const enumValues = field.enum.reduce((acc, enumValue) => {
|
|
44
|
+
acc[enumValue] = { value: enumValue }
|
|
45
|
+
return acc
|
|
46
|
+
}, {})
|
|
47
|
+
meta.type = new graphql.GraphQLEnumType({ name: key, values: enumValues })
|
|
48
|
+
} else {
|
|
49
|
+
meta.type = sqlTypeToGraphQL(field.sqlType)
|
|
50
|
+
}
|
|
40
51
|
if (field.primaryKey) {
|
|
41
52
|
meta.primaryKeyType = field.type
|
|
42
53
|
meta.type = graphql.GraphQLID
|
|
@@ -55,7 +66,12 @@ function constructGraph (app, entity, opts) {
|
|
|
55
66
|
resolvers.Mutation = resolvers.Mutation || {}
|
|
56
67
|
loaders.Query = loaders.Query || {}
|
|
57
68
|
|
|
58
|
-
const getBy = camelcase(['get', singular,
|
|
69
|
+
const getBy = camelcase(['get', singular, ...(primaryKeys.map((key, i) => {
|
|
70
|
+
if (i === 0) {
|
|
71
|
+
return ['by', key]
|
|
72
|
+
}
|
|
73
|
+
return ['and', key]
|
|
74
|
+
}).flat())].join('_'))
|
|
59
75
|
|
|
60
76
|
const whereArgType = new graphql.GraphQLInputObjectType({
|
|
61
77
|
name: `${entityName}WhereArguments`,
|
|
@@ -70,6 +86,7 @@ function constructGraph (app, entity, opts) {
|
|
|
70
86
|
gte: { type: fields[field].type },
|
|
71
87
|
lt: { type: fields[field].type },
|
|
72
88
|
lte: { type: fields[field].type },
|
|
89
|
+
like: { type: fields[field].type },
|
|
73
90
|
in: { type: new graphql.GraphQLList(fields[field].type) },
|
|
74
91
|
nin: { type: new graphql.GraphQLList(fields[field].type) }
|
|
75
92
|
}
|
|
@@ -82,14 +99,21 @@ function constructGraph (app, entity, opts) {
|
|
|
82
99
|
queryTopFields[getBy] = {
|
|
83
100
|
type,
|
|
84
101
|
args: {
|
|
85
|
-
|
|
102
|
+
...(primaryKeys.reduce((acc, primaryKey) => {
|
|
103
|
+
acc[primaryKey] = { type: new graphql.GraphQLNonNull(fields[primaryKey].type) }
|
|
104
|
+
return acc
|
|
105
|
+
}, {}))
|
|
86
106
|
}
|
|
87
107
|
}
|
|
88
108
|
loaders.Query[getBy] = {
|
|
89
109
|
loader (queries, ctx) {
|
|
90
110
|
const keys = []
|
|
91
111
|
for (const query of queries) {
|
|
92
|
-
|
|
112
|
+
const pairs = []
|
|
113
|
+
for (const key of primaryKeys) {
|
|
114
|
+
pairs.push({ key, value: query.params[key] })
|
|
115
|
+
}
|
|
116
|
+
keys.push(pairs)
|
|
93
117
|
}
|
|
94
118
|
return loadMany(keys, queries, ctx)
|
|
95
119
|
},
|
|
@@ -127,7 +151,9 @@ function constructGraph (app, entity, opts) {
|
|
|
127
151
|
|
|
128
152
|
resolvers.Query[plural] = (_, query, ctx, info) => {
|
|
129
153
|
const requestedFields = info.fieldNodes[0].selectionSet.selections.map((s) => s.name.value)
|
|
130
|
-
|
|
154
|
+
for (const primaryKey of primaryKeys) {
|
|
155
|
+
requestedFields.push(primaryKey)
|
|
156
|
+
}
|
|
131
157
|
return entity.find({ ...query, fields: [...requestedFields, ...relationalFields], ctx })
|
|
132
158
|
}
|
|
133
159
|
|
|
@@ -161,7 +187,9 @@ function constructGraph (app, entity, opts) {
|
|
|
161
187
|
|
|
162
188
|
resolvers.Query[count] = async (_, query, ctx, info) => {
|
|
163
189
|
const requestedFields = info.fieldNodes[0].selectionSet.selections.map((s) => s.name.value)
|
|
164
|
-
|
|
190
|
+
for (const primaryKey of primaryKeys) {
|
|
191
|
+
requestedFields.push(primaryKey)
|
|
192
|
+
}
|
|
165
193
|
const total = await entity.count({ ...query, fields: [...requestedFields, ...relationalFields], ctx })
|
|
166
194
|
return { total }
|
|
167
195
|
}
|
|
@@ -209,14 +237,21 @@ function constructGraph (app, entity, opts) {
|
|
|
209
237
|
|
|
210
238
|
federationReplacements.push({
|
|
211
239
|
find: new RegExp(`type ${entityName}`),
|
|
212
|
-
replace: `type ${entityName} @key(fields: "${
|
|
240
|
+
replace: `type ${entityName} @key(fields: "${primaryKeys}")`
|
|
213
241
|
})
|
|
214
242
|
|
|
215
243
|
if (federationMetadata) {
|
|
216
244
|
loaders[entityName] = loaders[entityName] || {}
|
|
217
245
|
loaders[entityName].__resolveReference = {
|
|
218
246
|
loader (queries, ctx) {
|
|
219
|
-
const keys =
|
|
247
|
+
const keys = []
|
|
248
|
+
for (const { obj } of queries) {
|
|
249
|
+
const pairs = []
|
|
250
|
+
for (const key of primaryKeys) {
|
|
251
|
+
pairs.push({ key, value: obj[key] })
|
|
252
|
+
}
|
|
253
|
+
keys.push(pairs)
|
|
254
|
+
}
|
|
220
255
|
return loadMany(keys, queries, ctx)
|
|
221
256
|
},
|
|
222
257
|
opts: {
|
|
@@ -235,25 +270,47 @@ function constructGraph (app, entity, opts) {
|
|
|
235
270
|
|
|
236
271
|
async function loadMany (keys, queries, ctx) {
|
|
237
272
|
const fields = getFields(queries)
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
273
|
+
|
|
274
|
+
// TODO this is inefficient as it might load
|
|
275
|
+
// more data than needed if there are more than
|
|
276
|
+
// one primary key
|
|
277
|
+
const where = keys.reduce((acc, pairs) => {
|
|
278
|
+
pairs.reduce((acc, { key, value }) => {
|
|
279
|
+
if (acc[key]) {
|
|
280
|
+
acc[key].in.push(value)
|
|
281
|
+
} else {
|
|
282
|
+
acc[key] = {
|
|
283
|
+
in: [value]
|
|
284
|
+
}
|
|
242
285
|
}
|
|
243
|
-
|
|
286
|
+
return acc
|
|
287
|
+
}, acc)
|
|
288
|
+
return acc
|
|
289
|
+
}, {})
|
|
290
|
+
|
|
291
|
+
const res = await entity.find({
|
|
292
|
+
where,
|
|
244
293
|
fields,
|
|
245
294
|
ctx
|
|
246
295
|
})
|
|
247
296
|
|
|
248
|
-
const map = {}
|
|
249
|
-
|
|
250
|
-
for (const row of res) {
|
|
251
|
-
map[row[primaryKey]] = row
|
|
252
|
-
}
|
|
253
|
-
|
|
254
297
|
const output = []
|
|
255
|
-
|
|
256
|
-
|
|
298
|
+
// TODO this is extremely inefficient
|
|
299
|
+
// we need a better data structure
|
|
300
|
+
for (const pair of keys) {
|
|
301
|
+
for (const row of res) {
|
|
302
|
+
let target = row
|
|
303
|
+
for (const { key, value } of pair) {
|
|
304
|
+
if (row[key] !== value) {
|
|
305
|
+
target = null
|
|
306
|
+
break
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (target) {
|
|
311
|
+
output.push(target)
|
|
312
|
+
}
|
|
313
|
+
}
|
|
257
314
|
}
|
|
258
315
|
|
|
259
316
|
return output
|
|
@@ -261,7 +318,9 @@ function constructGraph (app, entity, opts) {
|
|
|
261
318
|
|
|
262
319
|
function getFields (queries) {
|
|
263
320
|
const fields = new Set([...relationalFields])
|
|
264
|
-
|
|
321
|
+
for (const primaryKey of primaryKeys) {
|
|
322
|
+
fields.add(primaryKey)
|
|
323
|
+
}
|
|
265
324
|
for (const query of queries) {
|
|
266
325
|
fromSelectionSet(query.info.fieldNodes[0].selectionSet, fields)
|
|
267
326
|
}
|
package/lib/relationship.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
/* eslint camelcase: 0 */
|
|
4
|
+
|
|
3
5
|
const camelcase = require('camelcase')
|
|
4
6
|
const {
|
|
5
7
|
fromSelectionSet
|
|
@@ -13,28 +15,32 @@ module.exports = function establishRelations (app, relations, resolvers, loaders
|
|
|
13
15
|
const entity = entities[key]
|
|
14
16
|
tablesTypeMap[entity.table] = metaMap.get(entity)
|
|
15
17
|
}
|
|
16
|
-
for (const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
assert(
|
|
18
|
+
for (const { table_name, foreign_table_name, column_name, foreign_column_name } of relations) {
|
|
19
|
+
const enhanceAssertLogMsg = `(table: "${table_name}", foreign table: "${foreign_table_name}", column: "${column_name}")`
|
|
20
|
+
|
|
21
|
+
assert(table_name, `table_name is required ${enhanceAssertLogMsg}`)
|
|
22
|
+
assert(foreign_table_name, `foreign_table_name is required ${enhanceAssertLogMsg}`)
|
|
23
|
+
assert(column_name.toLowerCase().endsWith('id'), `The column with id reference must be ended with "id" postfix ${enhanceAssertLogMsg}`)
|
|
20
24
|
|
|
21
|
-
const current = tablesTypeMap[
|
|
22
|
-
const foreign = tablesTypeMap[
|
|
23
|
-
assert(foreign !== undefined, `No foreign table named "${
|
|
25
|
+
const current = tablesTypeMap[table_name]
|
|
26
|
+
const foreign = tablesTypeMap[foreign_table_name]
|
|
27
|
+
assert(foreign !== undefined, `No foreign table named "${foreign_table_name}" was found ${enhanceAssertLogMsg}`)
|
|
24
28
|
|
|
25
29
|
// current to foreign
|
|
26
30
|
{
|
|
27
|
-
const lowered = lowerCaseFirst(camelcase(cutOutIdEnding(
|
|
31
|
+
const lowered = lowerCaseFirst(camelcase(cutOutIdEnding(column_name)))
|
|
28
32
|
if (!relationships[current.type] || relationships[current.type][lowered] !== false) {
|
|
29
33
|
current.fields[lowered] = { type: foreign.type }
|
|
30
|
-
const originalField = camelcase(
|
|
34
|
+
const originalField = camelcase(column_name)
|
|
31
35
|
delete current.fields[originalField]
|
|
32
36
|
loaders[current.type] = loaders[current.type] || resolvers[current.type] || {}
|
|
37
|
+
const key = camelcase(foreign_column_name)
|
|
33
38
|
loaders[current.type][lowered] = {
|
|
34
39
|
loader (queries, ctx) {
|
|
35
|
-
const keys =
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
const keys = []
|
|
41
|
+
for (const { obj } of queries) {
|
|
42
|
+
keys.push([{ key, value: obj[originalField].toString() }])
|
|
43
|
+
}
|
|
38
44
|
return foreign.loadMany(keys, queries, ctx)
|
|
39
45
|
},
|
|
40
46
|
opts: {
|
|
@@ -44,17 +50,18 @@ module.exports = function establishRelations (app, relations, resolvers, loaders
|
|
|
44
50
|
}
|
|
45
51
|
}
|
|
46
52
|
|
|
47
|
-
// foreign to current
|
|
48
|
-
|
|
53
|
+
// foreign to current, we skip this if the current table has a composite primary key
|
|
54
|
+
// TODO implement support for this case
|
|
55
|
+
if (current.entity.primaryKeys.size === 1) {
|
|
49
56
|
const lowered = lowerCaseFirst(camelcase(current.entity.table))
|
|
50
57
|
if (!relationships[foreign.type] || relationships[foreign.type][lowered] !== false) {
|
|
51
58
|
foreign.fields[lowered] = queryTopFields[lowered]
|
|
52
59
|
resolvers[foreign.type] = resolvers[foreign.type] || {}
|
|
53
60
|
const resolveRelation = async function (obj, args, ctx, info) {
|
|
54
61
|
const fields = fromSelectionSet(info.fieldNodes[0].selectionSet, new Set())
|
|
55
|
-
const toSearch = { ...args, fields: [...fields,
|
|
62
|
+
const toSearch = { ...args, fields: [...fields, column_name], ctx }
|
|
56
63
|
toSearch.where = toSearch.where || {}
|
|
57
|
-
toSearch.where[camelcase(
|
|
64
|
+
toSearch.where[camelcase(column_name)] = { eq: obj.id }
|
|
58
65
|
return current.entity.find(toSearch)
|
|
59
66
|
}
|
|
60
67
|
if (resolvers[foreign.type][lowered] === undefined) {
|
package/lib/subscriptions.js
CHANGED
|
@@ -9,6 +9,14 @@ function setupSubscriptions (app, metaMap, resolvers, ignores) {
|
|
|
9
9
|
if (ignores.indexOf(field.singularName) >= 0) {
|
|
10
10
|
continue
|
|
11
11
|
}
|
|
12
|
+
// TODO currently we are not supporting subscriptions for
|
|
13
|
+
// entities that have a composite primary key
|
|
14
|
+
/* istanbul ignore next */
|
|
15
|
+
if (field.primaryKeys.size !== 1) {
|
|
16
|
+
continue
|
|
17
|
+
}
|
|
18
|
+
const entity = app.platformatic.entities[field.singularName]
|
|
19
|
+
const primaryKey = entity.primaryKeys.values().next().value
|
|
12
20
|
const { type } = meta
|
|
13
21
|
const saved = `${field.singularName}Saved`
|
|
14
22
|
fields[saved] = {
|
|
@@ -18,11 +26,9 @@ function setupSubscriptions (app, metaMap, resolvers, ignores) {
|
|
|
18
26
|
subscribe: async function * (_, query, ctx, info) {
|
|
19
27
|
const log = ctx.reply.request.log
|
|
20
28
|
const { pubsub } = ctx
|
|
21
|
-
const entity = app.platformatic.entities[field.singularName]
|
|
22
29
|
const topic = await entity.getSubscriptionTopic({ action: 'save', ctx })
|
|
23
30
|
log.trace({ topic }, 'subscribed')
|
|
24
31
|
const res = await pubsub.subscribe(topic)
|
|
25
|
-
const primaryKey = entity.primaryKey
|
|
26
32
|
const fields = meta.getFields([{ info }])
|
|
27
33
|
|
|
28
34
|
for await (const msg of res) {
|
|
@@ -55,8 +61,8 @@ function setupSubscriptions (app, metaMap, resolvers, ignores) {
|
|
|
55
61
|
type: new graphql.GraphQLObjectType({
|
|
56
62
|
name: `${field.name}Deleted`,
|
|
57
63
|
fields: {
|
|
58
|
-
[
|
|
59
|
-
type: meta.fields[
|
|
64
|
+
[primaryKey]: {
|
|
65
|
+
type: meta.fields[primaryKey].type
|
|
60
66
|
}
|
|
61
67
|
}
|
|
62
68
|
})
|
|
@@ -64,7 +70,6 @@ function setupSubscriptions (app, metaMap, resolvers, ignores) {
|
|
|
64
70
|
resolvers.Subscription[deleted] = {
|
|
65
71
|
subscribe: async (_, query, ctx, info) => {
|
|
66
72
|
const { pubsub } = ctx
|
|
67
|
-
const entity = app.platformatic.entities[field.singularName]
|
|
68
73
|
const topic = await entity.getSubscriptionTopic({ action: 'delete', ctx })
|
|
69
74
|
const res = await pubsub.subscribe(topic)
|
|
70
75
|
ctx.reply.request.log.trace({ topic }, 'subscribed')
|
package/lib/utils.js
CHANGED
package/package.json
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/sql-graphql",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Map SQL dbs to GraphQL",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/
|
|
8
|
+
"url": "git+https://github.com/platformatic/platformatic.git"
|
|
9
9
|
},
|
|
10
10
|
"author": "Matteo Collina <hello@matteocollina.com>",
|
|
11
11
|
"license": "Apache-2.0",
|
|
12
12
|
"bugs": {
|
|
13
|
-
"url": "https://github.com/
|
|
13
|
+
"url": "https://github.com/platformatic/platformatic/issues"
|
|
14
14
|
},
|
|
15
|
-
"homepage": "https://github.com/
|
|
15
|
+
"homepage": "https://github.com/platformatic/platformatic#readme",
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"@platformatic/sql-events": "0.
|
|
18
|
-
"@platformatic/sql-mapper": "0.
|
|
17
|
+
"@platformatic/sql-events": "0.8.0",
|
|
18
|
+
"@platformatic/sql-mapper": "0.8.0",
|
|
19
19
|
"fastify": "^4.6.0",
|
|
20
20
|
"snazzy": "^9.0.0",
|
|
21
21
|
"standard": "^17.0.0",
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { clear, connInfo, isSQLite, isMysql, isPg } = require('./helper')
|
|
4
|
+
const { test } = require('tap')
|
|
5
|
+
const fastify = require('fastify')
|
|
6
|
+
const sqlMapper = require('@platformatic/sql-mapper')
|
|
7
|
+
const sqlGraphQL = require('..')
|
|
8
|
+
|
|
9
|
+
test('composite primary keys', async ({ equal, same, teardown, rejects }) => {
|
|
10
|
+
/* https://github.com/platformatic/platformatic/issues/299 */
|
|
11
|
+
async function onDatabaseLoad (db, sql) {
|
|
12
|
+
await clear(db, sql)
|
|
13
|
+
|
|
14
|
+
if (isSQLite) {
|
|
15
|
+
await db.query(sql`CREATE TABLE pages (
|
|
16
|
+
id INTEGER PRIMARY KEY,
|
|
17
|
+
the_title VARCHAR(42)
|
|
18
|
+
);`)
|
|
19
|
+
|
|
20
|
+
await db.query(sql`CREATE TABLE users (
|
|
21
|
+
id INTEGER PRIMARY KEY,
|
|
22
|
+
username VARCHAR(255) NOT NULL
|
|
23
|
+
);`)
|
|
24
|
+
|
|
25
|
+
await db.query(sql`CREATE TABLE editors (
|
|
26
|
+
page_id INTEGER NOT NULL,
|
|
27
|
+
user_id INTEGER NOT NULL,
|
|
28
|
+
role VARCHAR(255) NOT NULL,
|
|
29
|
+
CONSTRAINT fk_editor_pages FOREIGN KEY (page_id) REFERENCES pages(id),
|
|
30
|
+
CONSTRAINT fk_editor_users FOREIGN KEY (user_id) REFERENCES users(id),
|
|
31
|
+
PRIMARY KEY (page_id, user_id)
|
|
32
|
+
);`)
|
|
33
|
+
} else if (isPg) {
|
|
34
|
+
await db.query(sql`CREATE TABLE pages (
|
|
35
|
+
id SERIAL PRIMARY KEY,
|
|
36
|
+
the_title VARCHAR(255) NOT NULL
|
|
37
|
+
);`)
|
|
38
|
+
|
|
39
|
+
await db.query(sql`CREATE TABLE users (
|
|
40
|
+
id SERIAL PRIMARY KEY,
|
|
41
|
+
username VARCHAR(255) NOT NULL
|
|
42
|
+
);`)
|
|
43
|
+
|
|
44
|
+
await db.query(sql`CREATE TABLE editors (
|
|
45
|
+
page_id INTEGER NOT NULL,
|
|
46
|
+
user_id INTEGER NOT NULL,
|
|
47
|
+
role VARCHAR(255) NOT NULL,
|
|
48
|
+
CONSTRAINT fk_editor_pages FOREIGN KEY (page_id) REFERENCES pages(id),
|
|
49
|
+
CONSTRAINT fk_editor_users FOREIGN KEY (user_id) REFERENCES users(id),
|
|
50
|
+
PRIMARY KEY (page_id, user_id)
|
|
51
|
+
);`)
|
|
52
|
+
} else if (isMysql) {
|
|
53
|
+
await db.query(sql`CREATE TABLE pages (
|
|
54
|
+
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
|
55
|
+
the_title VARCHAR(255) NOT NULL
|
|
56
|
+
);`)
|
|
57
|
+
|
|
58
|
+
await db.query(sql`CREATE TABLE users (
|
|
59
|
+
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
|
60
|
+
username VARCHAR(255) NOT NULL
|
|
61
|
+
);`)
|
|
62
|
+
|
|
63
|
+
await db.query(sql`CREATE TABLE editors (
|
|
64
|
+
page_id INTEGER NOT NULL,
|
|
65
|
+
user_id INTEGER NOT NULL,
|
|
66
|
+
role VARCHAR(255) NOT NULL,
|
|
67
|
+
CONSTRAINT \`fk_editor_pages\` FOREIGN KEY (page_id) REFERENCES pages (id) ON DELETE CASCADE ON UPDATE RESTRICT,
|
|
68
|
+
CONSTRAINT \`fk_editor_users\` FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE ON UPDATE RESTRICT,
|
|
69
|
+
PRIMARY KEY (page_id, user_id)
|
|
70
|
+
);`)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const app = fastify()
|
|
75
|
+
app.register(sqlMapper, {
|
|
76
|
+
...connInfo,
|
|
77
|
+
onDatabaseLoad
|
|
78
|
+
})
|
|
79
|
+
app.register(sqlGraphQL)
|
|
80
|
+
teardown(app.close.bind(app))
|
|
81
|
+
|
|
82
|
+
await app.ready()
|
|
83
|
+
|
|
84
|
+
{
|
|
85
|
+
const res = await app.inject({
|
|
86
|
+
method: 'POST',
|
|
87
|
+
url: '/graphql',
|
|
88
|
+
body: {
|
|
89
|
+
query: `
|
|
90
|
+
mutation {
|
|
91
|
+
savePage(input: { theTitle: "foobar" }) {
|
|
92
|
+
id
|
|
93
|
+
theTitle
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
`
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
equal(res.statusCode, 200, 'savePage status code')
|
|
100
|
+
same(res.json(), {
|
|
101
|
+
data: {
|
|
102
|
+
savePage: {
|
|
103
|
+
id: 1,
|
|
104
|
+
theTitle: 'foobar'
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}, 'savePage response')
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
{
|
|
111
|
+
const res = await app.inject({
|
|
112
|
+
method: 'POST',
|
|
113
|
+
url: '/graphql',
|
|
114
|
+
body: {
|
|
115
|
+
query: `
|
|
116
|
+
mutation {
|
|
117
|
+
saveUser(input: { username: "mcollina" }) {
|
|
118
|
+
id
|
|
119
|
+
username
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
`
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
equal(res.statusCode, 200, 'saveUser status code')
|
|
126
|
+
same(res.json(), {
|
|
127
|
+
data: {
|
|
128
|
+
saveUser: {
|
|
129
|
+
id: 1,
|
|
130
|
+
username: 'mcollina'
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}, 'saveUser response')
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
{
|
|
137
|
+
const res = await app.inject({
|
|
138
|
+
method: 'POST',
|
|
139
|
+
url: '/graphql',
|
|
140
|
+
body: {
|
|
141
|
+
query: `
|
|
142
|
+
mutation {
|
|
143
|
+
saveUser(input: { username: "lucamaraschi" }) {
|
|
144
|
+
id
|
|
145
|
+
username
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
`
|
|
149
|
+
}
|
|
150
|
+
})
|
|
151
|
+
equal(res.statusCode, 200, 'saveUser status code')
|
|
152
|
+
same(res.json(), {
|
|
153
|
+
data: {
|
|
154
|
+
saveUser: {
|
|
155
|
+
id: 2,
|
|
156
|
+
username: 'lucamaraschi'
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}, 'saveUser response')
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
{
|
|
163
|
+
const res = await app.inject({
|
|
164
|
+
method: 'POST',
|
|
165
|
+
url: '/graphql',
|
|
166
|
+
body: {
|
|
167
|
+
query: `
|
|
168
|
+
mutation {
|
|
169
|
+
saveEditor(input: { userId: "1", pageId: "1", role: "admin" }) {
|
|
170
|
+
user {
|
|
171
|
+
id
|
|
172
|
+
username
|
|
173
|
+
}
|
|
174
|
+
page {
|
|
175
|
+
id
|
|
176
|
+
theTitle
|
|
177
|
+
}
|
|
178
|
+
role
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
`
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
equal(res.statusCode, 200, 'saveEditor status code')
|
|
185
|
+
same(res.json(), {
|
|
186
|
+
data: {
|
|
187
|
+
saveEditor: {
|
|
188
|
+
user: {
|
|
189
|
+
id: 1,
|
|
190
|
+
username: 'mcollina'
|
|
191
|
+
},
|
|
192
|
+
page: {
|
|
193
|
+
id: 1,
|
|
194
|
+
theTitle: 'foobar'
|
|
195
|
+
},
|
|
196
|
+
role: 'admin'
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}, 'saveEditor response')
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
{
|
|
203
|
+
const res = await app.inject({
|
|
204
|
+
method: 'POST',
|
|
205
|
+
url: '/graphql',
|
|
206
|
+
body: {
|
|
207
|
+
query: `
|
|
208
|
+
mutation {
|
|
209
|
+
saveEditor(input: { userId: "2", pageId: "1", role: "author" }) {
|
|
210
|
+
user {
|
|
211
|
+
id
|
|
212
|
+
username
|
|
213
|
+
}
|
|
214
|
+
page {
|
|
215
|
+
id
|
|
216
|
+
theTitle
|
|
217
|
+
}
|
|
218
|
+
role
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
`
|
|
222
|
+
}
|
|
223
|
+
})
|
|
224
|
+
equal(res.statusCode, 200, 'saveEditor status code')
|
|
225
|
+
same(res.json(), {
|
|
226
|
+
data: {
|
|
227
|
+
saveEditor: {
|
|
228
|
+
user: {
|
|
229
|
+
id: 2,
|
|
230
|
+
username: 'lucamaraschi'
|
|
231
|
+
},
|
|
232
|
+
page: {
|
|
233
|
+
id: 1,
|
|
234
|
+
theTitle: 'foobar'
|
|
235
|
+
},
|
|
236
|
+
role: 'author'
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}, 'saveEditor response')
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
{
|
|
243
|
+
const res = await app.inject({
|
|
244
|
+
method: 'POST',
|
|
245
|
+
url: '/graphql',
|
|
246
|
+
body: {
|
|
247
|
+
query: `
|
|
248
|
+
mutation {
|
|
249
|
+
saveEditor(input: { userId: "1", pageId: "1", role: "captain" }) {
|
|
250
|
+
user {
|
|
251
|
+
id
|
|
252
|
+
username
|
|
253
|
+
}
|
|
254
|
+
page {
|
|
255
|
+
id
|
|
256
|
+
theTitle
|
|
257
|
+
}
|
|
258
|
+
role
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
`
|
|
262
|
+
}
|
|
263
|
+
})
|
|
264
|
+
equal(res.statusCode, 200, 'saveEditor status code')
|
|
265
|
+
same(res.json(), {
|
|
266
|
+
data: {
|
|
267
|
+
saveEditor: {
|
|
268
|
+
user: {
|
|
269
|
+
id: 1,
|
|
270
|
+
username: 'mcollina'
|
|
271
|
+
},
|
|
272
|
+
page: {
|
|
273
|
+
id: 1,
|
|
274
|
+
theTitle: 'foobar'
|
|
275
|
+
},
|
|
276
|
+
role: 'captain'
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}, 'saveEditor response')
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
{
|
|
283
|
+
const res = await app.inject({
|
|
284
|
+
method: 'post',
|
|
285
|
+
url: '/graphql',
|
|
286
|
+
body: {
|
|
287
|
+
query: `
|
|
288
|
+
query {
|
|
289
|
+
editors(orderBy: { field: role, direction: DESC }) {
|
|
290
|
+
user {
|
|
291
|
+
id
|
|
292
|
+
username
|
|
293
|
+
}
|
|
294
|
+
page {
|
|
295
|
+
id
|
|
296
|
+
theTitle
|
|
297
|
+
}
|
|
298
|
+
role
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
`
|
|
302
|
+
}
|
|
303
|
+
})
|
|
304
|
+
equal(res.statusCode, 200, 'editors status code')
|
|
305
|
+
same(res.json(), {
|
|
306
|
+
data: {
|
|
307
|
+
editors: [{
|
|
308
|
+
user: {
|
|
309
|
+
id: '1',
|
|
310
|
+
username: 'mcollina'
|
|
311
|
+
},
|
|
312
|
+
page: {
|
|
313
|
+
id: '1',
|
|
314
|
+
theTitle: 'foobar'
|
|
315
|
+
},
|
|
316
|
+
role: 'captain'
|
|
317
|
+
}, {
|
|
318
|
+
user: {
|
|
319
|
+
id: '2',
|
|
320
|
+
username: 'lucamaraschi'
|
|
321
|
+
},
|
|
322
|
+
page: {
|
|
323
|
+
id: '1',
|
|
324
|
+
theTitle: 'foobar'
|
|
325
|
+
},
|
|
326
|
+
role: 'author'
|
|
327
|
+
}]
|
|
328
|
+
}
|
|
329
|
+
}, 'editor response')
|
|
330
|
+
}
|
|
331
|
+
})
|
package/test/datatypes.test.js
CHANGED
|
@@ -15,7 +15,9 @@ test('[PG] simple db simple graphql schema', { skip: !isPg }, async ({ pass, tea
|
|
|
15
15
|
|
|
16
16
|
await clear(db, sql)
|
|
17
17
|
|
|
18
|
-
await db.query(sql`
|
|
18
|
+
await db.query(sql`
|
|
19
|
+
CREATE TYPE simple_enum as ENUM ('value1', 'value2');
|
|
20
|
+
CREATE TABLE simple_types (
|
|
19
21
|
id SERIAL8 PRIMARY KEY,
|
|
20
22
|
published BOOL,
|
|
21
23
|
current DOUBLE PRECISION,
|
|
@@ -26,7 +28,8 @@ test('[PG] simple db simple graphql schema', { skip: !isPg }, async ({ pass, tea
|
|
|
26
28
|
uuid UUID UNIQUE,
|
|
27
29
|
a_real real,
|
|
28
30
|
a_smallint smallint,
|
|
29
|
-
a_decimal decimal
|
|
31
|
+
a_decimal decimal,
|
|
32
|
+
an_enum simple_enum
|
|
30
33
|
);`)
|
|
31
34
|
}
|
|
32
35
|
})
|
|
@@ -55,7 +58,9 @@ test('[PG] simple db simple graphql schema', { skip: !isPg }, async ({ pass, tea
|
|
|
55
58
|
uuid: "12345678-1234-1234-1234-123456789012",
|
|
56
59
|
aReal: 1.2,
|
|
57
60
|
aSmallint: 42,
|
|
58
|
-
aDecimal: 42
|
|
61
|
+
aDecimal: 42,
|
|
62
|
+
anEnum: value1
|
|
63
|
+
}) {
|
|
59
64
|
id
|
|
60
65
|
published
|
|
61
66
|
current
|
|
@@ -67,6 +72,7 @@ test('[PG] simple db simple graphql schema', { skip: !isPg }, async ({ pass, tea
|
|
|
67
72
|
aReal
|
|
68
73
|
aSmallint
|
|
69
74
|
aDecimal
|
|
75
|
+
anEnum
|
|
70
76
|
}
|
|
71
77
|
}
|
|
72
78
|
`
|
|
@@ -86,7 +92,8 @@ test('[PG] simple db simple graphql schema', { skip: !isPg }, async ({ pass, tea
|
|
|
86
92
|
uuid: '12345678-1234-1234-1234-123456789012',
|
|
87
93
|
aReal: 1.2,
|
|
88
94
|
aSmallint: 42,
|
|
89
|
-
aDecimal: 42
|
|
95
|
+
aDecimal: 42,
|
|
96
|
+
anEnum: 'value1'
|
|
90
97
|
}
|
|
91
98
|
}
|
|
92
99
|
}, 'saveSimpleType response')
|
|
@@ -111,6 +118,7 @@ test('[PG] simple db simple graphql schema', { skip: !isPg }, async ({ pass, tea
|
|
|
111
118
|
aReal
|
|
112
119
|
aSmallint
|
|
113
120
|
aDecimal
|
|
121
|
+
anEnum
|
|
114
122
|
}
|
|
115
123
|
}
|
|
116
124
|
`
|
|
@@ -130,7 +138,8 @@ test('[PG] simple db simple graphql schema', { skip: !isPg }, async ({ pass, tea
|
|
|
130
138
|
uuid: '12345678-1234-1234-1234-123456789012',
|
|
131
139
|
aReal: 1.2,
|
|
132
140
|
aSmallint: 42,
|
|
133
|
-
aDecimal: 42
|
|
141
|
+
aDecimal: 42,
|
|
142
|
+
anEnum: 'value1'
|
|
134
143
|
}
|
|
135
144
|
}
|
|
136
145
|
}, 'getSimpleTypeById response')
|
|
@@ -262,7 +271,8 @@ test('[MySQL] simple db simple graphql schema', { skip: !isMysql }, async ({ pas
|
|
|
262
271
|
uuid ${uuidType} UNIQUE,
|
|
263
272
|
a_real real,
|
|
264
273
|
a_smallint smallint,
|
|
265
|
-
a_decimal decimal
|
|
274
|
+
a_decimal decimal,
|
|
275
|
+
an_enum enum ('value1', 'value2')
|
|
266
276
|
);`)
|
|
267
277
|
}
|
|
268
278
|
})
|
|
@@ -293,7 +303,9 @@ test('[MySQL] simple db simple graphql schema', { skip: !isMysql }, async ({ pas
|
|
|
293
303
|
uuid: "12345678-1234-1234-1234-123456789012",
|
|
294
304
|
aReal: 1.2,
|
|
295
305
|
aSmallint: 42,
|
|
296
|
-
aDecimal: 42
|
|
306
|
+
aDecimal: 42,
|
|
307
|
+
anEnum: value1
|
|
308
|
+
}) {
|
|
297
309
|
id
|
|
298
310
|
published
|
|
299
311
|
current
|
|
@@ -305,6 +317,7 @@ test('[MySQL] simple db simple graphql schema', { skip: !isMysql }, async ({ pas
|
|
|
305
317
|
aReal
|
|
306
318
|
aSmallint
|
|
307
319
|
aDecimal
|
|
320
|
+
anEnum
|
|
308
321
|
}
|
|
309
322
|
}
|
|
310
323
|
`
|
|
@@ -324,7 +337,8 @@ test('[MySQL] simple db simple graphql schema', { skip: !isMysql }, async ({ pas
|
|
|
324
337
|
uuid: '12345678-1234-1234-1234-123456789012',
|
|
325
338
|
aReal: 1.2,
|
|
326
339
|
aSmallint: 42,
|
|
327
|
-
aDecimal: 42
|
|
340
|
+
aDecimal: 42,
|
|
341
|
+
anEnum: 'value1'
|
|
328
342
|
}
|
|
329
343
|
}
|
|
330
344
|
}, 'saveSimpleType response')
|
|
@@ -349,6 +363,7 @@ test('[MySQL] simple db simple graphql schema', { skip: !isMysql }, async ({ pas
|
|
|
349
363
|
aReal
|
|
350
364
|
aSmallint
|
|
351
365
|
aDecimal
|
|
366
|
+
anEnum
|
|
352
367
|
}
|
|
353
368
|
}
|
|
354
369
|
`
|
|
@@ -368,7 +383,8 @@ test('[MySQL] simple db simple graphql schema', { skip: !isMysql }, async ({ pas
|
|
|
368
383
|
uuid: '12345678-1234-1234-1234-123456789012',
|
|
369
384
|
aReal: 1.2,
|
|
370
385
|
aSmallint: 42,
|
|
371
|
-
aDecimal: 42
|
|
386
|
+
aDecimal: 42,
|
|
387
|
+
anEnum: 'value1'
|
|
372
388
|
}
|
|
373
389
|
}
|
|
374
390
|
}, 'getSimpleTypeById response')
|
package/test/helper.js
CHANGED
|
@@ -30,6 +30,11 @@ if (!process.env.DB || process.env.DB === 'postgresql') {
|
|
|
30
30
|
module.exports.connInfo = connInfo
|
|
31
31
|
|
|
32
32
|
module.exports.clear = async function (db, sql) {
|
|
33
|
+
try {
|
|
34
|
+
await db.query(sql`DROP TABLE editors`)
|
|
35
|
+
} catch (err) {
|
|
36
|
+
}
|
|
37
|
+
|
|
33
38
|
try {
|
|
34
39
|
await db.query(sql`DROP TABLE pages`)
|
|
35
40
|
} catch (err) {
|
|
@@ -86,4 +91,9 @@ module.exports.clear = async function (db, sql) {
|
|
|
86
91
|
await db.query(sql`DROP TABLE individuals`)
|
|
87
92
|
} catch (err) {
|
|
88
93
|
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
await db.query(sql`DROP Type simple_enum`)
|
|
97
|
+
} catch (err) {
|
|
98
|
+
}
|
|
89
99
|
}
|
package/test/simple.test.js
CHANGED
|
@@ -4,7 +4,7 @@ const { test } = require('tap')
|
|
|
4
4
|
const sqlGraphQL = require('..')
|
|
5
5
|
const sqlMapper = require('@platformatic/sql-mapper')
|
|
6
6
|
const fastify = require('fastify')
|
|
7
|
-
const { clear, connInfo, isSQLite, isMysql, isMariaDB } = require('./helper')
|
|
7
|
+
const { clear, connInfo, isSQLite, isMysql, isMariaDB, isPg } = require('./helper')
|
|
8
8
|
|
|
9
9
|
async function createBasicPages (db, sql) {
|
|
10
10
|
if (isSQLite) {
|
|
@@ -901,3 +901,59 @@ test('deserialize JSON columns', { skip: isSQLite }, async (t) => {
|
|
|
901
901
|
same(json.data.getPageById.metadata, jsonData)
|
|
902
902
|
}
|
|
903
903
|
})
|
|
904
|
+
|
|
905
|
+
test('deserialize JSONB columns', { skip: !isPg }, async (t) => {
|
|
906
|
+
const { pass, teardown, same } = t
|
|
907
|
+
const app = fastify()
|
|
908
|
+
const jsonData = {
|
|
909
|
+
foo: 'bar',
|
|
910
|
+
baz: 42,
|
|
911
|
+
items: ['foo', 'bar'],
|
|
912
|
+
nested: {
|
|
913
|
+
hello: 'world'
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
app.register(sqlMapper, {
|
|
917
|
+
...connInfo,
|
|
918
|
+
async onDatabaseLoad (db, sql) {
|
|
919
|
+
pass('onDatabaseLoad called')
|
|
920
|
+
|
|
921
|
+
await clear(db, sql)
|
|
922
|
+
|
|
923
|
+
await db.query(sql`CREATE TABLE pages (
|
|
924
|
+
id SERIAL PRIMARY KEY,
|
|
925
|
+
title VARCHAR(255) NOT NULL,
|
|
926
|
+
metadata JSONB NOT NULL
|
|
927
|
+
);`)
|
|
928
|
+
|
|
929
|
+
await db.query(sql`INSERT INTO pages (id, title, metadata) VALUES (1, 'Hello World', ${JSON.stringify(jsonData)})`)
|
|
930
|
+
}
|
|
931
|
+
})
|
|
932
|
+
app.register(sqlGraphQL, {
|
|
933
|
+
graphiql: false
|
|
934
|
+
})
|
|
935
|
+
teardown(app.close.bind(app))
|
|
936
|
+
|
|
937
|
+
await app.ready()
|
|
938
|
+
const res = await app.inject({
|
|
939
|
+
method: 'POST',
|
|
940
|
+
url: '/graphql',
|
|
941
|
+
body: {
|
|
942
|
+
query: `
|
|
943
|
+
query {
|
|
944
|
+
getPageById(id: 1) {
|
|
945
|
+
id
|
|
946
|
+
title
|
|
947
|
+
metadata
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
`
|
|
951
|
+
}
|
|
952
|
+
})
|
|
953
|
+
const json = res.json()
|
|
954
|
+
if (isMariaDB) {
|
|
955
|
+
same(json.data.getPageById.metadata, JSON.stringify(jsonData))
|
|
956
|
+
} else {
|
|
957
|
+
same(json.data.getPageById.metadata, jsonData)
|
|
958
|
+
}
|
|
959
|
+
})
|
package/test/where.test.js
CHANGED
|
@@ -812,3 +812,291 @@ test('delete all', async ({ pass, teardown, same, equal }) => {
|
|
|
812
812
|
}, 'posts response')
|
|
813
813
|
}
|
|
814
814
|
})
|
|
815
|
+
|
|
816
|
+
test('like', async ({ pass, teardown, same, equal }) => {
|
|
817
|
+
const app = fastify()
|
|
818
|
+
app.register(sqlMapper, {
|
|
819
|
+
...connInfo,
|
|
820
|
+
async onDatabaseLoad (db, sql) {
|
|
821
|
+
pass('onDatabaseLoad called')
|
|
822
|
+
|
|
823
|
+
await clear(db, sql)
|
|
824
|
+
|
|
825
|
+
if (isSQLite) {
|
|
826
|
+
await db.query(sql`CREATE TABLE posts (
|
|
827
|
+
id INTEGER PRIMARY KEY,
|
|
828
|
+
title VARCHAR(42),
|
|
829
|
+
long_text TEXT,
|
|
830
|
+
counter INTEGER
|
|
831
|
+
);`)
|
|
832
|
+
} else {
|
|
833
|
+
await db.query(sql`CREATE TABLE posts (
|
|
834
|
+
id SERIAL PRIMARY KEY,
|
|
835
|
+
title VARCHAR(42),
|
|
836
|
+
long_text TEXT,
|
|
837
|
+
counter INTEGER
|
|
838
|
+
);`)
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
})
|
|
842
|
+
app.register(sqlGraphQL)
|
|
843
|
+
teardown(app.close.bind(app))
|
|
844
|
+
|
|
845
|
+
await app.ready()
|
|
846
|
+
|
|
847
|
+
const posts = [{
|
|
848
|
+
title: 'Dog',
|
|
849
|
+
longText: 'The dog barks',
|
|
850
|
+
counter: 10
|
|
851
|
+
}, {
|
|
852
|
+
title: 'Cat',
|
|
853
|
+
longText: 'The cat meows',
|
|
854
|
+
counter: 20
|
|
855
|
+
}, {
|
|
856
|
+
title: 'Mouse',
|
|
857
|
+
longText: 'The mouse likes cheese',
|
|
858
|
+
counter: 30
|
|
859
|
+
}, {
|
|
860
|
+
title: 'Duck',
|
|
861
|
+
longText: 'A duck tale',
|
|
862
|
+
counter: 40
|
|
863
|
+
}]
|
|
864
|
+
|
|
865
|
+
await app.inject({
|
|
866
|
+
method: 'POST',
|
|
867
|
+
url: '/graphql',
|
|
868
|
+
body: {
|
|
869
|
+
query: `
|
|
870
|
+
mutation batch($inputs : [PostInput]!) {
|
|
871
|
+
insertPosts(inputs: $inputs) {
|
|
872
|
+
id
|
|
873
|
+
title
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
`,
|
|
877
|
+
variables: {
|
|
878
|
+
inputs: posts
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
})
|
|
882
|
+
|
|
883
|
+
{
|
|
884
|
+
const res = await app.inject({
|
|
885
|
+
method: 'POST',
|
|
886
|
+
url: '/graphql',
|
|
887
|
+
body: {
|
|
888
|
+
query: `
|
|
889
|
+
query {
|
|
890
|
+
posts {
|
|
891
|
+
id
|
|
892
|
+
title
|
|
893
|
+
longText
|
|
894
|
+
counter
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
`
|
|
898
|
+
}
|
|
899
|
+
})
|
|
900
|
+
equal(res.statusCode, 200, 'posts status code')
|
|
901
|
+
same(res.json(), {
|
|
902
|
+
data: {
|
|
903
|
+
posts: [{
|
|
904
|
+
id: '1',
|
|
905
|
+
title: 'Dog',
|
|
906
|
+
longText: 'The dog barks',
|
|
907
|
+
counter: 10
|
|
908
|
+
}, {
|
|
909
|
+
id: '2',
|
|
910
|
+
title: 'Cat',
|
|
911
|
+
longText: 'The cat meows',
|
|
912
|
+
counter: 20
|
|
913
|
+
}, {
|
|
914
|
+
id: '3',
|
|
915
|
+
title: 'Mouse',
|
|
916
|
+
longText: 'The mouse likes cheese',
|
|
917
|
+
counter: 30
|
|
918
|
+
}, {
|
|
919
|
+
id: '4',
|
|
920
|
+
title: 'Duck',
|
|
921
|
+
longText: 'A duck tale',
|
|
922
|
+
counter: 40
|
|
923
|
+
}]
|
|
924
|
+
}
|
|
925
|
+
}, 'posts response')
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
{
|
|
929
|
+
const res = await app.inject({
|
|
930
|
+
method: 'POST',
|
|
931
|
+
url: '/graphql',
|
|
932
|
+
body: {
|
|
933
|
+
query: `
|
|
934
|
+
query {
|
|
935
|
+
posts(where: { longText: { like: "The%" } }) {
|
|
936
|
+
id
|
|
937
|
+
title
|
|
938
|
+
longText
|
|
939
|
+
counter
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
`
|
|
943
|
+
}
|
|
944
|
+
})
|
|
945
|
+
equal(res.statusCode, 200, 'where: { longText: { like: "The%" } } status code')
|
|
946
|
+
same(res.json(), {
|
|
947
|
+
data: {
|
|
948
|
+
posts: [{
|
|
949
|
+
id: '1',
|
|
950
|
+
title: 'Dog',
|
|
951
|
+
longText: 'The dog barks',
|
|
952
|
+
counter: 10
|
|
953
|
+
}, {
|
|
954
|
+
id: '2',
|
|
955
|
+
title: 'Cat',
|
|
956
|
+
longText: 'The cat meows',
|
|
957
|
+
counter: 20
|
|
958
|
+
}, {
|
|
959
|
+
id: '3',
|
|
960
|
+
title: 'Mouse',
|
|
961
|
+
longText: 'The mouse likes cheese',
|
|
962
|
+
counter: 30
|
|
963
|
+
}]
|
|
964
|
+
}
|
|
965
|
+
}, 'where: { longText: { like: "The%" } } response')
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
{
|
|
969
|
+
const res = await app.inject({
|
|
970
|
+
method: 'POST',
|
|
971
|
+
url: '/graphql',
|
|
972
|
+
body: {
|
|
973
|
+
query: `
|
|
974
|
+
query {
|
|
975
|
+
posts(where: { longText: { like: "%s" } }) {
|
|
976
|
+
id
|
|
977
|
+
title
|
|
978
|
+
longText
|
|
979
|
+
counter
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
`
|
|
983
|
+
}
|
|
984
|
+
})
|
|
985
|
+
equal(res.statusCode, 200, 'where: { longText: { like: "%s" } } status code')
|
|
986
|
+
same(res.json(), {
|
|
987
|
+
data: {
|
|
988
|
+
posts: [{
|
|
989
|
+
id: '1',
|
|
990
|
+
title: 'Dog',
|
|
991
|
+
longText: 'The dog barks',
|
|
992
|
+
counter: 10
|
|
993
|
+
}, {
|
|
994
|
+
id: '2',
|
|
995
|
+
title: 'Cat',
|
|
996
|
+
longText: 'The cat meows',
|
|
997
|
+
counter: 20
|
|
998
|
+
}]
|
|
999
|
+
}
|
|
1000
|
+
}, 'where: { longText: { like: "%s" } } response')
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
{
|
|
1004
|
+
const res = await app.inject({
|
|
1005
|
+
method: 'POST',
|
|
1006
|
+
url: '/graphql',
|
|
1007
|
+
body: {
|
|
1008
|
+
query: `
|
|
1009
|
+
query {
|
|
1010
|
+
posts(where: { longText: { like: "%o%" } }) {
|
|
1011
|
+
id
|
|
1012
|
+
title
|
|
1013
|
+
longText
|
|
1014
|
+
counter
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
`
|
|
1018
|
+
}
|
|
1019
|
+
})
|
|
1020
|
+
equal(res.statusCode, 200, 'where: { longText: { like: "%o%" } } status code')
|
|
1021
|
+
same(res.json(), {
|
|
1022
|
+
data: {
|
|
1023
|
+
posts: [{
|
|
1024
|
+
id: '1',
|
|
1025
|
+
title: 'Dog',
|
|
1026
|
+
longText: 'The dog barks',
|
|
1027
|
+
counter: 10
|
|
1028
|
+
}, {
|
|
1029
|
+
id: '2',
|
|
1030
|
+
title: 'Cat',
|
|
1031
|
+
longText: 'The cat meows',
|
|
1032
|
+
counter: 20
|
|
1033
|
+
}, {
|
|
1034
|
+
id: '3',
|
|
1035
|
+
title: 'Mouse',
|
|
1036
|
+
longText: 'The mouse likes cheese',
|
|
1037
|
+
counter: 30
|
|
1038
|
+
}]
|
|
1039
|
+
}
|
|
1040
|
+
}, 'where: { longText: { like: "%o%" } } response')
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
{
|
|
1044
|
+
const res = await app.inject({
|
|
1045
|
+
method: 'POST',
|
|
1046
|
+
url: '/graphql',
|
|
1047
|
+
body: {
|
|
1048
|
+
query: `
|
|
1049
|
+
query {
|
|
1050
|
+
posts(where: { counter: { like: 10 } }) {
|
|
1051
|
+
id
|
|
1052
|
+
title
|
|
1053
|
+
longText
|
|
1054
|
+
counter
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
`
|
|
1058
|
+
}
|
|
1059
|
+
})
|
|
1060
|
+
equal(res.statusCode, 200, 'where: { counter: { like: 10 } } status code')
|
|
1061
|
+
same(res.json(), {
|
|
1062
|
+
data: {
|
|
1063
|
+
posts: [{
|
|
1064
|
+
id: '1',
|
|
1065
|
+
title: 'Dog',
|
|
1066
|
+
longText: 'The dog barks',
|
|
1067
|
+
counter: 10
|
|
1068
|
+
}]
|
|
1069
|
+
}
|
|
1070
|
+
}, 'where: { counter: { like: 10 } } response')
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
{
|
|
1074
|
+
const res = await app.inject({
|
|
1075
|
+
method: 'POST',
|
|
1076
|
+
url: '/graphql',
|
|
1077
|
+
body: {
|
|
1078
|
+
query: `
|
|
1079
|
+
query {
|
|
1080
|
+
posts(where: { counter: { like: 40 } }) {
|
|
1081
|
+
id
|
|
1082
|
+
title
|
|
1083
|
+
longText
|
|
1084
|
+
counter
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
`
|
|
1088
|
+
}
|
|
1089
|
+
})
|
|
1090
|
+
equal(res.statusCode, 200, 'where: { counter: { like: 40 } } status code')
|
|
1091
|
+
same(res.json(), {
|
|
1092
|
+
data: {
|
|
1093
|
+
posts: [{
|
|
1094
|
+
id: '4',
|
|
1095
|
+
title: 'Duck',
|
|
1096
|
+
longText: 'A duck tale',
|
|
1097
|
+
counter: 40
|
|
1098
|
+
}]
|
|
1099
|
+
}
|
|
1100
|
+
}, 'where: { counter: { like: 40 } } response')
|
|
1101
|
+
}
|
|
1102
|
+
})
|