import pytest import requests from unittest.mock import MagicMock, patch import threading import time import json # Import HookServer from api_hooks.py from api_hooks import HookServer # No need for HookServerInstance, HookHandler here from api_hook_client import ApiHookClient @pytest.fixture(scope="module") def hook_server_fixture(): # Mock the 'app' object that HookServer expects mock_app = MagicMock() mock_app.test_hooks_enabled = True # Essential for the server to start mock_app.project = {'name': 'test_project'} mock_app.disc_entries = [{'role': 'user', 'content': 'hello'}] mock_app._pending_gui_tasks = [] mock_app._pending_gui_tasks_lock = threading.Lock() # Use an ephemeral port (0) to avoid conflicts server = HookServer(mock_app, port=0) server.start() # Wait a moment for the server thread to start and bind time.sleep(0.1) # Get the actual port assigned by the OS actual_port = server.server.server_address[1] # Update the base_url for the client to use the actual port client_base_url = f"http://127.0.0.1:{actual_port}" yield client_base_url, mock_app # Yield the base URL and the mock_app server.stop() def test_get_status_success(hook_server_fixture): """ Test that get_status successfully retrieves the server status when the HookServer is running. This is the 'Green Phase'. """ base_url, _ = hook_server_fixture client = ApiHookClient(base_url=base_url) status = client.get_status() assert status == {'status': 'ok'} def test_get_project_success(hook_server_fixture): """ Test successful retrieval of project data. """ base_url, mock_app = hook_server_fixture client = ApiHookClient(base_url=base_url) project = client.get_project() assert project == {'project': mock_app.project} def test_post_project_success(hook_server_fixture): """Test successful posting and updating of project data.""" base_url, mock_app = hook_server_fixture client = ApiHookClient(base_url=base_url) new_project_data = {'name': 'updated_project', 'version': '1.0'} response = client.post_project(new_project_data) assert response == {'status': 'updated'} # Verify that the mock_app.project was updated. Note: the mock_app is reused. # The actual server state is in the real app, but for testing client, we check mock. # This part depends on how the actual server modifies the app.project. # For HookHandler, it does `app.project = data.get('project', app.project)` # So, the mock_app.project will actually be the *old* value, because the mock_app # is not the real app instance. This test is primarily for the client-server interaction. # To test the side effect on app.project, one would need to inspect the server's app instance, # which is not directly exposed by the fixture in a simple way. # For now, we focus on the client's ability to send and receive the success status. def test_get_session_success(hook_server_fixture): """ Test successful retrieval of session data. """ base_url, mock_app = hook_server_fixture client = ApiHookClient(base_url=base_url) session = client.get_session() assert session == {'session': {'entries': mock_app.disc_entries}} def test_post_session_success(hook_server_fixture): """ Test successful posting and updating of session data. """ base_url, mock_app = hook_server_fixture client = ApiHookClient(base_url=base_url) new_session_entries = [{'role': 'agent', 'content': 'hi'}] response = client.post_session(new_session_entries) assert response == {'status': 'updated'} # Similar note as post_project about mock_app.disc_entries not being updated here. def test_post_gui_success(hook_server_fixture): """ Test successful posting of GUI data. """ base_url, mock_app = hook_server_fixture client = ApiHookClient(base_url=base_url) gui_data = {'command': 'set_text', 'id': 'some_item', 'value': 'new_text'} response = client.post_gui(gui_data) assert response == {'status': 'queued'} assert mock_app._pending_gui_tasks == [gui_data] # This should be updated by the server logic. def test_get_status_connection_error_handling(): """ Test that ApiHookClient correctly handles a connection error. """ client = ApiHookClient(base_url="http://127.0.0.1:1") # Use a port that is highly unlikely to be listening with pytest.raises(requests.exceptions.Timeout): client.get_status() def test_post_project_server_error_handling(hook_server_fixture): """ Test that ApiHookClient correctly handles a server-side error (e.g., 500). This requires mocking the server\'s response within the fixture or a specific test. For simplicity, we\'ll simulate this by causing the HookHandler to raise an exception for a specific path, but that\'s complex with the current fixture. A simpler way for client-side testing is to mock the requests call directly for this scenario. """ base_url, _ = hook_server_fixture client = ApiHookClient(base_url=base_url) with patch('requests.post') as mock_post: mock_response = MagicMock() mock_response.status_code = 500 mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError("500 Server Error", response=mock_response) mock_response.text = "Internal Server Error" mock_post.return_value = mock_response with pytest.raises(requests.exceptions.HTTPError) as excinfo: client.post_project({'name': 'error_project'}) assert "HTTP error 500" in str(excinfo.value) def test_unsupported_method_error(): """ Test that calling an unsupported HTTP method raises a ValueError. """ client = ApiHookClient() with pytest.raises(ValueError, match="Unsupported HTTP method"): client._make_request('PUT', '/some_endpoint', data={'key': 'value'})