#!/usr/bin/env python3 """Tests for scripts/reset_pipeline_state.py — 10 tests.""" import json import os import sys import tempfile from datetime import datetime, timezone, timedelta sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from reset_pipeline_state import reset_pipeline_state, classify_stale, parse_timestamp def test_no_state_file(): """Reset on missing file returns empty.""" state, removed = reset_pipeline_state("/nonexistent/pipeline_state.json") assert state == {} assert removed == [] print("PASS: test_no_state_file") def test_empty_state(): """Empty JSON object is untouched.""" with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f: json.dump({}, f) path = f.name try: state, removed = reset_pipeline_state(path) assert state == {} assert removed == [] finally: os.unlink(path) print("PASS: test_empty_state") def test_fresh_complete_kept(): """Recent complete entry is kept.""" now = datetime.now(timezone.utc) entry = {"state": "complete", "updated": now.isoformat()} with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f: json.dump({"my-pipeline": entry}, f) path = f.name try: state, removed = reset_pipeline_state(path) assert "my-pipeline" in state assert removed == [] finally: os.unlink(path) print("PASS: test_fresh_complete_kept") def test_old_complete_removed(): """Complete entry older than 24h is removed.""" old = (datetime.now(timezone.utc) - timedelta(hours=30)).isoformat() entry = {"state": "complete", "updated": old} with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f: json.dump({"old-pipeline": entry}, f) path = f.name try: state, removed = reset_pipeline_state(path) assert "old-pipeline" not in state assert len(removed) == 1 assert "old-pipeline" in removed[0] finally: os.unlink(path) print("PASS: test_old_complete_removed") def test_stuck_running_removed(): """Running entry older than 6h is treated as stuck and removed.""" old = (datetime.now(timezone.utc) - timedelta(hours=10)).isoformat() entry = {"state": "running", "updated": old} with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f: json.dump({"stuck-pipeline": entry}, f) path = f.name try: state, removed = reset_pipeline_state(path) assert "stuck-pipeline" not in state assert len(removed) == 1 finally: os.unlink(path) print("PASS: test_stuck_running_removed") def test_old_failed_removed(): """Failed entry older than 24h is removed.""" old = (datetime.now(timezone.utc) - timedelta(hours=48)).isoformat() entry = {"state": "failed", "updated": old} with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f: json.dump({"failed-pipeline": entry}, f) path = f.name try: state, removed = reset_pipeline_state(path) assert "failed-pipeline" not in state finally: os.unlink(path) print("PASS: test_old_failed_removed") def test_running_kept_if_fresh(): """Fresh running entry is kept.""" now = datetime.now(timezone.utc) entry = {"state": "running", "updated": now.isoformat()} with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f: json.dump({"active-pipeline": entry}, f) path = f.name try: state, removed = reset_pipeline_state(path) assert "active-pipeline" in state assert removed == [] finally: os.unlink(path) print("PASS: test_running_kept_if_fresh") def test_dry_run_does_not_modify(): """Dry run reports removals but doesn't change the file.""" old = (datetime.now(timezone.utc) - timedelta(hours=30)).isoformat() content = json.dumps({"old-pipeline": {"state": "complete", "updated": old}}) with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f: f.write(content) path = f.name try: state, removed = reset_pipeline_state(path, dry_run=True) assert "old-pipeline" not in state assert len(removed) == 1 # File should be unchanged with open(path) as f: file_state = json.load(f) assert "old-pipeline" in file_state finally: os.unlink(path) print("PASS: test_dry_run_does_not_modify") def test_mixed_entries(): """Mix of fresh and stale entries — only stale removed.""" now = datetime.now(timezone.utc) old = (now - timedelta(hours=30)).isoformat() state_data = { "fresh-complete": {"state": "complete", "updated": now.isoformat()}, "stale-complete": {"state": "complete", "updated": old}, "fresh-running": {"state": "running", "updated": now.isoformat()}, "stuck-running": {"state": "running", "updated": (now - timedelta(hours=10)).isoformat()}, } with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f: json.dump(state_data, f) path = f.name try: state, removed = reset_pipeline_state(path) assert "fresh-complete" in state assert "fresh-running" in state assert "stale-complete" not in state assert "stuck-running" not in state assert len(removed) == 2 finally: os.unlink(path) print("PASS: test_mixed_entries") def test_corrupted_entry_removed(): """Non-dict entries are removed.""" with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f: json.dump({"broken": "not_a_dict", "also-bad": 42}, f) path = f.name try: state, removed = reset_pipeline_state(path) assert "broken" not in state assert "also-bad" not in state finally: os.unlink(path) print("PASS: test_corrupted_entry_removed") def run_all(): test_no_state_file() test_empty_state() test_fresh_complete_kept() test_old_complete_removed() test_stuck_running_removed() test_old_failed_removed() test_running_kept_if_fresh() test_dry_run_does_not_modify() test_mixed_entries() test_corrupted_entry_removed() print("\nAll 10 tests passed!") if __name__ == "__main__": run_all()