pgsql-test 2.17.5 → 2.18.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 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.connection.user, connOpts.connection.password, connOpts.rootDb);
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.connection.user, config.database);
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.connection.user,
86
- password: connOpts.connection.password
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.connection.user, connOpts.connection.password, connOpts.rootDb);
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.connection.user, config.database);
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.connection.user,
82
- password: connOpts.connection.password
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.17.5",
3
+ "version": "2.18.0",
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.0.9",
64
- "@pgpmjs/env": "^2.8.5",
63
+ "@pgpmjs/core": "^3.1.0",
64
+ "@pgpmjs/env": "^2.8.6",
65
65
  "@pgpmjs/logger": "^1.3.5",
66
- "@pgpmjs/server-utils": "^2.8.7",
67
- "@pgpmjs/types": "^2.12.5",
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.7",
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": "63d51202de99ec03c329e00e5cd1dff4bc60b367"
74
+ "gitHead": "ac6c6b866ea6a578baf61208b22fcb12fdd46e5d"
75
75
  }