graphile-test 0.1.1 → 2.0.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/LICENSE +1 -1
- package/README.md +66 -39
- package/clean.d.ts +9 -0
- package/clean.js +67 -0
- package/esm/clean.js +57 -0
- package/esm/graphile-test.js +172 -0
- package/esm/index.js +2 -0
- package/graphile-test.d.ts +12 -0
- package/graphile-test.js +179 -0
- package/index.d.ts +2 -0
- package/index.js +18 -0
- package/package.json +30 -58
- package/main/clean.js +0 -104
- package/main/env.js +0 -28
- package/main/gql.js +0 -15
- package/main/graphql.js +0 -346
- package/main/index.js +0 -53
- package/module/clean.js +0 -46
- package/module/env.js +0 -19
- package/module/gql.js +0 -2
- package/module/graphql.js +0 -152
- package/module/index.js +0 -4
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
The MIT License (MIT)
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2024 Dan Lynch <pyramation@gmail.com>
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -1,51 +1,53 @@
|
|
|
1
|
-
# graphile-test
|
|
1
|
+
# graphile-test
|
|
2
|
+
|
|
3
|
+
<p align="center" width="100%">
|
|
4
|
+
<img height="250" src="https://github.com/user-attachments/assets/d0456af5-b6e9-422e-a45d-2574d5be490f" />
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center" width="100%">
|
|
8
|
+
<a href="https://github.com/launchql/launchql-2.0/actions/workflows/run-tests.yaml">
|
|
9
|
+
<img height="20" src="https://github.com/launchql/launchql-2.0/actions/workflows/run-tests.yaml/badge.svg" />
|
|
10
|
+
</a>
|
|
11
|
+
<a href="https://github.com/launchql/launchql-2.0/blob/main/LICENSE-MIT">
|
|
12
|
+
<img height="20" src="https://img.shields.io/badge/license-MIT-blue.svg"/>
|
|
13
|
+
</a>
|
|
14
|
+
<a href="https://www.npmjs.com/package/graphile-test">
|
|
15
|
+
<img height="20" src="https://img.shields.io/github/package-json/v/launchql/launchql-2.0?filename=packages%2Fgraphile-test%2Fpackage.json"/>
|
|
16
|
+
</a>
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
## Install
|
|
2
20
|
|
|
3
21
|
```sh
|
|
4
|
-
npm install graphile-test
|
|
22
|
+
npm install graphile-test
|
|
5
23
|
```
|
|
6
24
|
|
|
7
|
-
|
|
25
|
+
---
|
|
8
26
|
|
|
9
|
-
##
|
|
27
|
+
## How to Use
|
|
10
28
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
```
|
|
14
|
-
PGUSER
|
|
15
|
-
PGPASSWORD
|
|
16
|
-
PGHOST
|
|
17
|
-
PGPORT
|
|
18
|
-
PGDATABASE
|
|
19
|
-
SCHEMA
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## postgres users
|
|
29
|
+
### 1. Create Required Postgres Role
|
|
23
30
|
|
|
24
31
|
```sql
|
|
25
32
|
CREATE ROLE authenticated;
|
|
26
33
|
```
|
|
27
34
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
Then in a test:
|
|
35
|
+
---
|
|
31
36
|
|
|
32
|
-
|
|
33
|
-
import { GraphQLTest, env, snapshot } from 'graphile-test';
|
|
34
|
-
import { MyGraphQuery } from '../utils/queries';
|
|
37
|
+
### 2. Write a Test
|
|
35
38
|
|
|
36
|
-
|
|
39
|
+
```ts
|
|
40
|
+
import { GraphQLTest, snapshot } from 'graphile-test';
|
|
41
|
+
import { MyGraphQLQuery } from '../src/queries';
|
|
37
42
|
|
|
38
|
-
const
|
|
39
|
-
|
|
43
|
+
const dbname = 'graphile_test_db';
|
|
44
|
+
const schemas = ['app_public'];
|
|
40
45
|
|
|
41
|
-
const { setup, teardown, graphQL } = GraphQLTest(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
},
|
|
47
|
-
getDbString()
|
|
48
|
-
);
|
|
46
|
+
const { setup, teardown, graphQL } = GraphQLTest({
|
|
47
|
+
dbname,
|
|
48
|
+
schemas,
|
|
49
|
+
authRole: 'postgres',
|
|
50
|
+
});
|
|
49
51
|
|
|
50
52
|
beforeAll(async () => {
|
|
51
53
|
await setup();
|
|
@@ -54,18 +56,43 @@ afterAll(async () => {
|
|
|
54
56
|
await teardown();
|
|
55
57
|
});
|
|
56
58
|
|
|
57
|
-
it('
|
|
59
|
+
it('query', async () => {
|
|
58
60
|
await graphQL(async query => {
|
|
59
|
-
const data = await query(
|
|
61
|
+
const data = await query(MyGraphQLQuery);
|
|
60
62
|
expect(snapshot(data)).toMatchSnapshot();
|
|
61
63
|
});
|
|
62
64
|
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Testing Setup
|
|
70
|
+
|
|
71
|
+
Before running tests, prepare your database:
|
|
63
72
|
|
|
73
|
+
```sh
|
|
74
|
+
createdb graphile_test_db
|
|
75
|
+
psql -f sql/test.sql graphile_test_db
|
|
64
76
|
```
|
|
65
77
|
|
|
66
|
-
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Environment Variables
|
|
81
|
+
|
|
82
|
+
You can override the default Postgres connection settings by setting the following environment variables:
|
|
67
83
|
|
|
68
84
|
```sh
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
85
|
+
export PGUSER=your_pg_user
|
|
86
|
+
export PGHOST=your_pg_host
|
|
87
|
+
export PGPORT=your_pg_port
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Once set, these will be automatically picked up by `graphile-test` when establishing connections.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Disclaimer
|
|
95
|
+
|
|
96
|
+
AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED “AS IS”, AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND.
|
|
97
|
+
|
|
98
|
+
No developer or entity involved in creating this software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the code, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value.
|
package/clean.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type AnyObject = Record<string, any>;
|
|
2
|
+
export declare const pruneDates: (row: AnyObject) => AnyObject;
|
|
3
|
+
export declare const pruneIds: (row: AnyObject) => AnyObject;
|
|
4
|
+
export declare const pruneIdArrays: (row: AnyObject) => AnyObject;
|
|
5
|
+
export declare const pruneUUIDs: (row: AnyObject) => AnyObject;
|
|
6
|
+
export declare const pruneHashes: (row: AnyObject) => AnyObject;
|
|
7
|
+
export declare const prune: (obj: AnyObject) => AnyObject;
|
|
8
|
+
export declare const snapshot: (obj: unknown) => unknown;
|
|
9
|
+
export {};
|
package/clean.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.snapshot = exports.prune = exports.pruneHashes = exports.pruneUUIDs = exports.pruneIdArrays = exports.pruneIds = exports.pruneDates = void 0;
|
|
4
|
+
const uuidRegexp = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
5
|
+
const idReplacement = (v) => (!v ? v : '[ID]');
|
|
6
|
+
function mapValues(obj, fn) {
|
|
7
|
+
return Object.entries(obj).reduce((acc, [key, value]) => {
|
|
8
|
+
acc[key] = fn(value, key);
|
|
9
|
+
return acc;
|
|
10
|
+
}, {});
|
|
11
|
+
}
|
|
12
|
+
const pruneDates = (row) => mapValues(row, (v, k) => {
|
|
13
|
+
if (!v) {
|
|
14
|
+
return v;
|
|
15
|
+
}
|
|
16
|
+
if (v instanceof Date) {
|
|
17
|
+
return '[DATE]';
|
|
18
|
+
}
|
|
19
|
+
else if (typeof v === 'string' &&
|
|
20
|
+
/(_at|At)$/.test(k) &&
|
|
21
|
+
/^20[0-9]{2}-[0-9]{2}-[0-9]{2}/.test(v)) {
|
|
22
|
+
return '[DATE]';
|
|
23
|
+
}
|
|
24
|
+
return v;
|
|
25
|
+
});
|
|
26
|
+
exports.pruneDates = pruneDates;
|
|
27
|
+
const pruneIds = (row) => mapValues(row, (v, k) => (k === 'id' || (typeof k === 'string' && k.endsWith('_id'))) &&
|
|
28
|
+
(typeof v === 'string' || typeof v === 'number')
|
|
29
|
+
? idReplacement(v)
|
|
30
|
+
: v);
|
|
31
|
+
exports.pruneIds = pruneIds;
|
|
32
|
+
const pruneIdArrays = (row) => mapValues(row, (v, k) => typeof k === 'string' && k.endsWith('_ids') && Array.isArray(v)
|
|
33
|
+
? `[UUIDs-${v.length}]`
|
|
34
|
+
: v);
|
|
35
|
+
exports.pruneIdArrays = pruneIdArrays;
|
|
36
|
+
const pruneUUIDs = (row) => mapValues(row, (v, k) => {
|
|
37
|
+
if (typeof v !== 'string') {
|
|
38
|
+
return v;
|
|
39
|
+
}
|
|
40
|
+
if (['uuid', 'queue_name'].includes(k) && uuidRegexp.test(v)) {
|
|
41
|
+
return '[UUID]';
|
|
42
|
+
}
|
|
43
|
+
if (k === 'gravatar' && /^[0-9a-f]{32}$/i.test(v)) {
|
|
44
|
+
return '[gUUID]';
|
|
45
|
+
}
|
|
46
|
+
return v;
|
|
47
|
+
});
|
|
48
|
+
exports.pruneUUIDs = pruneUUIDs;
|
|
49
|
+
const pruneHashes = (row) => mapValues(row, (v, k) => typeof k === 'string' &&
|
|
50
|
+
k.endsWith('_hash') &&
|
|
51
|
+
typeof v === 'string' &&
|
|
52
|
+
v.startsWith('$')
|
|
53
|
+
? '[hash]'
|
|
54
|
+
: v);
|
|
55
|
+
exports.pruneHashes = pruneHashes;
|
|
56
|
+
const prune = (obj) => (0, exports.pruneHashes)((0, exports.pruneUUIDs)((0, exports.pruneIds)((0, exports.pruneIdArrays)((0, exports.pruneDates)(obj)))));
|
|
57
|
+
exports.prune = prune;
|
|
58
|
+
const snapshot = (obj) => {
|
|
59
|
+
if (Array.isArray(obj)) {
|
|
60
|
+
return obj.map(exports.snapshot);
|
|
61
|
+
}
|
|
62
|
+
else if (obj && typeof obj === 'object') {
|
|
63
|
+
return mapValues((0, exports.prune)(obj), exports.snapshot);
|
|
64
|
+
}
|
|
65
|
+
return obj;
|
|
66
|
+
};
|
|
67
|
+
exports.snapshot = snapshot;
|
package/esm/clean.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const uuidRegexp = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
2
|
+
const idReplacement = (v) => (!v ? v : '[ID]');
|
|
3
|
+
function mapValues(obj, fn) {
|
|
4
|
+
return Object.entries(obj).reduce((acc, [key, value]) => {
|
|
5
|
+
acc[key] = fn(value, key);
|
|
6
|
+
return acc;
|
|
7
|
+
}, {});
|
|
8
|
+
}
|
|
9
|
+
export const pruneDates = (row) => mapValues(row, (v, k) => {
|
|
10
|
+
if (!v) {
|
|
11
|
+
return v;
|
|
12
|
+
}
|
|
13
|
+
if (v instanceof Date) {
|
|
14
|
+
return '[DATE]';
|
|
15
|
+
}
|
|
16
|
+
else if (typeof v === 'string' &&
|
|
17
|
+
/(_at|At)$/.test(k) &&
|
|
18
|
+
/^20[0-9]{2}-[0-9]{2}-[0-9]{2}/.test(v)) {
|
|
19
|
+
return '[DATE]';
|
|
20
|
+
}
|
|
21
|
+
return v;
|
|
22
|
+
});
|
|
23
|
+
export const pruneIds = (row) => mapValues(row, (v, k) => (k === 'id' || (typeof k === 'string' && k.endsWith('_id'))) &&
|
|
24
|
+
(typeof v === 'string' || typeof v === 'number')
|
|
25
|
+
? idReplacement(v)
|
|
26
|
+
: v);
|
|
27
|
+
export const pruneIdArrays = (row) => mapValues(row, (v, k) => typeof k === 'string' && k.endsWith('_ids') && Array.isArray(v)
|
|
28
|
+
? `[UUIDs-${v.length}]`
|
|
29
|
+
: v);
|
|
30
|
+
export const pruneUUIDs = (row) => mapValues(row, (v, k) => {
|
|
31
|
+
if (typeof v !== 'string') {
|
|
32
|
+
return v;
|
|
33
|
+
}
|
|
34
|
+
if (['uuid', 'queue_name'].includes(k) && uuidRegexp.test(v)) {
|
|
35
|
+
return '[UUID]';
|
|
36
|
+
}
|
|
37
|
+
if (k === 'gravatar' && /^[0-9a-f]{32}$/i.test(v)) {
|
|
38
|
+
return '[gUUID]';
|
|
39
|
+
}
|
|
40
|
+
return v;
|
|
41
|
+
});
|
|
42
|
+
export const pruneHashes = (row) => mapValues(row, (v, k) => typeof k === 'string' &&
|
|
43
|
+
k.endsWith('_hash') &&
|
|
44
|
+
typeof v === 'string' &&
|
|
45
|
+
v.startsWith('$')
|
|
46
|
+
? '[hash]'
|
|
47
|
+
: v);
|
|
48
|
+
export const prune = (obj) => pruneHashes(pruneUUIDs(pruneIds(pruneIdArrays(pruneDates(obj)))));
|
|
49
|
+
export const snapshot = (obj) => {
|
|
50
|
+
if (Array.isArray(obj)) {
|
|
51
|
+
return obj.map(snapshot);
|
|
52
|
+
}
|
|
53
|
+
else if (obj && typeof obj === 'object') {
|
|
54
|
+
return mapValues(prune(obj), snapshot);
|
|
55
|
+
}
|
|
56
|
+
return obj;
|
|
57
|
+
};
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { getGraphileSettings } from '@launchql/graphile-settings';
|
|
2
|
+
import pg from 'pg';
|
|
3
|
+
import { createPostGraphileSchema, withPostGraphileContext } from 'postgraphile';
|
|
4
|
+
import { graphql } from 'graphql';
|
|
5
|
+
// @ts-ignore
|
|
6
|
+
import MockReq from 'mock-req';
|
|
7
|
+
import { print } from 'graphql/language/printer';
|
|
8
|
+
import { getEnvOptions } from '@launchql/types';
|
|
9
|
+
const opt = getEnvOptions();
|
|
10
|
+
export const GraphQLTest = ({ dbname, schemas, authRole = 'authenticated' }) => {
|
|
11
|
+
const getDbString = (db) => `postgres://${opt.pg.user}:${opt.pg.password}@${opt.pg.host}:${opt.pg.port}/${db}`;
|
|
12
|
+
const options = {
|
|
13
|
+
...getGraphileSettings({
|
|
14
|
+
graphile: {
|
|
15
|
+
schema: schemas
|
|
16
|
+
}
|
|
17
|
+
}),
|
|
18
|
+
graphqlRoute: '/graphql',
|
|
19
|
+
graphiqlRoute: '/graphiql'
|
|
20
|
+
};
|
|
21
|
+
pg.defaults.poolSize = 1;
|
|
22
|
+
const POSTGRAPHILE_AUTHENTICATOR_ROLE = authRole;
|
|
23
|
+
let ctx = null;
|
|
24
|
+
const setup = async () => {
|
|
25
|
+
const rootPgPool = new pg.Pool({
|
|
26
|
+
connectionString: getDbString(dbname)
|
|
27
|
+
});
|
|
28
|
+
const schema = await createPostGraphileSchema(rootPgPool, schemas, options);
|
|
29
|
+
ctx = { rootPgPool, options, schema };
|
|
30
|
+
};
|
|
31
|
+
const teardown = async () => {
|
|
32
|
+
try {
|
|
33
|
+
if (!ctx)
|
|
34
|
+
return;
|
|
35
|
+
const { rootPgPool } = ctx;
|
|
36
|
+
ctx = null;
|
|
37
|
+
await rootPgPool.end();
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
console.error(e);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const graphQL = async (...args) => {
|
|
44
|
+
if (!ctx)
|
|
45
|
+
throw new Error('Context is not initialized. Did you run setup()?');
|
|
46
|
+
let reqOptions = {};
|
|
47
|
+
let checker;
|
|
48
|
+
if (args.length === 1) {
|
|
49
|
+
checker = args[0];
|
|
50
|
+
}
|
|
51
|
+
else if (args.length === 2) {
|
|
52
|
+
reqOptions = args[0];
|
|
53
|
+
checker = args[1];
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
throw new Error('Invalid arguments supplied to graphQL');
|
|
57
|
+
}
|
|
58
|
+
const { schema, rootPgPool, options } = ctx;
|
|
59
|
+
const req = new MockReq({
|
|
60
|
+
url: options.graphqlRoute || '/graphql',
|
|
61
|
+
method: 'POST',
|
|
62
|
+
headers: {
|
|
63
|
+
Accept: 'application/json',
|
|
64
|
+
'Content-Type': 'application/json'
|
|
65
|
+
},
|
|
66
|
+
...reqOptions
|
|
67
|
+
});
|
|
68
|
+
const pgSettingsGenerator = options.pgSettings;
|
|
69
|
+
// @ts-ignore
|
|
70
|
+
const pgSettings = typeof pgSettingsGenerator === 'function' ? await pgSettingsGenerator(req) : pgSettingsGenerator || {};
|
|
71
|
+
return await withPostGraphileContext({ ...options, pgPool: rootPgPool, pgSettings }, async (context) => {
|
|
72
|
+
const replacementPgClient = await rootPgPool.connect();
|
|
73
|
+
await replacementPgClient.query('begin');
|
|
74
|
+
await replacementPgClient.query("select set_config('role', $1, true)", [POSTGRAPHILE_AUTHENTICATOR_ROLE]);
|
|
75
|
+
for (const [key, value] of Object.entries(pgSettings)) {
|
|
76
|
+
await replacementPgClient.query("select set_config($1, $2, true)", [key, String(value)]);
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const query = async (q, variables) => {
|
|
80
|
+
if (typeof q !== 'string')
|
|
81
|
+
q = print(q);
|
|
82
|
+
return await graphql(schema, q, null, { ...context, pgClient: replacementPgClient }, variables);
|
|
83
|
+
};
|
|
84
|
+
return await checker(query, replacementPgClient);
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
await replacementPgClient.query('rollback');
|
|
88
|
+
replacementPgClient.release();
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
const graphQLQuery = async (...args) => {
|
|
93
|
+
if (!ctx)
|
|
94
|
+
throw new Error('Context is not initialized. Did you run setup()?');
|
|
95
|
+
let reqOptions = {};
|
|
96
|
+
let Query;
|
|
97
|
+
let vars;
|
|
98
|
+
let commit = false;
|
|
99
|
+
if (args.length === 1) {
|
|
100
|
+
Query = args[0];
|
|
101
|
+
}
|
|
102
|
+
else if (args.length === 2) {
|
|
103
|
+
if (typeof args[1] === 'boolean') {
|
|
104
|
+
Query = args[0];
|
|
105
|
+
commit = args[1];
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
Query = args[0];
|
|
109
|
+
vars = args[1];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
else if (args.length === 3) {
|
|
113
|
+
if (typeof args[2] === 'boolean') {
|
|
114
|
+
Query = args[0];
|
|
115
|
+
vars = args[1];
|
|
116
|
+
commit = args[2];
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
reqOptions = args[0];
|
|
120
|
+
Query = args[1];
|
|
121
|
+
vars = args[2];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
else if (args.length === 4) {
|
|
125
|
+
reqOptions = args[0];
|
|
126
|
+
Query = args[1];
|
|
127
|
+
vars = args[2];
|
|
128
|
+
commit = args[3];
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
throw new Error('Invalid arguments supplied to graphQLQuery');
|
|
132
|
+
}
|
|
133
|
+
const { schema, rootPgPool, options } = ctx;
|
|
134
|
+
const req = new MockReq({
|
|
135
|
+
url: options.graphqlRoute || '/graphql',
|
|
136
|
+
method: 'POST',
|
|
137
|
+
headers: {
|
|
138
|
+
Accept: 'application/json',
|
|
139
|
+
'Content-Type': 'application/json'
|
|
140
|
+
},
|
|
141
|
+
...reqOptions
|
|
142
|
+
});
|
|
143
|
+
const pgSettingsGenerator = options.pgSettings;
|
|
144
|
+
// @ts-ignore
|
|
145
|
+
const pgSettings = typeof pgSettingsGenerator === 'function' ? await pgSettingsGenerator(req) : pgSettingsGenerator || {};
|
|
146
|
+
return await withPostGraphileContext({ ...options, pgPool: rootPgPool, pgSettings }, async (context) => {
|
|
147
|
+
const replacementPgClient = await rootPgPool.connect();
|
|
148
|
+
await replacementPgClient.query('begin');
|
|
149
|
+
await replacementPgClient.query("select set_config('role', $1, true)", [POSTGRAPHILE_AUTHENTICATOR_ROLE]);
|
|
150
|
+
for (const [key, value] of Object.entries(pgSettings)) {
|
|
151
|
+
await replacementPgClient.query("select set_config($1, $2, true)", [key, String(value)]);
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
if (typeof Query !== 'string')
|
|
155
|
+
Query = print(Query);
|
|
156
|
+
return await graphql(schema, Query, null, { ...context, pgClient: replacementPgClient }, vars);
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
await replacementPgClient.query(commit ? 'commit' : 'rollback');
|
|
160
|
+
replacementPgClient.release();
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
};
|
|
164
|
+
return {
|
|
165
|
+
setup,
|
|
166
|
+
teardown,
|
|
167
|
+
graphQL,
|
|
168
|
+
graphQLQuery,
|
|
169
|
+
// @ts-ignore
|
|
170
|
+
withContext: (cb) => cb(ctx)
|
|
171
|
+
};
|
|
172
|
+
};
|
package/esm/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface GraphQLTestOptions {
|
|
2
|
+
dbname: string;
|
|
3
|
+
schemas: string[];
|
|
4
|
+
authRole?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare const GraphQLTest: ({ dbname, schemas, authRole }: GraphQLTestOptions) => {
|
|
7
|
+
setup: () => Promise<void>;
|
|
8
|
+
teardown: () => Promise<void>;
|
|
9
|
+
graphQL: (...args: any[]) => Promise<any>;
|
|
10
|
+
graphQLQuery: (...args: any[]) => Promise<any>;
|
|
11
|
+
withContext: <T>(cb: (ctx: typeof ctx) => T) => T;
|
|
12
|
+
};
|
package/graphile-test.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.GraphQLTest = void 0;
|
|
7
|
+
const graphile_settings_1 = require("@launchql/graphile-settings");
|
|
8
|
+
const pg_1 = __importDefault(require("pg"));
|
|
9
|
+
const postgraphile_1 = require("postgraphile");
|
|
10
|
+
const graphql_1 = require("graphql");
|
|
11
|
+
// @ts-ignore
|
|
12
|
+
const mock_req_1 = __importDefault(require("mock-req"));
|
|
13
|
+
const printer_1 = require("graphql/language/printer");
|
|
14
|
+
const types_1 = require("@launchql/types");
|
|
15
|
+
const opt = (0, types_1.getEnvOptions)();
|
|
16
|
+
const GraphQLTest = ({ dbname, schemas, authRole = 'authenticated' }) => {
|
|
17
|
+
const getDbString = (db) => `postgres://${opt.pg.user}:${opt.pg.password}@${opt.pg.host}:${opt.pg.port}/${db}`;
|
|
18
|
+
const options = {
|
|
19
|
+
...(0, graphile_settings_1.getGraphileSettings)({
|
|
20
|
+
graphile: {
|
|
21
|
+
schema: schemas
|
|
22
|
+
}
|
|
23
|
+
}),
|
|
24
|
+
graphqlRoute: '/graphql',
|
|
25
|
+
graphiqlRoute: '/graphiql'
|
|
26
|
+
};
|
|
27
|
+
pg_1.default.defaults.poolSize = 1;
|
|
28
|
+
const POSTGRAPHILE_AUTHENTICATOR_ROLE = authRole;
|
|
29
|
+
let ctx = null;
|
|
30
|
+
const setup = async () => {
|
|
31
|
+
const rootPgPool = new pg_1.default.Pool({
|
|
32
|
+
connectionString: getDbString(dbname)
|
|
33
|
+
});
|
|
34
|
+
const schema = await (0, postgraphile_1.createPostGraphileSchema)(rootPgPool, schemas, options);
|
|
35
|
+
ctx = { rootPgPool, options, schema };
|
|
36
|
+
};
|
|
37
|
+
const teardown = async () => {
|
|
38
|
+
try {
|
|
39
|
+
if (!ctx)
|
|
40
|
+
return;
|
|
41
|
+
const { rootPgPool } = ctx;
|
|
42
|
+
ctx = null;
|
|
43
|
+
await rootPgPool.end();
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
console.error(e);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const graphQL = async (...args) => {
|
|
50
|
+
if (!ctx)
|
|
51
|
+
throw new Error('Context is not initialized. Did you run setup()?');
|
|
52
|
+
let reqOptions = {};
|
|
53
|
+
let checker;
|
|
54
|
+
if (args.length === 1) {
|
|
55
|
+
checker = args[0];
|
|
56
|
+
}
|
|
57
|
+
else if (args.length === 2) {
|
|
58
|
+
reqOptions = args[0];
|
|
59
|
+
checker = args[1];
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
throw new Error('Invalid arguments supplied to graphQL');
|
|
63
|
+
}
|
|
64
|
+
const { schema, rootPgPool, options } = ctx;
|
|
65
|
+
const req = new mock_req_1.default({
|
|
66
|
+
url: options.graphqlRoute || '/graphql',
|
|
67
|
+
method: 'POST',
|
|
68
|
+
headers: {
|
|
69
|
+
Accept: 'application/json',
|
|
70
|
+
'Content-Type': 'application/json'
|
|
71
|
+
},
|
|
72
|
+
...reqOptions
|
|
73
|
+
});
|
|
74
|
+
const pgSettingsGenerator = options.pgSettings;
|
|
75
|
+
// @ts-ignore
|
|
76
|
+
const pgSettings = typeof pgSettingsGenerator === 'function' ? await pgSettingsGenerator(req) : pgSettingsGenerator || {};
|
|
77
|
+
return await (0, postgraphile_1.withPostGraphileContext)({ ...options, pgPool: rootPgPool, pgSettings }, async (context) => {
|
|
78
|
+
const replacementPgClient = await rootPgPool.connect();
|
|
79
|
+
await replacementPgClient.query('begin');
|
|
80
|
+
await replacementPgClient.query("select set_config('role', $1, true)", [POSTGRAPHILE_AUTHENTICATOR_ROLE]);
|
|
81
|
+
for (const [key, value] of Object.entries(pgSettings)) {
|
|
82
|
+
await replacementPgClient.query("select set_config($1, $2, true)", [key, String(value)]);
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const query = async (q, variables) => {
|
|
86
|
+
if (typeof q !== 'string')
|
|
87
|
+
q = (0, printer_1.print)(q);
|
|
88
|
+
return await (0, graphql_1.graphql)(schema, q, null, { ...context, pgClient: replacementPgClient }, variables);
|
|
89
|
+
};
|
|
90
|
+
return await checker(query, replacementPgClient);
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
await replacementPgClient.query('rollback');
|
|
94
|
+
replacementPgClient.release();
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
const graphQLQuery = async (...args) => {
|
|
99
|
+
if (!ctx)
|
|
100
|
+
throw new Error('Context is not initialized. Did you run setup()?');
|
|
101
|
+
let reqOptions = {};
|
|
102
|
+
let Query;
|
|
103
|
+
let vars;
|
|
104
|
+
let commit = false;
|
|
105
|
+
if (args.length === 1) {
|
|
106
|
+
Query = args[0];
|
|
107
|
+
}
|
|
108
|
+
else if (args.length === 2) {
|
|
109
|
+
if (typeof args[1] === 'boolean') {
|
|
110
|
+
Query = args[0];
|
|
111
|
+
commit = args[1];
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
Query = args[0];
|
|
115
|
+
vars = args[1];
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else if (args.length === 3) {
|
|
119
|
+
if (typeof args[2] === 'boolean') {
|
|
120
|
+
Query = args[0];
|
|
121
|
+
vars = args[1];
|
|
122
|
+
commit = args[2];
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
reqOptions = args[0];
|
|
126
|
+
Query = args[1];
|
|
127
|
+
vars = args[2];
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else if (args.length === 4) {
|
|
131
|
+
reqOptions = args[0];
|
|
132
|
+
Query = args[1];
|
|
133
|
+
vars = args[2];
|
|
134
|
+
commit = args[3];
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
throw new Error('Invalid arguments supplied to graphQLQuery');
|
|
138
|
+
}
|
|
139
|
+
const { schema, rootPgPool, options } = ctx;
|
|
140
|
+
const req = new mock_req_1.default({
|
|
141
|
+
url: options.graphqlRoute || '/graphql',
|
|
142
|
+
method: 'POST',
|
|
143
|
+
headers: {
|
|
144
|
+
Accept: 'application/json',
|
|
145
|
+
'Content-Type': 'application/json'
|
|
146
|
+
},
|
|
147
|
+
...reqOptions
|
|
148
|
+
});
|
|
149
|
+
const pgSettingsGenerator = options.pgSettings;
|
|
150
|
+
// @ts-ignore
|
|
151
|
+
const pgSettings = typeof pgSettingsGenerator === 'function' ? await pgSettingsGenerator(req) : pgSettingsGenerator || {};
|
|
152
|
+
return await (0, postgraphile_1.withPostGraphileContext)({ ...options, pgPool: rootPgPool, pgSettings }, async (context) => {
|
|
153
|
+
const replacementPgClient = await rootPgPool.connect();
|
|
154
|
+
await replacementPgClient.query('begin');
|
|
155
|
+
await replacementPgClient.query("select set_config('role', $1, true)", [POSTGRAPHILE_AUTHENTICATOR_ROLE]);
|
|
156
|
+
for (const [key, value] of Object.entries(pgSettings)) {
|
|
157
|
+
await replacementPgClient.query("select set_config($1, $2, true)", [key, String(value)]);
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
if (typeof Query !== 'string')
|
|
161
|
+
Query = (0, printer_1.print)(Query);
|
|
162
|
+
return await (0, graphql_1.graphql)(schema, Query, null, { ...context, pgClient: replacementPgClient }, vars);
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
await replacementPgClient.query(commit ? 'commit' : 'rollback');
|
|
166
|
+
replacementPgClient.release();
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
};
|
|
170
|
+
return {
|
|
171
|
+
setup,
|
|
172
|
+
teardown,
|
|
173
|
+
graphQL,
|
|
174
|
+
graphQLQuery,
|
|
175
|
+
// @ts-ignore
|
|
176
|
+
withContext: (cb) => cb(ctx)
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
exports.GraphQLTest = GraphQLTest;
|
package/index.d.ts
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./clean"), exports);
|
|
18
|
+
__exportStar(require("./graphile-test"), exports);
|