@pgpm/verify 0.15.2 → 0.15.4

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/Makefile CHANGED
@@ -1,5 +1,5 @@
1
1
  EXTENSION = pgpm-verify
2
- DATA = sql/pgpm-verify--0.14.0.sql
2
+ DATA = sql/pgpm-verify--0.15.3.sql
3
3
 
4
4
  PG_CONFIG = pg_config
5
5
  PGXS := $(shell $(PG_CONFIG) --pgxs)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pgpm/verify",
3
- "version": "0.15.2",
3
+ "version": "0.15.4",
4
4
  "description": "Verification utilities for PGPM deploy/verify/revert workflow",
5
5
  "author": "Dan Lynch <pyramation@gmail.com>",
6
6
  "contributors": [
@@ -21,7 +21,7 @@
21
21
  "test:watch": "jest --watch"
22
22
  },
23
23
  "devDependencies": {
24
- "pgpm": "^1.0.0"
24
+ "pgpm": "^1.2.2"
25
25
  },
26
26
  "repository": {
27
27
  "type": "git",
@@ -31,5 +31,5 @@
31
31
  "bugs": {
32
32
  "url": "https://github.com/constructive-io/pgpm-modules/issues"
33
33
  },
34
- "gitHead": "92a241bab64c7b20e85e55a7bd314089907fabba"
34
+ "gitHead": "aad0dbef0336d6c18d027120ef9addc418822edd"
35
35
  }
@@ -1,6 +1,6 @@
1
1
  # pgpm-verify extension
2
2
  comment = 'pgpm-verify extension'
3
- default_version = '0.14.0'
3
+ default_version = '0.15.3'
4
4
  module_pathname = '$libdir/pgpm-verify'
5
5
  requires = 'plpgsql'
6
6
  relocatable = false
@@ -129,12 +129,26 @@ $EOFCODE$ LANGUAGE plpgsql IMMUTABLE;
129
129
  CREATE FUNCTION verify_function(_name text, _user text DEFAULT NULL) RETURNS boolean AS $EOFCODE$
130
130
  DECLARE
131
131
  check_user text;
132
+ func_oid oid;
132
133
  BEGIN
133
134
  IF (_user IS NOT NULL) THEN
134
135
  check_user = _user;
135
136
  ELSE
136
137
  check_user = CURRENT_USER;
137
138
  END IF;
