pgsql-test 2.11.3 → 2.11.5
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.js +78 -25
- package/connect.js +13 -5
- package/esm/admin.js +78 -25
- package/esm/connect.js +13 -5
- package/package.json +3 -3
package/admin.js
CHANGED
|
@@ -97,7 +97,33 @@ class DbAdmin {
|
|
|
97
97
|
}
|
|
98
98
|
async grantRole(role, user, dbName) {
|
|
99
99
|
const db = dbName ?? this.config.database;
|
|
100
|
-
const sql = `
|
|
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
127
|
await this.streamSql(sql, db);
|
|
102
128
|
}
|
|
103
129
|
async grantConnect(role, dbName) {
|
|
@@ -114,40 +140,67 @@ class DbAdmin {
|
|
|
114
140
|
const adminRole = (0, roles_1.getRoleName)('administrator', this.roleConfig);
|
|
115
141
|
const sql = `
|
|
116
142
|
DO $$
|
|
143
|
+
DECLARE
|
|
144
|
+
v_user TEXT := '${user.replace(/'/g, "''")}';
|
|
145
|
+
v_password TEXT := '${password.replace(/'/g, "''")}';
|
|
117
146
|
BEGIN
|
|
118
147
|
-- Create role if it doesn't exist
|
|
119
|
-
|
|
120
|
-
CREATE ROLE
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
+
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.
|
|
124
158
|
IF NOT EXISTS (
|
|
125
|
-
SELECT 1 FROM pg_auth_members am
|
|
126
|
-
JOIN pg_roles r1 ON am.roleid = r1.oid
|
|
127
|
-
JOIN pg_roles r2 ON am.member = r2.oid
|
|
128
|
-
WHERE r1.rolname = '${anonRole}' AND r2.rolname =
|
|
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
|
|
129
163
|
) THEN
|
|
130
|
-
|
|
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;
|
|
131
172
|
END IF;
|
|
132
|
-
|
|
133
|
-
-- Grant authenticated role if not already granted
|
|
173
|
+
|
|
134
174
|
IF NOT EXISTS (
|
|
135
|
-
SELECT 1 FROM pg_auth_members am
|
|
136
|
-
JOIN pg_roles r1 ON am.roleid = r1.oid
|
|
137
|
-
JOIN pg_roles r2 ON am.member = r2.oid
|
|
138
|
-
WHERE r1.rolname = '${authRole}' AND r2.rolname =
|
|
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
|
|
139
179
|
) THEN
|
|
140
|
-
|
|
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;
|
|
141
188
|
END IF;
|
|
142
|
-
|
|
143
|
-
-- Grant administrator role if not already granted
|
|
189
|
+
|
|
144
190
|
IF NOT EXISTS (
|
|
145
|
-
SELECT 1 FROM pg_auth_members am
|
|
146
|
-
JOIN pg_roles r1 ON am.roleid = r1.oid
|
|
147
|
-
JOIN pg_roles r2 ON am.member = r2.oid
|
|
148
|
-
WHERE r1.rolname = '${adminRole}' AND r2.rolname =
|
|
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
|
|
149
195
|
) THEN
|
|
150
|
-
|
|
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;
|
|
151
204
|
END IF;
|
|
152
205
|
END $$;
|
|
153
206
|
`.trim();
|
package/connect.js
CHANGED
|
@@ -49,10 +49,16 @@ const getConnections = async (cn = {}, seedAdapters = [seed_1.seed.launchql()])
|
|
|
49
49
|
await admin.grantConnect(connOpts.connection.user, config.database);
|
|
50
50
|
manager = manager_1.PgTestConnector.getInstance();
|
|
51
51
|
const pg = manager.getClient(config);
|
|
52
|
+
let teardownPromise = null;
|
|
52
53
|
const teardown = async () => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
if (teardownPromise)
|
|
55
|
+
return teardownPromise;
|
|
56
|
+
teardownPromise = (async () => {
|
|
57
|
+
manager.beginTeardown();
|
|
58
|
+
await (0, pg_cache_1.teardownPgPools)();
|
|
59
|
+
await manager.closeAll();
|
|
60
|
+
})();
|
|
61
|
+
return teardownPromise;
|
|
56
62
|
};
|
|
57
63
|
if (seedAdapters.length) {
|
|
58
64
|
try {
|
|
@@ -64,8 +70,10 @@ const getConnections = async (cn = {}, seedAdapters = [seed_1.seed.launchql()])
|
|
|
64
70
|
});
|
|
65
71
|
}
|
|
66
72
|
catch (error) {
|
|
67
|
-
|
|
68
|
-
|
|
73
|
+
const err = error;
|
|
74
|
+
const msg = err && (err.stack || err.message) ? (err.stack || err.message) : String(err);
|
|
75
|
+
process.stderr.write(`[pgsql-test] Seed error (continuing): ${msg}\n`);
|
|
76
|
+
// continue without teardown to allow caller-managed lifecycle
|
|
69
77
|
}
|
|
70
78
|
}
|
|
71
79
|
const dbConfig = {
|
package/esm/admin.js
CHANGED
|
@@ -94,7 +94,33 @@ export class DbAdmin {
|
|
|
94
94
|
}
|
|
95
95
|
async grantRole(role, user, dbName) {
|
|
96
96
|
const db = dbName ?? this.config.database;
|
|
97
|
-
const sql = `
|
|
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
124
|
await this.streamSql(sql, db);
|
|
99
125
|
}
|
|
100
126
|
async grantConnect(role, dbName) {
|
|
@@ -111,40 +137,67 @@ export class DbAdmin {
|
|
|
111
137
|
const adminRole = getRoleName('administrator', this.roleConfig);
|
|
112
138
|
const sql = `
|
|
113
139
|
DO $$
|
|
140
|
+
DECLARE
|
|
141
|
+
v_user TEXT := '${user.replace(/'/g, "''")}';
|
|
142
|
+
v_password TEXT := '${password.replace(/'/g, "''")}';
|
|
114
143
|
BEGIN
|
|
115
144
|
-- Create role if it doesn't exist
|
|
116
|
-
|
|
117
|
-
CREATE ROLE
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
+
END;
|
|
152
|
+
|
|
153
|
+
-- CI/CD concurrency note: GRANT role membership can race on pg_auth_members unique index
|
|
154
|
+
-- We pre-check membership and still catch unique_violation to handle TOCTOU safely.
|
|
121
155
|
IF NOT EXISTS (
|
|
122
|
-
SELECT 1 FROM pg_auth_members am
|
|
123
|
-
JOIN pg_roles r1 ON am.roleid = r1.oid
|
|
124
|
-
JOIN pg_roles r2 ON am.member = r2.oid
|
|
125
|
-
WHERE r1.rolname = '${anonRole}' AND r2.rolname =
|
|
156
|
+
SELECT 1 FROM pg_auth_members am
|
|
157
|
+
JOIN pg_roles r1 ON am.roleid = r1.oid
|
|
158
|
+
JOIN pg_roles r2 ON am.member = r2.oid
|
|
159
|
+
WHERE r1.rolname = '${anonRole.replace(/'/g, "''")}' AND r2.rolname = v_user
|
|
126
160
|
) THEN
|
|
127
|
-
|
|
161
|
+
BEGIN
|
|
162
|
+
EXECUTE format('GRANT %I TO %I', '${anonRole.replace(/'/g, "''")}', v_user);
|
|
163
|
+
EXCEPTION
|
|
164
|
+
WHEN unique_violation THEN
|
|
165
|
+
NULL;
|
|
166
|
+
WHEN undefined_object THEN
|
|
167
|
+
RAISE NOTICE 'Missing role when granting % to %', '${anonRole.replace(/'/g, "''")}', v_user;
|
|
168
|
+
END;
|
|
128
169
|
END IF;
|
|
129
|
-
|
|
130
|
-
-- Grant authenticated role if not already granted
|
|
170
|
+
|
|
131
171
|
IF NOT EXISTS (
|
|
132
|
-
SELECT 1 FROM pg_auth_members am
|
|
133
|
-
JOIN pg_roles r1 ON am.roleid = r1.oid
|
|
134
|
-
JOIN pg_roles r2 ON am.member = r2.oid
|
|
135
|
-
WHERE r1.rolname = '${authRole}' AND r2.rolname =
|
|
172
|
+
SELECT 1 FROM pg_auth_members am
|
|
173
|
+
JOIN pg_roles r1 ON am.roleid = r1.oid
|
|
174
|
+
JOIN pg_roles r2 ON am.member = r2.oid
|
|
175
|
+
WHERE r1.rolname = '${authRole.replace(/'/g, "''")}' AND r2.rolname = v_user
|
|
136
176
|
) THEN
|
|
137
|
-
|
|
177
|
+
BEGIN
|
|
178
|
+
EXECUTE format('GRANT %I TO %I', '${authRole.replace(/'/g, "''")}', v_user);
|
|
179
|
+
EXCEPTION
|
|
180
|
+
WHEN unique_violation THEN
|
|
181
|
+
NULL;
|
|
182
|
+
WHEN undefined_object THEN
|
|
183
|
+
RAISE NOTICE 'Missing role when granting % to %', '${authRole.replace(/'/g, "''")}', v_user;
|
|
184
|
+
END;
|
|
138
185
|
END IF;
|
|
139
|
-
|
|
140
|
-
-- Grant administrator role if not already granted
|
|
186
|
+
|
|
141
187
|
IF NOT EXISTS (
|
|
142
|
-
SELECT 1 FROM pg_auth_members am
|
|
143
|
-
JOIN pg_roles r1 ON am.roleid = r1.oid
|
|
144
|
-
JOIN pg_roles r2 ON am.member = r2.oid
|
|
145
|
-
WHERE r1.rolname = '${adminRole}' AND r2.rolname =
|
|
188
|
+
SELECT 1 FROM pg_auth_members am
|
|
189
|
+
JOIN pg_roles r1 ON am.roleid = r1.oid
|
|
190
|
+
JOIN pg_roles r2 ON am.member = r2.oid
|
|
191
|
+
WHERE r1.rolname = '${adminRole.replace(/'/g, "''")}' AND r2.rolname = v_user
|
|
146
192
|
) THEN
|
|
147
|
-
|
|
193
|
+
BEGIN
|
|
194
|
+
EXECUTE format('GRANT %I TO %I', '${adminRole.replace(/'/g, "''")}', v_user);
|
|
195
|
+
EXCEPTION
|
|
196
|
+
WHEN unique_violation THEN
|
|
197
|
+
NULL;
|
|
198
|
+
WHEN undefined_object THEN
|
|
199
|
+
RAISE NOTICE 'Missing role when granting % to %', '${adminRole.replace(/'/g, "''")}', v_user;
|
|
200
|
+
END;
|
|
148
201
|
END IF;
|
|
149
202
|
END $$;
|
|
150
203
|
`.trim();
|
package/esm/connect.js
CHANGED
|
@@ -45,10 +45,16 @@ export const getConnections = async (cn = {}, seedAdapters = [seed.launchql()])
|
|
|
45
45
|
await admin.grantConnect(connOpts.connection.user, config.database);
|
|
46
46
|
manager = PgTestConnector.getInstance();
|
|
47
47
|
const pg = manager.getClient(config);
|
|
48
|
+
let teardownPromise = null;
|
|
48
49
|
const teardown = async () => {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
if (teardownPromise)
|
|
51
|
+
return teardownPromise;
|
|
52
|
+
teardownPromise = (async () => {
|
|
53
|
+
manager.beginTeardown();
|
|
54
|
+
await teardownPgPools();
|
|
55
|
+
await manager.closeAll();
|
|
56
|
+
})();
|
|
57
|
+
return teardownPromise;
|
|
52
58
|
};
|
|
53
59
|
if (seedAdapters.length) {
|
|
54
60
|
try {
|
|
@@ -60,8 +66,10 @@ export const getConnections = async (cn = {}, seedAdapters = [seed.launchql()])
|
|
|
60
66
|
});
|
|
61
67
|
}
|
|
62
68
|
catch (error) {
|
|
63
|
-
|
|
64
|
-
|
|
69
|
+
const err = error;
|
|
70
|
+
const msg = err && (err.stack || err.message) ? (err.stack || err.message) : String(err);
|
|
71
|
+
process.stderr.write(`[pgsql-test] Seed error (continuing): ${msg}\n`);
|
|
72
|
+
// continue without teardown to allow caller-managed lifecycle
|
|
65
73
|
}
|
|
66
74
|
}
|
|
67
75
|
const dbConfig = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pgsql-test",
|
|
3
|
-
"version": "2.11.
|
|
3
|
+
"version": "2.11.5",
|
|
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,7 +60,7 @@
|
|
|
60
60
|
"@types/pg-copy-streams": "^1.2.5"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@launchql/core": "^2.11.
|
|
63
|
+
"@launchql/core": "^2.11.5",
|
|
64
64
|
"@launchql/env": "^2.4.1",
|
|
65
65
|
"@launchql/server-utils": "^2.4.1",
|
|
66
66
|
"@launchql/types": "^2.6.0",
|
|
@@ -69,5 +69,5 @@
|
|
|
69
69
|
"pg-copy-streams": "^6.0.6",
|
|
70
70
|
"pg-env": "^1.1.0"
|
|
71
71
|
},
|
|
72
|
-
"gitHead": "
|
|
72
|
+
"gitHead": "8830d7a2b4d2ded6bc33a657449ab92e0c948348"
|
|
73
73
|
}
|