@platformatic/sql-graphql 0.9.2 → 0.11.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/index.js +15 -1
- package/lib/entity-to-type.js +1 -0
- package/lib/relationship.js +13 -8
- package/lib/utils.js +3 -1
- package/package.json +4 -4
- package/test/created_updated_at.test.js +517 -0
- package/test/datatypes.test.js +79 -0
- package/test/fix-428.test.js +172 -0
- package/test/helper.js +15 -0
- package/test/inserted_updated_at.test.js +17 -1
- package/test/nested.test.js +252 -0
- package/test/schema-error.test.js +51 -0
package/index.js
CHANGED
|
@@ -118,7 +118,21 @@ async function mapperToGraphql (app, opts) {
|
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
let sdl =
|
|
121
|
+
let sdl = ''
|
|
122
|
+
try {
|
|
123
|
+
sdl = graphql.printSchema(new graphql.GraphQLSchema({ query, mutation, subscription }))
|
|
124
|
+
} catch (error) {
|
|
125
|
+
// The next lines are excluded from test coverage:
|
|
126
|
+
// it's quite hard to test the following lines for all of the DB types
|
|
127
|
+
/* istanbul ignore next */
|
|
128
|
+
app.log.debug({ query, mutation, subscription }, 'GraphQL input schema')
|
|
129
|
+
/* istanbul ignore next */
|
|
130
|
+
const newError = new Error('Error printing the GraphQL schema')
|
|
131
|
+
/* istanbul ignore next */
|
|
132
|
+
newError.cause = error
|
|
133
|
+
/* istanbul ignore next */
|
|
134
|
+
throw newError
|
|
135
|
+
}
|
|
122
136
|
|
|
123
137
|
if (opts.federationMetadata) {
|
|
124
138
|
for (const replacement of federationReplacements) {
|
package/lib/entity-to-type.js
CHANGED
package/lib/relationship.js
CHANGED
|
@@ -20,19 +20,20 @@ module.exports = function establishRelations (app, relations, resolvers, loaders
|
|
|
20
20
|
|
|
21
21
|
assert(table_name, `table_name is required ${enhanceAssertLogMsg}`)
|
|
22
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}`)
|
|
24
23
|
|
|
25
24
|
const current = tablesTypeMap[table_name]
|
|
26
25
|
const foreign = tablesTypeMap[foreign_table_name]
|
|
27
26
|
assert(foreign !== undefined, `No foreign table named "${foreign_table_name}" was found ${enhanceAssertLogMsg}`)
|
|
28
27
|
|
|
29
|
-
// current to foreign
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
// current to foreign, we skip this if the foreign table has a composite primary key
|
|
29
|
+
// TODO implement support for this case
|
|
30
|
+
/* istanbul ignore else */
|
|
31
|
+
if (foreign.entity.primaryKeys.size === 1) {
|
|
32
|
+
const lowered = generateRelationshipName(column_name, foreign)
|
|
32
33
|
if (!relationships[current.type] || relationships[current.type][lowered] !== false) {
|
|
33
|
-
current.fields[lowered] = { type: foreign.type }
|
|
34
34
|
const originalField = camelcase(column_name)
|
|
35
35
|
delete current.fields[originalField]
|
|
36
|
+
current.fields[lowered] = { type: foreign.type }
|
|
36
37
|
loaders[current.type] = loaders[current.type] || resolvers[current.type] || {}
|
|
37
38
|
const key = camelcase(foreign_column_name)
|
|
38
39
|
loaders[current.type][lowered] = {
|
|
@@ -59,7 +60,7 @@ module.exports = function establishRelations (app, relations, resolvers, loaders
|
|
|
59
60
|
resolvers[foreign.type] = resolvers[foreign.type] || {}
|
|
60
61
|
const resolveRelation = async function (obj, args, ctx, info) {
|
|
61
62
|
const fields = fromSelectionSet(info.fieldNodes[0].selectionSet, new Set())
|
|
62
|
-
const toSearch = { ...args, fields: [...fields, column_name], ctx }
|
|
63
|
+
const toSearch = { ...args, fields: [...fields, column_name, ...current.relationalFields], ctx }
|
|
63
64
|
toSearch.where = toSearch.where || {}
|
|
64
65
|
toSearch.where[camelcase(column_name)] = { eq: obj.id }
|
|
65
66
|
return current.entity.find(toSearch)
|
|
@@ -84,9 +85,13 @@ function lowerCaseFirst (str) {
|
|
|
84
85
|
return str.charAt(0).toLowerCase() + str.slice(1)
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
function
|
|
88
|
+
function generateRelationshipName (str, foreign) {
|
|
89
|
+
const primaryKey = foreign.entity.primaryKeys.values().next().value
|
|
88
90
|
str = str.toString()
|
|
89
|
-
|
|
91
|
+
if (str.endsWith('_' + primaryKey)) {
|
|
92
|
+
str = str.slice(0, -2)
|
|
93
|
+
}
|
|
94
|
+
return lowerCaseFirst(camelcase(str))
|
|
90
95
|
}
|
|
91
96
|
|
|
92
97
|
function findPrimaryColumnName (camelCasedFields) {
|
package/lib/utils.js
CHANGED
|
@@ -21,11 +21,13 @@ function sqlTypeToGraphQL (sqlType) {
|
|
|
21
21
|
case 'decimal':
|
|
22
22
|
return graphql.GraphQLInt
|
|
23
23
|
case 'bigint':
|
|
24
|
-
return graphql.
|
|
24
|
+
return graphql.GraphQLString
|
|
25
25
|
case 'int2':
|
|
26
26
|
return graphql.GraphQLInt
|
|
27
27
|
case 'int4':
|
|
28
28
|
return graphql.GraphQLInt
|
|
29
|
+
case 'int8':
|
|
30
|
+
return graphql.GraphQLString
|
|
29
31
|
case 'varchar':
|
|
30
32
|
return graphql.GraphQLString
|
|
31
33
|
case 'text':
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/sql-graphql",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Map SQL dbs to GraphQL",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
@@ -14,14 +14,14 @@
|
|
|
14
14
|
},
|
|
15
15
|
"homepage": "https://github.com/platformatic/platformatic#readme",
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"@platformatic/sql-events": "0.9.2",
|
|
18
|
-
"@platformatic/sql-mapper": "0.9.2",
|
|
19
17
|
"fastify": "^4.6.0",
|
|
20
18
|
"snazzy": "^9.0.0",
|
|
21
19
|
"standard": "^17.0.0",
|
|
22
20
|
"tap": "^16.0.0",
|
|
23
21
|
"tsd": "^0.25.0",
|
|
24
|
-
"ws": "^8.9.0"
|
|
22
|
+
"ws": "^8.9.0",
|
|
23
|
+
"@platformatic/sql-events": "0.11.0",
|
|
24
|
+
"@platformatic/sql-mapper": "0.11.0"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"camelcase": "^6.0.0",
|
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { test } = require('tap')
|
|
4
|
+
const sqlGraphQL = require('..')
|
|
5
|
+
const sqlMapper = require('@platformatic/sql-mapper')
|
|
6
|
+
const fastify = require('fastify')
|
|
7
|
+
const { clear, connInfo, isSQLite, isMysql } = require('./helper')
|
|
8
|
+
const { setTimeout } = require('timers/promises')
|
|
9
|
+
|
|
10
|
+
async function createBasicPages (db, sql) {
|
|
11
|
+
if (isSQLite) {
|
|
12
|
+
await db.query(sql`CREATE TABLE pages (
|
|
13
|
+
id INTEGER PRIMARY KEY,
|
|
14
|
+
title VARCHAR(42),
|
|
15
|
+
created_at TIMESTAMP,
|
|
16
|
+
updated_at TIMESTAMP
|
|
17
|
+
);`)
|
|
18
|
+
} else if (isMysql) {
|
|
19
|
+
await db.query(sql`CREATE TABLE pages (
|
|
20
|
+
id SERIAL PRIMARY KEY,
|
|
21
|
+
title VARCHAR(42),
|
|
22
|
+
created_at TIMESTAMP NULL DEFAULT NULL,
|
|
23
|
+
updated_at TIMESTAMP NULL DEFAULT NULL
|
|
24
|
+
);`)
|
|
25
|
+
} else {
|
|
26
|
+
await db.query(sql`CREATE TABLE pages (
|
|
27
|
+
id SERIAL PRIMARY KEY,
|
|
28
|
+
title VARCHAR(42),
|
|
29
|
+
created_at TIMESTAMP,
|
|
30
|
+
updated_at TIMESTAMP
|
|
31
|
+
);`)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
test('created_at updated_at happy path', async ({ pass, teardown, same, equal, not, comment }) => {
|
|
36
|
+
const app = fastify()
|
|
37
|
+
app.register(sqlMapper, {
|
|
38
|
+
...connInfo,
|
|
39
|
+
async onDatabaseLoad (db, sql) {
|
|
40
|
+
pass('onDatabaseLoad called')
|
|
41
|
+
|
|
42
|
+
await clear(db, sql)
|
|
43
|
+
await createBasicPages(db, sql)
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
app.register(sqlGraphQL)
|
|
47
|
+
teardown(app.close.bind(app))
|
|
48
|
+
|
|
49
|
+
await app.ready()
|
|
50
|
+
|
|
51
|
+
let original
|
|
52
|
+
|
|
53
|
+
{
|
|
54
|
+
const res = await app.inject({
|
|
55
|
+
method: 'POST',
|
|
56
|
+
url: '/graphql',
|
|
57
|
+
body: {
|
|
58
|
+
query: `
|
|
59
|
+
mutation {
|
|
60
|
+
savePage(input: { title: "Hello" }) {
|
|
61
|
+
id
|
|
62
|
+
title
|
|
63
|
+
createdAt
|
|
64
|
+
updatedAt
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
`
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
equal(res.statusCode, 200, 'savePage status code')
|
|
71
|
+
const data = res.json().data
|
|
72
|
+
not(data.savePage.createdAt, null, 'createdAt')
|
|
73
|
+
not(data.savePage.updatedAt, null, 'updatedAt')
|
|
74
|
+
comment(`createdAt: ${data.savePage.createdAt}`)
|
|
75
|
+
comment(`updatedAt: ${data.savePage.updatedAt}`)
|
|
76
|
+
original = data.savePage
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
{
|
|
80
|
+
const res = await app.inject({
|
|
81
|
+
method: 'POST',
|
|
82
|
+
url: '/graphql',
|
|
83
|
+
body: {
|
|
84
|
+
query: `
|
|
85
|
+
query {
|
|
86
|
+
getPageById(id: 1) {
|
|
87
|
+
id
|
|
88
|
+
title
|
|
89
|
+
createdAt
|
|
90
|
+
updatedAt
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
`
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
equal(res.statusCode, 200, 'pages status code')
|
|
97
|
+
const data = res.json().data
|
|
98
|
+
equal(data.getPageById.createdAt, original.createdAt, 'createdAt')
|
|
99
|
+
equal(data.getPageById.updatedAt, original.updatedAt, 'updatedAt')
|
|
100
|
+
comment(`createdAt: ${data.getPageById.createdAt}`)
|
|
101
|
+
comment(`updatedAt: ${data.getPageById.updatedAt}`)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
await setTimeout(1000) // await 1s
|
|
105
|
+
|
|
106
|
+
let updated
|
|
107
|
+
{
|
|
108
|
+
const res = await app.inject({
|
|
109
|
+
method: 'POST',
|
|
110
|
+
url: '/graphql',
|
|
111
|
+
body: {
|
|
112
|
+
query: `
|
|
113
|
+
mutation {
|
|
114
|
+
savePage(input: { id: 1, title: "Hello World" }) {
|
|
115
|
+
id
|
|
116
|
+
title
|
|
117
|
+
createdAt
|
|
118
|
+
updatedAt
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
`
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
const data = res.json().data
|
|
125
|
+
equal(data.savePage.createdAt, original.createdAt, 'createdAt')
|
|
126
|
+
not(data.savePage.updatedAt, original.updatedAt, 'updatedAt')
|
|
127
|
+
updated = data.savePage
|
|
128
|
+
comment(`createdAt: ${data.savePage.createdAt}`)
|
|
129
|
+
comment(`updatedAt: ${data.savePage.updatedAt}`)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
{
|
|
133
|
+
const res = await app.inject({
|
|
134
|
+
method: 'POST',
|
|
135
|
+
url: '/graphql',
|
|
136
|
+
body: {
|
|
137
|
+
query: `
|
|
138
|
+
query {
|
|
139
|
+
getPageById(id: 1) {
|
|
140
|
+
id
|
|
141
|
+
title
|
|
142
|
+
createdAt
|
|
143
|
+
updatedAt
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
`
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
equal(res.statusCode, 200, 'pages status code')
|
|
150
|
+
const data = res.json().data
|
|
151
|
+
equal(data.getPageById.createdAt, updated.createdAt, 'createdAt')
|
|
152
|
+
equal(data.getPageById.updatedAt, updated.updatedAt, 'updatedAt')
|
|
153
|
+
comment(`createdAt: ${data.getPageById.createdAt}`)
|
|
154
|
+
comment(`updatedAt: ${data.getPageById.updatedAt}`)
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
test('cannot set created_at', async ({ pass, teardown, same, equal, not, comment }) => {
|
|
159
|
+
const app = fastify()
|
|
160
|
+
app.register(sqlMapper, {
|
|
161
|
+
...connInfo,
|
|
162
|
+
async onDatabaseLoad (db, sql) {
|
|
163
|
+
pass('onDatabaseLoad called')
|
|
164
|
+
|
|
165
|
+
await clear(db, sql)
|
|
166
|
+
await createBasicPages(db, sql)
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
app.register(sqlGraphQL)
|
|
170
|
+
teardown(app.close.bind(app))
|
|
171
|
+
|
|
172
|
+
await app.ready()
|
|
173
|
+
|
|
174
|
+
{
|
|
175
|
+
const res = await app.inject({
|
|
176
|
+
method: 'POST',
|
|
177
|
+
url: '/graphql',
|
|
178
|
+
body: {
|
|
179
|
+
query: `
|
|
180
|
+
mutation {
|
|
181
|
+
savePage(input: { title: "Hello", createdAt: "${new Date().toISOString()}" }) {
|
|
182
|
+
id
|
|
183
|
+
title
|
|
184
|
+
createdAt
|
|
185
|
+
updatedAt
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
`
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
equal(res.statusCode, 400, 'savePage status code')
|
|
192
|
+
const data = res.json()
|
|
193
|
+
equal(data.errors[0].message, 'Field "createdAt" is not defined by type "PageInput".')
|
|
194
|
+
}
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
test('cannot set updated_at', async ({ pass, teardown, same, equal, not, comment }) => {
|
|
198
|
+
const app = fastify()
|
|
199
|
+
app.register(sqlMapper, {
|
|
200
|
+
...connInfo,
|
|
201
|
+
async onDatabaseLoad (db, sql) {
|
|
202
|
+
pass('onDatabaseLoad called')
|
|
203
|
+
|
|
204
|
+
await clear(db, sql)
|
|
205
|
+
await createBasicPages(db, sql)
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
app.register(sqlGraphQL)
|
|
209
|
+
teardown(app.close.bind(app))
|
|
210
|
+
|
|
211
|
+
await app.ready()
|
|
212
|
+
|
|
213
|
+
{
|
|
214
|
+
const res = await app.inject({
|
|
215
|
+
method: 'POST',
|
|
216
|
+
url: '/graphql',
|
|
217
|
+
body: {
|
|
218
|
+
query: `
|
|
219
|
+
mutation {
|
|
220
|
+
savePage(input: { title: "Hello" }) {
|
|
221
|
+
id
|
|
222
|
+
title
|
|
223
|
+
createdAt
|
|
224
|
+
updatedAt
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
`
|
|
228
|
+
}
|
|
229
|
+
})
|
|
230
|
+
equal(res.statusCode, 200, 'savePage status code')
|
|
231
|
+
const data = res.json().data
|
|
232
|
+
not(data.savePage.createdAt, null, 'createdAt')
|
|
233
|
+
not(data.savePage.updatedAt, null, 'updatedAt')
|
|
234
|
+
comment(`createdAt: ${data.savePage.createdAt}`)
|
|
235
|
+
comment(`updatedAt: ${data.savePage.updatedAt}`)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
{
|
|
239
|
+
const res = await app.inject({
|
|
240
|
+
method: 'POST',
|
|
241
|
+
url: '/graphql',
|
|
242
|
+
body: {
|
|
243
|
+
query: `
|
|
244
|
+
mutation {
|
|
245
|
+
savePage(input: { id: 1, title: "Hello World", updatedAt: "${new Date().toISOString()}" }) {
|
|
246
|
+
id
|
|
247
|
+
title
|
|
248
|
+
createdAt
|
|
249
|
+
updatedAt
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
`
|
|
253
|
+
}
|
|
254
|
+
})
|
|
255
|
+
equal(res.statusCode, 400, 'savePage status code')
|
|
256
|
+
const data = res.json()
|
|
257
|
+
equal(data.errors[0].message, 'Field "updatedAt" is not defined by type "PageInput".')
|
|
258
|
+
}
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
test('do not assign created_at updated_at', async ({ pass, teardown, same, equal, not, comment }) => {
|
|
262
|
+
const app = fastify()
|
|
263
|
+
app.register(sqlMapper, {
|
|
264
|
+
...connInfo,
|
|
265
|
+
autoTimestamp: false,
|
|
266
|
+
async onDatabaseLoad (db, sql) {
|
|
267
|
+
pass('onDatabaseLoad called')
|
|
268
|
+
|
|
269
|
+
await clear(db, sql)
|
|
270
|
+
await createBasicPages(db, sql)
|
|
271
|
+
}
|
|
272
|
+
})
|
|
273
|
+
app.register(sqlGraphQL)
|
|
274
|
+
teardown(app.close.bind(app))
|
|
275
|
+
|
|
276
|
+
await app.ready()
|
|
277
|
+
|
|
278
|
+
{
|
|
279
|
+
const res = await app.inject({
|
|
280
|
+
method: 'POST',
|
|
281
|
+
url: '/graphql',
|
|
282
|
+
body: {
|
|
283
|
+
query: `
|
|
284
|
+
mutation {
|
|
285
|
+
savePage(input: { title: "Hello" }) {
|
|
286
|
+
id
|
|
287
|
+
title
|
|
288
|
+
createdAt
|
|
289
|
+
updatedAt
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
`
|
|
293
|
+
}
|
|
294
|
+
})
|
|
295
|
+
equal(res.statusCode, 200, 'savePage status code')
|
|
296
|
+
const data = res.json().data
|
|
297
|
+
equal(data.savePage.createdAt, null, 'createdAt')
|
|
298
|
+
equal(data.savePage.updatedAt, null, 'updatedAt')
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
{
|
|
302
|
+
const res = await app.inject({
|
|
303
|
+
method: 'POST',
|
|
304
|
+
url: '/graphql',
|
|
305
|
+
body: {
|
|
306
|
+
query: `
|
|
307
|
+
query {
|
|
308
|
+
getPageById(id: 1) {
|
|
309
|
+
id
|
|
310
|
+
title
|
|
311
|
+
createdAt
|
|
312
|
+
updatedAt
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
`
|
|
316
|
+
}
|
|
317
|
+
})
|
|
318
|
+
equal(res.statusCode, 200, 'pages status code')
|
|
319
|
+
const data = res.json().data
|
|
320
|
+
equal(data.getPageById.createdAt, null, 'createdAt')
|
|
321
|
+
equal(data.getPageById.updatedAt, null, 'updatedAt')
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
{
|
|
325
|
+
const res = await app.inject({
|
|
326
|
+
method: 'POST',
|
|
327
|
+
url: '/graphql',
|
|
328
|
+
body: {
|
|
329
|
+
query: `
|
|
330
|
+
mutation {
|
|
331
|
+
savePage(input: { id: 1, title: "Hello World" }) {
|
|
332
|
+
id
|
|
333
|
+
title
|
|
334
|
+
createdAt
|
|
335
|
+
updatedAt
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
`
|
|
339
|
+
}
|
|
340
|
+
})
|
|
341
|
+
const data = res.json().data
|
|
342
|
+
equal(data.savePage.createdAt, null, 'createdAt')
|
|
343
|
+
equal(data.savePage.updatedAt, null, 'updatedAt')
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
{
|
|
347
|
+
const res = await app.inject({
|
|
348
|
+
method: 'POST',
|
|
349
|
+
url: '/graphql',
|
|
350
|
+
body: {
|
|
351
|
+
query: `
|
|
352
|
+
query {
|
|
353
|
+
getPageById(id: 1) {
|
|
354
|
+
id
|
|
355
|
+
title
|
|
356
|
+
createdAt
|
|
357
|
+
updatedAt
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
`
|
|
361
|
+
}
|
|
362
|
+
})
|
|
363
|
+
equal(res.statusCode, 200, 'pages status code')
|
|
364
|
+
const data = res.json().data
|
|
365
|
+
equal(data.getPageById.createdAt, null, 'createdAt')
|
|
366
|
+
equal(data.getPageById.updatedAt, null, 'updatedAt')
|
|
367
|
+
}
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
test('bulk insert adds created_at updated_at', async ({ pass, teardown, same, equal, not, comment }) => {
|
|
371
|
+
const app = fastify()
|
|
372
|
+
app.register(sqlMapper, {
|
|
373
|
+
...connInfo,
|
|
374
|
+
async onDatabaseLoad (db, sql) {
|
|
375
|
+
pass('onDatabaseLoad called')
|
|
376
|
+
|
|
377
|
+
await clear(db, sql)
|
|
378
|
+
await createBasicPages(db, sql)
|
|
379
|
+
}
|
|
380
|
+
})
|
|
381
|
+
app.register(sqlGraphQL)
|
|
382
|
+
teardown(app.close.bind(app))
|
|
383
|
+
|
|
384
|
+
await app.ready()
|
|
385
|
+
|
|
386
|
+
{
|
|
387
|
+
const res = await app.inject({
|
|
388
|
+
method: 'POST',
|
|
389
|
+
url: '/graphql',
|
|
390
|
+
body: {
|
|
391
|
+
query: `
|
|
392
|
+
mutation {
|
|
393
|
+
savePage(input: { title: "Hello" }) {
|
|
394
|
+
id
|
|
395
|
+
title
|
|
396
|
+
createdAt
|
|
397
|
+
updatedAt
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
`
|
|
401
|
+
}
|
|
402
|
+
})
|
|
403
|
+
equal(res.statusCode, 200, 'savePage status code')
|
|
404
|
+
const data = res.json().data
|
|
405
|
+
not(data.savePage.createdAt, null, 'createdAt')
|
|
406
|
+
not(data.savePage.updatedAt, null, 'updatedAt')
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
{
|
|
410
|
+
const res = await app.inject({
|
|
411
|
+
method: 'POST',
|
|
412
|
+
url: '/graphql',
|
|
413
|
+
body: {
|
|
414
|
+
query: `
|
|
415
|
+
mutation batch($inputs : [PageInput]!) {
|
|
416
|
+
insertPages (inputs: $inputs) {
|
|
417
|
+
id
|
|
418
|
+
title
|
|
419
|
+
createdAt
|
|
420
|
+
updatedAt
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
`,
|
|
424
|
+
variables: {
|
|
425
|
+
inputs: [
|
|
426
|
+
{ title: 'Page 1' },
|
|
427
|
+
{ title: 'Page 2' },
|
|
428
|
+
{ title: 'Page 3' }
|
|
429
|
+
]
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
})
|
|
433
|
+
equal(res.statusCode, 200, 'savePage status code')
|
|
434
|
+
const pages = res.json().data.insertPages
|
|
435
|
+
for (const page of pages) {
|
|
436
|
+
not(page.createdAt, null, 'createdAt')
|
|
437
|
+
not(page.updatedAt, null, 'updatedAt')
|
|
438
|
+
equal(page.createdAt, page.updatedAt, 'createdAt === updatedAt')
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
{
|
|
443
|
+
const res = await app.inject({
|
|
444
|
+
method: 'POST',
|
|
445
|
+
url: '/graphql',
|
|
446
|
+
body: {
|
|
447
|
+
query: `
|
|
448
|
+
query {
|
|
449
|
+
pages {
|
|
450
|
+
id
|
|
451
|
+
title
|
|
452
|
+
createdAt
|
|
453
|
+
updatedAt
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
`
|
|
457
|
+
}
|
|
458
|
+
})
|
|
459
|
+
equal(res.statusCode, 200, 'pages status code')
|
|
460
|
+
const pages = res.json().data.pages
|
|
461
|
+
for (const page of pages) {
|
|
462
|
+
not(page.createdAt, null, 'createdAt')
|
|
463
|
+
not(page.updatedAt, null, 'updatedAt')
|
|
464
|
+
equal(page.createdAt, page.updatedAt, 'createdAt === updatedAt')
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
test('bulk insert with autoTimestamp=false do not had created_at updated_at', async ({ pass, teardown, same, equal, not, comment }) => {
|
|
470
|
+
const app = fastify()
|
|
471
|
+
app.register(sqlMapper, {
|
|
472
|
+
...connInfo,
|
|
473
|
+
autoTimestamp: false,
|
|
474
|
+
async onDatabaseLoad (db, sql) {
|
|
475
|
+
pass('onDatabaseLoad called')
|
|
476
|
+
|
|
477
|
+
await clear(db, sql)
|
|
478
|
+
await createBasicPages(db, sql)
|
|
479
|
+
}
|
|
480
|
+
})
|
|
481
|
+
app.register(sqlGraphQL)
|
|
482
|
+
teardown(app.close.bind(app))
|
|
483
|
+
|
|
484
|
+
await app.ready()
|
|
485
|
+
|
|
486
|
+
{
|
|
487
|
+
const res = await app.inject({
|
|
488
|
+
method: 'POST',
|
|
489
|
+
url: '/graphql',
|
|
490
|
+
body: {
|
|
491
|
+
query: `
|
|
492
|
+
mutation batch($inputs : [PageInput]!) {
|
|
493
|
+
insertPages (inputs: $inputs) {
|
|
494
|
+
id
|
|
495
|
+
title
|
|
496
|
+
createdAt
|
|
497
|
+
updatedAt
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
`,
|
|
501
|
+
variables: {
|
|
502
|
+
inputs: [
|
|
503
|
+
{ title: 'Page 1' },
|
|
504
|
+
{ title: 'Page 2' },
|
|
505
|
+
{ title: 'Page 3' }
|
|
506
|
+
]
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
})
|
|
510
|
+
equal(res.statusCode, 200, 'savePage status code')
|
|
511
|
+
const pages = res.json().data.insertPages
|
|
512
|
+
for (const page of pages) {
|
|
513
|
+
equal(page.createdAt, null, 'createdAt')
|
|
514
|
+
equal(page.updatedAt, null, 'updatedAt')
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
})
|
package/test/datatypes.test.js
CHANGED
|
@@ -722,3 +722,82 @@ test('[SQLite] - UUID', { skip: !isSQLite }, async ({ pass, teardown, same, equa
|
|
|
722
722
|
}, 'savePage response')
|
|
723
723
|
}
|
|
724
724
|
})
|
|
725
|
+
|
|
726
|
+
test('BIGINT!', { skip: isSQLite, only: true }, async ({ pass, teardown, same, equal }) => {
|
|
727
|
+
const app = fastify()
|
|
728
|
+
app.register(sqlMapper, {
|
|
729
|
+
...connInfo,
|
|
730
|
+
async onDatabaseLoad (db, sql) {
|
|
731
|
+
pass('onDatabaseLoad called')
|
|
732
|
+
|
|
733
|
+
await clear(db, sql)
|
|
734
|
+
|
|
735
|
+
await db.query(sql`
|
|
736
|
+
CREATE TABLE simple_types (
|
|
737
|
+
id SERIAL PRIMARY KEY,
|
|
738
|
+
counter BIGINT
|
|
739
|
+
);`)
|
|
740
|
+
}
|
|
741
|
+
})
|
|
742
|
+
teardown(app.close.bind(app))
|
|
743
|
+
|
|
744
|
+
app.register(sqlGraphQL)
|
|
745
|
+
|
|
746
|
+
await app.ready()
|
|
747
|
+
|
|
748
|
+
const counter = BigInt(Number.MAX_SAFE_INTEGER) + 1000n
|
|
749
|
+
|
|
750
|
+
{
|
|
751
|
+
const res = await app.inject({
|
|
752
|
+
method: 'POST',
|
|
753
|
+
url: '/graphql',
|
|
754
|
+
body: {
|
|
755
|
+
query: `
|
|
756
|
+
mutation {
|
|
757
|
+
saveSimpleType(input: {
|
|
758
|
+
counter: "${counter}"
|
|
759
|
+
}) {
|
|
760
|
+
id
|
|
761
|
+
counter
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
`
|
|
765
|
+
}
|
|
766
|
+
})
|
|
767
|
+
equal(res.statusCode, 200, 'saveSimpleType status code')
|
|
768
|
+
same(res.json(), {
|
|
769
|
+
data: {
|
|
770
|
+
saveSimpleType: {
|
|
771
|
+
id: '1',
|
|
772
|
+
counter
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}, 'saveSimpleType response')
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
{
|
|
779
|
+
const res = await app.inject({
|
|
780
|
+
method: 'POST',
|
|
781
|
+
url: '/graphql',
|
|
782
|
+
body: {
|
|
783
|
+
query: `
|
|
784
|
+
query {
|
|
785
|
+
getSimpleTypeById(id: 1) {
|
|
786
|
+
id
|
|
787
|
+
counter
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
`
|
|
791
|
+
}
|
|
792
|
+
})
|
|
793
|
+
equal(res.statusCode, 200, 'getSimpleTypeById status code')
|
|
794
|
+
same(res.json(), {
|
|
795
|
+
data: {
|
|
796
|
+
getSimpleTypeById: {
|
|
797
|
+
id: 1,
|
|
798
|
+
counter
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}, 'getSimpleTypeById response')
|
|
802
|
+
}
|
|
803
|
+
})
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { test } = require('tap')
|
|
4
|
+
const sqlGraphQL = require('..')
|
|
5
|
+
const sqlMapper = require('@platformatic/sql-mapper')
|
|
6
|
+
const fastify = require('fastify')
|
|
7
|
+
const { connInfo, isPg } = require('./helper')
|
|
8
|
+
|
|
9
|
+
async function runMigrations (db, sql) {
|
|
10
|
+
await db.query(sql`
|
|
11
|
+
create table company (
|
|
12
|
+
id SERIAL primary key,
|
|
13
|
+
name varchar(60)
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
create table social_medias (
|
|
17
|
+
id SERIAL primary key,
|
|
18
|
+
name varchar(60)
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
create table company_social_medias (
|
|
22
|
+
id SERIAL primary key,
|
|
23
|
+
username varchar not null,
|
|
24
|
+
social_medias_id integer not null,
|
|
25
|
+
company_id integer not null,
|
|
26
|
+
constraint "fk_company_id" foreign key ("company_id") references company,
|
|
27
|
+
constraint "fk_social_medias_id" foreign key ("social_medias_id") references social_medias
|
|
28
|
+
);
|
|
29
|
+
`)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function clean (db, sql) {
|
|
33
|
+
await db.query(sql`
|
|
34
|
+
drop table if exists company_social_medias;
|
|
35
|
+
drop table if exists company;
|
|
36
|
+
drop table if exists social_medias;
|
|
37
|
+
`)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
test('do not crash', { skip: !isPg }, async ({ pass, teardown, same, equal }) => {
|
|
41
|
+
const app = fastify()
|
|
42
|
+
app.register(sqlMapper, {
|
|
43
|
+
...connInfo,
|
|
44
|
+
async onDatabaseLoad (db, sql) {
|
|
45
|
+
pass('onDatabaseLoad called')
|
|
46
|
+
await clean(db, sql)
|
|
47
|
+
|
|
48
|
+
await runMigrations(db, sql)
|
|
49
|
+
teardown(async () => {
|
|
50
|
+
await clean(db, sql)
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
await app.register(sqlGraphQL)
|
|
55
|
+
teardown(app.close.bind(app))
|
|
56
|
+
|
|
57
|
+
await app.ready()
|
|
58
|
+
|
|
59
|
+
{
|
|
60
|
+
const res = await app.inject({
|
|
61
|
+
method: 'POST',
|
|
62
|
+
url: '/graphql',
|
|
63
|
+
body: {
|
|
64
|
+
query: `
|
|
65
|
+
mutation {
|
|
66
|
+
saveCompany(input: { name: "Platformatic" }) {
|
|
67
|
+
id
|
|
68
|
+
name
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
`
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
equal(res.statusCode, 200, 'saveCompany status code')
|
|
75
|
+
same(res.json(), {
|
|
76
|
+
data: {
|
|
77
|
+
saveCompany: {
|
|
78
|
+
id: 1,
|
|
79
|
+
name: 'Platformatic'
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}, 'saveCompany response')
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
{
|
|
86
|
+
const res = await app.inject({
|
|
87
|
+
method: 'POST',
|
|
88
|
+
url: '/graphql',
|
|
89
|
+
body: {
|
|
90
|
+
query: `
|
|
91
|
+
mutation {
|
|
92
|
+
saveSocialMedia(input: { name: "Twitter" }) {
|
|
93
|
+
id
|
|
94
|
+
name
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
`
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
equal(res.statusCode, 200, 'saveSocialMedia status code')
|
|
101
|
+
same(res.json(), {
|
|
102
|
+
data: {
|
|
103
|
+
saveSocialMedia: {
|
|
104
|
+
id: 1,
|
|
105
|
+
name: 'Twitter'
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}, 'saveSocialMedia response')
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
{
|
|
112
|
+
const res = await app.inject({
|
|
113
|
+
method: 'POST',
|
|
114
|
+
url: '/graphql',
|
|
115
|
+
body: {
|
|
116
|
+
query: `
|
|
117
|
+
mutation {
|
|
118
|
+
saveCompanySocialMedia(input: { companyId: 1, socialMediasId: 1, username: "platformatic" }) {
|
|
119
|
+
id
|
|
120
|
+
username
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
`
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
equal(res.statusCode, 200, 'saveCompanySocialMedia status code')
|
|
127
|
+
same(res.json(), {
|
|
128
|
+
data: {
|
|
129
|
+
saveCompanySocialMedia: {
|
|
130
|
+
id: 1,
|
|
131
|
+
username: 'platformatic'
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}, 'saveCompanySocialMedia response')
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
{
|
|
138
|
+
const res = await app.inject({
|
|
139
|
+
method: 'POST',
|
|
140
|
+
url: '/graphql',
|
|
141
|
+
body: {
|
|
142
|
+
query: `
|
|
143
|
+
query {
|
|
144
|
+
company {
|
|
145
|
+
name
|
|
146
|
+
companySocialMedias {
|
|
147
|
+
username
|
|
148
|
+
socialMedias {
|
|
149
|
+
name
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
`
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
equal(res.statusCode, 200, 'complex status code')
|
|
158
|
+
same(res.json(), {
|
|
159
|
+
data: {
|
|
160
|
+
company: [{
|
|
161
|
+
name: 'Platformatic',
|
|
162
|
+
companySocialMedias: [{
|
|
163
|
+
username: 'platformatic',
|
|
164
|
+
socialMedias: {
|
|
165
|
+
name: 'Twitter'
|
|
166
|
+
}
|
|
167
|
+
}]
|
|
168
|
+
}]
|
|
169
|
+
}
|
|
170
|
+
}, 'complex response')
|
|
171
|
+
}
|
|
172
|
+
})
|
package/test/helper.js
CHANGED
|
@@ -96,4 +96,19 @@ module.exports.clear = async function (db, sql) {
|
|
|
96
96
|
await db.query(sql`DROP Type simple_enum`)
|
|
97
97
|
} catch (err) {
|
|
98
98
|
}
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
await db.query(sql`DROP TABLE "myschema"."mytable"`)
|
|
102
|
+
} catch (err) {
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
await db.query(sql`DROP TABLE "myschema"."template"`)
|
|
107
|
+
} catch (err) {
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
await db.query(sql`DROP SCHEMA "myschema"`)
|
|
112
|
+
} catch (err) {
|
|
113
|
+
}
|
|
99
114
|
}
|
|
@@ -36,6 +36,10 @@ test('inserted_at updated_at happy path', async ({ pass, teardown, same, equal,
|
|
|
36
36
|
const app = fastify()
|
|
37
37
|
app.register(sqlMapper, {
|
|
38
38
|
...connInfo,
|
|
39
|
+
autoTimestamp: {
|
|
40
|
+
createdAt: 'inserted_at',
|
|
41
|
+
updatedAt: 'updated_at'
|
|
42
|
+
},
|
|
39
43
|
async onDatabaseLoad (db, sql) {
|
|
40
44
|
pass('onDatabaseLoad called')
|
|
41
45
|
|
|
@@ -159,6 +163,10 @@ test('cannot set inserted_at', async ({ pass, teardown, same, equal, not, commen
|
|
|
159
163
|
const app = fastify()
|
|
160
164
|
app.register(sqlMapper, {
|
|
161
165
|
...connInfo,
|
|
166
|
+
autoTimestamp: {
|
|
167
|
+
createdAt: 'inserted_at',
|
|
168
|
+
updatedAt: 'updated_at'
|
|
169
|
+
},
|
|
162
170
|
async onDatabaseLoad (db, sql) {
|
|
163
171
|
pass('onDatabaseLoad called')
|
|
164
172
|
|
|
@@ -198,6 +206,10 @@ test('cannot set updated_at', async ({ pass, teardown, same, equal, not, comment
|
|
|
198
206
|
const app = fastify()
|
|
199
207
|
app.register(sqlMapper, {
|
|
200
208
|
...connInfo,
|
|
209
|
+
autoTimestamp: {
|
|
210
|
+
createdAt: 'inserted_at',
|
|
211
|
+
updatedAt: 'updated_at'
|
|
212
|
+
},
|
|
201
213
|
async onDatabaseLoad (db, sql) {
|
|
202
214
|
pass('onDatabaseLoad called')
|
|
203
215
|
|
|
@@ -371,6 +383,10 @@ test('bulk insert adds inserted_at updated_at', async ({ pass, teardown, same, e
|
|
|
371
383
|
const app = fastify()
|
|
372
384
|
app.register(sqlMapper, {
|
|
373
385
|
...connInfo,
|
|
386
|
+
autoTimestamp: {
|
|
387
|
+
createdAt: 'inserted_at',
|
|
388
|
+
updatedAt: 'updated_at'
|
|
389
|
+
},
|
|
374
390
|
async onDatabaseLoad (db, sql) {
|
|
375
391
|
pass('onDatabaseLoad called')
|
|
376
392
|
|
|
@@ -466,7 +482,7 @@ test('bulk insert adds inserted_at updated_at', async ({ pass, teardown, same, e
|
|
|
466
482
|
}
|
|
467
483
|
})
|
|
468
484
|
|
|
469
|
-
test('bulk insert with autoTimestamp=false do not
|
|
485
|
+
test('bulk insert with autoTimestamp=false do not had inserted_at updated_at', async ({ pass, teardown, same, equal, not, comment }) => {
|
|
470
486
|
const app = fastify()
|
|
471
487
|
app.register(sqlMapper, {
|
|
472
488
|
...connInfo,
|
package/test/nested.test.js
CHANGED
|
@@ -860,3 +860,255 @@ test('nested update', async ({ pass, teardown, same, equal }) => {
|
|
|
860
860
|
}, 'savePage response')
|
|
861
861
|
}
|
|
862
862
|
})
|
|
863
|
+
|
|
864
|
+
test('nested resolver without `id` suffix', async ({ pass, teardown, same, equal }) => {
|
|
865
|
+
const app = fastify()
|
|
866
|
+
app.register(sqlMapper, {
|
|
867
|
+
...connInfo,
|
|
868
|
+
async onDatabaseLoad (db, sql) {
|
|
869
|
+
pass('onDatabaseLoad called')
|
|
870
|
+
|
|
871
|
+
await clear(db, sql)
|
|
872
|
+
|
|
873
|
+
if (isMysql) {
|
|
874
|
+
await db.query(sql`
|
|
875
|
+
CREATE TABLE categories (
|
|
876
|
+
id SERIAL PRIMARY KEY,
|
|
877
|
+
name VARCHAR(255)
|
|
878
|
+
);
|
|
879
|
+
CREATE TABLE pages (
|
|
880
|
+
id SERIAL PRIMARY KEY,
|
|
881
|
+
title VARCHAR(42),
|
|
882
|
+
category BIGINT UNSIGNED,
|
|
883
|
+
FOREIGN KEY (category) REFERENCES categories(id) ON DELETE CASCADE
|
|
884
|
+
);
|
|
885
|
+
`)
|
|
886
|
+
} else if (isSQLite) {
|
|
887
|
+
await db.query(sql`
|
|
888
|
+
CREATE TABLE categories (
|
|
889
|
+
id INTEGER PRIMARY KEY,
|
|
890
|
+
name VARCHAR(42)
|
|
891
|
+
);
|
|
892
|
+
`)
|
|
893
|
+
await db.query(sql`
|
|
894
|
+
CREATE TABLE pages (
|
|
895
|
+
id INTEGER PRIMARY KEY,
|
|
896
|
+
title VARCHAR(42),
|
|
897
|
+
category INTEGER REFERENCES categories(id)
|
|
898
|
+
);
|
|
899
|
+
`)
|
|
900
|
+
} else {
|
|
901
|
+
await db.query(sql`
|
|
902
|
+
CREATE TABLE categories (
|
|
903
|
+
id SERIAL PRIMARY KEY,
|
|
904
|
+
name VARCHAR(42)
|
|
905
|
+
);
|
|
906
|
+
CREATE TABLE pages (
|
|
907
|
+
id SERIAL PRIMARY KEY,
|
|
908
|
+
title VARCHAR(42),
|
|
909
|
+
category INTEGER REFERENCES categories(id)
|
|
910
|
+
);
|
|
911
|
+
`)
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
})
|
|
915
|
+
app.register(sqlGraphQL)
|
|
916
|
+
teardown(app.close.bind(app))
|
|
917
|
+
|
|
918
|
+
const categories = [{
|
|
919
|
+
name: 'Pets'
|
|
920
|
+
}, {
|
|
921
|
+
name: 'Food'
|
|
922
|
+
}]
|
|
923
|
+
|
|
924
|
+
await app.inject({
|
|
925
|
+
method: 'POST',
|
|
926
|
+
url: '/graphql',
|
|
927
|
+
body: {
|
|
928
|
+
query: `
|
|
929
|
+
mutation batch($inputs: [CategoryInput]!) {
|
|
930
|
+
insertCategories(inputs: $inputs) {
|
|
931
|
+
id
|
|
932
|
+
name
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
`,
|
|
936
|
+
variables: {
|
|
937
|
+
inputs: categories
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
})
|
|
941
|
+
|
|
942
|
+
{
|
|
943
|
+
const res = await app.inject({
|
|
944
|
+
method: 'POST',
|
|
945
|
+
url: '/graphql',
|
|
946
|
+
body: {
|
|
947
|
+
query: `
|
|
948
|
+
mutation {
|
|
949
|
+
savePage(input: { title: "Hello", category: 1 }) {
|
|
950
|
+
id
|
|
951
|
+
title
|
|
952
|
+
category {
|
|
953
|
+
id
|
|
954
|
+
name
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
`
|
|
959
|
+
}
|
|
960
|
+
})
|
|
961
|
+
equal(res.statusCode, 200, 'savePage status code')
|
|
962
|
+
same(res.json(), {
|
|
963
|
+
data: {
|
|
964
|
+
savePage: {
|
|
965
|
+
id: 1,
|
|
966
|
+
title: 'Hello',
|
|
967
|
+
category: {
|
|
968
|
+
id: 1,
|
|
969
|
+
name: 'Pets'
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}, 'savePage response')
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
{
|
|
977
|
+
const res = await app.inject({
|
|
978
|
+
method: 'POST',
|
|
979
|
+
url: '/graphql',
|
|
980
|
+
body: {
|
|
981
|
+
query: `
|
|
982
|
+
query {
|
|
983
|
+
getPageById(id: 1) {
|
|
984
|
+
id
|
|
985
|
+
title
|
|
986
|
+
category {
|
|
987
|
+
id
|
|
988
|
+
name
|
|
989
|
+
pages {
|
|
990
|
+
id
|
|
991
|
+
title
|
|
992
|
+
category {
|
|
993
|
+
id
|
|
994
|
+
name
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
`
|
|
1001
|
+
}
|
|
1002
|
+
})
|
|
1003
|
+
equal(res.statusCode, 200, 'pages status code')
|
|
1004
|
+
same(res.json(), {
|
|
1005
|
+
data: {
|
|
1006
|
+
getPageById: {
|
|
1007
|
+
id: 1,
|
|
1008
|
+
title: 'Hello',
|
|
1009
|
+
category: {
|
|
1010
|
+
id: 1,
|
|
1011
|
+
name: 'Pets',
|
|
1012
|
+
pages: [{
|
|
1013
|
+
id: 1,
|
|
1014
|
+
title: 'Hello',
|
|
1015
|
+
category: {
|
|
1016
|
+
id: 1,
|
|
1017
|
+
name: 'Pets'
|
|
1018
|
+
}
|
|
1019
|
+
}]
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
}, 'pages response')
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// Without ids
|
|
1027
|
+
{
|
|
1028
|
+
const res = await app.inject({
|
|
1029
|
+
method: 'POST',
|
|
1030
|
+
url: '/graphql',
|
|
1031
|
+
body: {
|
|
1032
|
+
query: `
|
|
1033
|
+
query {
|
|
1034
|
+
getPageById(id: 1) {
|
|
1035
|
+
title
|
|
1036
|
+
category {
|
|
1037
|
+
name
|
|
1038
|
+
pages {
|
|
1039
|
+
title
|
|
1040
|
+
category {
|
|
1041
|
+
name
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
`
|
|
1048
|
+
}
|
|
1049
|
+
})
|
|
1050
|
+
equal(res.statusCode, 200, 'pages status code')
|
|
1051
|
+
same(res.json(), {
|
|
1052
|
+
data: {
|
|
1053
|
+
getPageById: {
|
|
1054
|
+
title: 'Hello',
|
|
1055
|
+
category: {
|
|
1056
|
+
name: 'Pets',
|
|
1057
|
+
pages: [{
|
|
1058
|
+
title: 'Hello',
|
|
1059
|
+
category: {
|
|
1060
|
+
name: 'Pets'
|
|
1061
|
+
}
|
|
1062
|
+
}]
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
}, 'pages response')
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
{
|
|
1070
|
+
const res = await app.inject({
|
|
1071
|
+
method: 'POST',
|
|
1072
|
+
url: '/graphql',
|
|
1073
|
+
body: {
|
|
1074
|
+
query: `
|
|
1075
|
+
query {
|
|
1076
|
+
categories {
|
|
1077
|
+
id
|
|
1078
|
+
name
|
|
1079
|
+
pages {
|
|
1080
|
+
id
|
|
1081
|
+
title
|
|
1082
|
+
category {
|
|
1083
|
+
id
|
|
1084
|
+
name
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
`
|
|
1090
|
+
}
|
|
1091
|
+
})
|
|
1092
|
+
equal(res.statusCode, 200, 'categories.posts status code')
|
|
1093
|
+
same(res.json(), {
|
|
1094
|
+
data: {
|
|
1095
|
+
categories: [{
|
|
1096
|
+
id: 1,
|
|
1097
|
+
name: 'Pets',
|
|
1098
|
+
pages: [{
|
|
1099
|
+
id: 1,
|
|
1100
|
+
title: 'Hello',
|
|
1101
|
+
category: {
|
|
1102
|
+
id: 1,
|
|
1103
|
+
name: 'Pets'
|
|
1104
|
+
}
|
|
1105
|
+
}]
|
|
1106
|
+
}, {
|
|
1107
|
+
id: 2,
|
|
1108
|
+
name: 'Food',
|
|
1109
|
+
pages: []
|
|
1110
|
+
}]
|
|
1111
|
+
}
|
|
1112
|
+
}, 'categories.posts response')
|
|
1113
|
+
}
|
|
1114
|
+
})
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { test } = require('tap')
|
|
4
|
+
const sqlGraphQL = require('..')
|
|
5
|
+
const sqlMapper = require('@platformatic/sql-mapper')
|
|
6
|
+
const fastify = require('fastify')
|
|
7
|
+
const { clear, connInfo, isPg } = require('./helper')
|
|
8
|
+
|
|
9
|
+
const connInfoWithSchema = {
|
|
10
|
+
...connInfo,
|
|
11
|
+
schema: ['public', 'myschema']
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
test('should catch GraphQL print schema errors', { skip: !isPg }, async ({ pass, teardown, same }) => {
|
|
15
|
+
const app = fastify()
|
|
16
|
+
app.register(sqlMapper, {
|
|
17
|
+
...connInfoWithSchema,
|
|
18
|
+
async onDatabaseLoad (db, sql) {
|
|
19
|
+
pass('onDatabaseLoad called')
|
|
20
|
+
|
|
21
|
+
await clear(db, sql)
|
|
22
|
+
|
|
23
|
+
await db.query(sql`
|
|
24
|
+
CREATE SCHEMA IF NOT EXISTS "myschema";
|
|
25
|
+
|
|
26
|
+
CREATE TABLE IF NOT EXISTS "myschema"."template" (
|
|
27
|
+
"id" uuid NOT NULL,
|
|
28
|
+
PRIMARY KEY ("id")
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
CREATE TABLE IF NOT EXISTS "myschema"."mytable" (
|
|
32
|
+
"id" int4 NOT NULL,
|
|
33
|
+
"template_id" uuid,
|
|
34
|
+
CONSTRAINT "mytable_template" FOREIGN KEY ("template_id") REFERENCES "myschema"."template"("id"),
|
|
35
|
+
PRIMARY KEY ("id")
|
|
36
|
+
);`)
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
app.register(sqlGraphQL)
|
|
41
|
+
teardown(app.close.bind(app))
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
await app.ready()
|
|
45
|
+
same(true, false, 'We check that this line never runs, since we should catch the previous error')
|
|
46
|
+
} catch (error) {
|
|
47
|
+
same(error.name, 'Error')
|
|
48
|
+
same(error.message, 'Error printing the GraphQL schema')
|
|
49
|
+
same(error.cause, new Error('MyschemaTemplate.mytable field config must be an object.'))
|
|
50
|
+
}
|
|
51
|
+
})
|