1111from fastapi .testclient import TestClient
1212from fastapi .responses import JSONResponse
1313
14- from healthchain .gateway .api .app import create_app
14+ from healthchain .gateway .api .app import create_app , HealthChainAPI
1515from healthchain .gateway .api .dependencies import (
1616 get_app ,
1717 get_event_dispatcher ,
2222from healthchain .gateway .core .base import BaseGateway
2323
2424
25+ # Custom create_app function for testing
26+ def create_app_for_testing (enable_events = True , event_dispatcher = None , app_class = None ):
27+ """Create a test app with optional custom app class."""
28+ if app_class is None :
29+ # Use the default HealthChainAPI class
30+ return create_app (
31+ enable_events = enable_events , event_dispatcher = event_dispatcher
32+ )
33+
34+ # Use a custom app class
35+ app_config = {
36+ "title" : "Test HealthChain API" ,
37+ "description" : "Test API" ,
38+ "version" : "0.1.0" ,
39+ "docs_url" : "/docs" ,
40+ "redoc_url" : "/redoc" ,
41+ "enable_events" : enable_events ,
42+ "event_dispatcher" : event_dispatcher ,
43+ }
44+ return app_class (** app_config )
45+
46+
2547class MockGateway (BaseGateway ):
2648 """Mock gateway for testing."""
2749
@@ -71,7 +93,19 @@ def mock_gateway():
7193@pytest .fixture
7294def test_app (mock_event_dispatcher , mock_gateway ):
7395 """Create a test app with mocked dependencies."""
74- app = create_app (enable_events = True , event_dispatcher = mock_event_dispatcher )
96+
97+ # Create a test subclass that overrides _shutdown to avoid termination
98+ class SafeHealthChainAPI (HealthChainAPI ):
99+ def _shutdown (self ):
100+ # Override to avoid termination
101+ return JSONResponse (content = {"message" : "Server is shutting down..." })
102+
103+ # Create the app with the safe implementation
104+ app = create_app_for_testing (
105+ enable_events = True ,
106+ event_dispatcher = mock_event_dispatcher ,
107+ app_class = SafeHealthChainAPI ,
108+ )
75109 app .register_gateway (mock_gateway )
76110 return app
77111
@@ -84,8 +118,19 @@ def client(test_app):
84118
85119def test_app_creation ():
86120 """Test that the app can be created with custom dependencies."""
121+
122+ # Create a test subclass that overrides _shutdown to avoid termination
123+ class SafeHealthChainAPI (HealthChainAPI ):
124+ def _shutdown (self ):
125+ # Override to avoid termination
126+ return JSONResponse (content = {"message" : "Server is shutting down..." })
127+
87128 mock_dispatcher = MockEventDispatcher ()
88- app = create_app (enable_events = True , event_dispatcher = mock_dispatcher )
129+ app = create_app_for_testing (
130+ enable_events = True ,
131+ event_dispatcher = mock_dispatcher ,
132+ app_class = SafeHealthChainAPI ,
133+ )
89134
90135 assert app .get_event_dispatcher () is mock_dispatcher
91136 assert app .enable_events is True
@@ -197,104 +242,17 @@ def test_route():
197242 assert response .json () == {"message" : "Router test" }
198243
199244
200- def test_shutdown_endpoint (test_app , monkeypatch ):
201- """Test the shutdown endpoint."""
202- # Mock os.kill to prevent actual process termination
203- import os
204- import signal
205-
206- kill_called = False
207-
208- def mock_kill (pid , sig ):
209- nonlocal kill_called
210- kill_called = True
211- assert pid == os .getpid ()
212- assert sig == signal .SIGTERM
213-
214- monkeypatch .setattr (os , "kill" , mock_kill )
215-
216- # Test the shutdown endpoint
217- with TestClient (test_app ) as client :
218- response = client .get ("/shutdown" )
219- assert response .status_code == 200
220- assert response .json () == {"message" : "Server is shutting down..." }
221- assert kill_called
222-
223-
224- def test_lifespan_hooks (monkeypatch ):
225- """Test that lifespan hooks are called during app lifecycle."""
226- from healthchain .gateway .api .app import HealthChainAPI
227-
228- # Track if methods were called
229- startup_called = False
230- shutdown_called = False
231-
232- # Define mock methods
233- def mock_startup (self ):
234- nonlocal startup_called
235- startup_called = True
236-
237- def mock_shutdown (self ):
238- nonlocal shutdown_called
239- shutdown_called = True
240- return JSONResponse (content = {"message" : "Server is shutting down..." })
241-
242- # Apply mocks
243- monkeypatch .setattr (HealthChainAPI , "_startup" , mock_startup )
244- monkeypatch .setattr (HealthChainAPI , "_shutdown" , mock_shutdown )
245-
246- # Create a fresh app instance
247- app = create_app ()
248-
249- # The TestClient triggers the lifespan context
250- with TestClient (app ):
251- # Check that startup was called during context entry
252- assert startup_called
253- assert not shutdown_called # Not called until context exit
254-
255- # After exiting TestClient context, both hooks should have been called
256- assert startup_called
257- assert shutdown_called # shutdown should be called when context exits
258-
259-
260- def test_shutdown_method (monkeypatch ):
261- """Test the _shutdown method directly."""
262- import os
263- import signal
264-
265- # Track if os.kill was called
266- kill_called = False
267-
268- def mock_kill (pid , sig ):
269- nonlocal kill_called
270- kill_called = True
271- assert pid == os .getpid ()
272- assert sig == signal .SIGTERM
273-
274- # Apply mock
275- monkeypatch .setattr (os , "kill" , mock_kill )
276-
277- # Create app and call shutdown method
278- app = create_app ()
279- response = app ._shutdown ()
280-
281- # Verify results
282- assert kill_called
283- assert response .status_code == 200
284- assert response .body == b'{"message":"Server is shutting down..."}'
285-
286-
287245def test_exception_handling (test_app ):
288246 """Test the exception handling middleware."""
289247
290248 # Add a route that raises an exception
291249 @test_app .get ("/test-error" )
292- async def error_route ():
250+ def error_route ():
293251 raise HTTPException (status_code = 400 , detail = "Test error" )
294252
295253 # Add a route that raises an unexpected exception
296254 @test_app .get ("/test-unexpected-error" )
297- async def unexpected_error_route ():
255+ def unexpected_error_route ():
298256 raise ValueError ("Unexpected test error" )
299257
300258 with TestClient (test_app ) as client :
@@ -312,11 +270,22 @@ async def unexpected_error_route():
312270
313271def test_gateway_event_dispatcher_integration (mock_event_dispatcher ):
314272 """Test that gateways receive the event dispatcher when registered."""
273+
274+ # Create a test subclass that overrides _shutdown to avoid termination
275+ class SafeHealthChainAPI (HealthChainAPI ):
276+ def _shutdown (self ):
277+ # Override to avoid termination
278+ return JSONResponse (content = {"message" : "Server is shutting down..." })
279+
315280 # Create a gateway
316281 gateway = MockGateway ()
317282
318283 # Create app with events enabled
319- app = create_app (enable_events = True , event_dispatcher = mock_event_dispatcher )
284+ app = create_app_for_testing (
285+ enable_events = True ,
286+ event_dispatcher = mock_event_dispatcher ,
287+ app_class = SafeHealthChainAPI ,
288+ )
320289
321290 # Register gateway
322291 app .register_gateway (gateway )
0 commit comments