]> begriffs open source - pg_scribe/blob - tests/test_status.sh
Apply diffs faster with synchronous_commit = off
[pg_scribe] / tests / test_status.sh
1 #!/usr/bin/env bash
2 #
3 # Test suite for pg_scribe --status command
4 #
5 # This test suite:
6 # - Creates temporary test databases
7 # - Tests various --status scenarios
8 # - Verifies status reporting and health checks
9 # - Cleans up all resources
10 #
11
12 set -euo pipefail
13
14 # Colors for test output
15 RED='\033[0;31m'
16 GREEN='\033[0;32m'
17 YELLOW='\033[0;33m'
18 BLUE='\033[0;34m'
19 NC='\033[0m' # No Color
20
21 # Test configuration
22 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
23 PG_SCRIBE="$SCRIPT_DIR/scripts/pg_scribe"
24 TEST_DIR="/tmp/pg_scribe_test_status_$$"
25 TEST_DB_PREFIX="pg_scribe_test_status_$$"
26 PGUSER="${PGUSER:-postgres}"
27
28 # Test counters
29 TESTS_RUN=0
30 TESTS_PASSED=0
31 TESTS_FAILED=0
32
33 # Cleanup tracking
34 DATABASES_TO_CLEANUP=()
35 PIDS_TO_CLEANUP=()
36
37 #
38 # Logging functions
39 #
40
41 log_test() {
42     echo -e "${BLUE}TEST:${NC} $*"
43 }
44
45 log_pass() {
46     echo -e "${GREEN}PASS:${NC} $*"
47     ((TESTS_PASSED++))
48 }
49
50 log_fail() {
51     echo -e "${RED}FAIL:${NC} $*"
52     ((TESTS_FAILED++))
53 }
54
55 log_info() {
56     echo -e "${YELLOW}INFO:${NC} $*"
57 }
58
59 #
60 # Helper functions
61 #
62
63 run_psql() {
64     local dbname="$1"
65     shift
66     psql -U "$PGUSER" -d "$dbname" -tAq "$@"
67 }
68
69 query_db() {
70     local dbname="$1"
71     local query="$2"
72     run_psql "$dbname" -c "$query" 2>/dev/null || true
73 }
74
75 create_test_db() {
76     local dbname="$1"
77     log_info "Creating test database: $dbname"
78
79     # Drop if exists
80     psql -U "$PGUSER" -d postgres -c "DROP DATABASE IF EXISTS $dbname;" &>/dev/null || true
81
82     # Create database
83     psql -U "$PGUSER" -d postgres -c "CREATE DATABASE $dbname;" &>/dev/null
84
85     DATABASES_TO_CLEANUP+=("$dbname")
86 }
87
88 # shellcheck disable=SC2317  # Function called from cleanup trap handler
89 drop_test_db() {
90     local dbname="$1"
91     log_info "Dropping test database: $dbname"
92
93     # Terminate connections
94     psql -U "$PGUSER" -d postgres -c "
95         SELECT pg_terminate_backend(pid)
96         FROM pg_stat_activity
97         WHERE datname = '$dbname' AND pid <> pg_backend_pid();
98     " &>/dev/null || true
99
100     # Drop database
101     psql -U "$PGUSER" -d postgres -c "DROP DATABASE IF EXISTS $dbname;" &>/dev/null || true
102 }
103
104 # shellcheck disable=SC2317  # Function called from cleanup trap handler
105 drop_replication_slot() {
106     local dbname="$1"
107     local slot="$2"
108     log_info "Dropping replication slot: $slot"
109
110     # Check if slot exists
111     local exists
112     exists=$(query_db "$dbname" "
113         SELECT 1 FROM pg_replication_slots WHERE slot_name = '$slot';
114     ")
115
116     if [[ -n "$exists" ]]; then
117         # Drop slot
118         query_db "$dbname" "SELECT pg_drop_replication_slot('$slot');" || true
119     fi
120 }
121
122 create_table_with_pk() {
123     local dbname="$1"
124     local table="$2"
125     query_db "$dbname" "
126         CREATE TABLE $table (
127             id SERIAL PRIMARY KEY,
128             name TEXT,
129             created_at TIMESTAMP DEFAULT now()
130         );
131     "
132 }
133
134 initialize_backup_system() {
135     local dbname="$1"
136     local slot="$2"
137     local backup_dir="$3"
138
139     # Create backup directory
140     mkdir -p "$backup_dir"
141
142     # Initialize (exit on failure - this is critical for tests)
143     local init_output
144     init_output=$("$PG_SCRIBE" --init -d "$dbname" -f "$backup_dir" -S "$slot" -U "$PGUSER" 2>&1)
145     local init_exit=$?
146
147     if [[ $init_exit -ne 0 && $init_exit -ne 10 ]]; then
148         log_info "ERROR: Failed to initialize backup system for $dbname (exit=$init_exit)"
149         echo "$init_output" >&2
150         return 1
151     fi
152     return 0
153 }
154
155 #
156 # Test cases
157 #
158
159 test_status_without_slot() {
160     ((TESTS_RUN++))
161     log_test "Status check when slot doesn't exist (should fail)"
162
163     local dbname="${TEST_DB_PREFIX}_noslot"
164     local slot="test_slot_noslot"
165
166     # Setup - create db but DON'T initialize
167     create_test_db "$dbname"
168     create_table_with_pk "$dbname" "users"
169
170     # Try to check status - should fail with exit code 3 (slot error)
171     local exit_code=0
172     "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -U "$PGUSER" &>/dev/null || exit_code=$?
173
174     if [[ $exit_code -eq 3 ]]; then
175         log_pass "Correctly failed with slot error"
176         return 0
177     else
178         log_fail "Expected exit code 3, got $exit_code"
179         return 1
180     fi
181 }
182
183 test_status_basic_healthy() {
184     ((TESTS_RUN++))
185     log_test "Basic status check (without backup directory)"
186
187     local dbname="${TEST_DB_PREFIX}_healthy"
188     local slot="test_slot_healthy"
189     local backup_dir="$TEST_DIR/healthy"
190
191     # Setup
192     create_test_db "$dbname"
193     create_table_with_pk "$dbname" "users"
194     initialize_backup_system "$dbname" "$slot" "$backup_dir"
195
196     # Check status WITHOUT backup directory - should succeed
197     # (warnings about inactive slot, but exit code 10 is acceptable)
198     local output_file="$TEST_DIR/healthy_output.txt"
199     local exit_code=0
200     "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -U "$PGUSER" &>"$output_file" || exit_code=$?
201
202     # Exit code 0 or 10 (warning about inactive slot) are acceptable
203     if [[ $exit_code -ne 0 && $exit_code -ne 10 ]]; then
204         log_fail "Status check failed with unexpected exit code $exit_code"
205         return 1
206     fi
207
208     # Verify output contains expected information
209     if ! grep -q "Slot Name:" "$output_file"; then
210         log_fail "Output missing slot name"
211         return 1
212     fi
213
214     if ! grep -q "Active:" "$output_file"; then
215         log_fail "Output missing active status"
216         return 1
217     fi
218
219     if ! grep -q "Restart Lag:" "$output_file"; then
220         log_fail "Output missing lag information"
221         return 1
222     fi
223
224     log_pass "Status check successful"
225     return 0
226 }
227
228 test_status_with_backup_directory() {
229     ((TESTS_RUN++))
230     log_test "Status check with chain inventory"
231
232     local dbname="${TEST_DB_PREFIX}_with_dir"
233     local slot="test_slot_with_dir"
234     local backup_dir="$TEST_DIR/with_dir"
235
236     # Setup
237     create_test_db "$dbname"
238     create_table_with_pk "$dbname" "users"
239     initialize_backup_system "$dbname" "$slot" "$backup_dir"
240
241     # Check status with backup directory
242     local output_file="$TEST_DIR/with_dir_output.txt"
243     local exit_code=0
244     "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -f "$backup_dir" -U "$PGUSER" &>"$output_file" || exit_code=$?
245
246     # Exit code 0 or 10 (warning) are both acceptable here
247     # (warning because slot is not active)
248     if [[ $exit_code -ne 0 && $exit_code -ne 10 ]]; then
249         log_fail "Status check failed with unexpected exit code $exit_code"
250         return 1
251     fi
252
253     # Verify chain inventory appears in output
254     if ! grep -q "Chain Inventory" "$output_file"; then
255         log_fail "Output missing chain inventory"
256         return 1
257     fi
258
259     # Should show at least one chain
260     if ! grep -q "chain-" "$output_file"; then
261         log_fail "Output missing chain listing"
262         return 1
263     fi
264
265     # Should show base backup size
266     if ! grep -q "Base backup:" "$output_file"; then
267         log_fail "Output missing base backup size"
268         return 1
269     fi
270
271     # Should show differentials count
272     if ! grep -q "Differentials:" "$output_file"; then
273         log_fail "Output missing differentials count"
274         return 1
275     fi
276
277     # Should show total size
278     if ! grep -q "Total size:" "$output_file"; then
279         log_fail "Output missing total size"
280         return 1
281     fi
282
283     log_pass "Chain inventory works correctly"
284     return 0
285 }
286
287 test_status_inactive_slot() {
288     ((TESTS_RUN++))
289     log_test "Status check with inactive slot (should warn)"
290
291     local dbname="${TEST_DB_PREFIX}_inactive"
292     local slot="test_slot_inactive"
293     local backup_dir="$TEST_DIR/inactive"
294
295     # Setup
296     create_test_db "$dbname"
297     create_table_with_pk "$dbname" "users"
298     initialize_backup_system "$dbname" "$slot" "$backup_dir"
299
300     # Slot exists but is inactive (no pg_recvlogical running)
301     # Check status - should succeed but with warning
302     local output_file="$TEST_DIR/inactive_output.txt"
303     local exit_code=0
304     "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -U "$PGUSER" &>"$output_file" || exit_code=$?
305
306     # Should exit with warning code (10)
307     if [[ $exit_code -ne 10 ]]; then
308         log_fail "Expected exit code 10 (warning), got $exit_code"
309         return 1
310     fi
311
312     # Should mention inactive status
313     if ! grep -qi "not active" "$output_file"; then
314         log_fail "Output should mention inactive slot"
315         return 1
316     fi
317
318     log_pass "Inactive slot warning works correctly"
319     return 0
320 }
321
322 test_status_with_active_streaming() {
323     ((TESTS_RUN++))
324     log_test "Status check with active streaming"
325
326     local dbname="${TEST_DB_PREFIX}_streaming"
327     local slot="test_slot_streaming"
328     local backup_dir="$TEST_DIR/streaming"
329
330     # Setup
331     create_test_db "$dbname"
332     create_table_with_pk "$dbname" "users"
333     initialize_backup_system "$dbname" "$slot" "$backup_dir"
334
335     # Start streaming in background (use backup directory for new chain system)
336     "$PG_SCRIBE" --start -d "$dbname" -f "$backup_dir" -S "$slot" -U "$PGUSER" -s 1 -F 1 &>/dev/null &
337     local pg_scribe_pid=$!
338     PIDS_TO_CLEANUP+=("$pg_scribe_pid")
339
340     # Wait for streaming to become active (with retry loop)
341     local retries=0
342     local max_retries=10
343     local slot_active=false
344
345     while [[ $retries -lt $max_retries ]]; do
346         sleep 1
347         local status_output="$TEST_DIR/streaming_status_retry_$retries.txt"
348         "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -U "$PGUSER" &>"$status_output" || true
349
350         if grep -q "Active:.*Yes" "$status_output"; then
351             slot_active=true
352             break
353         fi
354         ((retries++))
355     done
356
357     # Final status check
358     local status_output="$TEST_DIR/streaming_status.txt"
359     local exit_code=0
360     "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -U "$PGUSER" &>"$status_output" || exit_code=$?
361
362     # Stop streaming
363     kill -INT "$pg_scribe_pid" 2>/dev/null || true
364     sleep 1
365
366     # Force kill if still running
367     if kill -0 "$pg_scribe_pid" 2>/dev/null; then
368         kill -9 "$pg_scribe_pid" 2>/dev/null || true
369     fi
370     wait "$pg_scribe_pid" 2>/dev/null || true
371
372     # Verify status showed active slot
373     if [[ "$slot_active" != "true" ]]; then
374         log_fail "Slot should be shown as active (checked $retries times)"
375         cat "$status_output" >&2
376         return 1
377     fi
378
379     log_pass "Active slot detected correctly"
380     return 0
381 }
382
383 test_status_with_incremental_backups() {
384     ((TESTS_RUN++))
385     log_test "Status check with active streaming chain"
386
387     local dbname="${TEST_DB_PREFIX}_with_inc"
388     local slot="test_slot_with_inc"
389     local backup_dir="$TEST_DIR/with_inc"
390
391     # Setup
392     create_test_db "$dbname"
393     create_table_with_pk "$dbname" "users"
394     initialize_backup_system "$dbname" "$slot" "$backup_dir"
395
396     # Start streaming to create active.sql
397     "$PG_SCRIBE" --start -d "$dbname" -f "$backup_dir" -S "$slot" -U "$PGUSER" -s 1 -F 1 &>/dev/null &
398     local pg_scribe_pid=$!
399     PIDS_TO_CLEANUP+=("$pg_scribe_pid")
400
401     # Give it a moment to start
402     sleep 2
403
404     # Make some changes to generate incremental data
405     query_db "$dbname" "INSERT INTO users (name) VALUES ('Test User');"
406
407     # Give it time to flush
408     sleep 2
409
410     # Check status with backup directory (while streaming is active)
411     local status_output="$TEST_DIR/with_inc_status.txt"
412     local exit_code=0
413     "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -f "$backup_dir" -U "$PGUSER" &>"$status_output" || exit_code=$?
414
415     # Stop streaming
416     kill -INT "$pg_scribe_pid" 2>/dev/null || true
417     sleep 1
418
419     # Force kill if still running
420     if kill -0 "$pg_scribe_pid" 2>/dev/null; then
421         kill -9 "$pg_scribe_pid" 2>/dev/null || true
422     fi
423     wait "$pg_scribe_pid" 2>/dev/null || true
424
425     # Should succeed (exit code 0)
426     if [[ $exit_code -ne 0 ]]; then
427         log_fail "Status check failed with unexpected exit code $exit_code"
428         cat "$status_output" >&2
429         return 1
430     fi
431
432     # Verify chain shows as active
433     if ! grep -q "ACTIVE - streaming" "$status_output"; then
434         log_fail "Output should show active streaming chain"
435         cat "$status_output" >&2
436         return 1
437     fi
438
439     # Should show PID
440     if ! grep -q "PID:" "$status_output"; then
441         log_fail "Output should show streaming PID"
442         cat "$status_output" >&2
443         return 1
444     fi
445
446     # Should show last activity
447     if ! grep -q "Last activity:" "$status_output"; then
448         log_fail "Output should show last activity timestamp"
449         cat "$status_output" >&2
450         return 1
451     fi
452
453     log_pass "Active streaming chain reported correctly"
454     return 0
455 }
456
457 test_status_with_sealed_differentials() {
458     ((TESTS_RUN++))
459     log_test "Status check with sealed differential files"
460
461     local dbname="${TEST_DB_PREFIX}_with_diffs"
462     local slot="test_slot_with_diffs"
463     local backup_dir="$TEST_DIR/with_diffs"
464
465     # Setup
466     create_test_db "$dbname"
467     create_table_with_pk "$dbname" "users"
468     initialize_backup_system "$dbname" "$slot" "$backup_dir"
469
470     # Find the chain directory
471     local chain_dir
472     chain_dir=$(find "$backup_dir" -maxdepth 1 -type d -name 'chain-*' 2>/dev/null | head -1)
473
474     if [[ -z "$chain_dir" ]]; then
475         log_fail "No chain directory found"
476         return 1
477     fi
478
479     # Create mock sealed differential files
480     touch "$chain_dir/diff-20251018T120000Z.sql"
481     touch "$chain_dir/diff-20251018T130000Z.sql"
482     touch "$chain_dir/diff-20251018T140000Z.sql"
483
484     # Check status
485     local output_file="$TEST_DIR/with_diffs_output.txt"
486     local exit_code=0
487     "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -f "$backup_dir" -U "$PGUSER" &>"$output_file" || exit_code=$?
488
489     # Should succeed with warning (inactive slot)
490     if [[ $exit_code -ne 0 && $exit_code -ne 10 ]]; then
491         log_fail "Status check failed with unexpected exit code $exit_code"
492         cat "$output_file" >&2
493         return 1
494     fi
495
496     # Should show 3 sealed differentials
497     if ! grep -q "Differentials:.*3 sealed" "$output_file"; then
498         log_fail "Should report 3 sealed differentials"
499         cat "$output_file" >&2
500         return 1
501     fi
502
503     log_pass "Sealed differentials counted correctly"
504     return 0
505 }
506
507 test_status_nonexistent_backup_dir() {
508     ((TESTS_RUN++))
509     log_test "Status check with nonexistent backup directory"
510
511     local dbname="${TEST_DB_PREFIX}_no_dir"
512     local slot="test_slot_no_dir"
513     local backup_dir="$TEST_DIR/no_dir"
514     local fake_dir="$TEST_DIR/does_not_exist"
515
516     # Setup - initialize but check status with different directory
517     create_test_db "$dbname"
518     create_table_with_pk "$dbname" "users"
519     initialize_backup_system "$dbname" "$slot" "$backup_dir"
520
521     # Check status with nonexistent directory
522     local output_file="$TEST_DIR/no_dir_output.txt"
523     local exit_code=0
524     "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -f "$fake_dir" -U "$PGUSER" &>"$output_file" || exit_code=$?
525
526     # Should exit with warning (10)
527     if [[ $exit_code -ne 10 ]]; then
528         log_fail "Expected exit code 10 (warning), got $exit_code"
529         return 1
530     fi
531
532     # Should warn about nonexistent directory
533     if ! grep -qi "does not exist" "$output_file"; then
534         log_fail "Should warn about nonexistent directory"
535         return 1
536     fi
537
538     log_pass "Nonexistent directory warning works correctly"
539     return 0
540 }
541
542 test_status_lag_display() {
543     ((TESTS_RUN++))
544     log_test "Status displays lag information correctly"
545
546     local dbname="${TEST_DB_PREFIX}_lag"
547     local slot="test_slot_lag"
548     local backup_dir="$TEST_DIR/lag"
549
550     # Setup
551     create_test_db "$dbname"
552     create_table_with_pk "$dbname" "users"
553
554     if ! initialize_backup_system "$dbname" "$slot" "$backup_dir"; then
555         log_fail "Failed to initialize backup system"
556         return 1
557     fi
558
559     # Check status
560     local output_file="$TEST_DIR/lag_output.txt"
561     "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -U "$PGUSER" &>"$output_file" || true
562
563     # Verify lag is displayed - use case-insensitive grep for ANSI color codes
564     if ! grep -i "restart.*lag" "$output_file"; then
565         log_fail "Output missing restart lag"
566         cat "$output_file" >&2
567         return 1
568     fi
569
570     if ! grep -i "confirmed.*lag" "$output_file"; then
571         log_fail "Output missing confirmed lag"
572         cat "$output_file" >&2
573         return 1
574     fi
575
576     # Verify lag values are in MB
577     if ! grep "MB" "$output_file"; then
578         log_fail "Lag should be displayed in MB"
579         cat "$output_file" >&2
580         return 1
581     fi
582
583     log_pass "Lag information displayed correctly"
584     return 0
585 }
586
587 test_status_multiple_chains() {
588     ((TESTS_RUN++))
589     log_test "Status with multiple chains shows all"
590
591     local dbname="${TEST_DB_PREFIX}_multi_chains"
592     local slot="test_slot_multi_chains"
593     local backup_dir="$TEST_DIR/multi_chains"
594
595     # Setup
596     create_test_db "$dbname"
597     create_table_with_pk "$dbname" "users"
598
599     if ! initialize_backup_system "$dbname" "$slot" "$backup_dir"; then
600         log_fail "Failed to initialize backup system"
601         return 1
602     fi
603
604     # Create additional chain directories manually
605     mkdir -p "$backup_dir/chain-20250101T120000Z"
606     touch "$backup_dir/chain-20250101T120000Z/base.sql"
607     touch "$backup_dir/chain-20250101T120000Z/globals.sql"
608     echo '{}' > "$backup_dir/chain-20250101T120000Z/metadata.json"
609
610     mkdir -p "$backup_dir/chain-20250102T120000Z"
611     touch "$backup_dir/chain-20250102T120000Z/base.sql"
612     touch "$backup_dir/chain-20250102T120000Z/globals.sql"
613     echo '{}' > "$backup_dir/chain-20250102T120000Z/metadata.json"
614
615     # Check status
616     local output_file="$TEST_DIR/multi_chains_output.txt"
617     "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -f "$backup_dir" -U "$PGUSER" &>"$output_file" || true
618
619     # Count chains displayed - should be 3
620     local chain_count
621     chain_count=$(grep -c "chain-" "$output_file" || echo 0)
622
623     if [[ $chain_count -lt 3 ]]; then
624         log_fail "Expected at least 3 chains in output, found $chain_count"
625         cat "$output_file" >&2
626         return 1
627     fi
628
629     # Verify chains are sorted (check that chain-20250101 comes before chain-20250102)
630     if ! grep -A5 "chain-20250101" "$output_file" | grep -q "chain-20250102"; then
631         log_fail "Chains should be displayed in sorted order"
632         cat "$output_file" >&2
633         return 1
634     fi
635
636     log_pass "Multiple chains displayed correctly"
637     return 0
638 }
639
640 #
641 # Cleanup
642 #
643
644 # shellcheck disable=SC2317  # Function called via trap handler
645 cleanup() {
646     log_info "Cleaning up test resources..."
647
648     # Kill any running pg_scribe processes
649     for pid in "${PIDS_TO_CLEANUP[@]}"; do
650         if kill -0 "$pid" 2>/dev/null; then
651             log_info "Stopping pg_scribe process $pid"
652             # Try graceful shutdown first (allows signal forwarding to child processes)
653             kill -TERM "$pid" 2>/dev/null || true
654
655             # Wait briefly for graceful shutdown
656             local timeout=2
657             local count=0
658             while kill -0 "$pid" 2>/dev/null && [[ $count -lt $timeout ]]; do
659                 sleep 0.5
660                 ((count++))
661             done
662
663             # Force kill if still running
664             if kill -0 "$pid" 2>/dev/null; then
665                 log_info "Force killing pg_scribe process $pid"
666                 kill -9 "$pid" 2>/dev/null || true
667             fi
668         fi
669     done
670
671     # Wait for child pg_recvlogical processes to fully terminate
672     # (They may take a moment to shut down after parent terminates)
673     sleep 1
674
675     # Drop replication slots
676     for dbname in "${DATABASES_TO_CLEANUP[@]}"; do
677         for slot in test_slot_noslot test_slot_healthy test_slot_with_dir test_slot_inactive test_slot_streaming test_slot_with_inc test_slot_with_diffs test_slot_no_dir test_slot_lag test_slot_multi_chains; do
678             drop_replication_slot "$dbname" "$slot" 2>/dev/null || true
679         done
680     done
681
682     # Drop databases
683     for dbname in "${DATABASES_TO_CLEANUP[@]}"; do
684         drop_test_db "$dbname"
685     done
686
687     # Remove test directory
688     if [[ -d "$TEST_DIR" ]]; then
689         rm -rf "$TEST_DIR"
690     fi
691
692     log_info "Cleanup complete"
693 }
694
695 #
696 # Main test runner
697 #
698
699 main() {
700     echo "========================================"
701     echo "pg_scribe --status Test Suite"
702     echo "========================================"
703     echo ""
704
705     # Verify pg_scribe exists
706     if [[ ! -x "$PG_SCRIBE" ]]; then
707         echo "ERROR: pg_scribe not found or not executable: $PG_SCRIBE"
708         exit 1
709     fi
710
711     # Verify PostgreSQL is running
712     if ! psql -U "$PGUSER" -d postgres -c "SELECT 1;" &>/dev/null; then
713         echo "ERROR: Cannot connect to PostgreSQL"
714         exit 1
715     fi
716
717     # Verify wal_level is logical
718     local wal_level
719     wal_level=$(psql -U "$PGUSER" -d postgres -tAq -c "SHOW wal_level;")
720     if [[ "$wal_level" != "logical" ]]; then
721         echo "ERROR: wal_level must be 'logical', currently: $wal_level"
722         echo "Update ~/.pgenv/pgsql/data/postgresql.conf and restart PostgreSQL"
723         exit 1
724     fi
725
726     # Create test directory
727     mkdir -p "$TEST_DIR"
728
729     # Set up cleanup trap
730     trap cleanup EXIT INT TERM
731
732     echo "Running tests..."
733     echo ""
734
735     # Run all tests (use || true to prevent set -e from exiting)
736     test_status_without_slot || true
737     test_status_basic_healthy || true
738     test_status_with_backup_directory || true
739     test_status_inactive_slot || true
740     test_status_with_active_streaming || true
741     test_status_with_incremental_backups || true
742     test_status_with_sealed_differentials || true
743     test_status_nonexistent_backup_dir || true
744     test_status_lag_display || true
745     test_status_multiple_chains || true
746
747     # Summary
748     echo ""
749     echo "========================================"
750     echo "Test Results"
751     echo "========================================"
752     echo "Tests run:    $TESTS_RUN"
753     echo -e "Tests passed: ${GREEN}$TESTS_PASSED${NC}"
754     echo -e "Tests failed: ${RED}$TESTS_FAILED${NC}"
755     echo ""
756
757     if [[ $TESTS_FAILED -eq 0 ]]; then
758         echo -e "${GREEN}All tests passed!${NC}"
759         exit 0
760     else
761         echo -e "${RED}Some tests failed!${NC}"
762         exit 1
763     fi
764 }
765
766 main "$@"