3 # Test suite for pg_scribe --stop command
6 # - Creates temporary test databases
7 # - Tests various --stop 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=()
41 echo -e "${BLUE}TEST:${NC} $*"
45 echo -e "${GREEN}PASS:${NC} $*"
50 echo -e "${RED}FAIL:${NC} $*"
55 echo -e "${YELLOW}INFO:${NC} $*"
65 psql -U "$PGUSER" -d "$dbname" -tAq "$@"
71 run_psql "$dbname" -c "$query" 2>/dev/null || true
76 log_info "Creating test database: $dbname"
79 psql -U "$PGUSER" -d postgres -c "DROP DATABASE IF EXISTS $dbname;" &>/dev/null || true
82 psql -U "$PGUSER" -d postgres -c "CREATE DATABASE $dbname;" &>/dev/null
84 DATABASES_TO_CLEANUP+=("$dbname")
87 # shellcheck disable=SC2317 # Function called from cleanup trap handler
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 # shellcheck disable=SC2317 # Function called from cleanup trap handler
104 drop_replication_slot() {
107 log_info "Dropping replication slot: $slot"
109 # Check if slot exists
111 exists=$(query_db "$dbname" "
112 SELECT 1 FROM pg_replication_slots WHERE slot_name = '$slot';
115 if [[ -n "$exists" ]]; then
117 query_db "$dbname" "SELECT pg_drop_replication_slot('$slot');" || true
121 create_table_with_pk() {
125 CREATE TABLE $table (
126 id SERIAL PRIMARY KEY,
128 created_at TIMESTAMP DEFAULT now()
133 # Initialize a backup directory (creates replication slot and initial backups)
134 init_backup_system() {
136 local backup_dir="$2"
139 mkdir -p "$backup_dir"
140 "$PG_SCRIBE" --init -d "$dbname" -f "$backup_dir" -S "$slot" -U "$PGUSER" &>/dev/null
143 # Start streaming in the background
144 start_streaming_background() {
146 local backup_dir="$2"
149 "$PG_SCRIBE" --start -d "$dbname" -f "$backup_dir" -S "$slot" -U "$PGUSER" &>/dev/null &
152 # Wait for pidfile to appear (instead of fixed sleep)
153 local pidfile="$backup_dir/.pg_scribe.pid"
157 while [[ ! -f "$pidfile" ]] && [[ $waited -lt $timeout ]]; do
159 waited=$((waited + 1))
162 # Verify process is still running
163 if ! kill -0 "$pid" 2>/dev/null; then
167 # Verify pidfile was created
168 if [[ ! -f "$pidfile" ]]; then
179 test_stop_requires_backup_dir() {
181 log_test "Stop requires backup directory"
184 "$PG_SCRIBE" --stop &>/dev/null || exit_code=$?
186 if [[ $exit_code -eq 5 ]]; then
187 log_pass "Correctly rejects missing backup directory"
190 log_fail "Expected exit code 5, got $exit_code"
195 test_stop_nonexistent_directory() {
197 log_test "Stop fails with nonexistent directory"
199 local backup_dir="$TEST_DIR/nonexistent"
202 "$PG_SCRIBE" --stop -f "$backup_dir" -U "$PGUSER" &>/dev/null || exit_code=$?
204 if [[ $exit_code -eq 4 ]]; then
205 log_pass "Correctly rejects nonexistent directory"
208 log_fail "Expected exit code 4, got $exit_code"
213 test_stop_no_active_process() {
215 log_test "Stop fails when no active process"
217 local dbname="${TEST_DB_PREFIX}_noprocess"
218 local backup_dir="$TEST_DIR/noprocess"
219 local slot="test_slot_noprocess"
221 # Setup - initialize but don't start
222 create_test_db "$dbname"
223 create_table_with_pk "$dbname" "users"
224 init_backup_system "$dbname" "$backup_dir" "$slot"
226 # Try to stop (should fail - no process running)
228 "$PG_SCRIBE" --stop -f "$backup_dir" -U "$PGUSER" &>/dev/null || exit_code=$?
230 if [[ $exit_code -eq 1 ]]; then
231 log_pass "Correctly fails when no process running"
234 log_fail "Expected exit code 1, got $exit_code"
239 test_stop_active_process() {
241 log_test "Stop successfully stops active streaming"
243 local dbname="${TEST_DB_PREFIX}_active"
244 local backup_dir="$TEST_DIR/active"
245 local slot="test_slot_active"
247 # Setup - initialize and start streaming
248 create_test_db "$dbname"
249 create_table_with_pk "$dbname" "users"
250 init_backup_system "$dbname" "$backup_dir" "$slot"
252 # Start streaming in background
254 pid=$(start_streaming_background "$dbname" "$backup_dir" "$slot")
256 if [[ -z "$pid" ]]; then
257 log_fail "Failed to start streaming"
262 if "$PG_SCRIBE" --stop -f "$backup_dir" -U "$PGUSER" &>/dev/null; then
263 # Verify process is actually stopped
265 if kill -0 "$pid" 2>/dev/null; then
266 log_fail "Process still running after stop"
267 kill -KILL "$pid" 2>/dev/null || true
271 # Verify pidfile was removed
272 local pidfile="$backup_dir/.pg_scribe.pid"
273 if [[ -f "$pidfile" ]]; then
274 log_fail "Pidfile not removed"
278 log_pass "Successfully stopped active streaming"
281 log_fail "Stop command failed"
282 kill -TERM "$pid" 2>/dev/null || true
287 test_stop_stale_pidfile() {
289 log_test "Stop handles stale pidfile gracefully"
291 local dbname="${TEST_DB_PREFIX}_stale"
292 local backup_dir="$TEST_DIR/stale"
293 local slot="test_slot_stale"
296 create_test_db "$dbname"
297 create_table_with_pk "$dbname" "users"
298 init_backup_system "$dbname" "$backup_dir" "$slot"
300 # Create stale pidfile (with non-existent PID)
301 local pidfile="$backup_dir/.pg_scribe.pid"
302 echo "99999" > "$pidfile"
304 # Stop should handle this gracefully (exit 0 and remove stale pidfile)
305 if "$PG_SCRIBE" --stop -f "$backup_dir" -U "$PGUSER" &>/dev/null; then
306 # Verify pidfile was removed
307 if [[ -f "$pidfile" ]]; then
308 log_fail "Stale pidfile not removed"
312 log_pass "Stale pidfile handled gracefully"
315 log_fail "Stop failed on stale pidfile"
324 # shellcheck disable=SC2317 # Function called via trap handler
326 log_info "Cleaning up test resources..."
328 # Kill any remaining pg_recvlogical processes
329 pkill -f "pg_recvlogical.*test_slot" 2>/dev/null || true
332 # Drop replication slots
333 for dbname in "${DATABASES_TO_CLEANUP[@]}"; do
334 for slot in test_slot_noprocess test_slot_active test_slot_stale; do
335 drop_replication_slot "$dbname" "$slot" 2>/dev/null || true
340 for dbname in "${DATABASES_TO_CLEANUP[@]}"; do
341 drop_test_db "$dbname"
344 # Remove test directory
345 if [[ -d "$TEST_DIR" ]]; then
349 log_info "Cleanup complete"
357 echo "========================================"
358 echo "pg_scribe --stop Test Suite"
359 echo "========================================"
362 # Verify pg_scribe exists
363 if [[ ! -x "$PG_SCRIBE" ]]; then
364 echo "ERROR: pg_scribe not found or not executable: $PG_SCRIBE"
368 # Verify PostgreSQL is running
369 if ! psql -U "$PGUSER" -d postgres -c "SELECT 1;" &>/dev/null; then
370 echo "ERROR: Cannot connect to PostgreSQL"
374 # Verify wal_level is logical
376 wal_level=$(psql -U "$PGUSER" -d postgres -tAq -c "SHOW wal_level;")
377 if [[ "$wal_level" != "logical" ]]; then
378 echo "ERROR: wal_level must be 'logical', currently: $wal_level"
379 echo "Update ~/.pgenv/pgsql/data/postgresql.conf and restart PostgreSQL"
383 # Create test directory
386 # Set up cleanup trap
387 trap cleanup EXIT INT TERM
389 echo "Running tests..."
392 # Run all tests (use || true to prevent set -e from exiting)
393 test_stop_requires_backup_dir || true
394 test_stop_nonexistent_directory || true
395 test_stop_no_active_process || true
396 test_stop_active_process || true
397 test_stop_stale_pidfile || true
401 echo "========================================"
403 echo "========================================"
404 echo "Tests run: $TESTS_RUN"
405 echo -e "Tests passed: ${GREEN}$TESTS_PASSED${NC}"
406 echo -e "Tests failed: ${RED}$TESTS_FAILED${NC}"
409 if [[ $TESTS_FAILED -eq 0 ]]; then
410 echo -e "${GREEN}All tests passed!${NC}"
413 echo -e "${RED}Some tests failed!${NC}"