139
+ IF position('(' IN _name) > 0 THEN
140
+ func_oid = to_regprocedure(_name);
141
+ IF func_oid IS NULL THEN
142
+ RAISE EXCEPTION 'Nonexistent function --> %', _name
143
+ USING HINT = 'Please check';
144
+ END IF;
145
+ IF has_function_privilege(check_user, func_oid, 'execute') THEN
146
+ RETURN TRUE;
147
+ ELSE
148
+ RAISE EXCEPTION 'Nonexistent function --> %', _name
149
+ USING HINT = 'Please check';
150
+ END IF;
151
+ END IF;
138
152
  IF EXISTS (
139
153
  SELECT
140
154
  has_function_privilege(check_user, p.oid, 'execute')
@@ -1,489 +0,0 @@
1
- import { getConnections, PgTestClient, snapshot } from 'pgsql-test';
2
-
3
- let pg: PgTestClient;
4
- let teardown: () => Promise<void>;
5
-
6
- describe('ext-verify utilities', () => {
7
- beforeAll(async () => {
8
- ({ pg, teardown } = await getConnections());
9
- });
10
-
11
- afterAll(async () => {
12
- await teardown();
13
- });
14
-
15
- beforeEach(async () => {
16
- await pg.beforeEach();
17
- });
18
-
19
- afterEach(async () => {
20
- await pg.afterEach();
21
- });
22
-
23
- describe('helper functions', () => {
24
- it('get_entity_from_str should extract entity name', async () => {
25
- const tests = [
26
- ['public.users', 'users'],
27
- ['users', 'users'],
28
- ['schema.table_name', 'table_name'],
29
- ['my_function', 'my_function']
30
- ];
31
-
32
- for (const [input, expected] of tests) {
33
- const [result] = await pg.any(
34
- `SELECT get_entity_from_str($1) as entity`,
35
- [input]
36
- );
37
- expect(result.entity).toBe(expected);
38
- }
39
- });
40
-
41
- it('get_schema_from_str should extract schema name', async () => {
42
- const tests = [
43
- ['public.users', 'public'],
44
- ['users', 'public'],
45
- ['my_schema.table_name', 'my_schema'],
46
- ['function_name', 'public']
47
- ];
48
-
49
- for (const [input, expected] of tests) {
50
- const [result] = await pg.any(
51
- `SELECT get_schema_from_str($1) as schema_name`,
52
- [input]
53
- );
54
- expect(result.schema_name).toBe(expected);
55
- }
56
- });
57
- });
58
-
59
- describe('schema verification', () => {
60
- it('verify_schema should return true for existing schema', async () => {
61
- const [result] = await pg.any(
62
- `SELECT verify_schema('public') as verified`
63
- );
64
- expect(result.verified).toBe(true);
65
- });
66
-
67
- it('verify_schema should throw for non-existent schema', async () => {
68
- await expect(
69
- pg.any(`SELECT verify_schema('nonexistent_schema')`)
70
- ).rejects.toThrow('Nonexistent schema');
71
- });
72
- });
73
-
74
- describe('table verification', () => {
75
- beforeEach(async () => {
76
- // Create a test table
77
- await pg.any(`
78
- CREATE TABLE test_table (
79
- id serial PRIMARY KEY,
80
- name text NOT NULL,
81
- email text UNIQUE
82
- )
83
- `);
84
- });
85
-
86
- it('verify_table should return true for existing table', async () => {
87
- const [result] = await pg.any(
88
- `SELECT verify_table('test_table') as verified`
89
- );
90
- expect(result.verified).toBe(true);
91
- });
92
-
93
- it('verify_table should work with schema-qualified names', async () => {
94
- const [result] = await pg.any(
95
- `SELECT verify_table('public.test_table') as verified`
96
- );
97
- expect(result.verified).toBe(true);
98
- });
99
-
100
- it('verify_table should throw for non-existent table', async () => {
101
- await expect(
102
- pg.any(`SELECT verify_table('nonexistent_table')`)
103
- ).rejects.toThrow('Nonexistent table');
104
- });
105
- });
106
-
107
- describe('constraint verification', () => {
108
- beforeEach(async () => {
109
- await pg.any(`
110
- CREATE TABLE test_constraints (
111
- id serial PRIMARY KEY,
112
- name text NOT NULL,
113
- email text UNIQUE,
114
- age integer CHECK (age > 0)
115
- )
116
- `);
117
- });
118
-
119
- it('verify_constraint should return true for existing constraint', async () => {
120
- // Get the actual constraint name (PostgreSQL generates names)
121
- const [constraint] = await pg.any(`
122
- SELECT conname
123
- FROM pg_constraint c
124
- JOIN pg_class t ON c.conrelid = t.oid
125
- WHERE t.relname = 'test_constraints' AND contype = 'u'
126
- LIMIT 1
127
- `);
128
-
129
- if (constraint) {
130
- const [result] = await pg.any(
131
- `SELECT verify_constraint('test_constraints', $1) as verified`,
132
- [constraint.conname]
133
- );
134
- expect(result.verified).toBe(true);
135
- }
136
- });
137
-
138
- it('verify_constraint should throw for non-existent constraint', async () => {
139
- await expect(
140
- pg.any(`SELECT verify_constraint('test_constraints', 'nonexistent_constraint')`)
141
- ).rejects.toThrow('Nonexistent constraint');
142
- });
143
- });
144
-
145
- describe('function verification', () => {
146
- beforeEach(async () => {
147
- await pg.any(`
148
- CREATE OR REPLACE FUNCTION test_function(x integer)
149
- RETURNS integer AS $$
150
- BEGIN
151
- RETURN x * 2;
152
- END;
153
- $$ LANGUAGE plpgsql;
154
- `);
155
- });
156
-
157
- it('verify_function should return true for existing function', async () => {
158
- const [result] = await pg.any(
159
- `SELECT verify_function('test_function') as verified`
160
- );
161
- expect(result.verified).toBe(true);
162
- });
163
-
164
- it('verify_function should work with schema-qualified names', async () => {
165
- const [result] = await pg.any(
166
- `SELECT verify_function('public.test_function') as verified`
167
- );
168
- expect(result.verified).toBe(true);
169
- });
170
-
171
- it('verify_function should throw for non-existent function', async () => {
172
- await expect(
173
- pg.any(`SELECT verify_function('nonexistent_function')`)
174
- ).rejects.toThrow('Nonexistent function');
175
- });
176
- });
177
-
178
- describe('view verification', () => {
179
- beforeEach(async () => {
180
- await pg.any(`
181
- CREATE TABLE test_view_base (
182
- id serial PRIMARY KEY,
183
- name text
184
- )
185
- `);
186
-
187
- await pg.any(`
188
- CREATE VIEW test_view AS
189
- SELECT id, name FROM test_view_base WHERE name IS NOT NULL
190
- `);
191
- });
192
-
193
- it('verify_view should return true for existing view', async () => {
194
- const [result] = await pg.any(
195
- `SELECT verify_view('test_view') as verified`
196
- );
197
- expect(result.verified).toBe(true);
198
- });
199
-
200
- it('verify_view should work with schema-qualified names', async () => {
201
- const [result] = await pg.any(
202
- `SELECT verify_view('public.test_view') as verified`
203
- );
204
- expect(result.verified).toBe(true);
205
- });
206
-
207
- it('verify_view should throw for non-existent view', async () => {
208
- await expect(
209
- pg.any(`SELECT verify_view('nonexistent_view')`)
210
- ).rejects.toThrow('Nonexistent view');
211
- });
212
- });
213
-
214
- describe('index verification', () => {
215
- beforeEach(async () => {
216
- await pg.any(`
217
- CREATE TABLE test_index_table (
218
- id serial PRIMARY KEY,
219
- name text,
220
- email text
221
- )
222
- `);
223
-
224
- await pg.any(`
225
- CREATE INDEX test_custom_index ON test_index_table (name)
226
- `);
227
- });
228
-
229
- it('verify_index should return true for existing index', async () => {
230
- const [result] = await pg.any(
231
- `SELECT verify_index('test_index_table', 'test_custom_index') as verified`
232
- );
233
- expect(result.verified).toBe(true);
234
- });
235
-
236
- it('verify_index should throw for non-existent index', async () => {
237
- await expect(
238
- pg.any(`SELECT verify_index('test_index_table', 'nonexistent_index')`)
239
- ).rejects.toThrow('Nonexistent index');
240
- });
241
-
242
- it('list_indexes should return index information', async () => {
243
- const results = await pg.any(
244
- `SELECT * FROM list_indexes('test_index_table', 'test_custom_index')`
245
- );
246
-
247
- expect(results).toHaveLength(1);
248
- expect(results[0].schema_name).toBe('public');
249
- expect(results[0].table_name).toBe('test_index_table');
250
- expect(results[0].index_name).toBe('test_custom_index');
251
- });
252
- });
253
-
254
- describe('trigger verification', () => {
255
- beforeEach(async () => {
256
- await pg.any(`
257
- CREATE TABLE test_trigger_table (
258
- id serial PRIMARY KEY,
259
- name text,
260
- updated_at timestamp DEFAULT now()
261
- )
262
- `);
263
-
264
- await pg.any(`
265
- CREATE OR REPLACE FUNCTION update_timestamp()
266
- RETURNS trigger AS $$
267
- BEGIN
268
- NEW.updated_at = now();
269
- RETURN NEW;
270
- END;
271
- $$ LANGUAGE plpgsql;
272
- `);
273
-
274
- await pg.any(`
275
- CREATE TRIGGER test_update_trigger
276
- BEFORE UPDATE ON test_trigger_table
277
- FOR EACH ROW EXECUTE FUNCTION update_timestamp()
278
- `);
279
- });
280
-
281
- it('verify_trigger should return true for existing trigger', async () => {
282
- const [result] = await pg.any(
283
- `SELECT verify_trigger('test_update_trigger') as verified`
284
- );
285
- expect(result.verified).toBe(true);
286
- });
287
-
288
- it('verify_trigger should throw for non-existent trigger', async () => {
289
- await expect(
290
- pg.any(`SELECT verify_trigger('nonexistent_trigger')`)
291
- ).rejects.toThrow('Nonexistent trigger');
292
- });
293
- });
294
-
295
- describe('type verification', () => {
296
- beforeEach(async () => {
297
- await pg.any(`
298
- CREATE TYPE test_enum AS ENUM ('active', 'inactive', 'pending')
299
- `);
300
-
301
- await pg.any(`
302
- CREATE TYPE test_composite AS (
303
- name text,
304
- value integer
305
- )
306
- `);
307
- });
308
-
309
- it('verify_type should return true for existing enum type', async () => {
310
- const [result] = await pg.any(
311
- `SELECT verify_type('test_enum') as verified`
312
- );
313
- expect(result.verified).toBe(true);
314
- });
315
-
316
- it('verify_type should return true for existing composite type', async () => {
317
- const [result] = await pg.any(
318
- `SELECT verify_type('test_composite') as verified`
319
- );
320
- expect(result.verified).toBe(true);
321
- });
322
-
323
- it('verify_type should throw for non-existent type', async () => {
324
- await expect(
325
- pg.any(`SELECT verify_type('nonexistent_type')`)
326
- ).rejects.toThrow('Nonexistent type');
327
- });
328
- });
329
-
330
- describe('domain verification', () => {
331
- beforeEach(async () => {
332
- await pg.any(`
333
- CREATE DOMAIN test_domain AS text CHECK (length(value) > 0)
334
- `);
335
- });
336
-
337
- it('verify_domain should return true for existing domain', async () => {
338
- const [result] = await pg.any(
339
- `SELECT verify_domain('test_domain') as verified`
340
- );
341
- expect(result.verified).toBe(true);
342
- });
343
-
344
- it('verify_domain should throw for non-existent domain', async () => {
345
- await expect(
346
- pg.any(`SELECT verify_domain('nonexistent_domain')`)
347
- ).rejects.toThrow('Nonexistent type');
348
- });
349
- });
350
-
351
- describe('role and membership verification', () => {
352
- it('verify_role should return true for current user', async () => {
353
- // Get current user
354
- const [currentUser] = await pg.any(`SELECT current_user as username`);
355
-
356
- const [result] = await pg.any(
357
- `SELECT verify_role($1) as verified`,
358
- [currentUser.username]
359
- );
360
- expect(result.verified).toBe(true);
361
- });
362
-
363
- it('verify_role should throw for non-existent role', async () => {
364
- await expect(
365
- pg.any(`SELECT verify_role('nonexistent_user_12345')`)
366
- ).rejects.toThrow('Nonexistent user');
367
- });
368
-
369
- it('list_memberships should return role memberships', async () => {
370
- const [currentUser] = await pg.any(`SELECT current_user as username`);
371
-
372
- const results = await pg.any(
373
- `SELECT * FROM list_memberships($1)`,
374
- [currentUser.username]
375
- );
376
-
377
- // Should at least include the user themselves
378
- expect(results.length).toBeGreaterThan(0);
379
- const usernames = results.map(r => r.rolname);
380
- expect(usernames).toContain(currentUser.username);
381
- });
382
- });
383
-
384
- describe('grant verification', () => {
385
- beforeEach(async () => {
386
- await pg.any(`
387
- CREATE TABLE test_grants_table (
388
- id serial PRIMARY KEY,
389
- data text
390
- )
391
- `);
392
- });
393
-
394
- it('verify_table_grant should work for existing grants', async () => {
395
- // Grant SELECT to current user (if not already granted)
396
- const [currentUser] = await pg.any(`SELECT current_user as username`);
397
-
398
- // The table owner should have all privileges
399
- const [result] = await pg.any(
400
- `SELECT verify_table_grant('test_grants_table', 'INSERT', $1) as verified`,
401
- [currentUser.username]
402
- );
403
- expect(result.verified).toBe(true);
404
- });
405
- });
406
-
407
- describe('extension verification', () => {
408
- it('verify_extension should return true for available extensions', async () => {
409
- // Most PostgreSQL installations have the plpgsql extension available
410
- const availableExtensions = await pg.any(`
411
- SELECT name FROM pg_available_extensions
412
- WHERE name IN ('plpgsql', 'uuid-ossp', 'pgcrypto')
413
- LIMIT 1
414
- `);
415
-
416
- if (availableExtensions.length > 0) {
417
- const [result] = await pg.any(
418
- `SELECT verify_extension($1) as verified`,
419
- [availableExtensions[0].name]
420
- );
421
- expect(result.verified).toBe(true);
422
- } else {
423
- // Skip this test if no common extensions are available
424
- expect(true).toBe(true);
425
- }
426
- });
427
-
428
- it('verify_extension should throw for non-existent extension', async () => {
429
- await expect(
430
- pg.any(`SELECT verify_extension('definitely_nonexistent_extension_12345')`)
431
- ).rejects.toThrow('Nonexistent extension');
432
- });
433
- });
434
-
435
- describe('security verification', () => {
436
- beforeEach(async () => {
437
- await pg.any(`
438
- CREATE TABLE test_security_table (
439
- id serial PRIMARY KEY,
440
- user_id text,
441
- data text
442
- )
443
- `);
444
-
445
- // Enable row level security
446
- await pg.any(`ALTER TABLE test_security_table ENABLE ROW LEVEL SECURITY`);
447
- });
448
-
449
- it('verify_security should return true for tables with RLS enabled', async () => {
450
- const [result] = await pg.any(
451
- `SELECT verify_security('test_security_table') as verified`
452
- );
453
- expect(result.verified).toBe(true);
454
- });
455
- });
456
-
457
- describe('policy verification', () => {
458
- beforeEach(async () => {
459
- await pg.any(`
460
- CREATE TABLE test_policy_table (
461
- id serial PRIMARY KEY,
462
- user_id text,
463
- data text
464
- )
465
- `);
466
-
467
- await pg.any(`ALTER TABLE test_policy_table ENABLE ROW LEVEL SECURITY`);
468
-
469
- await pg.any(`
470
- CREATE POLICY test_policy ON test_policy_table
471
- FOR SELECT
472
- USING (user_id = current_user)
473
- `);
474
- });
475
-
476
- it('verify_policy should return true for existing policy', async () => {
477
- const [result] = await pg.any(
478
- `SELECT verify_policy('test_policy', 'test_policy_table') as verified`
479
- );
480
- expect(result.verified).toBe(true);
481
- });
482
-
483
- it('verify_policy should throw for non-existent policy', async () => {
484
- await expect(
485
- pg.any(`SELECT verify_policy('nonexistent_policy', 'test_policy_table')`)
486
- ).rejects.toThrow('Nonexistent policy');
487
- });
488
- });
489
- });