pgsql-test 0.0.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 +21 -0
- package/README.md +232 -36
- package/admin.d.ts +24 -0
- package/admin.js +130 -0
- package/connect.d.ts +18 -0
- package/connect.js +95 -0
- package/esm/admin.js +126 -0
- package/esm/connect.js +90 -0
- package/{src/index.ts → esm/index.js} +1 -1
- package/esm/legacy-connect.js +25 -0
- package/esm/manager.js +117 -0
- package/esm/seed.js +35 -0
- package/esm/stream.js +43 -0
- package/esm/test-client.js +88 -0
- package/index.d.ts +2 -0
- package/index.js +18 -0
- package/legacy-connect.d.ts +11 -0
- package/legacy-connect.js +30 -0
- package/manager.d.ts +21 -0
- package/manager.js +124 -0
- package/package.json +9 -5
- package/seed.d.ts +22 -0
- package/seed.js +38 -0
- package/stream.d.ts +2 -0
- package/stream.js +46 -0
- package/test-client.d.ts +25 -0
- package/test-client.js +92 -0
- package/__tests__/postgres-test.connections.test.ts +0 -81
- package/__tests__/postgres-test.grants.test.ts +0 -53
- package/__tests__/postgres-test.records.test.ts +0 -66
- package/__tests__/postgres-test.template.test.ts +0 -52
- package/__tests__/postgres-test.test.ts +0 -36
- package/jest.config.js +0 -18
- package/sql/roles.sql +0 -48
- package/sql/test.sql +0 -36
- package/src/admin.ts +0 -135
- package/src/connect.ts +0 -42
- package/src/legacy-connect.ts +0 -34
- package/src/manager.ts +0 -142
- package/src/stream.ts +0 -61
- package/src/test-client.ts +0 -113
- package/src/utils.ts +0 -48
- package/test-utils/index.ts +0 -2
- package/tsconfig.esm.json +0 -9
- package/tsconfig.json +0 -9
package/manager.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
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.PgTestConnector = void 0;
|
|
7
|
+
const pg_1 = require("pg");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const admin_1 = require("./admin");
|
|
10
|
+
const types_1 = require("@launchql/types");
|
|
11
|
+
const test_client_1 = require("./test-client");
|
|
12
|
+
const SYS_EVENTS = ['SIGTERM'];
|
|
13
|
+
const end = (pool) => {
|
|
14
|
+
try {
|
|
15
|
+
if (pool.ended || pool.ending) {
|
|
16
|
+
console.warn(chalk_1.default.yellow('⚠️ pg pool already ended or ending'));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
pool.end();
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
console.error(chalk_1.default.red('❌ pg pool termination error:'), err);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
class PgTestConnector {
|
|
26
|
+
static instance;
|
|
27
|
+
clients = new Set();
|
|
28
|
+
pgPools = new Map();
|
|
29
|
+
seenDbConfigs = new Map();
|
|
30
|
+
verbose = false;
|
|
31
|
+
constructor(verbose = false) {
|
|
32
|
+
this.verbose = verbose;
|
|
33
|
+
SYS_EVENTS.forEach((event) => {
|
|
34
|
+
process.on(event, () => {
|
|
35
|
+
this.log(chalk_1.default.magenta(`⏹ Received ${event}, closing all connections...`));
|
|
36
|
+
this.closeAll();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
static getInstance(verbose = false) {
|
|
41
|
+
if (!PgTestConnector.instance) {
|
|
42
|
+
PgTestConnector.instance = new PgTestConnector(verbose);
|
|
43
|
+
}
|
|
44
|
+
return PgTestConnector.instance;
|
|
45
|
+
}
|
|
46
|
+
log(...args) {
|
|
47
|
+
if (this.verbose)
|
|
48
|
+
console.log(...args);
|
|
49
|
+
}
|
|
50
|
+
poolKey(config) {
|
|
51
|
+
return `${config.user}@${config.host}:${config.port}/${config.database}`;
|
|
52
|
+
}
|
|
53
|
+
dbKey(config) {
|
|
54
|
+
return `${config.host}:${config.port}/${config.database}`;
|
|
55
|
+
}
|
|
56
|
+
getPool(config) {
|
|
57
|
+
const key = this.poolKey(config);
|
|
58
|
+
if (!this.pgPools.has(key)) {
|
|
59
|
+
const pool = new pg_1.Pool(config);
|
|
60
|
+
this.pgPools.set(key, pool);
|
|
61
|
+
this.log(chalk_1.default.blue(`📘 Created new pg pool: ${chalk_1.default.white(key)}`));
|
|
62
|
+
}
|
|
63
|
+
return this.pgPools.get(key);
|
|
64
|
+
}
|
|
65
|
+
getClient(config) {
|
|
66
|
+
const client = new test_client_1.PgTestClient(config);
|
|
67
|
+
this.clients.add(client);
|
|
68
|
+
const key = this.dbKey(config);
|
|
69
|
+
this.seenDbConfigs.set(key, config);
|
|
70
|
+
this.log(chalk_1.default.green(`🔌 New PgTestClient connected to ${config.database}`));
|
|
71
|
+
return client;
|
|
72
|
+
}
|
|
73
|
+
async closeAll() {
|
|
74
|
+
this.log(chalk_1.default.cyan('\n🧹 Closing all PgTestClients...'));
|
|
75
|
+
await Promise.all(Array.from(this.clients).map(async (client) => {
|
|
76
|
+
try {
|
|
77
|
+
await client.close();
|
|
78
|
+
this.log(chalk_1.default.green(`✅ Closed client for ${client.config.database}`));
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
console.warn(chalk_1.default.red(`❌ Error closing PgTestClient for ${client.config.database}:`), err);
|
|
82
|
+
}
|
|
83
|
+
}));
|
|
84
|
+
this.clients.clear();
|
|
85
|
+
this.log(chalk_1.default.cyan('\n🧯 Disposing pg pools...'));
|
|
86
|
+
for (const [key, pool] of this.pgPools.entries()) {
|
|
87
|
+
this.log(chalk_1.default.gray(`🧯 Disposing pg pool [${key}]`));
|
|
88
|
+
end(pool);
|
|
89
|
+
}
|
|
90
|
+
this.pgPools.clear();
|
|
91
|
+
this.log(chalk_1.default.cyan('\n🗑️ Dropping seen databases...'));
|
|
92
|
+
await Promise.all(Array.from(this.seenDbConfigs.values()).map(async (config) => {
|
|
93
|
+
try {
|
|
94
|
+
// somehow an "admin" db had app_user creds?
|
|
95
|
+
const rootPg = (0, types_1.getPgEnvOptions)();
|
|
96
|
+
const admin = new admin_1.DbAdmin({ ...config, user: rootPg.user, password: rootPg.password }, this.verbose);
|
|
97
|
+
// console.log(config);
|
|
98
|
+
admin.drop();
|
|
99
|
+
this.log(chalk_1.default.yellow(`🧨 Dropped database: ${chalk_1.default.white(config.database)}`));
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
console.warn(chalk_1.default.red(`❌ Failed to drop database ${config.database}:`), err);
|
|
103
|
+
}
|
|
104
|
+
}));
|
|
105
|
+
this.seenDbConfigs.clear();
|
|
106
|
+
this.log(chalk_1.default.green('\n✅ All PgTestClients closed, pools disposed, databases dropped.'));
|
|
107
|
+
}
|
|
108
|
+
close() {
|
|
109
|
+
this.closeAll();
|
|
110
|
+
}
|
|
111
|
+
drop(config) {
|
|
112
|
+
const key = this.dbKey(config);
|
|
113
|
+
// for drop, no need for conn opts
|
|
114
|
+
const admin = new admin_1.DbAdmin(config, this.verbose);
|
|
115
|
+
admin.drop();
|
|
116
|
+
this.log(chalk_1.default.red(`🧨 Dropped database: ${chalk_1.default.white(config.database)}`));
|
|
117
|
+
this.seenDbConfigs.delete(key);
|
|
118
|
+
}
|
|
119
|
+
kill(client) {
|
|
120
|
+
client.close();
|
|
121
|
+
this.drop(client.config);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
exports.PgTestConnector = PgTestConnector;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pgsql-test",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"author": "Dan Lynch <pyramation@gmail.com>",
|
|
5
5
|
"description": "PostgreSQL Testing in TypeScript",
|
|
6
6
|
"main": "index.js",
|
|
@@ -30,7 +30,11 @@
|
|
|
30
30
|
"test:watch": "jest --watch"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@launchql/
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
"@launchql/migrate": "^2.0.12",
|
|
34
|
+
"@launchql/server-utils": "^2.0.5",
|
|
35
|
+
"@launchql/types": "^2.0.5",
|
|
36
|
+
"chalk": "^4.1.0",
|
|
37
|
+
"deepmerge": "^4.3.1"
|
|
38
|
+
},
|
|
39
|
+
"gitHead": "77a8acdaa9513791b3bb47b11dcfc3b141d856ab"
|
|
40
|
+
}
|
package/seed.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { PgConfig } from "@launchql/types";
|
|
2
|
+
import { DbAdmin } from "./admin";
|
|
3
|
+
import { PgTestClient } from "./test-client";
|
|
4
|
+
interface SeedContext {
|
|
5
|
+
admin: DbAdmin;
|
|
6
|
+
config: PgConfig;
|
|
7
|
+
pg: PgTestClient;
|
|
8
|
+
}
|
|
9
|
+
export interface SeedAdapter {
|
|
10
|
+
seed(ctx: SeedContext): Promise<void> | void;
|
|
11
|
+
}
|
|
12
|
+
declare function sqlfile(files: string[]): SeedAdapter;
|
|
13
|
+
declare function fn(fn: (ctx: SeedContext) => Promise<void>): SeedAdapter;
|
|
14
|
+
declare function csv(fn: (ctx: SeedContext) => Promise<void>): SeedAdapter;
|
|
15
|
+
declare function compose(adapters: SeedAdapter[]): SeedAdapter;
|
|
16
|
+
export declare const seed: {
|
|
17
|
+
compose: typeof compose;
|
|
18
|
+
fn: typeof fn;
|
|
19
|
+
csv: typeof csv;
|
|
20
|
+
sqlfile: typeof sqlfile;
|
|
21
|
+
};
|
|
22
|
+
export {};
|
package/seed.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.seed = void 0;
|
|
4
|
+
function sqlfile(files) {
|
|
5
|
+
return {
|
|
6
|
+
seed(ctx) {
|
|
7
|
+
for (const file of files) {
|
|
8
|
+
ctx.admin.loadSql(file, ctx.config.database);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function fn(fn) {
|
|
14
|
+
return {
|
|
15
|
+
seed: fn
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function csv(fn) {
|
|
19
|
+
throw new Error('not yet implemented');
|
|
20
|
+
return {
|
|
21
|
+
seed: fn
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function compose(adapters) {
|
|
25
|
+
return {
|
|
26
|
+
async seed(ctx) {
|
|
27
|
+
for (const adapter of adapters) {
|
|
28
|
+
await adapter.seed(ctx);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
exports.seed = {
|
|
34
|
+
compose,
|
|
35
|
+
fn,
|
|
36
|
+
csv,
|
|
37
|
+
sqlfile
|
|
38
|
+
};
|
package/stream.d.ts
ADDED
package/stream.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.streamSql = streamSql;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const stream_1 = require("stream");
|
|
6
|
+
const types_1 = require("@launchql/types");
|
|
7
|
+
function setArgs(config) {
|
|
8
|
+
const args = [
|
|
9
|
+
'-U', config.user,
|
|
10
|
+
'-h', config.host,
|
|
11
|
+
'-d', config.database
|
|
12
|
+
];
|
|
13
|
+
if (config.port) {
|
|
14
|
+
args.push('-p', String(config.port));
|
|
15
|
+
}
|
|
16
|
+
return args;
|
|
17
|
+
}
|
|
18
|
+
// Converts a string to a readable stream (replaces streamify-string)
|
|
19
|
+
function stringToStream(text) {
|
|
20
|
+
const stream = new stream_1.Readable({
|
|
21
|
+
read() {
|
|
22
|
+
this.push(text);
|
|
23
|
+
this.push(null);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
return stream;
|
|
27
|
+
}
|
|
28
|
+
async function streamSql(config, sql) {
|
|
29
|
+
const args = setArgs(config);
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
const sqlStream = stringToStream(sql);
|
|
32
|
+
const proc = (0, child_process_1.spawn)('psql', args, {
|
|
33
|
+
env: (0, types_1.getSpawnEnvWithPg)(config)
|
|
34
|
+
});
|
|
35
|
+
sqlStream.pipe(proc.stdin);
|
|
36
|
+
proc.on('close', (code) => {
|
|
37
|
+
resolve();
|
|
38
|
+
});
|
|
39
|
+
proc.on('error', (error) => {
|
|
40
|
+
reject(error);
|
|
41
|
+
});
|
|
42
|
+
proc.stderr.on('data', (data) => {
|
|
43
|
+
reject(new Error(data.toString()));
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
package/test-client.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { QueryResult } from 'pg';
|
|
2
|
+
import { PgConfig } from '@launchql/types';
|
|
3
|
+
export declare class PgTestClient {
|
|
4
|
+
config: PgConfig;
|
|
5
|
+
private client;
|
|
6
|
+
private ctxStmts;
|
|
7
|
+
private _ended;
|
|
8
|
+
constructor(config: PgConfig);
|
|
9
|
+
close(): void;
|
|
10
|
+
begin(): Promise<void>;
|
|
11
|
+
savepoint(name?: string): Promise<void>;
|
|
12
|
+
rollback(name?: string): Promise<void>;
|
|
13
|
+
commit(): Promise<void>;
|
|
14
|
+
beforeEach(): Promise<void>;
|
|
15
|
+
afterEach(): Promise<void>;
|
|
16
|
+
setContext(ctx: Record<string, string | null>): void;
|
|
17
|
+
any<T = any>(query: string, values?: any[]): Promise<T[]>;
|
|
18
|
+
one<T = any>(query: string, values?: any[]): Promise<T>;
|
|
19
|
+
oneOrNone<T = any>(query: string, values?: any[]): Promise<T | null>;
|
|
20
|
+
many<T = any>(query: string, values?: any[]): Promise<T[]>;
|
|
21
|
+
manyOrNone<T = any>(query: string, values?: any[]): Promise<T[]>;
|
|
22
|
+
none(query: string, values?: any[]): Promise<void>;
|
|
23
|
+
result(query: string, values?: any[]): Promise<import('pg').QueryResult>;
|
|
24
|
+
query<T = any>(query: string, values?: any[]): Promise<QueryResult<T>>;
|
|
25
|
+
}
|
package/test-client.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PgTestClient = void 0;
|
|
4
|
+
const pg_1 = require("pg");
|
|
5
|
+
class PgTestClient {
|
|
6
|
+
config;
|
|
7
|
+
client;
|
|
8
|
+
ctxStmts = '';
|
|
9
|
+
_ended = false;
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.config = config;
|
|
12
|
+
this.client = new pg_1.Client({
|
|
13
|
+
host: this.config.host,
|
|
14
|
+
port: this.config.port,
|
|
15
|
+
database: this.config.database,
|
|
16
|
+
user: this.config.user,
|
|
17
|
+
password: this.config.password
|
|
18
|
+
});
|
|
19
|
+
this.client.connect();
|
|
20
|
+
}
|
|
21
|
+
close() {
|
|
22
|
+
if (!this._ended) {
|
|
23
|
+
this._ended = true;
|
|
24
|
+
this.client.end();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async begin() {
|
|
28
|
+
await this.client.query('BEGIN;');
|
|
29
|
+
}
|
|
30
|
+
async savepoint(name = 'lqlsavepoint') {
|
|
31
|
+
await this.client.query(`SAVEPOINT "${name}";`);
|
|
32
|
+
}
|
|
33
|
+
async rollback(name = 'lqlsavepoint') {
|
|
34
|
+
await this.client.query(`ROLLBACK TO SAVEPOINT "${name}";`);
|
|
35
|
+
}
|
|
36
|
+
async commit() {
|
|
37
|
+
await this.client.query('COMMIT;');
|
|
38
|
+
}
|
|
39
|
+
async beforeEach() {
|
|
40
|
+
await this.begin();
|
|
41
|
+
await this.savepoint();
|
|
42
|
+
}
|
|
43
|
+
async afterEach() {
|
|
44
|
+
await this.rollback();
|
|
45
|
+
await this.commit();
|
|
46
|
+
}
|
|
47
|
+
setContext(ctx) {
|
|
48
|
+
this.ctxStmts = Object.entries(ctx)
|
|
49
|
+
.map(([key, val]) => val === null
|
|
50
|
+
? `SELECT set_config('${key}', NULL, true);`
|
|
51
|
+
: `SELECT set_config('${key}', '${val}', true);`)
|
|
52
|
+
.join('\n');
|
|
53
|
+
}
|
|
54
|
+
async any(query, values) {
|
|
55
|
+
const result = await this.query(query, values);
|
|
56
|
+
return result.rows;
|
|
57
|
+
}
|
|
58
|
+
async one(query, values) {
|
|
59
|
+
const rows = await this.any(query, values);
|
|
60
|
+
if (rows.length !== 1) {
|
|
61
|
+
throw new Error('Expected exactly one result');
|
|
62
|
+
}
|
|
63
|
+
return rows[0];
|
|
64
|
+
}
|
|
65
|
+
async oneOrNone(query, values) {
|
|
66
|
+
const rows = await this.any(query, values);
|
|
67
|
+
return rows[0] || null;
|
|
68
|
+
}
|
|
69
|
+
async many(query, values) {
|
|
70
|
+
const rows = await this.any(query, values);
|
|
71
|
+
if (rows.length === 0)
|
|
72
|
+
throw new Error('Expected many rows, got none');
|
|
73
|
+
return rows;
|
|
74
|
+
}
|
|
75
|
+
async manyOrNone(query, values) {
|
|
76
|
+
return this.any(query, values);
|
|
77
|
+
}
|
|
78
|
+
async none(query, values) {
|
|
79
|
+
await this.query(query, values);
|
|
80
|
+
}
|
|
81
|
+
async result(query, values) {
|
|
82
|
+
return this.query(query, values);
|
|
83
|
+
}
|
|
84
|
+
async query(query, values) {
|
|
85
|
+
if (this.ctxStmts) {
|
|
86
|
+
await this.client.query(this.ctxStmts);
|
|
87
|
+
}
|
|
88
|
+
const result = await this.client.query(query, values);
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
exports.PgTestClient = PgTestClient;
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { getConnections } from '../src/connect';
|
|
2
|
-
import { PgTestClient } from '../src/test-client';
|
|
3
|
-
|
|
4
|
-
let conn: PgTestClient;
|
|
5
|
-
let db: PgTestClient;
|
|
6
|
-
let teardown: () => Promise<void>;
|
|
7
|
-
|
|
8
|
-
beforeAll(async () => {
|
|
9
|
-
({ conn, db, teardown } = await getConnections());
|
|
10
|
-
|
|
11
|
-
// Setup schema + seed ONCE globally
|
|
12
|
-
await db.query(`
|
|
13
|
-
CREATE TABLE users (
|
|
14
|
-
id SERIAL PRIMARY KEY,
|
|
15
|
-
name TEXT NOT NULL
|
|
16
|
-
);
|
|
17
|
-
CREATE TABLE posts (
|
|
18
|
-
id SERIAL PRIMARY KEY,
|
|
19
|
-
user_id INT NOT NULL REFERENCES users(id),
|
|
20
|
-
content TEXT NOT NULL
|
|
21
|
-
);
|
|
22
|
-
`);
|
|
23
|
-
|
|
24
|
-
await db.query(`
|
|
25
|
-
INSERT INTO users (name) VALUES ('Alice'), ('Bob');
|
|
26
|
-
INSERT INTO posts (user_id, content) VALUES
|
|
27
|
-
(1, 'Hello world!'),
|
|
28
|
-
(2, 'Graphile is cool!');
|
|
29
|
-
`);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
afterAll(async () => {
|
|
33
|
-
await teardown();
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
describe('anonymous', () => {
|
|
37
|
-
beforeEach(async () => {
|
|
38
|
-
await db.beforeEach(); // this starts tx + savepoint
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
afterEach(async () => {
|
|
42
|
-
await db.afterEach(); // this rolls back and commits
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('inserts a user but rollback leaves baseline intact', async () => {
|
|
46
|
-
await db.query(`INSERT INTO users (name) VALUES ('Carol')`);
|
|
47
|
-
const res = await db.query('SELECT COUNT(*) FROM users');
|
|
48
|
-
expect(res.rows[0].count).toBe('3');
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('should still have 2 users after rollback', async () => {
|
|
52
|
-
const res = await db.query('SELECT COUNT(*) FROM users');
|
|
53
|
-
expect(res.rows[0].count).toBe('2');
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('runs under anonymous context', async () => {
|
|
57
|
-
const result = await conn.query('SELECT current_setting(\'role\', true) AS role');
|
|
58
|
-
console.log(JSON.stringify({result}, null, 2))
|
|
59
|
-
console.error(JSON.stringify({result}, null, 2))
|
|
60
|
-
// expect(result.rows[0].role).toBe('anonymous');
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
describe('authenticated', () => {
|
|
65
|
-
beforeEach(async () => {
|
|
66
|
-
conn.setContext({
|
|
67
|
-
role: 'authenticated'
|
|
68
|
-
});
|
|
69
|
-
await conn.beforeEach(); // required for rollback later
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
afterEach(async () => {
|
|
73
|
-
await conn.afterEach(); // now safe to rollback
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('runs under authenticated context', async () => {
|
|
77
|
-
const result = await conn.query('SELECT current_setting(\'role\', true) AS role');
|
|
78
|
-
// expect(result.rows[0].role).toBe('authenticated');
|
|
79
|
-
console.error('why no JWT')
|
|
80
|
-
});
|
|
81
|
-
});
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { getPgEnvOptions, PgConfig } from '@launchql/types';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
getConnection,
|
|
5
|
-
Connection,
|
|
6
|
-
} from '../src';
|
|
7
|
-
import { randomUUID } from 'crypto';
|
|
8
|
-
import { PgTestClient } from '../src/test-client';
|
|
9
|
-
import { DbAdmin } from '../src/admin';
|
|
10
|
-
import { resolve } from 'path';
|
|
11
|
-
|
|
12
|
-
const sql = (file: string) => resolve(__dirname, '../sql', file);
|
|
13
|
-
|
|
14
|
-
const TEST_DB_BASE = `postgres_test_${randomUUID()}`;
|
|
15
|
-
|
|
16
|
-
function setupBaseDB(config: PgConfig): void {
|
|
17
|
-
const admin = new DbAdmin(config);
|
|
18
|
-
admin.create(config.database)
|
|
19
|
-
admin.loadSql(sql('test.sql'), config.database);
|
|
20
|
-
admin.loadSql(sql('roles.sql'), config.database);
|
|
21
|
-
admin.drop(config.database);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const config = getPgEnvOptions({
|
|
25
|
-
database: TEST_DB_BASE
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
beforeAll(() => {
|
|
29
|
-
setupBaseDB(config);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
afterAll(() => {
|
|
33
|
-
Connection.getManager().closeAll();
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
describe('Postgres Test Framework', () => {
|
|
38
|
-
let db: PgTestClient;
|
|
39
|
-
|
|
40
|
-
afterEach(() => {
|
|
41
|
-
// if (db) closeConnection(db);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('creates a test DB with hot mode (FAST_TEST)', () => {
|
|
45
|
-
db = getConnection({ hot: true, extensions: ['uuid-ossp'] });
|
|
46
|
-
expect(db).toBeDefined();
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('creates a test DB from scratch (default)', () => {
|
|
50
|
-
db = getConnection({});
|
|
51
|
-
expect(db).toBeDefined();
|
|
52
|
-
});
|
|
53
|
-
});
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { getConnections } from '../src/connect';
|
|
2
|
-
import { PgTestClient } from '../src/test-client';
|
|
3
|
-
|
|
4
|
-
let conn: PgTestClient;
|
|
5
|
-
let db: PgTestClient;
|
|
6
|
-
let teardown: () => Promise<void>;
|
|
7
|
-
|
|
8
|
-
const setupSchemaSQL = `
|
|
9
|
-
CREATE TABLE users (
|
|
10
|
-
id SERIAL PRIMARY KEY,
|
|
11
|
-
name TEXT NOT NULL
|
|
12
|
-
);
|
|
13
|
-
|
|
14
|
-
CREATE TABLE posts (
|
|
15
|
-
id SERIAL PRIMARY KEY,
|
|
16
|
-
user_id INT NOT NULL REFERENCES users(id),
|
|
17
|
-
content TEXT NOT NULL
|
|
18
|
-
);
|
|
19
|
-
`;
|
|
20
|
-
|
|
21
|
-
const seedDataSQL = `
|
|
22
|
-
INSERT INTO users (name) VALUES ('Alice'), ('Bob');
|
|
23
|
-
INSERT INTO posts (user_id, content) VALUES
|
|
24
|
-
(1, 'Hello world!'),
|
|
25
|
-
(2, 'Graphile is cool!');
|
|
26
|
-
`;
|
|
27
|
-
|
|
28
|
-
beforeAll(async () => {
|
|
29
|
-
({ conn, db, teardown } = await getConnections());
|
|
30
|
-
// create schema + seed *once*
|
|
31
|
-
await db.query(setupSchemaSQL);
|
|
32
|
-
await db.query(seedDataSQL);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
afterAll(async () => {
|
|
36
|
-
await teardown();
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
describe('Postgres Test Framework', () => {
|
|
40
|
-
beforeEach(async () => {
|
|
41
|
-
await db.beforeEach(); // BEGIN + SAVEPOINT
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
afterEach(async () => {
|
|
45
|
-
await db.afterEach(); // ROLLBACK TO SAVEPOINT + COMMIT
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('should have 2 users initially', async () => {
|
|
49
|
-
const { rows } = await db.query('SELECT COUNT(*) FROM users');
|
|
50
|
-
expect(rows[0].count).toBe('2');
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('inserts a user but rollback leaves baseline intact', async () => {
|
|
54
|
-
await db.query(`INSERT INTO users (name) VALUES ('Carol')`);
|
|
55
|
-
let res = await db.query('SELECT COUNT(*) FROM users');
|
|
56
|
-
expect(res.rows[0].count).toBe('3'); // inside this tx
|
|
57
|
-
|
|
58
|
-
// after rollback (next test) we’ll still see 2
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('still sees 2 users after previous insert test', async () => {
|
|
62
|
-
const { rows } = await db.query('SELECT COUNT(*) FROM users');
|
|
63
|
-
expect(rows[0].count).toBe('2');
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
});
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { getPgEnvOptions, PgConfig } from '@launchql/types';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
getConnection,
|
|
6
|
-
Connection
|
|
7
|
-
} from '../src';
|
|
8
|
-
import { PgTestClient } from '../src/test-client';
|
|
9
|
-
import { DbAdmin } from '../src/admin';
|
|
10
|
-
|
|
11
|
-
const sql = (file: string) => path.resolve(__dirname, '../sql', file);
|
|
12
|
-
|
|
13
|
-
const TEMPLATE_NAME = 'test_template';
|
|
14
|
-
const TEST_DB_BASE = 'postgres_test_db_template';
|
|
15
|
-
|
|
16
|
-
function setupTemplateDB(config: PgConfig, template: string): void {
|
|
17
|
-
const admin = new DbAdmin(config);
|
|
18
|
-
try {
|
|
19
|
-
admin.drop(config.database);
|
|
20
|
-
} catch {}
|
|
21
|
-
admin.create(config.database);
|
|
22
|
-
admin.loadSql(sql('test.sql'), config.database);
|
|
23
|
-
admin.loadSql(sql('roles.sql'), config.database);
|
|
24
|
-
admin.cleanupTemplate(template);
|
|
25
|
-
admin.createTemplateFromBase(config.database, template);
|
|
26
|
-
admin.drop(config.database);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const config = getPgEnvOptions({
|
|
30
|
-
database: TEST_DB_BASE
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
beforeAll(() => {
|
|
34
|
-
setupTemplateDB(config, TEMPLATE_NAME);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
afterAll(() => {
|
|
38
|
-
Connection.getManager().closeAll();
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
describe('Template Database Test', () => {
|
|
42
|
-
let db: PgTestClient;
|
|
43
|
-
|
|
44
|
-
afterEach(() => {
|
|
45
|
-
// if (db) closeConnection(db);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('creates a test DB from a template', () => {
|
|
49
|
-
db = getConnection({ template: TEMPLATE_NAME });
|
|
50
|
-
expect(db).toBeDefined();
|
|
51
|
-
});
|
|
52
|
-
});
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { getPgEnvOptions, PgConfig } from '@launchql/types';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
getConnection,
|
|
5
|
-
Connection
|
|
6
|
-
} from '../src';
|
|
7
|
-
import { randomUUID } from 'crypto';
|
|
8
|
-
import { PgTestClient } from '../src/test-client';
|
|
9
|
-
|
|
10
|
-
const TEST_DB_BASE = `postgres_test_${randomUUID()}`;
|
|
11
|
-
|
|
12
|
-
const config = getPgEnvOptions({
|
|
13
|
-
database: TEST_DB_BASE
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
afterAll(() => {
|
|
17
|
-
Connection.getManager().closeAll();
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
describe('Postgres Test Framework', () => {
|
|
21
|
-
let db: PgTestClient;
|
|
22
|
-
|
|
23
|
-
afterEach(() => {
|
|
24
|
-
// if (db) closeConnection(db);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('creates a test DB with hot mode (FAST_TEST)', () => {
|
|
28
|
-
db = getConnection({ hot: true, extensions: ['uuid-ossp'] });
|
|
29
|
-
expect(db).toBeDefined();
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('creates a test DB from scratch (default)', () => {
|
|
33
|
-
db = getConnection({});
|
|
34
|
-
expect(db).toBeDefined();
|
|
35
|
-
});
|
|
36
|
-
});
|
package/jest.config.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
|
2
|
-
module.exports = {
|
|
3
|
-
preset: "ts-jest",
|
|
4
|
-
testEnvironment: "node",
|
|
5
|
-
transform: {
|
|
6
|
-
"^.+\\.tsx?$": [
|
|
7
|
-
"ts-jest",
|
|
8
|
-
{
|
|
9
|
-
babelConfig: false,
|
|
10
|
-
tsconfig: "tsconfig.json",
|
|
11
|
-
},
|
|
12
|
-
],
|
|
13
|
-
},
|
|
14
|
-
transformIgnorePatterns: [`/node_modules/*`],
|
|
15
|
-
testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
|
|
16
|
-
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
|
|
17
|
-
modulePathIgnorePatterns: ["dist/*"]
|
|
18
|
-
};
|