pgsql-test 2.9.0 → 2.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/admin.d.ts +3 -1
- package/admin.js +8 -3
- package/connect.js +9 -4
- package/esm/admin.js +8 -3
- package/esm/connect.js +9 -4
- package/esm/index.js +1 -0
- package/esm/manager.js +5 -2
- package/esm/roles.js +32 -0
- package/esm/test-client.js +46 -1
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/manager.d.ts +2 -2
- package/manager.js +5 -2
- package/package.json +7 -7
- package/roles.d.ts +17 -0
- package/roles.js +38 -0
- package/test-client.d.ts +19 -3
- package/test-client.js +46 -1
package/admin.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { PgTestConnectionOptions } from '@launchql/types';
|
|
1
2
|
import { PgConfig } from 'pg-env';
|
|
2
3
|
import { SeedAdapter } from './seed/types';
|
|
3
4
|
export declare class DbAdmin {
|
|
4
5
|
private config;
|
|
5
6
|
private verbose;
|
|
6
|
-
|
|
7
|
+
private roleConfig?;
|
|
8
|
+
constructor(config: PgConfig, verbose?: boolean, roleConfig?: PgTestConnectionOptions);
|
|
7
9
|
private getEnv;
|
|
8
10
|
private run;
|
|
9
11
|
private safeDropDb;
|
package/admin.js
CHANGED
|
@@ -5,14 +5,17 @@ const logger_1 = require("@launchql/logger");
|
|
|
5
5
|
const child_process_1 = require("child_process");
|
|
6
6
|
const fs_1 = require("fs");
|
|
7
7
|
const pg_env_1 = require("pg-env");
|
|
8
|
+
const roles_1 = require("./roles");
|
|
8
9
|
const stream_1 = require("./stream");
|
|
9
10
|
const log = new logger_1.Logger('db-admin');
|
|
10
11
|
class DbAdmin {
|
|
11
12
|
config;
|
|
12
13
|
verbose;
|
|
13
|
-
|
|
14
|
+
roleConfig;
|
|
15
|
+
constructor(config, verbose = false, roleConfig) {
|
|
14
16
|
this.config = config;
|
|
15
17
|
this.verbose = verbose;
|
|
18
|
+
this.roleConfig = roleConfig;
|
|
16
19
|
this.config = (0, pg_env_1.getPgEnvOptions)(config);
|
|
17
20
|
}
|
|
18
21
|
getEnv() {
|
|
@@ -103,13 +106,15 @@ class DbAdmin {
|
|
|
103
106
|
await this.streamSql(sql, db);
|
|
104
107
|
}
|
|
105
108
|
async createUserRole(user, password, dbName) {
|
|
109
|
+
const anonRole = (0, roles_1.getRoleName)('anonymous', this.roleConfig);
|
|
110
|
+
const authRole = (0, roles_1.getRoleName)('authenticated', this.roleConfig);
|
|
106
111
|
const sql = `
|
|
107
112
|
DO $$
|
|
108
113
|
BEGIN
|
|
109
114
|
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${user}') THEN
|
|
110
115
|
CREATE ROLE ${user} LOGIN PASSWORD '${password}';
|
|
111
|
-
GRANT
|
|
112
|
-
GRANT
|
|
116
|
+
GRANT ${anonRole} TO ${user};
|
|
117
|
+
GRANT ${authRole} TO ${user};
|
|
113
118
|
END IF;
|
|
114
119
|
END $$;
|
|
115
120
|
`.trim();
|
package/connect.js
CHANGED
|
@@ -7,13 +7,14 @@ const pg_cache_1 = require("pg-cache");
|
|
|
7
7
|
const pg_env_1 = require("pg-env");
|
|
8
8
|
const admin_1 = require("./admin");
|
|
9
9
|
const manager_1 = require("./manager");
|
|
10
|
+
const roles_1 = require("./roles");
|
|
10
11
|
const seed_1 = require("./seed");
|
|
11
12
|
let manager;
|
|
12
13
|
const getPgRootAdmin = (connOpts = {}) => {
|
|
13
14
|
const opts = (0, pg_env_1.getPgEnvOptions)({
|
|
14
15
|
database: connOpts.rootDb
|
|
15
16
|
});
|
|
16
|
-
const admin = new admin_1.DbAdmin(opts);
|
|
17
|
+
const admin = new admin_1.DbAdmin(opts, false, connOpts);
|
|
17
18
|
return admin;
|
|
18
19
|
};
|
|
19
20
|
exports.getPgRootAdmin = getPgRootAdmin;
|
|
@@ -34,7 +35,7 @@ const getConnections = async (cn = {}, seedAdapters = [seed_1.seed.launchql()])
|
|
|
34
35
|
const connOpts = cn.db;
|
|
35
36
|
const root = (0, exports.getPgRootAdmin)(connOpts);
|
|
36
37
|
await root.createUserRole(connOpts.connection.user, connOpts.connection.password, connOpts.rootDb);
|
|
37
|
-
const admin = new admin_1.DbAdmin(config);
|
|
38
|
+
const admin = new admin_1.DbAdmin(config, false, connOpts);
|
|
38
39
|
if (process.env.TEST_DB) {
|
|
39
40
|
config.database = process.env.TEST_DB;
|
|
40
41
|
}
|
|
@@ -67,12 +68,16 @@ const getConnections = async (cn = {}, seedAdapters = [seed_1.seed.launchql()])
|
|
|
67
68
|
throw error;
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
|
-
const
|
|
71
|
+
const dbConfig = {
|
|
71
72
|
...config,
|
|
72
73
|
user: connOpts.connection.user,
|
|
73
74
|
password: connOpts.connection.password
|
|
75
|
+
};
|
|
76
|
+
const db = manager.getClient(dbConfig, {
|
|
77
|
+
auth: connOpts.auth,
|
|
78
|
+
roles: connOpts.roles
|
|
74
79
|
});
|
|
75
|
-
db.setContext({ role:
|
|
80
|
+
db.setContext({ role: (0, roles_1.getDefaultRole)(connOpts) });
|
|
76
81
|
return { pg, db, teardown, manager, admin };
|
|
77
82
|
};
|
|
78
83
|
exports.getConnections = getConnections;
|
package/esm/admin.js
CHANGED
|
@@ -2,14 +2,17 @@ import { Logger } from '@launchql/logger';
|
|
|
2
2
|
import { execSync } from 'child_process';
|
|
3
3
|
import { existsSync } from 'fs';
|
|
4
4
|
import { getPgEnvOptions } from 'pg-env';
|
|
5
|
+
import { getRoleName } from './roles';
|
|
5
6
|
import { streamSql as stream } from './stream';
|
|
6
7
|
const log = new Logger('db-admin');
|
|
7
8
|
export class DbAdmin {
|
|
8
9
|
config;
|
|
9
10
|
verbose;
|
|
10
|
-
|
|
11
|
+
roleConfig;
|
|
12
|
+
constructor(config, verbose = false, roleConfig) {
|
|
11
13
|
this.config = config;
|
|
12
14
|
this.verbose = verbose;
|
|
15
|
+
this.roleConfig = roleConfig;
|
|
13
16
|
this.config = getPgEnvOptions(config);
|
|
14
17
|
}
|
|
15
18
|
getEnv() {
|
|
@@ -100,13 +103,15 @@ export class DbAdmin {
|
|
|
100
103
|
await this.streamSql(sql, db);
|
|
101
104
|
}
|
|
102
105
|
async createUserRole(user, password, dbName) {
|
|
106
|
+
const anonRole = getRoleName('anonymous', this.roleConfig);
|
|
107
|
+
const authRole = getRoleName('authenticated', this.roleConfig);
|
|
103
108
|
const sql = `
|
|
104
109
|
DO $$
|
|
105
110
|
BEGIN
|
|
106
111
|
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${user}') THEN
|
|
107
112
|
CREATE ROLE ${user} LOGIN PASSWORD '${password}';
|
|
108
|
-
GRANT
|
|
109
|
-
GRANT
|
|
113
|
+
GRANT ${anonRole} TO ${user};
|
|
114
|
+
GRANT ${authRole} TO ${user};
|
|
110
115
|
END IF;
|
|
111
116
|
END $$;
|
|
112
117
|
`.trim();
|
package/esm/connect.js
CHANGED
|
@@ -4,13 +4,14 @@ import { teardownPgPools } from 'pg-cache';
|
|
|
4
4
|
import { getPgEnvOptions, } from 'pg-env';
|
|
5
5
|
import { DbAdmin } from './admin';
|
|
6
6
|
import { PgTestConnector } from './manager';
|
|
7
|
+
import { getDefaultRole } from './roles';
|
|
7
8
|
import { seed } from './seed';
|
|
8
9
|
let manager;
|
|
9
10
|
export const getPgRootAdmin = (connOpts = {}) => {
|
|
10
11
|
const opts = getPgEnvOptions({
|
|
11
12
|
database: connOpts.rootDb
|
|
12
13
|
});
|
|
13
|
-
const admin = new DbAdmin(opts);
|
|
14
|
+
const admin = new DbAdmin(opts, false, connOpts);
|
|
14
15
|
return admin;
|
|
15
16
|
};
|
|
16
17
|
const getConnOopts = (cn = {}) => {
|
|
@@ -30,7 +31,7 @@ export const getConnections = async (cn = {}, seedAdapters = [seed.launchql()])
|
|
|
30
31
|
const connOpts = cn.db;
|
|
31
32
|
const root = getPgRootAdmin(connOpts);
|
|
32
33
|
await root.createUserRole(connOpts.connection.user, connOpts.connection.password, connOpts.rootDb);
|
|
33
|
-
const admin = new DbAdmin(config);
|
|
34
|
+
const admin = new DbAdmin(config, false, connOpts);
|
|
34
35
|
if (process.env.TEST_DB) {
|
|
35
36
|
config.database = process.env.TEST_DB;
|
|
36
37
|
}
|
|
@@ -63,11 +64,15 @@ export const getConnections = async (cn = {}, seedAdapters = [seed.launchql()])
|
|
|
63
64
|
throw error;
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
|
-
const
|
|
67
|
+
const dbConfig = {
|
|
67
68
|
...config,
|
|
68
69
|
user: connOpts.connection.user,
|
|
69
70
|
password: connOpts.connection.password
|
|
71
|
+
};
|
|
72
|
+
const db = manager.getClient(dbConfig, {
|
|
73
|
+
auth: connOpts.auth,
|
|
74
|
+
roles: connOpts.roles
|
|
70
75
|
});
|
|
71
|
-
db.setContext({ role:
|
|
76
|
+
db.setContext({ role: getDefaultRole(connOpts) });
|
|
72
77
|
return { pg, db, teardown, manager, admin };
|
|
73
78
|
};
|
package/esm/index.js
CHANGED
package/esm/manager.js
CHANGED
|
@@ -68,11 +68,14 @@ export class PgTestConnector {
|
|
|
68
68
|
}
|
|
69
69
|
return this.pgPools.get(key);
|
|
70
70
|
}
|
|
71
|
-
getClient(config) {
|
|
71
|
+
getClient(config, opts = {}) {
|
|
72
72
|
if (this.shuttingDown) {
|
|
73
73
|
throw new Error('PgTestConnector is shutting down; no new clients allowed');
|
|
74
74
|
}
|
|
75
|
-
const client = new PgTestClient(config, {
|
|
75
|
+
const client = new PgTestClient(config, {
|
|
76
|
+
trackConnect: (p) => this.registerConnect(p),
|
|
77
|
+
...opts
|
|
78
|
+
});
|
|
76
79
|
this.clients.add(client);
|
|
77
80
|
const key = this.dbKey(config);
|
|
78
81
|
this.seenDbConfigs.set(key, config);
|
package/esm/roles.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default role mapping configuration
|
|
3
|
+
*/
|
|
4
|
+
export const DEFAULT_ROLE_MAPPING = {
|
|
5
|
+
anonymous: 'anonymous',
|
|
6
|
+
authenticated: 'authenticated',
|
|
7
|
+
administrator: 'administrator',
|
|
8
|
+
default: 'anonymous'
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Get resolved role mapping with defaults
|
|
12
|
+
*/
|
|
13
|
+
export const getRoleMapping = (options) => {
|
|
14
|
+
return {
|
|
15
|
+
...DEFAULT_ROLE_MAPPING,
|
|
16
|
+
...(options?.roles || {})
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Get role name by key with fallback to default mapping
|
|
21
|
+
*/
|
|
22
|
+
export const getRoleName = (roleKey, options) => {
|
|
23
|
+
const mapping = getRoleMapping(options);
|
|
24
|
+
return mapping[roleKey];
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Get default role name
|
|
28
|
+
*/
|
|
29
|
+
export const getDefaultRole = (options) => {
|
|
30
|
+
const mapping = getRoleMapping(options);
|
|
31
|
+
return mapping.default;
|
|
32
|
+
};
|
package/esm/test-client.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { Client } from 'pg';
|
|
2
|
+
import { getRoleName } from './roles';
|
|
2
3
|
export class PgTestClient {
|
|
3
4
|
config;
|
|
4
5
|
client;
|
|
6
|
+
opts;
|
|
5
7
|
ctxStmts = '';
|
|
8
|
+
contextSettings = {};
|
|
6
9
|
_ended = false;
|
|
7
10
|
connectPromise = null;
|
|
8
11
|
constructor(config, opts = {}) {
|
|
12
|
+
this.opts = opts;
|
|
9
13
|
this.config = config;
|
|
10
14
|
this.client = new Client({
|
|
11
15
|
host: this.config.host,
|
|
@@ -56,12 +60,53 @@ export class PgTestClient {
|
|
|
56
60
|
await this.commit();
|
|
57
61
|
}
|
|
58
62
|
setContext(ctx) {
|
|
59
|
-
this.
|
|
63
|
+
Object.assign(this.contextSettings, ctx);
|
|
64
|
+
this.ctxStmts = Object.entries(this.contextSettings)
|
|
60
65
|
.map(([key, val]) => val === null
|
|
61
66
|
? `SELECT set_config('${key}', NULL, true);`
|
|
62
67
|
: `SELECT set_config('${key}', '${val}', true);`)
|
|
63
68
|
.join('\n');
|
|
64
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Set authentication context for the current session.
|
|
72
|
+
* Configures role and user ID using cascading defaults from options → opts.auth → RoleMapping.
|
|
73
|
+
*/
|
|
74
|
+
auth(options = {}) {
|
|
75
|
+
const role = options.role ?? this.opts.auth?.role ?? getRoleName('authenticated', this.opts);
|
|
76
|
+
const userIdKey = options.userIdKey ?? this.opts.auth?.userIdKey ?? 'jwt.claims.user_id';
|
|
77
|
+
const userId = options.userId ?? this.opts.auth?.userId ?? null;
|
|
78
|
+
this.setContext({
|
|
79
|
+
role,
|
|
80
|
+
[userIdKey]: userId !== null ? String(userId) : null
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Commit current transaction to make data visible to other connections, then start fresh transaction.
|
|
85
|
+
* Maintains test isolation by creating a savepoint and reapplying session context.
|
|
86
|
+
*/
|
|
87
|
+
async publish() {
|
|
88
|
+
await this.commit(); // make data visible to other sessions
|
|
89
|
+
await this.begin(); // fresh tx
|
|
90
|
+
await this.savepoint(); // keep rollback harness
|
|
91
|
+
await this.ctxQuery(); // reapply all setContext()
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Clear all session context variables and reset to default anonymous role.
|
|
95
|
+
*/
|
|
96
|
+
clearContext() {
|
|
97
|
+
const defaultRole = getRoleName('anonymous', this.opts);
|
|
98
|
+
const nulledSettings = {};
|
|
99
|
+
Object.keys(this.contextSettings).forEach(key => {
|
|
100
|
+
nulledSettings[key] = null;
|
|
101
|
+
});
|
|
102
|
+
nulledSettings.role = defaultRole;
|
|
103
|
+
this.ctxStmts = Object.entries(nulledSettings)
|
|
104
|
+
.map(([key, val]) => val === null
|
|
105
|
+
? `SELECT set_config('${key}', NULL, true);`
|
|
106
|
+
: `SELECT set_config('${key}', '${val}', true);`)
|
|
107
|
+
.join('\n');
|
|
108
|
+
this.contextSettings = { role: defaultRole };
|
|
109
|
+
}
|
|
65
110
|
async any(query, values) {
|
|
66
111
|
const result = await this.query(query, values);
|
|
67
112
|
return result.rows;
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -17,5 +17,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
__exportStar(require("./admin"), exports);
|
|
18
18
|
__exportStar(require("./connect"), exports);
|
|
19
19
|
__exportStar(require("./manager"), exports);
|
|
20
|
+
__exportStar(require("./roles"), exports);
|
|
20
21
|
__exportStar(require("./seed"), exports);
|
|
21
22
|
__exportStar(require("./test-client"), exports);
|
package/manager.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Pool } from 'pg';
|
|
2
2
|
import { PgConfig } from 'pg-env';
|
|
3
|
-
import { PgTestClient } from './test-client';
|
|
3
|
+
import { PgTestClient, PgTestClientOpts } from './test-client';
|
|
4
4
|
export declare class PgTestConnector {
|
|
5
5
|
private static instance;
|
|
6
6
|
private readonly clients;
|
|
@@ -17,7 +17,7 @@ export declare class PgTestConnector {
|
|
|
17
17
|
private registerConnect;
|
|
18
18
|
private awaitPendingConnects;
|
|
19
19
|
getPool(config: PgConfig): Pool;
|
|
20
|
-
getClient(config: PgConfig): PgTestClient;
|
|
20
|
+
getClient(config: PgConfig, opts?: Partial<PgTestClientOpts>): PgTestClient;
|
|
21
21
|
closeAll(): Promise<void>;
|
|
22
22
|
close(): void;
|
|
23
23
|
drop(config: PgConfig): void;
|
package/manager.js
CHANGED
|
@@ -71,11 +71,14 @@ class PgTestConnector {
|
|
|
71
71
|
}
|
|
72
72
|
return this.pgPools.get(key);
|
|
73
73
|
}
|
|
74
|
-
getClient(config) {
|
|
74
|
+
getClient(config, opts = {}) {
|
|
75
75
|
if (this.shuttingDown) {
|
|
76
76
|
throw new Error('PgTestConnector is shutting down; no new clients allowed');
|
|
77
77
|
}
|
|
78
|
-
const client = new test_client_1.PgTestClient(config, {
|
|
78
|
+
const client = new test_client_1.PgTestClient(config, {
|
|
79
|
+
trackConnect: (p) => this.registerConnect(p),
|
|
80
|
+
...opts
|
|
81
|
+
});
|
|
79
82
|
this.clients.add(client);
|
|
80
83
|
const key = this.dbKey(config);
|
|
81
84
|
this.seenDbConfigs.set(key, config);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pgsql-test",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.0",
|
|
4
4
|
"author": "Dan Lynch <pyramation@gmail.com>",
|
|
5
5
|
"description": "pgsql-test offers isolated, role-aware, and rollback-friendly PostgreSQL environments for integration tests — giving developers realistic test coverage without external state pollution",
|
|
6
6
|
"main": "index.js",
|
|
@@ -60,14 +60,14 @@
|
|
|
60
60
|
"@types/pg-copy-streams": "^1.2.5"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@launchql/core": "^2.
|
|
64
|
-
"@launchql/env": "^2.
|
|
65
|
-
"@launchql/server-utils": "^2.
|
|
66
|
-
"@launchql/types": "^2.
|
|
63
|
+
"@launchql/core": "^2.11.1",
|
|
64
|
+
"@launchql/env": "^2.4.1",
|
|
65
|
+
"@launchql/server-utils": "^2.4.1",
|
|
66
|
+
"@launchql/types": "^2.6.0",
|
|
67
67
|
"pg": "^8.16.0",
|
|
68
|
-
"pg-cache": "^1.
|
|
68
|
+
"pg-cache": "^1.3.1",
|
|
69
69
|
"pg-copy-streams": "^6.0.6",
|
|
70
70
|
"pg-env": "^1.1.0"
|
|
71
71
|
},
|
|
72
|
-
"gitHead": "
|
|
72
|
+
"gitHead": "6b7bfcdaef0133012fd4b0cbe2aea185d77de3ed"
|
|
73
73
|
}
|
package/roles.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { PgTestConnectionOptions, RoleMapping } from '@launchql/types';
|
|
2
|
+
/**
|
|
3
|
+
* Default role mapping configuration
|
|
4
|
+
*/
|
|
5
|
+
export declare const DEFAULT_ROLE_MAPPING: Required<RoleMapping>;
|
|
6
|
+
/**
|
|
7
|
+
* Get resolved role mapping with defaults
|
|
8
|
+
*/
|
|
9
|
+
export declare const getRoleMapping: (options?: PgTestConnectionOptions) => Required<RoleMapping>;
|
|
10
|
+
/**
|
|
11
|
+
* Get role name by key with fallback to default mapping
|
|
12
|
+
*/
|
|
13
|
+
export declare const getRoleName: (roleKey: keyof Omit<RoleMapping, "default">, options?: PgTestConnectionOptions) => string;
|
|
14
|
+
/**
|
|
15
|
+
* Get default role name
|
|
16
|
+
*/
|
|
17
|
+
export declare const getDefaultRole: (options?: PgTestConnectionOptions) => string;
|
package/roles.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getDefaultRole = exports.getRoleName = exports.getRoleMapping = exports.DEFAULT_ROLE_MAPPING = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Default role mapping configuration
|
|
6
|
+
*/
|
|
7
|
+
exports.DEFAULT_ROLE_MAPPING = {
|
|
8
|
+
anonymous: 'anonymous',
|
|
9
|
+
authenticated: 'authenticated',
|
|
10
|
+
administrator: 'administrator',
|
|
11
|
+
default: 'anonymous'
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Get resolved role mapping with defaults
|
|
15
|
+
*/
|
|
16
|
+
const getRoleMapping = (options) => {
|
|
17
|
+
return {
|
|
18
|
+
...exports.DEFAULT_ROLE_MAPPING,
|
|
19
|
+
...(options?.roles || {})
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
exports.getRoleMapping = getRoleMapping;
|
|
23
|
+
/**
|
|
24
|
+
* Get role name by key with fallback to default mapping
|
|
25
|
+
*/
|
|
26
|
+
const getRoleName = (roleKey, options) => {
|
|
27
|
+
const mapping = (0, exports.getRoleMapping)(options);
|
|
28
|
+
return mapping[roleKey];
|
|
29
|
+
};
|
|
30
|
+
exports.getRoleName = getRoleName;
|
|
31
|
+
/**
|
|
32
|
+
* Get default role name
|
|
33
|
+
*/
|
|
34
|
+
const getDefaultRole = (options) => {
|
|
35
|
+
const mapping = (0, exports.getRoleMapping)(options);
|
|
36
|
+
return mapping.default;
|
|
37
|
+
};
|
|
38
|
+
exports.getDefaultRole = getDefaultRole;
|
package/test-client.d.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { Client, QueryResult } from 'pg';
|
|
2
2
|
import { PgConfig } from 'pg-env';
|
|
3
|
-
|
|
3
|
+
import { AuthOptions, PgTestConnectionOptions } from '@launchql/types';
|
|
4
|
+
export type PgTestClientOpts = {
|
|
4
5
|
deferConnect?: boolean;
|
|
5
6
|
trackConnect?: (p: Promise<any>) => void;
|
|
6
|
-
}
|
|
7
|
+
} & Partial<PgTestConnectionOptions>;
|
|
7
8
|
export declare class PgTestClient {
|
|
8
9
|
config: PgConfig;
|
|
9
10
|
client: Client;
|
|
11
|
+
private opts;
|
|
10
12
|
private ctxStmts;
|
|
13
|
+
private contextSettings;
|
|
11
14
|
private _ended;
|
|
12
15
|
private connectPromise;
|
|
13
16
|
constructor(config: PgConfig, opts?: PgTestClientOpts);
|
|
@@ -20,6 +23,20 @@ export declare class PgTestClient {
|
|
|
20
23
|
beforeEach(): Promise<void>;
|
|
21
24
|
afterEach(): Promise<void>;
|
|
22
25
|
setContext(ctx: Record<string, string | null>): void;
|
|
26
|
+
/**
|
|
27
|
+
* Set authentication context for the current session.
|
|
28
|
+
* Configures role and user ID using cascading defaults from options → opts.auth → RoleMapping.
|
|
29
|
+
*/
|
|
30
|
+
auth(options?: AuthOptions): void;
|
|
31
|
+
/**
|
|
32
|
+
* Commit current transaction to make data visible to other connections, then start fresh transaction.
|
|
33
|
+
* Maintains test isolation by creating a savepoint and reapplying session context.
|
|
34
|
+
*/
|
|
35
|
+
publish(): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Clear all session context variables and reset to default anonymous role.
|
|
38
|
+
*/
|
|
39
|
+
clearContext(): void;
|
|
23
40
|
any<T = any>(query: string, values?: any[]): Promise<T[]>;
|
|
24
41
|
one<T = any>(query: string, values?: any[]): Promise<T>;
|
|
25
42
|
oneOrNone<T = any>(query: string, values?: any[]): Promise<T | null>;
|
|
@@ -30,4 +47,3 @@ export declare class PgTestClient {
|
|
|
30
47
|
query<T = any>(query: string, values?: any[]): Promise<QueryResult<T>>;
|
|
31
48
|
ctxQuery(): Promise<void>;
|
|
32
49
|
}
|
|
33
|
-
export {};
|
package/test-client.js
CHANGED
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.PgTestClient = void 0;
|
|
4
4
|
const pg_1 = require("pg");
|
|
5
|
+
const roles_1 = require("./roles");
|
|
5
6
|
class PgTestClient {
|
|
6
7
|
config;
|
|
7
8
|
client;
|
|
9
|
+
opts;
|
|
8
10
|
ctxStmts = '';
|
|
11
|
+
contextSettings = {};
|
|
9
12
|
_ended = false;
|
|
10
13
|
connectPromise = null;
|
|
11
14
|
constructor(config, opts = {}) {
|
|
15
|
+
this.opts = opts;
|
|
12
16
|
this.config = config;
|
|
13
17
|
this.client = new pg_1.Client({
|
|
14
18
|
host: this.config.host,
|
|
@@ -59,12 +63,53 @@ class PgTestClient {
|
|
|
59
63
|
await this.commit();
|
|
60
64
|
}
|
|
61
65
|
setContext(ctx) {
|
|
62
|
-
this.
|
|
66
|
+
Object.assign(this.contextSettings, ctx);
|
|
67
|
+
this.ctxStmts = Object.entries(this.contextSettings)
|
|
63
68
|
.map(([key, val]) => val === null
|
|
64
69
|
? `SELECT set_config('${key}', NULL, true);`
|
|
65
70
|
: `SELECT set_config('${key}', '${val}', true);`)
|
|
66
71
|
.join('\n');
|
|
67
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Set authentication context for the current session.
|
|
75
|
+
* Configures role and user ID using cascading defaults from options → opts.auth → RoleMapping.
|
|
76
|
+
*/
|
|
77
|
+
auth(options = {}) {
|
|
78
|
+
const role = options.role ?? this.opts.auth?.role ?? (0, roles_1.getRoleName)('authenticated', this.opts);
|
|
79
|
+
const userIdKey = options.userIdKey ?? this.opts.auth?.userIdKey ?? 'jwt.claims.user_id';
|
|
80
|
+
const userId = options.userId ?? this.opts.auth?.userId ?? null;
|
|
81
|
+
this.setContext({
|
|
82
|
+
role,
|
|
83
|
+
[userIdKey]: userId !== null ? String(userId) : null
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Commit current transaction to make data visible to other connections, then start fresh transaction.
|
|
88
|
+
* Maintains test isolation by creating a savepoint and reapplying session context.
|
|
89
|
+
*/
|
|
90
|
+
async publish() {
|
|
91
|
+
await this.commit(); // make data visible to other sessions
|
|
92
|
+
await this.begin(); // fresh tx
|
|
93
|
+
await this.savepoint(); // keep rollback harness
|
|
94
|
+
await this.ctxQuery(); // reapply all setContext()
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Clear all session context variables and reset to default anonymous role.
|
|
98
|
+
*/
|
|
99
|
+
clearContext() {
|
|
100
|
+
const defaultRole = (0, roles_1.getRoleName)('anonymous', this.opts);
|
|
101
|
+
const nulledSettings = {};
|
|
102
|
+
Object.keys(this.contextSettings).forEach(key => {
|
|
103
|
+
nulledSettings[key] = null;
|
|
104
|
+
});
|
|
105
|
+
nulledSettings.role = defaultRole;
|
|
106
|
+
this.ctxStmts = Object.entries(nulledSettings)
|
|
107
|
+
.map(([key, val]) => val === null
|
|
108
|
+
? `SELECT set_config('${key}', NULL, true);`
|
|
109
|
+
: `SELECT set_config('${key}', '${val}', true);`)
|
|
110
|
+
.join('\n');
|
|
111
|
+
this.contextSettings = { role: defaultRole };
|
|
112
|
+
}
|
|
68
113
|
async any(query, values) {
|
|
69
114
|
const result = await this.query(query, values);
|
|
70
115
|
return result.rows;
|