3 # Test suite for pg_scribe --status command
6 # - Creates temporary test databases
7 # - Tests various --status scenarios
8 # - Verifies status reporting and health checks
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_status_$$"
25 TEST_DB_PREFIX="pg_scribe_test_status_$$"
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")
88 # shellcheck disable=SC2317 # Function called from cleanup trap handler
91 log_info "Dropping test database: $dbname"
93 # Terminate connections
94 psql -U "$PGUSER" -d postgres -c "
95 SELECT pg_terminate_backend(pid)
97 WHERE datname = '$dbname' AND pid <> pg_backend_pid();
101 psql -U "$PGUSER" -d postgres -c "DROP DATABASE IF EXISTS $dbname;" &>/dev/null || true
104 # shellcheck disable=SC2317 # Function called from cleanup trap handler
105 drop_replication_slot() {
108 log_info "Dropping replication slot: $slot"
110 # Check if slot exists
112 exists=$(query_db "$dbname" "
113 SELECT 1 FROM pg_replication_slots WHERE slot_name = '$slot';
116 if [[ -n "$exists" ]]; then
118 query_db "$dbname" "SELECT pg_drop_replication_slot('$slot');" || true
122 check_slot_exists() {
126 exists=$(query_db "$dbname" "
127 SELECT 1 FROM pg_replication_slots WHERE slot_name = '$slot';
132 create_table_with_pk() {
136 CREATE TABLE $table (
137 id SERIAL PRIMARY KEY,
139 created_at TIMESTAMP DEFAULT now()
144 initialize_backup_system() {
147 local backup_dir="$3"
149 # Create backup directory
150 mkdir -p "$backup_dir"
152 # Initialize (exit on failure - this is critical for tests)
154 init_output=$("$PG_SCRIBE" --init -d "$dbname" -f "$backup_dir" -S "$slot" -U "$PGUSER" 2>&1)
157 if [[ $init_exit -ne 0 && $init_exit -ne 10 ]]; then
158 log_info "ERROR: Failed to initialize backup system for $dbname (exit=$init_exit)"
159 echo "$init_output" >&2
169 test_status_without_slot() {
171 log_test "Status check when slot doesn't exist (should fail)"
173 local dbname="${TEST_DB_PREFIX}_noslot"
174 local slot="test_slot_noslot"
176 # Setup - create db but DON'T initialize
177 create_test_db "$dbname"
178 create_table_with_pk "$dbname" "users"
180 # Try to check status - should fail with exit code 3 (slot error)
182 "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -U "$PGUSER" &>/dev/null || exit_code=$?
184 if [[ $exit_code -eq 3 ]]; then
185 log_pass "Correctly failed with slot error"
188 log_fail "Expected exit code 3, got $exit_code"
193 test_status_basic_healthy() {
195 log_test "Basic status check (without backup directory)"
197 local dbname="${TEST_DB_PREFIX}_healthy"
198 local slot="test_slot_healthy"
199 local backup_dir="$TEST_DIR/healthy"
202 create_test_db "$dbname"
203 create_table_with_pk "$dbname" "users"
204 initialize_backup_system "$dbname" "$slot" "$backup_dir"
206 # Check status WITHOUT backup directory - should succeed
207 # (warnings about inactive slot, but exit code 10 is acceptable)
208 local output_file="$TEST_DIR/healthy_output.txt"
210 "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -U "$PGUSER" &>"$output_file" || exit_code=$?
212 # Exit code 0 or 10 (warning about inactive slot) are acceptable
213 if [[ $exit_code -ne 0 && $exit_code -ne 10 ]]; then
214 log_fail "Status check failed with unexpected exit code $exit_code"
218 # Verify output contains expected information
219 if ! grep -q "Slot Name:" "$output_file"; then
220 log_fail "Output missing slot name"
224 if ! grep -q "Active:" "$output_file"; then
225 log_fail "Output missing active status"
229 if ! grep -q "Restart Lag:" "$output_file"; then
230 log_fail "Output missing lag information"
234 log_pass "Status check successful"
238 test_status_with_backup_directory() {
240 log_test "Status check with backup directory analysis"
242 local dbname="${TEST_DB_PREFIX}_with_dir"
243 local slot="test_slot_with_dir"
244 local backup_dir="$TEST_DIR/with_dir"
247 create_test_db "$dbname"
248 create_table_with_pk "$dbname" "users"
249 initialize_backup_system "$dbname" "$slot" "$backup_dir"
251 # Check status with backup directory
252 local output_file="$TEST_DIR/with_dir_output.txt"
254 "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -f "$backup_dir" -U "$PGUSER" &>"$output_file" || exit_code=$?
256 # Exit code 0 or 10 (warning) are both acceptable here
257 # (warning because no incremental backups exist yet)
258 if [[ $exit_code -ne 0 && $exit_code -ne 10 ]]; then
259 log_fail "Status check failed with unexpected exit code $exit_code"
263 # Verify backup directory analysis appears in output
264 if ! grep -q "Backup Directory Analysis" "$output_file"; then
265 log_fail "Output missing backup directory analysis"
269 if ! grep -q "Base Backups:" "$output_file"; then
270 log_fail "Output missing base backup count"
274 if ! grep -q "Metadata File:" "$output_file"; then
275 log_fail "Output missing metadata file status"
279 log_pass "Backup directory analysis works correctly"
283 test_status_inactive_slot() {
285 log_test "Status check with inactive slot (should warn)"
287 local dbname="${TEST_DB_PREFIX}_inactive"
288 local slot="test_slot_inactive"
289 local backup_dir="$TEST_DIR/inactive"
292 create_test_db "$dbname"
293 create_table_with_pk "$dbname" "users"
294 initialize_backup_system "$dbname" "$slot" "$backup_dir"
296 # Slot exists but is inactive (no pg_recvlogical running)
297 # Check status - should succeed but with warning
298 local output_file="$TEST_DIR/inactive_output.txt"
300 "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -U "$PGUSER" &>"$output_file" || exit_code=$?
302 # Should exit with warning code (10)
303 if [[ $exit_code -ne 10 ]]; then
304 log_fail "Expected exit code 10 (warning), got $exit_code"
308 # Should mention inactive status
309 if ! grep -qi "not active" "$output_file"; then
310 log_fail "Output should mention inactive slot"
314 log_pass "Inactive slot warning works correctly"
318 test_status_with_active_streaming() {
320 log_test "Status check with active streaming"
322 local dbname="${TEST_DB_PREFIX}_streaming"
323 local slot="test_slot_streaming"
324 local backup_dir="$TEST_DIR/streaming"
325 local output_file="$TEST_DIR/streaming_incremental.sql"
328 create_test_db "$dbname"
329 create_table_with_pk "$dbname" "users"
330 initialize_backup_system "$dbname" "$slot" "$backup_dir"
332 # Start streaming in background
333 "$PG_SCRIBE" --start -d "$dbname" -f "$output_file" -S "$slot" -U "$PGUSER" -s 1 -F 1 &>/dev/null &
334 local pg_scribe_pid=$!
335 PIDS_TO_CLEANUP+=("$pg_scribe_pid")
337 # Give it a moment to activate
340 # Check status - slot should now be active
341 local status_output="$TEST_DIR/streaming_status.txt"
343 "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -U "$PGUSER" &>"$status_output" || exit_code=$?
346 kill -INT "$pg_scribe_pid" 2>/dev/null || true
349 # Force kill if still running
350 if kill -0 "$pg_scribe_pid" 2>/dev/null; then
351 kill -9 "$pg_scribe_pid" 2>/dev/null || true
353 wait "$pg_scribe_pid" 2>/dev/null || true
355 # Verify status showed active slot
356 if ! grep -q "Active:.*Yes" "$status_output"; then
357 log_fail "Slot should be shown as active"
361 log_pass "Active slot detected correctly"
365 test_status_with_incremental_backups() {
367 log_test "Status check with incremental backup files"
369 local dbname="${TEST_DB_PREFIX}_with_inc"
370 local slot="test_slot_with_inc"
371 local backup_dir="$TEST_DIR/with_inc"
372 local output_file="$backup_dir/incremental.sql"
375 create_test_db "$dbname"
376 create_table_with_pk "$dbname" "users"
377 initialize_backup_system "$dbname" "$slot" "$backup_dir"
379 # Start streaming to create incremental backup
380 "$PG_SCRIBE" --start -d "$dbname" -f "$output_file" -S "$slot" -U "$PGUSER" -s 1 -F 1 &>/dev/null &
381 local pg_scribe_pid=$!
382 PIDS_TO_CLEANUP+=("$pg_scribe_pid")
384 # Give it a moment to start
387 # Make some changes to generate incremental data
388 query_db "$dbname" "INSERT INTO users (name) VALUES ('Test User');"
390 # Give it time to flush
394 kill -INT "$pg_scribe_pid" 2>/dev/null || true
397 # Force kill if still running
398 if kill -0 "$pg_scribe_pid" 2>/dev/null; then
399 kill -9 "$pg_scribe_pid" 2>/dev/null || true
401 wait "$pg_scribe_pid" 2>/dev/null || true
403 # Check status with backup directory
404 local status_output="$TEST_DIR/with_inc_status.txt"
406 "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -f "$backup_dir" -U "$PGUSER" &>"$status_output" || exit_code=$?
408 # Should succeed (exit code 0) since we have backups now
409 if [[ $exit_code -ne 0 ]]; then
410 log_fail "Status check failed with exit code $exit_code"
414 # Verify incremental files are reported
415 if ! grep -q "Incremental Files:" "$status_output"; then
416 log_fail "Output missing incremental file count"
420 # Should show at least 1 incremental file
421 if grep -q "Incremental Files:.*0" "$status_output"; then
422 log_fail "Should report at least 1 incremental file"
426 log_pass "Incremental backup files reported correctly"
430 test_status_missing_metadata() {
432 log_test "Status check with missing metadata file (should warn)"
434 local dbname="${TEST_DB_PREFIX}_no_meta"
435 local slot="test_slot_no_meta"
436 local backup_dir="$TEST_DIR/no_meta"
439 create_test_db "$dbname"
440 create_table_with_pk "$dbname" "users"
441 initialize_backup_system "$dbname" "$slot" "$backup_dir"
443 # Remove metadata file
444 rm -f "$backup_dir/pg_scribe_metadata.txt"
447 local output_file="$TEST_DIR/no_meta_output.txt"
449 "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -f "$backup_dir" -U "$PGUSER" &>"$output_file" || exit_code=$?
451 # Should exit with warning (10)
452 if [[ $exit_code -ne 10 ]]; then
453 log_fail "Expected exit code 10 (warning), got $exit_code"
457 # Should warn about missing metadata
458 if ! grep -qi "metadata.*not found" "$output_file"; then
459 log_fail "Should warn about missing metadata file"
463 log_pass "Missing metadata warning works correctly"
467 test_status_nonexistent_backup_dir() {
469 log_test "Status check with nonexistent backup directory"
471 local dbname="${TEST_DB_PREFIX}_no_dir"
472 local slot="test_slot_no_dir"
473 local backup_dir="$TEST_DIR/no_dir"
474 local fake_dir="$TEST_DIR/does_not_exist"
476 # Setup - initialize but check status with different directory
477 create_test_db "$dbname"
478 create_table_with_pk "$dbname" "users"
479 initialize_backup_system "$dbname" "$slot" "$backup_dir"
481 # Check status with nonexistent directory
482 local output_file="$TEST_DIR/no_dir_output.txt"
484 "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -f "$fake_dir" -U "$PGUSER" &>"$output_file" || exit_code=$?
486 # Should exit with warning (10)
487 if [[ $exit_code -ne 10 ]]; then
488 log_fail "Expected exit code 10 (warning), got $exit_code"
492 # Should warn about nonexistent directory
493 if ! grep -qi "does not exist" "$output_file"; then
494 log_fail "Should warn about nonexistent directory"
498 log_pass "Nonexistent directory warning works correctly"
502 test_status_lag_display() {
504 log_test "Status displays lag information correctly"
506 local dbname="${TEST_DB_PREFIX}_lag"
507 local slot="test_slot_lag"
508 local backup_dir="$TEST_DIR/lag"
511 create_test_db "$dbname"
512 create_table_with_pk "$dbname" "users"
514 if ! initialize_backup_system "$dbname" "$slot" "$backup_dir"; then
515 log_fail "Failed to initialize backup system"
520 local output_file="$TEST_DIR/lag_output.txt"
521 "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -U "$PGUSER" &>"$output_file" || true
523 # Verify lag is displayed - use case-insensitive grep for ANSI color codes
524 if ! grep -i "restart.*lag" "$output_file"; then
525 log_fail "Output missing restart lag"
526 cat "$output_file" >&2
530 if ! grep -i "confirmed.*lag" "$output_file"; then
531 log_fail "Output missing confirmed lag"
532 cat "$output_file" >&2
536 # Verify lag values are in MB
537 if ! grep "MB" "$output_file"; then
538 log_fail "Lag should be displayed in MB"
539 cat "$output_file" >&2
543 log_pass "Lag information displayed correctly"
547 test_status_multiple_base_backups() {
549 log_test "Status with multiple base backups shows latest"
551 local dbname="${TEST_DB_PREFIX}_multi_base"
552 local slot="test_slot_multi_base"
553 local backup_dir="$TEST_DIR/multi_base"
556 create_test_db "$dbname"
557 create_table_with_pk "$dbname" "users"
559 if ! initialize_backup_system "$dbname" "$slot" "$backup_dir"; then
560 log_fail "Failed to initialize backup system"
564 # Create additional base backup files manually
566 touch "$backup_dir/base-20250101-000000.sql"
568 touch "$backup_dir/base-20250102-000000.sql"
571 local output_file="$TEST_DIR/multi_base_output.txt"
572 "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -f "$backup_dir" -U "$PGUSER" &>"$output_file" || true
574 # Count base backups - should be 3
576 count=$(find "$backup_dir" -maxdepth 1 -name 'base-*.sql' 2>/dev/null | wc -l)
578 # Should report multiple base backups (at least 3)
579 if ! grep -q "Base Backups:" "$output_file"; then
580 log_fail "Output missing base backup count"
581 cat "$output_file" >&2
585 # Check that we have at least 3 base backups
586 if [[ $count -lt 3 ]]; then
587 log_fail "Expected at least 3 base backups, found $count"
588 ls -la "$backup_dir" >&2
592 # Should show latest base backup
593 if ! grep -q "Latest Base:" "$output_file"; then
594 log_fail "Should show latest base backup"
595 cat "$output_file" >&2
599 log_pass "Multiple base backups handled correctly"
607 # shellcheck disable=SC2317 # Function called via trap handler
609 log_info "Cleaning up test resources..."
611 # Kill any running pg_scribe processes
612 for pid in "${PIDS_TO_CLEANUP[@]}"; do
613 if kill -0 "$pid" 2>/dev/null; then
614 log_info "Stopping pg_scribe process $pid"
615 # Try graceful shutdown first (allows signal forwarding to child processes)
616 kill -TERM "$pid" 2>/dev/null || true
618 # Wait briefly for graceful shutdown
621 while kill -0 "$pid" 2>/dev/null && [[ $count -lt $timeout ]]; do
626 # Force kill if still running
627 if kill -0 "$pid" 2>/dev/null; then
628 log_info "Force killing pg_scribe process $pid"
629 kill -9 "$pid" 2>/dev/null || true
634 # Wait for child pg_recvlogical processes to fully terminate
635 # (They may take a moment to shut down after parent terminates)
638 # Drop replication slots
639 for dbname in "${DATABASES_TO_CLEANUP[@]}"; do
640 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_no_meta test_slot_no_dir test_slot_lag test_slot_multi_base; do
641 drop_replication_slot "$dbname" "$slot" 2>/dev/null || true
646 for dbname in "${DATABASES_TO_CLEANUP[@]}"; do
647 drop_test_db "$dbname"
650 # Remove test directory
651 if [[ -d "$TEST_DIR" ]]; then
655 log_info "Cleanup complete"
663 echo "========================================"
664 echo "pg_scribe --status Test Suite"
665 echo "========================================"
668 # Verify pg_scribe exists
669 if [[ ! -x "$PG_SCRIBE" ]]; then
670 echo "ERROR: pg_scribe not found or not executable: $PG_SCRIBE"
674 # Verify PostgreSQL is running
675 if ! psql -U "$PGUSER" -d postgres -c "SELECT 1;" &>/dev/null; then
676 echo "ERROR: Cannot connect to PostgreSQL"
680 # Verify wal_level is logical
682 wal_level=$(psql -U "$PGUSER" -d postgres -tAq -c "SHOW wal_level;")
683 if [[ "$wal_level" != "logical" ]]; then
684 echo "ERROR: wal_level must be 'logical', currently: $wal_level"
685 echo "Update ~/.pgenv/pgsql/data/postgresql.conf and restart PostgreSQL"
689 # Create test directory
692 # Set up cleanup trap
693 trap cleanup EXIT INT TERM
695 echo "Running tests..."
698 # Run all tests (use || true to prevent set -e from exiting)
699 test_status_without_slot || true
700 test_status_basic_healthy || true
701 test_status_with_backup_directory || true
702 test_status_inactive_slot || true
703 test_status_with_active_streaming || true
704 test_status_with_incremental_backups || true
705 test_status_missing_metadata || true
706 test_status_nonexistent_backup_dir || true
707 test_status_lag_display || true
708 test_status_multiple_base_backups || true
712 echo "========================================"
714 echo "========================================"
715 echo "Tests run: $TESTS_RUN"
716 echo -e "Tests passed: ${GREEN}$TESTS_PASSED${NC}"
717 echo -e "Tests failed: ${RED}$TESTS_FAILED${NC}"
720 if [[ $TESTS_FAILED -eq 0 ]]; then
721 echo -e "${GREEN}All tests passed!${NC}"
724 echo -e "${RED}Some tests failed!${NC}"