3 # Test suite for pg_scribe --init command
6 # - Creates temporary test databases
7 # - Tests various --init scenarios
8 # - Verifies expected outcomes
9 # - Cleans up all resources
14 # Colors for test output
19 NC='\033[0m' # No Color
22 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
23 PG_SCRIBE="$SCRIPT_DIR/scripts/pg_scribe"
24 TEST_DIR="/tmp/pg_scribe_test_$$"
25 TEST_DB_PREFIX="pg_scribe_test_$$"
26 PGUSER="${PGUSER:-postgres}"
34 DATABASES_TO_CLEANUP=()
42 echo -e "${BLUE}TEST:${NC} $*"
46 echo -e "${GREEN}PASS:${NC} $*"
51 echo -e "${RED}FAIL:${NC} $*"
56 echo -e "${YELLOW}INFO:${NC} $*"
66 psql -U "$PGUSER" -d "$dbname" -tAq "$@"
72 run_psql "$dbname" -c "$query" 2>/dev/null || true
77 log_info "Creating test database: $dbname"
80 psql -U "$PGUSER" -d postgres -c "DROP DATABASE IF EXISTS $dbname;" &>/dev/null || true
83 psql -U "$PGUSER" -d postgres -c "CREATE DATABASE $dbname;" &>/dev/null
85 DATABASES_TO_CLEANUP+=("$dbname")
90 log_info "Dropping test database: $dbname"
92 # Terminate connections
93 psql -U "$PGUSER" -d postgres -c "
94 SELECT pg_terminate_backend(pid)
96 WHERE datname = '$dbname' AND pid <> pg_backend_pid();
100 psql -U "$PGUSER" -d postgres -c "DROP DATABASE IF EXISTS $dbname;" &>/dev/null || true
103 drop_replication_slot() {
106 log_info "Dropping replication slot: $slot"
108 # Check if slot exists
109 local exists=$(query_db "$dbname" "
110 SELECT 1 FROM pg_replication_slots WHERE slot_name = '$slot';
113 if [[ -n "$exists" ]]; then
115 query_db "$dbname" "SELECT pg_drop_replication_slot('$slot');" || true
119 check_slot_exists() {
122 local exists=$(query_db "$dbname" "
123 SELECT 1 FROM pg_replication_slots WHERE slot_name = '$slot';
128 check_extension_exists() {
131 local exists=$(query_db "$dbname" "
132 SELECT 1 FROM pg_extension WHERE extname = '$extension';
137 create_table_with_pk() {
141 CREATE TABLE $table (
142 id SERIAL PRIMARY KEY,
144 created_at TIMESTAMP DEFAULT now()
149 create_table_without_pk() {
153 CREATE TABLE $table (
164 test_basic_init_success() {
166 log_test "Basic --init success"
168 local dbname="${TEST_DB_PREFIX}_basic"
169 local slot="test_slot_basic"
170 local backup_dir="$TEST_DIR/basic"
173 create_test_db "$dbname"
174 create_table_with_pk "$dbname" "users"
175 mkdir -p "$backup_dir"
178 if "$PG_SCRIBE" --init -d "$dbname" -f "$backup_dir" -S "$slot" -U "$PGUSER" &>/dev/null; then
179 # Verify slot created
180 if ! check_slot_exists "$dbname" "$slot"; then
181 log_fail "Replication slot not created"
185 # Verify extension installed
186 if ! check_extension_exists "$dbname" "wal2sql"; then
187 log_fail "wal2sql extension not installed"
191 # Verify files created (use compgen to check for glob matches)
192 local base_files=("$backup_dir"/base-*.sql)
193 if [[ ! -f "${base_files[0]}" ]]; then
194 log_fail "Base backup file not created"
198 local globals_files=("$backup_dir"/globals-*.sql)
199 if [[ ! -f "${globals_files[0]}" ]]; then
200 log_fail "Globals backup file not created"
204 if [[ ! -f "$backup_dir/pg_scribe_metadata.txt" ]]; then
205 log_fail "Metadata file not created"
209 # Verify metadata content
210 if ! grep -q "Database: $dbname" "$backup_dir/pg_scribe_metadata.txt"; then
211 log_fail "Metadata missing database name"
215 if ! grep -q "Replication Slot: $slot" "$backup_dir/pg_scribe_metadata.txt"; then
216 log_fail "Metadata missing slot name"
220 log_pass "Basic init successful"
223 log_fail "Init command failed"
228 test_init_validation_failure() {
230 log_test "Init validation failure (table without replica identity)"
232 local dbname="${TEST_DB_PREFIX}_nopk"
233 local slot="test_slot_nopk"
234 local backup_dir="$TEST_DIR/nopk"
236 # Setup - create table WITHOUT primary key
237 create_test_db "$dbname"
238 create_table_without_pk "$dbname" "bad_table"
239 mkdir -p "$backup_dir"
241 # Run init - should fail with exit code 5
243 "$PG_SCRIBE" --init -d "$dbname" -f "$backup_dir" -S "$slot" -U "$PGUSER" &>/dev/null || exit_code=$?
245 if [[ $exit_code -eq 5 ]]; then
246 # Verify slot was NOT created
247 if check_slot_exists "$dbname" "$slot"; then
248 log_fail "Replication slot should not be created on validation failure"
252 log_pass "Validation failure detected correctly"
255 log_fail "Expected exit code 5, got $exit_code"
260 test_init_force_flag() {
262 log_test "Init with --force flag (bypass validation)"
264 local dbname="${TEST_DB_PREFIX}_force"
265 local slot="test_slot_force"
266 local backup_dir="$TEST_DIR/force"
268 # Setup - create table WITHOUT primary key
269 create_test_db "$dbname"
270 create_table_without_pk "$dbname" "bad_table"
271 mkdir -p "$backup_dir"
273 # Run init with --force - should succeed despite validation failure
274 if "$PG_SCRIBE" --init -d "$dbname" -f "$backup_dir" -S "$slot" -U "$PGUSER" --force &>/dev/null; then
275 # Verify slot was created
276 if ! check_slot_exists "$dbname" "$slot"; then
277 log_fail "Replication slot should be created with --force"
281 log_pass "Force flag bypassed validation"
284 log_fail "Init with --force should succeed"
289 test_init_non_idempotency() {
291 log_test "Init refuses to run on already-initialized directory"
293 local dbname="${TEST_DB_PREFIX}_nonidempotent"
294 local slot="test_slot_nonidempotent"
295 local backup_dir="$TEST_DIR/nonidempotent"
298 create_test_db "$dbname"
299 create_table_with_pk "$dbname" "users"
300 mkdir -p "$backup_dir"
302 # Run init first time
303 if ! "$PG_SCRIBE" --init -d "$dbname" -f "$backup_dir" -S "$slot" -U "$PGUSER" &>/dev/null; then
304 log_fail "First init failed"
308 # Run init second time - should FAIL with validation error
310 "$PG_SCRIBE" --init -d "$dbname" -f "$backup_dir" -S "$slot" -U "$PGUSER" &>/dev/null || exit_code=$?
312 if [[ $exit_code -eq 5 ]]; then
313 # Verify slot still exists from first init
314 if ! check_slot_exists "$dbname" "$slot"; then
315 log_fail "Replication slot should still exist from first init"
319 # Verify only 1 base backup file (from first init)
320 local base_count=$(ls "$backup_dir"/base-*.sql 2>/dev/null | wc -l)
321 if [[ $base_count -ne 1 ]]; then
322 log_fail "Expected 1 base backup file, got $base_count"
326 log_pass "Init correctly refuses to reinitialize"
329 log_fail "Expected exit code 5 (validation error), got $exit_code"
334 test_init_multiple_tables() {
336 log_test "Init with multiple tables"
338 local dbname="${TEST_DB_PREFIX}_multi"
339 local slot="test_slot_multi"
340 local backup_dir="$TEST_DIR/multi"
342 # Setup - create multiple tables
343 create_test_db "$dbname"
344 create_table_with_pk "$dbname" "users"
345 create_table_with_pk "$dbname" "orders"
346 create_table_with_pk "$dbname" "products"
349 query_db "$dbname" "INSERT INTO users (name) VALUES ('Alice'), ('Bob');"
350 query_db "$dbname" "INSERT INTO orders (name) VALUES ('Order1');"
351 query_db "$dbname" "INSERT INTO products (name) VALUES ('Widget');"
353 mkdir -p "$backup_dir"
356 if "$PG_SCRIBE" --init -d "$dbname" -f "$backup_dir" -S "$slot" -U "$PGUSER" &>/dev/null; then
357 # Verify base backup contains all tables
358 local base_file=$(ls "$backup_dir"/base-*.sql | head -1)
360 if ! grep -q "CREATE TABLE public.users" "$base_file"; then
361 log_fail "Base backup missing users table"
365 if ! grep -q "CREATE TABLE public.orders" "$base_file"; then
366 log_fail "Base backup missing orders table"
370 if ! grep -q "CREATE TABLE public.products" "$base_file"; then
371 log_fail "Base backup missing products table"
375 log_pass "Multiple tables backed up successfully"
378 log_fail "Init failed"
383 test_init_with_unlogged_table() {
385 log_test "Init with unlogged table (warning but success)"
387 local dbname="${TEST_DB_PREFIX}_unlogged"
388 local slot="test_slot_unlogged"
389 local backup_dir="$TEST_DIR/unlogged"
392 create_test_db "$dbname"
393 create_table_with_pk "$dbname" "normal_table"
394 query_db "$dbname" "CREATE UNLOGGED TABLE unlogged_table (id SERIAL PRIMARY KEY, data TEXT);"
395 mkdir -p "$backup_dir"
397 # Run init - should succeed with warning (exit code 0 or 10)
398 local output_file="$TEST_DIR/unlogged_output.txt"
400 "$PG_SCRIBE" --init -d "$dbname" -f "$backup_dir" -S "$slot" -U "$PGUSER" &>"$output_file" || exit_code=$?
402 # Exit code 0 (success) or 10 (warning) are both acceptable
403 if [[ $exit_code -eq 0 || $exit_code -eq 10 ]]; then
404 # Check for warning message
405 if ! grep -i "unlogged" "$output_file"; then
406 log_fail "Expected warning about unlogged table"
410 # Verify backup was created
411 local base_files=("$backup_dir"/base-*.sql)
412 if [[ ! -f "${base_files[0]}" ]]; then
413 log_fail "Base backup file not created"
417 log_pass "Unlogged table warning shown correctly"
420 log_fail "Init failed with exit code $exit_code"
425 test_backup_content_validity() {
427 log_test "Verify backup SQL is valid"
429 local dbname="${TEST_DB_PREFIX}_valid"
430 local slot="test_slot_valid"
431 local backup_dir="$TEST_DIR/valid"
432 local restore_dbname="${TEST_DB_PREFIX}_restored"
435 create_test_db "$dbname"
436 create_table_with_pk "$dbname" "test_data"
437 query_db "$dbname" "INSERT INTO test_data (name) VALUES ('Test Row 1'), ('Test Row 2');"
438 mkdir -p "$backup_dir"
441 if ! "$PG_SCRIBE" --init -d "$dbname" -f "$backup_dir" -S "$slot" -U "$PGUSER" &>/dev/null; then
442 log_fail "Init failed"
446 # Try to restore the backup to a new database
447 create_test_db "$restore_dbname"
449 local base_file=$(ls "$backup_dir"/base-*.sql | head -1)
450 local globals_file=$(ls "$backup_dir"/globals-*.sql | head -1)
452 # Apply globals (roles, etc.)
453 if ! psql -U "$PGUSER" -d postgres -f "$globals_file" &>/dev/null; then
454 log_fail "Failed to restore globals"
459 if ! psql -U "$PGUSER" -d "$restore_dbname" -f "$base_file" &>/dev/null; then
460 log_fail "Failed to restore base backup"
465 local count=$(query_db "$restore_dbname" "SELECT COUNT(*) FROM test_data;")
466 if [[ "$count" -ne 2 ]]; then
467 log_fail "Expected 2 rows, got $count"
471 log_pass "Backup SQL is valid and restorable"
480 log_info "Cleaning up test resources..."
482 # Drop replication slots
483 for dbname in "${DATABASES_TO_CLEANUP[@]}"; do
484 # Try to drop any slots for this database
485 for slot in test_slot_basic test_slot_nopk test_slot_force test_slot_nonidempotent test_slot_multi test_slot_unlogged test_slot_valid; do
486 drop_replication_slot "$dbname" "$slot" 2>/dev/null || true
491 for dbname in "${DATABASES_TO_CLEANUP[@]}"; do
492 drop_test_db "$dbname"
495 # Remove test directory
496 if [[ -d "$TEST_DIR" ]]; then
500 log_info "Cleanup complete"
508 echo "========================================"
509 echo "pg_scribe --init Test Suite"
510 echo "========================================"
513 # Verify pg_scribe exists
514 if [[ ! -x "$PG_SCRIBE" ]]; then
515 echo "ERROR: pg_scribe not found or not executable: $PG_SCRIBE"
519 # Verify PostgreSQL is running
520 if ! psql -U "$PGUSER" -d postgres -c "SELECT 1;" &>/dev/null; then
521 echo "ERROR: Cannot connect to PostgreSQL"
525 # Verify wal_level is logical
526 local wal_level=$(psql -U "$PGUSER" -d postgres -tAq -c "SHOW wal_level;")
527 if [[ "$wal_level" != "logical" ]]; then
528 echo "ERROR: wal_level must be 'logical', currently: $wal_level"
529 echo "Update ~/.pgenv/pgsql/data/postgresql.conf and restart PostgreSQL"
533 # Create test directory
536 # Set up cleanup trap
537 trap cleanup EXIT INT TERM
539 echo "Running tests..."
542 # Run all tests (use || true to prevent set -e from exiting)
543 test_basic_init_success || true
544 test_init_validation_failure || true
545 test_init_force_flag || true
546 test_init_non_idempotency || true
547 test_init_multiple_tables || true
548 test_init_with_unlogged_table || true
549 test_backup_content_validity || true
553 echo "========================================"
555 echo "========================================"
556 echo "Tests run: $TESTS_RUN"
557 echo -e "Tests passed: ${GREEN}$TESTS_PASSED${NC}"
558 echo -e "Tests failed: ${RED}$TESTS_FAILED${NC}"
561 if [[ $TESTS_FAILED -eq 0 ]]; then
562 echo -e "${GREEN}All tests passed!${NC}"
565 echo -e "${RED}Some tests failed!${NC}"