diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..7fed8df --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,254 @@ +"""Tests for the CLI module.""" + +import json +import os +import tempfile +from unittest.mock import MagicMock, patch + +import pytest +from click.testing import CliRunner + +from api_snapshot.cli.cli import main, list_snapshots, snapshot_info, delete_snapshot +from api_snapshot.cli.record import record_command, record_multi_command +from api_snapshot.cli.serve import serve_command, serve_info_command +from api_snapshot.snapshot.manager import SnapshotManager + + +class TestMainCommand: + """Tests for main command group.""" + + def test_main_help(self): + """Test main command help.""" + runner = CliRunner() + + result = runner.invoke(main, ["--help"]) + + assert result.exit_code == 0 + assert "API Snapshot CLI" in result.output + assert "record" in result.output + assert "serve" in result.output + assert "list" in result.output + + def test_main_version(self): + """Test main command version.""" + runner = CliRunner() + + result = runner.invoke(main, ["--version"]) + + assert result.exit_code == 0 + assert "0.1.0" in result.output + + +class TestListCommand: + """Tests for list command.""" + + def test_list_empty(self, temp_dir): + """Test listing with no snapshots.""" + runner = CliRunner() + + result = runner.invoke(list_snapshots, obj={"snapshot_dir": temp_dir}) + + assert result.exit_code == 0 + assert "No snapshots found" in result.output + + def test_list_with_snapshots(self, temp_dir): + """Test listing with snapshots.""" + from api_snapshot.recorder.recorder import RecordedRequest, RecordedResponse, RequestResponsePair + + manager = SnapshotManager(temp_dir) + + req = RecordedRequest( + method="GET", + url="https://api.example.com", + headers={}, + body=None + ) + resp = RecordedResponse( + status_code=200, + headers={}, + body="{}", + latency_ms=100 + ) + pair = RequestResponsePair(request=req, response=resp) + + manager.save_snapshot("test-snap", requests=[pair]) + + runner = CliRunner() + + result = runner.invoke(list_snapshots, obj={"snapshot_dir": temp_dir}) + + assert result.exit_code == 0 + assert "test-snap" in result.output + + +class TestSnapshotInfoCommand: + """Tests for info command.""" + + def test_info_nonexistent(self, temp_dir): + """Test info for non-existent snapshot.""" + runner = CliRunner() + + result = runner.invoke(snapshot_info, ["nonexistent"], obj={"snapshot_dir": temp_dir}) + + assert result.exit_code != 0 + assert "not found" in result.output + + def test_info_existing(self, temp_dir): + """Test info for existing snapshot.""" + from api_snapshot.recorder.recorder import RecordedRequest, RecordedResponse, RequestResponsePair + + manager = SnapshotManager(temp_dir) + + req = RecordedRequest( + method="GET", + url="https://api.example.com/users", + headers={}, + body=None + ) + resp = RecordedResponse( + status_code=200, + headers={"Content-Type": "application/json"}, + body='{"users": []}', + latency_ms=100 + ) + pair = RequestResponsePair(request=req, response=resp) + + manager.save_snapshot( + "my-snapshot", + requests=[pair], + description="Test description" + ) + + runner = CliRunner() + + result = runner.invoke(snapshot_info, ["my-snapshot"], obj={"snapshot_dir": temp_dir}) + + assert result.exit_code == 0 + assert "my-snapshot" in result.output + assert "Test description" in result.output + + +class TestDeleteCommand: + """Tests for delete command.""" + + def test_delete_nonexistent(self, temp_dir): + """Test deleting non-existent snapshot.""" + runner = CliRunner() + + result = runner.invoke( + delete_snapshot, + ["nonexistent", "--force"], + obj={"snapshot_dir": temp_dir} + ) + + assert result.exit_code != 0 + assert "not found" in result.output + + def test_delete_with_confirmation(self, temp_dir): + """Test deleting with confirmation.""" + from api_snapshot.recorder.recorder import RecordedRequest, RecordedResponse, RequestResponsePair + + manager = SnapshotManager(temp_dir) + + req = RecordedRequest( + method="GET", + url="https://api.example.com", + headers={}, + body=None + ) + resp = RecordedResponse( + status_code=200, + headers={}, + body="{}", + latency_ms=50 + ) + pair = RequestResponsePair(request=req, response=resp) + + manager.save_snapshot("to-delete", requests=[pair]) + + runner = CliRunner() + + result = runner.invoke( + delete_snapshot, + ["to-delete", "--force"], + obj={"snapshot_dir": temp_dir} + ) + + assert result.exit_code == 0 + assert "Deleted" in result.output + + assert not manager.snapshot_exists("to-delete") + + +class TestServeCommand: + """Tests for serve command.""" + + def test_serve_nonexistent_snapshot(self, temp_dir): + """Test serving non-existent snapshot.""" + runner = CliRunner() + + result = runner.invoke( + serve_command, + ["nonexistent"], + obj={"snapshot_dir": temp_dir} + ) + + assert result.exit_code != 0 + assert "not found" in result.output + + def test_serve_info_nonexistent(self, temp_dir): + """Test serve-info for non-existent snapshot.""" + runner = CliRunner() + + result = runner.invoke( + serve_info_command, + ["nonexistent"], + obj={"snapshot_dir": temp_dir} + ) + + assert result.exit_code != 0 + assert "not found" in result.output + + +class TestCLIIntegration: + """Integration tests for CLI.""" + + def test_cli_commands_chain(self, temp_dir): + """Test chaining CLI commands together.""" + from api_snapshot.recorder.recorder import RecordedRequest, RecordedResponse, RequestResponsePair + + runner = CliRunner() + + req = RecordedRequest( + method="GET", + url="https://api.example.com/test", + headers={}, + body=None + ) + resp = RecordedResponse( + status_code=200, + headers={"Content-Type": "application/json"}, + body='{"message": "hello"}', + latency_ms=50 + ) + pair = RequestResponsePair(request=req, response=resp) + + manager = SnapshotManager(temp_dir) + manager.save_snapshot("test-chain", requests=[pair]) + + list_result = runner.invoke( + list_snapshots, + obj={"snapshot_dir": temp_dir} + ) + + assert list_result.exit_code == 0 + assert "test-chain" in list_result.output + + info_result = runner.invoke( + snapshot_info, + ["test-chain"], + obj={"snapshot_dir": temp_dir} + ) + + assert info_result.exit_code == 0 + assert "test-chain" in info_result.output