pgsql-test 2.17.5 → 2.18.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/admin.d.ts +1 -1
- package/admin.js +4 -98
- package/connect.js +4 -4
- package/esm/admin.js +4 -98
- package/esm/connect.js +4 -4
- package/package.json +7 -7
package/admin.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export declare class DbAdmin {
|
|
|
19
19
|
cleanupTemplate(template: string): void;
|
|
20
20
|
grantRole(role: string, user: string, dbName?: string): Promise<void>;
|
|
21
21
|
grantConnect(role: string, dbName?: string): Promise<void>;
|
|
22
|
-
createUserRole(user: string, password: string, dbName: string): Promise<void>;
|
|
22
|
+
createUserRole(user: string, password: string, dbName: string, useLocksForRoles?: boolean): Promise<void>;
|
|
23
23
|
loadSql(file: string, dbName: string): void;
|
|
24
24
|
streamSql(sql: string, dbName: string): Promise<void>;
|
|
25
25
|
createSeededTemplate(templateName: string, adapter: SeedAdapter): Promise<void>;
|
package/admin.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DbAdmin = void 0;
|
|
4
|
+
const core_1 = require("@pgpmjs/core");
|
|
4
5
|
const logger_1 = require("@pgpmjs/logger");
|
|
5
6
|
const child_process_1 = require("child_process");
|
|
6
7
|
const fs_1 = require("fs");
|
|
@@ -97,33 +98,7 @@ class DbAdmin {
|
|
|
97
98
|
}
|
|
98
99
|
async grantRole(role, user, dbName) {
|
|
99
100
|
const db = dbName ?? this.config.database;
|
|
100
|
-
const sql =
|
|
101
|
-
DO $$
|
|
102
|
-
DECLARE
|
|
103
|
-
v_user TEXT := '${user.replace(/'/g, "''")}';
|
|
104
|
-
v_role TEXT := '${role.replace(/'/g, "''")}';
|
|
105
|
-
BEGIN
|
|
106
|
-
-- Pre-check to avoid unnecessary GRANTs; still catch TOCTOU under concurrency
|
|
107
|
-
IF NOT EXISTS (
|
|
108
|
-
SELECT 1 FROM pg_auth_members am
|
|
109
|
-
JOIN pg_roles r1 ON am.roleid = r1.oid
|
|
110
|
-
JOIN pg_roles r2 ON am.member = r2.oid
|
|
111
|
-
WHERE r1.rolname = v_role AND r2.rolname = v_user
|
|
112
|
-
) THEN
|
|
113
|
-
BEGIN
|
|
114
|
-
EXECUTE format('GRANT %I TO %I', v_role, v_user);
|
|
115
|
-
EXCEPTION
|
|
116
|
-
WHEN unique_violation THEN
|
|
117
|
-
-- Concurrent membership grant; safe to ignore
|
|
118
|
-
NULL;
|
|
119
|
-
WHEN undefined_object THEN
|
|
120
|
-
-- Role or user missing; emit notice and continue
|
|
121
|
-
RAISE NOTICE 'Missing role when granting % to %', v_role, v_user;
|
|
122
|
-
END;
|
|
123
|
-
END IF;
|
|
124
|
-
END
|
|
125
|
-
$$;
|
|
126
|
-
`;
|
|
101
|
+
const sql = (0, core_1.generateGrantRoleSQL)(role, user);
|
|
127
102
|
await this.streamSql(sql, db);
|
|
128
103
|
}
|
|
129
104
|
async grantConnect(role, dbName) {
|
|
@@ -131,82 +106,13 @@ $$;
|
|
|
131
106
|
const sql = `GRANT CONNECT ON DATABASE "${db}" TO ${role};`;
|
|
132
107
|
await this.streamSql(sql, db);
|
|
133
108
|
}
|
|
134
|
-
// TODO: make adminRole a configurable option
|
|
135
109
|
// ONLY granting admin role for testing purposes, normally the db connection for apps won't have admin role
|
|
136
110
|
// DO NOT USE THIS FOR PRODUCTION
|
|
137
|
-
async createUserRole(user, password, dbName) {
|
|
111
|
+
async createUserRole(user, password, dbName, useLocksForRoles = false) {
|
|
138
112
|
const anonRole = (0, roles_1.getRoleName)('anonymous', this.roleConfig);
|
|
139
113
|
const authRole = (0, roles_1.getRoleName)('authenticated', this.roleConfig);
|
|
140
114
|
const adminRole = (0, roles_1.getRoleName)('administrator', this.roleConfig);
|
|
141
|
-
const sql =
|
|
142
|
-
DO $$
|
|
143
|
-
DECLARE
|
|
144
|
-
v_user TEXT := '${user.replace(/'/g, "''")}';
|
|
145
|
-
v_password TEXT := '${password.replace(/'/g, "''")}';
|
|
146
|
-
BEGIN
|
|
147
|
-
-- Create role if it doesn't exist
|
|
148
|
-
BEGIN
|
|
149
|
-
EXECUTE format('CREATE ROLE %I LOGIN PASSWORD %L', v_user, v_password);
|
|
150
|
-
EXCEPTION
|
|
151
|
-
WHEN duplicate_object THEN
|
|
152
|
-
-- Role already exists; optionally sync attributes here with ALTER ROLE
|
|
153
|
-
NULL;
|
|
154
|
-
WHEN unique_violation THEN
|
|
155
|
-
-- Concurrent CREATE ROLE hit unique index; safe to ignore
|
|
156
|
-
NULL;
|
|
157
|
-
END;
|
|
158
|
-
|
|
159
|
-
-- CI/CD concurrency note: GRANT role membership can race on pg_auth_members unique index
|
|
160
|
-
-- We pre-check membership and still catch unique_violation to handle TOCTOU safely.
|
|
161
|
-
IF NOT EXISTS (
|
|
162
|
-
SELECT 1 FROM pg_auth_members am
|
|
163
|
-
JOIN pg_roles r1 ON am.roleid = r1.oid
|
|
164
|
-
JOIN pg_roles r2 ON am.member = r2.oid
|
|
165
|
-
WHERE r1.rolname = '${anonRole.replace(/'/g, "''")}' AND r2.rolname = v_user
|
|
166
|
-
) THEN
|
|
167
|
-
BEGIN
|
|
168
|
-
EXECUTE format('GRANT %I TO %I', '${anonRole.replace(/'/g, "''")}', v_user);
|
|
169
|
-
EXCEPTION
|
|
170
|
-
WHEN unique_violation THEN
|
|
171
|
-
NULL;
|
|
172
|
-
WHEN undefined_object THEN
|
|
173
|
-
RAISE NOTICE 'Missing role when granting % to %', '${anonRole.replace(/'/g, "''")}', v_user;
|
|
174
|
-
END;
|
|
175
|
-
END IF;
|
|
176
|
-
|
|
177
|
-
IF NOT EXISTS (
|
|
178
|
-
SELECT 1 FROM pg_auth_members am
|
|
179
|
-
JOIN pg_roles r1 ON am.roleid = r1.oid
|
|
180
|
-
JOIN pg_roles r2 ON am.member = r2.oid
|
|
181
|
-
WHERE r1.rolname = '${authRole.replace(/'/g, "''")}' AND r2.rolname = v_user
|
|
182
|
-
) THEN
|
|
183
|
-
BEGIN
|
|
184
|
-
EXECUTE format('GRANT %I TO %I', '${authRole.replace(/'/g, "''")}', v_user);
|
|
185
|
-
EXCEPTION
|
|
186
|
-
WHEN unique_violation THEN
|
|
187
|
-
NULL;
|
|
188
|
-
WHEN undefined_object THEN
|
|
189
|
-
RAISE NOTICE 'Missing role when granting % to %', '${authRole.replace(/'/g, "''")}', v_user;
|
|
190
|
-
END;
|
|
191
|
-
END IF;
|
|
192
|
-
|
|
193
|
-
IF NOT EXISTS (
|
|
194
|
-
SELECT 1 FROM pg_auth_members am
|
|
195
|
-
JOIN pg_roles r1 ON am.roleid = r1.oid
|
|
196
|
-
JOIN pg_roles r2 ON am.member = r2.oid
|
|
197
|
-
WHERE r1.rolname = '${adminRole.replace(/'/g, "''")}' AND r2.rolname = v_user
|
|
198
|
-
) THEN
|
|
199
|
-
BEGIN
|
|
200
|
-
EXECUTE format('GRANT %I TO %I', '${adminRole.replace(/'/g, "''")}', v_user);
|
|
201
|
-
EXCEPTION
|
|
202
|
-
WHEN unique_violation THEN
|
|
203
|
-
NULL;
|
|
204
|
-
WHEN undefined_object THEN
|
|
205
|
-
RAISE NOTICE 'Missing role when granting % to %', '${adminRole.replace(/'/g, "''")}', v_user;
|
|
206
|
-
END;
|
|
207
|
-
END IF;
|
|
208
|
-
END $$;
|
|
209
|
-
`.trim();
|
|
115
|
+
const sql = (0, core_1.generateCreateUserWithGrantsSQL)(user, password, [anonRole, authRole, adminRole], useLocksForRoles);
|
|
210
116
|
await this.streamSql(sql, dbName);
|
|
211
117
|
}
|
|
212
118
|
loadSql(file, dbName) {
|
package/connect.js
CHANGED
|
@@ -38,7 +38,7 @@ const getConnections = async (cn = {}, seedAdapters = [seed_1.seed.pgpm()]) => {
|
|
|
38
38
|
const config = cn.pg;
|
|
39
39
|
const connOpts = cn.db;
|
|
40
40
|
const root = (0, exports.getPgRootAdmin)(config, connOpts);
|
|
41
|
-
await root.createUserRole(connOpts.
|
|
41
|
+
await root.createUserRole(connOpts.connections.app.user, connOpts.connections.app.password, connOpts.rootDb);
|
|
42
42
|
const admin = new admin_1.DbAdmin(config, false, connOpts);
|
|
43
43
|
if (process.env.TEST_DB) {
|
|
44
44
|
config.database = process.env.TEST_DB;
|
|
@@ -50,7 +50,7 @@ const getConnections = async (cn = {}, seedAdapters = [seed_1.seed.pgpm()]) => {
|
|
|
50
50
|
admin.create(config.database);
|
|
51
51
|
admin.installExtensions(connOpts.extensions);
|
|
52
52
|
}
|
|
53
|
-
await admin.grantConnect(connOpts.
|
|
53
|
+
await admin.grantConnect(connOpts.connections.app.user, config.database);
|
|
54
54
|
manager = manager_1.PgTestConnector.getInstance(config);
|
|
55
55
|
const pg = manager.getClient(config);
|
|
56
56
|
let teardownPromise = null;
|
|
@@ -82,8 +82,8 @@ const getConnections = async (cn = {}, seedAdapters = [seed_1.seed.pgpm()]) => {
|
|
|
82
82
|
}
|
|
83
83
|
const dbConfig = {
|
|
84
84
|
...config,
|
|
85
|
-
user: connOpts.
|
|
86
|
-
password: connOpts.
|
|
85
|
+
user: connOpts.connections.app.user,
|
|
86
|
+
password: connOpts.connections.app.password
|
|
87
87
|
};
|
|
88
88
|
const db = manager.getClient(dbConfig, {
|
|
89
89
|
auth: connOpts.auth,
|
package/esm/admin.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { generateCreateUserWithGrantsSQL, generateGrantRoleSQL } from '@pgpmjs/core';
|
|
1
2
|
import { Logger } from '@pgpmjs/logger';
|
|
2
3
|
import { execSync } from 'child_process';
|
|
3
4
|
import { existsSync } from 'fs';
|
|
@@ -94,33 +95,7 @@ export class DbAdmin {
|
|
|
94
95
|
}
|
|
95
96
|
async grantRole(role, user, dbName) {
|
|
96
97
|
const db = dbName ?? this.config.database;
|
|
97
|
-
const sql =
|
|
98
|
-
DO $$
|
|
99
|
-
DECLARE
|
|
100
|
-
v_user TEXT := '${user.replace(/'/g, "''")}';
|
|
101
|
-
v_role TEXT := '${role.replace(/'/g, "''")}';
|
|
102
|
-
BEGIN
|
|
103
|
-
-- Pre-check to avoid unnecessary GRANTs; still catch TOCTOU under concurrency
|
|
104
|
-
IF NOT EXISTS (
|
|
105
|
-
SELECT 1 FROM pg_auth_members am
|
|
106
|
-
JOIN pg_roles r1 ON am.roleid = r1.oid
|
|
107
|
-
JOIN pg_roles r2 ON am.member = r2.oid
|
|
108
|
-
WHERE r1.rolname = v_role AND r2.rolname = v_user
|
|
109
|
-
) THEN
|
|
110
|
-
BEGIN
|
|
111
|
-
EXECUTE format('GRANT %I TO %I', v_role, v_user);
|
|
112
|
-
EXCEPTION
|
|
113
|
-
WHEN unique_violation THEN
|
|
114
|
-
-- Concurrent membership grant; safe to ignore
|
|
115
|
-
NULL;
|
|
116
|
-
WHEN undefined_object THEN
|
|
117
|
-
-- Role or user missing; emit notice and continue
|
|
118
|
-
RAISE NOTICE 'Missing role when granting % to %', v_role, v_user;
|
|
119
|
-
END;
|
|
120
|
-
END IF;
|
|
121
|
-
END
|
|
122
|
-
$$;
|
|
123
|
-
`;
|
|
98
|
+
const sql = generateGrantRoleSQL(role, user);
|
|
124
99
|
await this.streamSql(sql, db);
|
|
125
100
|
}
|
|
126
101
|
async grantConnect(role, dbName) {
|
|
@@ -128,82 +103,13 @@ $$;
|
|
|
128
103
|
const sql = `GRANT CONNECT ON DATABASE "${db}" TO ${role};`;
|
|
129
104
|
await this.streamSql(sql, db);
|
|
130
105
|
}
|
|
131
|
-
// TODO: make adminRole a configurable option
|
|
132
106
|
// ONLY granting admin role for testing purposes, normally the db connection for apps won't have admin role
|
|
133
107
|
// DO NOT USE THIS FOR PRODUCTION
|
|
134
|
-
async createUserRole(user, password, dbName) {
|
|
108
|
+
async createUserRole(user, password, dbName, useLocksForRoles = false) {
|
|
135
109
|
const anonRole = getRoleName('anonymous', this.roleConfig);
|
|
136
110
|
const authRole = getRoleName('authenticated', this.roleConfig);
|
|
137
111
|
const adminRole = getRoleName('administrator', this.roleConfig);
|
|
138
|
-
const sql =
|
|
139
|
-
DO $$
|
|
140
|
-
DECLARE
|
|
141
|
-
v_user TEXT := '${user.replace(/'/g, "''")}';
|
|
142
|
-
v_password TEXT := '${password.replace(/'/g, "''")}';
|
|
143
|
-
BEGIN
|
|
144
|
-
-- Create role if it doesn't exist
|
|
145
|
-
BEGIN
|
|
146
|
-
EXECUTE format('CREATE ROLE %I LOGIN PASSWORD %L', v_user, v_password);
|
|
147
|
-
EXCEPTION
|
|
148
|
-
WHEN duplicate_object THEN
|
|
149
|
-
-- Role already exists; optionally sync attributes here with ALTER ROLE
|
|
150
|
-
NULL;
|
|
151
|
-
WHEN unique_violation THEN
|
|
152
|
-
-- Concurrent CREATE ROLE hit unique index; safe to ignore
|
|
153
|
-
NULL;
|
|
154
|
-
END;
|
|
155
|
-
|
|
156
|
-
-- CI/CD concurrency note: GRANT role membership can race on pg_auth_members unique index
|
|
157
|
-
-- We pre-check membership and still catch unique_violation to handle TOCTOU safely.
|
|
158
|
-
IF NOT EXISTS (
|
|
159
|
-
SELECT 1 FROM pg_auth_members am
|
|
160
|
-
JOIN pg_roles r1 ON am.roleid = r1.oid
|
|
161
|
-
JOIN pg_roles r2 ON am.member = r2.oid
|
|
162
|
-
WHERE r1.rolname = '${anonRole.replace(/'/g, "''")}' AND r2.rolname = v_user
|
|
163
|
-
) THEN
|
|
164
|
-
BEGIN
|
|
165
|
-
EXECUTE format('GRANT %I TO %I', '${anonRole.replace(/'/g, "''")}', v_user);
|
|
166
|
-
EXCEPTION
|
|
167
|
-
WHEN unique_violation THEN
|
|
168
|
-
NULL;
|
|
169
|
-
WHEN undefined_object THEN
|
|
170
|
-
RAISE NOTICE 'Missing role when granting % to %', '${anonRole.replace(/'/g, "''")}', v_user;
|
|
171
|
-
END;
|
|
172
|
-
END IF;
|
|
173
|
-
|
|
174
|
-
IF NOT EXISTS (
|
|
175
|
-
SELECT 1 FROM pg_auth_members am
|
|
176
|
-
JOIN pg_roles r1 ON am.roleid = r1.oid
|
|
177
|
-
JOIN pg_roles r2 ON am.member = r2.oid
|
|
178
|
-
WHERE r1.rolname = '${authRole.replace(/'/g, "''")}' AND r2.rolname = v_user
|
|
179
|
-
) THEN
|
|
180
|
-
BEGIN
|
|
181
|
-
EXECUTE format('GRANT %I TO %I', '${authRole.replace(/'/g, "''")}', v_user);
|
|
182
|
-
EXCEPTION
|
|
183
|
-
WHEN unique_violation THEN
|
|
184
|
-
NULL;
|
|
185
|
-
WHEN undefined_object THEN
|
|
186
|
-
RAISE NOTICE 'Missing role when granting % to %', '${authRole.replace(/'/g, "''")}', v_user;
|
|
187
|
-
END;
|
|
188
|
-
END IF;
|
|
189
|
-
|
|
190
|
-
IF NOT EXISTS (
|
|
191
|
-
SELECT 1 FROM pg_auth_members am
|
|
192
|
-
JOIN pg_roles r1 ON am.roleid = r1.oid
|
|
193
|
-
JOIN pg_roles r2 ON am.member = r2.oid
|
|
194
|
-
WHERE r1.rolname = '${adminRole.replace(/'/g, "''")}' AND r2.rolname = v_user
|
|
195
|
-
) THEN
|
|
196
|
-
BEGIN
|
|
197
|
-
EXECUTE format('GRANT %I TO %I', '${adminRole.replace(/'/g, "''")}', v_user);
|
|
198
|
-
EXCEPTION
|
|
199
|
-
WHEN unique_violation THEN
|
|
200
|
-
NULL;
|
|
201
|
-
WHEN undefined_object THEN
|
|
202
|
-
RAISE NOTICE 'Missing role when granting % to %', '${adminRole.replace(/'/g, "''")}', v_user;
|
|
203
|
-
END;
|
|
204
|
-
END IF;
|
|
205
|
-
END $$;
|
|
206
|
-
`.trim();
|
|
112
|
+
const sql = generateCreateUserWithGrantsSQL(user, password, [anonRole, authRole, adminRole], useLocksForRoles);
|
|
207
113
|
await this.streamSql(sql, dbName);
|
|
208
114
|
}
|
|
209
115
|
loadSql(file, dbName) {
|
package/esm/connect.js
CHANGED
|
@@ -34,7 +34,7 @@ export const getConnections = async (cn = {}, seedAdapters = [seed.pgpm()]) => {
|
|
|
34
34
|
const config = cn.pg;
|
|
35
35
|
const connOpts = cn.db;
|
|
36
36
|
const root = getPgRootAdmin(config, connOpts);
|
|
37
|
-
await root.createUserRole(connOpts.
|
|
37
|
+
await root.createUserRole(connOpts.connections.app.user, connOpts.connections.app.password, connOpts.rootDb);
|
|
38
38
|
const admin = new DbAdmin(config, false, connOpts);
|
|
39
39
|
if (process.env.TEST_DB) {
|
|
40
40
|
config.database = process.env.TEST_DB;
|
|
@@ -46,7 +46,7 @@ export const getConnections = async (cn = {}, seedAdapters = [seed.pgpm()]) => {
|
|
|
46
46
|
admin.create(config.database);
|
|
47
47
|
admin.installExtensions(connOpts.extensions);
|
|
48
48
|
}
|
|
49
|
-
await admin.grantConnect(connOpts.
|
|
49
|
+
await admin.grantConnect(connOpts.connections.app.user, config.database);
|
|
50
50
|
manager = PgTestConnector.getInstance(config);
|
|
51
51
|
const pg = manager.getClient(config);
|
|
52
52
|
let teardownPromise = null;
|
|
@@ -78,8 +78,8 @@ export const getConnections = async (cn = {}, seedAdapters = [seed.pgpm()]) => {
|
|
|
78
78
|
}
|
|
79
79
|
const dbConfig = {
|
|
80
80
|
...config,
|
|
81
|
-
user: connOpts.
|
|
82
|
-
password: connOpts.
|
|
81
|
+
user: connOpts.connections.app.user,
|
|
82
|
+
password: connOpts.connections.app.password
|
|
83
83
|
};
|
|
84
84
|
const db = manager.getClient(dbConfig, {
|
|
85
85
|
auth: connOpts.auth,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pgsql-test",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.18.1",
|
|
4
4
|
"author": "Constructive <developers@constructive.io>",
|
|
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,16 +60,16 @@
|
|
|
60
60
|
"makage": "^0.1.9"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@pgpmjs/core": "^3.
|
|
64
|
-
"@pgpmjs/env": "^2.8.
|
|
63
|
+
"@pgpmjs/core": "^3.1.1",
|
|
64
|
+
"@pgpmjs/env": "^2.8.7",
|
|
65
65
|
"@pgpmjs/logger": "^1.3.5",
|
|
66
|
-
"@pgpmjs/server-utils": "^2.8.
|
|
67
|
-
"@pgpmjs/types": "^2.12.
|
|
66
|
+
"@pgpmjs/server-utils": "^2.8.8",
|
|
67
|
+
"@pgpmjs/types": "^2.12.6",
|
|
68
68
|
"csv-parse": "^6.1.0",
|
|
69
69
|
"pg": "^8.16.3",
|
|
70
|
-
"pg-cache": "^1.6.
|
|
70
|
+
"pg-cache": "^1.6.8",
|
|
71
71
|
"pg-copy-streams": "^7.0.0",
|
|
72
72
|
"pg-env": "^1.2.4"
|
|
73
73
|
},
|
|
74
|
-
"gitHead": "
|
|
74
|
+
"gitHead": "248d4056d3191b71bcab5892ef98b09164de9f7f"
|
|
75
75
|
}
|