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 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 = `GRANT ${role} TO ${user};`;
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
- IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${user}') THEN
120
- CREATE ROLE ${user} LOGIN PASSWORD '${password}';
121
- END IF;
122
-
123
- -- Grant anonymous role if not already granted
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 = '${user}'
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
- GRANT ${anonRole} TO ${user};
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 = '${user}'
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
- GRANT ${authRole} TO ${user};
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 = '${user}'
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
- GRANT ${adminRole} TO ${user};
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
- manager.beginTeardown();
54
- await (0, pg_cache_1.teardownPgPools)();
55
- await manager.closeAll();
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
- await teardown();
68
- throw error;
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 = `GRANT ${role} TO ${user};`;
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
- IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${user}') THEN
117
- CREATE ROLE ${user} LOGIN PASSWORD '${password}';
118
- END IF;
119
-
120
- -- Grant anonymous role if not already granted
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 = '${user}'
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
- GRANT ${anonRole} TO ${user};
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 = '${user}'
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
- GRANT ${authRole} TO ${user};
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 = '${user}'
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
- GRANT ${adminRole} TO ${user};
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
- manager.beginTeardown();
50
- await teardownPgPools();
51
- await manager.closeAll();
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
- await teardown();
64
- throw error;
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",
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.3",
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": "28385f0196db4572fcad05fcde359d660c7affc4"
72
+ "gitHead": "8830d7a2b4d2ded6bc33a657449ab92e0c948348"
73
73
  }