Skip to content

Commit 5d40032

Browse files
committed
feat(test): add pgTAP testing infrastructure
Implement pgTAP test framework to provide industry-standard PostgreSQL testing capabilities alongside existing custom assertion helpers. Changes: - Add pgTAP to Docker containers (PostgreSQL 14-17) - Create pgTAP test runner script (tasks/test-pgtap.sh) - Add structure tests: schema, types, functions, operators - Add functionality tests: equality operators - Update docker-compose.yml to build custom images with pgTAP Structure tests verify EQL schema elements exist with correct signatures. Functionality tests validate encrypted data equality operations using pgTAP assertions. This implements Tasks 1-3 from docs/pgtap-implementation-plan.md.
1 parent 6ba78f6 commit 5d40032

File tree

9 files changed

+478
-5
lines changed

9 files changed

+478
-5
lines changed

tasks/test-pgtap.sh

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#!/usr/bin/env bash
2+
#MISE description="Run pgTAP tests with pg_prove"
3+
#USAGE flag "--postgres <version>" help="Run tests for specified Postgres version" default="17" {
4+
#USAGE choices "14" "15" "16" "17"
5+
#USAGE }
6+
7+
set -euo pipefail
8+
9+
POSTGRES_VERSION=${usage_postgres}
10+
11+
connection_url=postgresql://${POSTGRES_USER:-$USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
12+
container_name=postgres-${POSTGRES_VERSION}
13+
14+
fail_if_postgres_not_running () {
15+
containers=$(docker ps --filter "name=^${container_name}$" --quiet)
16+
if [ -z "${containers}" ]; then
17+
echo "error: Docker container for PostgreSQL is not running"
18+
echo "error: Try running 'mise run postgres:up ${container_name}' to start the container"
19+
exit 65
20+
fi
21+
}
22+
23+
# setup
24+
fail_if_postgres_not_running
25+
mise run build --force
26+
mise run reset --force --postgres ${POSTGRES_VERSION}
27+
28+
echo
29+
echo '###############################################'
30+
echo '# Installing release/cipherstash-encrypt.sql'
31+
echo '###############################################'
32+
echo
33+
34+
# Install EQL
35+
cat release/cipherstash-encrypt.sql | docker exec -i ${container_name} psql ${connection_url} -f-
36+
37+
# Install test helpers
38+
cat tests/test_helpers.sql | docker exec -i ${container_name} psql ${connection_url} -f-
39+
cat tests/ore.sql | docker exec -i ${container_name} psql ${connection_url} -f-
40+
cat tests/ste_vec.sql | docker exec -i ${container_name} psql ${connection_url} -f-
41+
42+
echo
43+
echo '###############################################'
44+
echo '# Installing pgTAP'
45+
echo '###############################################'
46+
echo
47+
48+
# Install pgTAP
49+
cat tests/install_pgtap.sql | docker exec -i ${container_name} psql ${connection_url} -f-
50+
51+
echo
52+
echo '###############################################'
53+
echo '# Running pgTAP structure tests'
54+
echo '###############################################'
55+
echo
56+
57+
# Run structure tests with pg_prove
58+
if [ -d "tests/pgtap/structure" ]; then
59+
docker exec -i ${container_name} pg_prove -v -d ${connection_url} /tests/pgtap/structure/*.sql 2>/dev/null || {
60+
# Fallback: copy tests to container and run
61+
for test_file in tests/pgtap/structure/*.sql; do
62+
if [ -f "$test_file" ]; then
63+
echo "Running: $test_file"
64+
cat "$test_file" | docker exec -i ${container_name} psql ${connection_url} -f-
65+
fi
66+
done
67+
}
68+
fi
69+
70+
echo
71+
echo '###############################################'
72+
echo '# Running pgTAP functionality tests'
73+
echo '###############################################'
74+
echo
75+
76+
# Run functionality tests with pg_prove
77+
if [ -d "tests/pgtap/functionality" ]; then
78+
docker exec -i ${container_name} pg_prove -v -d ${connection_url} /tests/pgtap/functionality/*.sql 2>/dev/null || {
79+
# Fallback: copy tests to container and run
80+
for test_file in tests/pgtap/functionality/*.sql; do
81+
if [ -f "$test_file" ]; then
82+
echo "Running: $test_file"
83+
cat "$test_file" | docker exec -i ${container_name} psql ${connection_url} -f-
84+
fi
85+
done
86+
}
87+
fi
88+
89+
echo
90+
echo '###############################################'
91+
echo "# ✅ ALL PGTAP TESTS PASSED "
92+
echo '###############################################'
93+
echo

tests/Dockerfile.pgtap

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
ARG POSTGRES_VERSION=17
2+
FROM postgres:${POSTGRES_VERSION}
3+
4+
# Install build dependencies and pgTAP
5+
RUN apt-get update && apt-get install -y \
6+
build-essential \
7+
postgresql-server-dev-${PG_MAJOR} \
8+
git \
9+
&& rm -rf /var/lib/apt/lists/*
10+
11+
# Install pgTAP
12+
RUN git clone https://github.com/theory/pgtap.git /tmp/pgtap \
13+
&& cd /tmp/pgtap \
14+
&& make \
15+
&& make install \
16+
&& rm -rf /tmp/pgtap

tests/docker-compose.yml

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
services:
22
postgres: &postgres
33
container_name: postgres
4-
image: postgres
4+
build:
5+
context: .
6+
dockerfile: Dockerfile.pgtap
7+
args:
8+
POSTGRES_VERSION: "17"
59
ports:
610
- 7432:7432
711
environment:
@@ -26,24 +30,40 @@ services:
2630

2731
postgres-17:
2832
<<: *postgres
29-
image: postgres:17
33+
build:
34+
context: .
35+
dockerfile: Dockerfile.pgtap
36+
args:
37+
POSTGRES_VERSION: "17"
3038
container_name: postgres-17
3139
#volumes: # uncomment if you need to inspect the container contents
3240
#- ./pg/data-17:/var/lib/postgresql/data
3341

3442
postgres-16:
3543
<<: *postgres
36-
image: postgres:16
44+
build:
45+
context: .
46+
dockerfile: Dockerfile.pgtap
47+
args:
48+
POSTGRES_VERSION: "16"
3749
container_name: postgres-16
3850

3951
postgres-15:
4052
<<: *postgres
41-
image: postgres:15
53+
build:
54+
context: .
55+
dockerfile: Dockerfile.pgtap
56+
args:
57+
POSTGRES_VERSION: "15"
4258
container_name: postgres-15
4359

4460
postgres-14:
4561
<<: *postgres
46-
image: postgres:14
62+
build:
63+
context: .
64+
dockerfile: Dockerfile.pgtap
65+
args:
66+
POSTGRES_VERSION: "14"
4767
container_name: postgres-14
4868

4969
networks:

tests/install_pgtap.sql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-- Install pgTAP extension for testing
2+
CREATE EXTENSION IF NOT EXISTS pgtap;
3+
4+
-- Verify pgTAP installation
5+
SELECT * FROM pg_available_extensions WHERE name = 'pgtap';
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
-- Test EQL equality operators
2+
-- Tests the = operator and eq() function for encrypted data
3+
4+
BEGIN;
5+
6+
-- Plan: count of tests to run
7+
SELECT plan(13);
8+
9+
-- Setup test data
10+
SELECT lives_ok(
11+
'SELECT create_table_with_encrypted()',
12+
'Should create table with encrypted column'
13+
);
14+
15+
SELECT lives_ok(
16+
'SELECT seed_encrypted_json()',
17+
'Should seed encrypted data'
18+
);
19+
20+
-- Test 1: eql_v2_encrypted = eql_v2_encrypted with unique index term (HMAC)
21+
DO $$
22+
DECLARE
23+
e eql_v2_encrypted;
24+
BEGIN
25+
e := create_encrypted_json(1, 'hm');
26+
27+
PERFORM results_eq(
28+
format('SELECT e FROM encrypted WHERE e = %L', e),
29+
format('SELECT e FROM encrypted WHERE id = 1'),
30+
'eql_v2_encrypted = eql_v2_encrypted finds matching record with HMAC index'
31+
);
32+
END;
33+
$$ LANGUAGE plpgsql;
34+
35+
-- Test 2: eql_v2_encrypted = eql_v2_encrypted with no match
36+
DO $$
37+
DECLARE
38+
e eql_v2_encrypted;
39+
BEGIN
40+
e := create_encrypted_json(91347, 'hm');
41+
42+
PERFORM is_empty(
43+
format('SELECT e FROM encrypted WHERE e = %L', e),
44+
'eql_v2_encrypted = eql_v2_encrypted returns no result for non-matching record'
45+
);
46+
END;
47+
$$ LANGUAGE plpgsql;
48+
49+
-- Test 3: eql_v2.eq() function test
50+
DO $$
51+
DECLARE
52+
e eql_v2_encrypted;
53+
BEGIN
54+
e := create_encrypted_json(1)::jsonb-'ob';
55+
56+
PERFORM results_eq(
57+
format('SELECT e FROM encrypted WHERE eql_v2.eq(e, %L)', e),
58+
format('SELECT e FROM encrypted WHERE id = 1'),
59+
'eql_v2.eq() finds matching record'
60+
);
61+
END;
62+
$$ LANGUAGE plpgsql;
63+
64+
-- Test 4: eql_v2.eq() with no match
65+
DO $$
66+
DECLARE
67+
e eql_v2_encrypted;
68+
BEGIN
69+
e := create_encrypted_json(91347)::jsonb-'ob';
70+
71+
PERFORM is_empty(
72+
format('SELECT e FROM encrypted WHERE eql_v2.eq(e, %L)', e),
73+
'eql_v2.eq() returns no result for non-matching record'
74+
);
75+
END;
76+
$$ LANGUAGE plpgsql;
77+
78+
-- Test 5: eql_v2_encrypted = jsonb
79+
DO $$
80+
DECLARE
81+
e jsonb;
82+
BEGIN
83+
e := create_encrypted_json(1)::jsonb-'ob';
84+
85+
PERFORM results_eq(
86+
format('SELECT e FROM encrypted WHERE e = %L::jsonb', e),
87+
format('SELECT e FROM encrypted WHERE id = 1'),
88+
'eql_v2_encrypted = jsonb finds matching record'
89+
);
90+
END;
91+
$$ LANGUAGE plpgsql;
92+
93+
-- Test 6: jsonb = eql_v2_encrypted
94+
DO $$
95+
DECLARE
96+
e jsonb;
97+
BEGIN
98+
e := create_encrypted_json(1)::jsonb-'ob';
99+
100+
PERFORM results_eq(
101+
format('SELECT e FROM encrypted WHERE %L::jsonb = e', e),
102+
format('SELECT e FROM encrypted WHERE id = 1'),
103+
'jsonb = eql_v2_encrypted finds matching record'
104+
);
105+
END;
106+
$$ LANGUAGE plpgsql;
107+
108+
-- Test 7: Blake3 equality - eql_v2_encrypted = eql_v2_encrypted
109+
DO $$
110+
DECLARE
111+
e eql_v2_encrypted;
112+
BEGIN
113+
e := create_encrypted_json(1, 'b3');
114+
115+
PERFORM results_eq(
116+
format('SELECT e FROM encrypted WHERE e = %L', e),
117+
format('SELECT e FROM encrypted WHERE id = 1'),
118+
'Blake3: eql_v2_encrypted = eql_v2_encrypted finds matching record'
119+
);
120+
END;
121+
$$ LANGUAGE plpgsql;
122+
123+
-- Test 8: Blake3 equality with no match
124+
DO $$
125+
DECLARE
126+
e eql_v2_encrypted;
127+
BEGIN
128+
e := create_encrypted_json(91347, 'b3');
129+
130+
PERFORM is_empty(
131+
format('SELECT e FROM encrypted WHERE e = %L', e),
132+
'Blake3: eql_v2_encrypted = eql_v2_encrypted returns no result for non-matching record'
133+
);
134+
END;
135+
$$ LANGUAGE plpgsql;
136+
137+
-- Test 9: Blake3 eql_v2.eq() function
138+
DO $$
139+
DECLARE
140+
e eql_v2_encrypted;
141+
BEGIN
142+
e := create_encrypted_json(1, 'b3');
143+
144+
PERFORM results_eq(
145+
format('SELECT e FROM encrypted WHERE eql_v2.eq(e, %L)', e),
146+
format('SELECT e FROM encrypted WHERE id = 1'),
147+
'Blake3: eql_v2.eq() finds matching record'
148+
);
149+
END;
150+
$$ LANGUAGE plpgsql;
151+
152+
-- Test 10: Blake3 eql_v2_encrypted = jsonb
153+
DO $$
154+
DECLARE
155+
e jsonb;
156+
BEGIN
157+
e := create_encrypted_json(1, 'b3');
158+
159+
PERFORM results_eq(
160+
format('SELECT e FROM encrypted WHERE e = %L::jsonb', e),
161+
format('SELECT e FROM encrypted WHERE id = 1'),
162+
'Blake3: eql_v2_encrypted = jsonb finds matching record'
163+
);
164+
END;
165+
$$ LANGUAGE plpgsql;
166+
167+
-- Test 11: Blake3 jsonb = eql_v2_encrypted
168+
DO $$
169+
DECLARE
170+
e jsonb;
171+
BEGIN
172+
e := create_encrypted_json(1, 'b3');
173+
174+
PERFORM results_eq(
175+
format('SELECT e FROM encrypted WHERE %L::jsonb = e', e),
176+
format('SELECT e FROM encrypted WHERE id = 1'),
177+
'Blake3: jsonb = eql_v2_encrypted finds matching record'
178+
);
179+
END;
180+
$$ LANGUAGE plpgsql;
181+
182+
-- Cleanup
183+
SELECT lives_ok(
184+
'SELECT drop_table_with_encrypted()',
185+
'Should drop test table'
186+
);
187+
188+
SELECT finish();
189+
ROLLBACK;

0 commit comments

Comments
 (0)