Skip to main content
Glama
mrosata
by mrosata
test_server.py52.7 kB
""" Comprehensive tests for FHIR MCP Server. These tests validate: 1. FHIR resource validation before server submission 2. Read/write permission enforcement (security) 3. Correct HTTP endpoint routing based on resourceType 4. Error handling and actionable error messages for LLM 5. Authentication header inclusion 6. Create vs Update logic (POST vs PUT) """ import pytest import respx import httpx from unittest.mock import Mock, patch import json # Import after patching to control config import server @pytest.fixture def valid_patient_resource(): """Sample valid Patient resource for testing.""" return { "resourceType": "Patient", "name": [{"family": "Smith", "given": ["John"]}], "gender": "male", "birthDate": "1980-01-01", } @pytest.fixture def valid_patient_with_id(): """Sample valid Patient resource with ID for update testing.""" return { "resourceType": "Patient", "id": "patient-123", "name": [{"family": "Smith", "given": ["John"]}], "gender": "male", } class TestWriteFHIRResource: """Tests for write_fhir_resource tool.""" @pytest.mark.asyncio @respx.mock async def test_create_valid_resource_without_id(self, valid_patient_resource): """Test creating a new resource (POST) when no ID is provided.""" with patch("server.validate_fhir_resource", return_value=(True, None)): with patch.object(server, "compiled_validator", Mock()): with patch.object(server, "FHIR_ALLOW_WRITE", True): with patch.object(server, "FHIR_ALLOW_READ", True): with patch.object( server, "FHIR_BASE_URL", "http://test-fhir.com/fhir" ): mock_response = valid_patient_resource.copy() mock_response["id"] = "new-patient-123" route = respx.post( "http://test-fhir.com/fhir/Patient" ).mock(return_value=httpx.Response(201, json=mock_response)) result = await server.write_fhir_resource.fn( valid_patient_resource ) assert route.called assert "Success" in result assert "new-patient-123" in result @pytest.mark.asyncio @respx.mock async def test_update_valid_resource_with_id(self, valid_patient_with_id): """Test updating an existing resource (PUT) when ID is provided.""" with patch("server.validate_fhir_resource", return_value=(True, None)): with patch.object(server, "compiled_validator", Mock()): with patch.object(server, "FHIR_ALLOW_WRITE", True): with patch.object(server, "FHIR_ALLOW_READ", True): with patch.object( server, "FHIR_BASE_URL", "http://test-fhir.com/fhir" ): route = respx.put( "http://test-fhir.com/fhir/Patient/patient-123" ).mock( return_value=httpx.Response( 200, json=valid_patient_with_id ) ) result = await server.write_fhir_resource.fn( valid_patient_with_id ) assert route.called assert "Success" in result assert "patient-123" in result @pytest.mark.asyncio async def test_invalid_resource_validation_fails(self, valid_patient_resource): """Test that invalid resources are rejected with validation errors.""" error_msg = "Missing required field: identifier" with patch("server.validate_fhir_resource", return_value=(False, error_msg)): with patch.object(server, "compiled_validator", Mock()): with patch.object(server, "FHIR_ALLOW_WRITE", True): result = await server.write_fhir_resource.fn(valid_patient_resource) assert "Validation failed" in result assert "Missing required field: identifier" in result @pytest.mark.asyncio async def test_missing_resource_type(self): """Test error when resourceType is missing.""" with patch("server.validate_fhir_resource", return_value=(True, None)): with patch.object(server, "compiled_validator", Mock()): with patch.object(server, "FHIR_ALLOW_WRITE", True): resource = {"name": [{"family": "Smith"}]} # No resourceType result = await server.write_fhir_resource.fn(resource) assert "Error" in result assert "resourceType" in result @pytest.mark.asyncio async def test_write_permission_denied(self, valid_patient_resource): """Test that write operations are blocked when FHIR_ALLOW_WRITE is false.""" with patch.object(server, "FHIR_ALLOW_WRITE", False): result = await server.write_fhir_resource.fn(valid_patient_resource) assert "Error" in result assert "not allowed" in result.lower() @pytest.mark.asyncio @respx.mock async def test_read_permission_hides_response_body(self, valid_patient_resource): """Test that response body is hidden when FHIR_ALLOW_READ is false.""" with patch("server.validate_fhir_resource", return_value=(True, None)): with patch.object(server, "compiled_validator", Mock()): with patch.object(server, "FHIR_ALLOW_WRITE", True): with patch.object(server, "FHIR_ALLOW_READ", False): with patch.object( server, "FHIR_BASE_URL", "http://test-fhir.com/fhir" ): mock_response = valid_patient_resource.copy() mock_response["id"] = "secret-123" respx.post("http://test-fhir.com/fhir/Patient").mock( return_value=httpx.Response(201, json=mock_response) ) result = await server.write_fhir_resource.fn( valid_patient_resource ) assert "Success" in result # Should NOT contain the full resource data assert '"resourceType"' not in result @pytest.mark.asyncio @respx.mock async def test_server_validation_error_400(self, valid_patient_resource): """Test handling of server validation errors (400).""" with patch("server.validate_fhir_resource", return_value=(True, None)): with patch.object(server, "compiled_validator", Mock()): with patch.object(server, "FHIR_ALLOW_WRITE", True): with patch.object( server, "FHIR_BASE_URL", "http://test-fhir.com/fhir" ): error_response = { "resourceType": "OperationOutcome", "issue": [ { "severity": "error", "code": "invalid", "diagnostics": "Invalid birthDate format", } ], } respx.post("http://test-fhir.com/fhir/Patient").mock( return_value=httpx.Response(400, json=error_response) ) result = await server.write_fhir_resource.fn( valid_patient_resource ) assert "400" in result assert "Invalid birthDate format" in result @pytest.mark.asyncio @respx.mock async def test_authentication_token_included(self, valid_patient_resource): """Test that auth token is included in request headers.""" with patch("server.validate_fhir_resource", return_value=(True, None)): with patch.object(server, "compiled_validator", Mock()): with patch.object(server, "FHIR_ALLOW_WRITE", True): with patch.object(server, "FHIR_ALLOW_READ", False): with patch.object( server, "FHIR_BASE_URL", "http://test-fhir.com/fhir" ): with patch.object( server, "FHIR_AUTH_TOKEN", "test-token-123" ): route = respx.post( "http://test-fhir.com/fhir/Patient" ).mock( return_value=httpx.Response(201, json={"id": "123"}) ) await server.write_fhir_resource.fn( valid_patient_resource ) assert route.called request = route.calls.last.request assert "Authorization" in request.headers assert ( request.headers["Authorization"] == "Bearer test-token-123" ) @pytest.mark.asyncio @respx.mock async def test_create_resource_with_builder_id(self, valid_patient_resource): """Test creating a resource with builder_id parameter.""" with patch("server.validate_fhir_resource", return_value=(True, None)): with patch.object(server, "compiled_validator", Mock()): with patch.object(server, "FHIR_ALLOW_WRITE", True): with patch.object(server, "FHIR_ALLOW_READ", True): with patch.object( server, "FHIR_BASE_URL", "http://test-fhir.com/fhir" ): mock_response = valid_patient_resource.copy() mock_response["id"] = "new-patient-123" route = respx.post( "http://test-fhir.com/fhir/Patient" ).mock(return_value=httpx.Response(201, json=mock_response)) result = await server.write_fhir_resource.fn( valid_patient_resource, {"Zus-Account": "builder-123"} ) assert route.called assert "Success" in result assert "new-patient-123" in result # Verify that the Zus-Account header is set correctly request = route.calls.last.request assert request.headers["Zus-Account"] == "builder-123" # Verify that the request body does NOT include builderID request_body = json.loads(request.content) assert "builderID" not in request_body @pytest.mark.asyncio @respx.mock async def test_create_resource_without_builder_id(self, valid_patient_resource): """Test creating a resource without builder_id parameter (should not include Zus-Account header).""" with patch("server.validate_fhir_resource", return_value=(True, None)): with patch.object(server, "compiled_validator", Mock()): with patch.object(server, "FHIR_ALLOW_WRITE", True): with patch.object(server, "FHIR_ALLOW_READ", True): with patch.object( server, "FHIR_BASE_URL", "http://test-fhir.com/fhir" ): mock_response = valid_patient_resource.copy() mock_response["id"] = "new-patient-123" route = respx.post( "http://test-fhir.com/fhir/Patient" ).mock(return_value=httpx.Response(201, json=mock_response)) result = await server.write_fhir_resource.fn( valid_patient_resource ) assert route.called assert "Success" in result assert "new-patient-123" in result # Verify that the Zus-Account header is NOT set request = route.calls.last.request assert "Zus-Account" not in request.headers @pytest.mark.asyncio @respx.mock async def test_update_resource_with_builder_id(self, valid_patient_with_id): """Test updating a resource with builder_id parameter.""" with patch("server.validate_fhir_resource", return_value=(True, None)): with patch.object(server, "compiled_validator", Mock()): with patch.object(server, "FHIR_ALLOW_WRITE", True): with patch.object(server, "FHIR_ALLOW_READ", True): with patch.object( server, "FHIR_BASE_URL", "http://test-fhir.com/fhir" ): route = respx.put( "http://test-fhir.com/fhir/Patient/patient-123" ).mock( return_value=httpx.Response(200, json=valid_patient_with_id) ) result = await server.write_fhir_resource.fn( valid_patient_with_id, {"Zus-Account": "builder-456"} ) assert route.called assert "Success" in result assert "patient-123" in result # Verify that the Zus-Account header is set correctly request = route.calls.last.request assert request.headers["Zus-Account"] == "builder-456" # Verify that the request body does NOT include builderID request_body = json.loads(request.content) assert "builderID" not in request_body @pytest.mark.asyncio @respx.mock async def test_create_observation_with_builder_id(self): """Test creating an Observation resource with builder_id parameter.""" observation_resource = { "resourceType": "Observation", "status": "final", "code": { "coding": [{"system": "http://loinc.org", "code": "33747-0", "display": "Temperature"}] }, "valueQuantity": { "value": 98.6, "unit": "F", "system": "http://unitsofmeasure.org", "code": "[degF]" } } with patch("server.validate_fhir_resource", return_value=(True, None)): with patch.object(server, "compiled_validator", Mock()): with patch.object(server, "FHIR_ALLOW_WRITE", True): with patch.object(server, "FHIR_ALLOW_READ", True): with patch.object( server, "FHIR_BASE_URL", "http://test-fhir.com/fhir" ): mock_response = observation_resource.copy() mock_response["id"] = "obs-123" route = respx.post( "http://test-fhir.com/fhir/Observation" ).mock(return_value=httpx.Response(201, json=mock_response)) result = await server.write_fhir_resource.fn( observation_resource, {"Zus-Account": "builder-789"} ) assert route.called assert "Success" in result assert "obs-123" in result # Verify that the Zus-Account header is set correctly request = route.calls.last.request assert request.headers["Zus-Account"] == "builder-789" # Verify that the request body does NOT include builderID request_body = json.loads(request.content) assert "builderID" not in request_body @pytest.mark.asyncio async def test_builder_id_does_not_modify_original_resource(self, valid_patient_resource): """Test that providing builder_id does not modify the original resource object.""" original_resource = valid_patient_resource.copy() with patch("server.validate_fhir_resource", return_value=(True, None)): with patch.object(server, "compiled_validator", Mock()): with patch.object(server, "FHIR_ALLOW_WRITE", True): with patch.object(server, "FHIR_ALLOW_READ", False): with patch.object( server, "FHIR_BASE_URL", "http://test-fhir.com/fhir" ): with respx.mock: respx.post("http://test-fhir.com/fhir/Patient").mock( return_value=httpx.Response(201, json={"id": "123"}) ) await server.write_fhir_resource.fn( valid_patient_resource, {"Zus-Account": "builder-123"} ) # Verify original resource is unchanged assert "builderID" not in original_resource assert original_resource == valid_patient_resource class TestReadFHIRResource: """Tests for read_fhir_resource tool.""" @pytest.mark.asyncio @respx.mock async def test_successful_read(self, valid_patient_with_id): """Test successfully reading a resource.""" with patch.object(server, "FHIR_ALLOW_READ", True): with patch.object(server, "FHIR_BASE_URL", "http://test-fhir.com/fhir"): route = respx.get("http://test-fhir.com/fhir/Patient/patient-123").mock( return_value=httpx.Response(200, json=valid_patient_with_id) ) result = await server.read_fhir_resource.fn("Patient", "patient-123") assert route.called assert "Patient" in result assert "patient-123" in result @pytest.mark.asyncio async def test_read_permission_denied(self): """Test that read operations are blocked when FHIR_ALLOW_READ is false.""" with patch.object(server, "FHIR_ALLOW_READ", False): result = await server.read_fhir_resource.fn("Patient", "123") assert "Error" in result assert "not allowed" in result.lower() @pytest.mark.asyncio @respx.mock async def test_resource_not_found_404(self): """Test 404 error when resource doesn't exist.""" with patch.object(server, "FHIR_ALLOW_READ", True): with patch.object(server, "FHIR_BASE_URL", "http://test-fhir.com/fhir"): respx.get("http://test-fhir.com/fhir/Patient/nonexistent").mock( return_value=httpx.Response(404, text="Not found") ) result = await server.read_fhir_resource.fn("Patient", "nonexistent") assert "Error" in result assert "not found" in result.lower() @pytest.mark.asyncio @respx.mock async def test_read_resource_with_builder_id(self, valid_patient_with_id): """Test reading a resource with builder_id parameter.""" with patch.object(server, "FHIR_ALLOW_READ", True): with patch.object(server, "FHIR_BASE_URL", "http://test-fhir.com/fhir"): route = respx.get("http://test-fhir.com/fhir/Patient/patient-123").mock( return_value=httpx.Response(200, json=valid_patient_with_id) ) result = await server.read_fhir_resource.fn("Patient", "patient-123", {"Zus-Account": "builder-123"}) assert route.called assert "Patient" in result assert "patient-123" in result # Verify that the Zus-Account header is set correctly request = route.calls.last.request assert request.headers["Zus-Account"] == "builder-123" @pytest.mark.asyncio @respx.mock async def test_read_resource_without_builder_id(self, valid_patient_with_id): """Test reading a resource without builder_id parameter.""" with patch.object(server, "FHIR_ALLOW_READ", True): with patch.object(server, "FHIR_BASE_URL", "http://test-fhir.com/fhir"): route = respx.get("http://test-fhir.com/fhir/Patient/patient-123").mock( return_value=httpx.Response(200, json=valid_patient_with_id) ) result = await server.read_fhir_resource.fn("Patient", "patient-123") assert route.called assert "Patient" in result assert "patient-123" in result # Verify that the Zus-Account header is NOT set request = route.calls.last.request assert "Zus-Account" not in request.headers class TestSearchFHIRResources: """Tests for search_fhir_resources tool.""" @pytest.mark.asyncio @respx.mock async def test_search_without_params(self): """Test searching without parameters returns all resources.""" with patch.object(server, "FHIR_ALLOW_READ", True): with patch.object(server, "FHIR_BASE_URL", "http://test-fhir.com/fhir"): bundle = { "resourceType": "Bundle", "type": "searchset", "total": 2, "entry": [ {"resource": {"resourceType": "Patient", "id": "1"}}, {"resource": {"resourceType": "Patient", "id": "2"}}, ], } route = respx.get("http://test-fhir.com/fhir/Patient").mock( return_value=httpx.Response(200, json=bundle) ) result = await server.search_fhir_resources.fn("Patient") assert route.called assert "Found 2 Patient" in result @pytest.mark.asyncio @respx.mock async def test_search_with_params(self): """Test searching with query parameters.""" with patch.object(server, "FHIR_ALLOW_READ", True): with patch.object(server, "FHIR_BASE_URL", "http://test-fhir.com/fhir"): bundle = { "resourceType": "Bundle", "type": "searchset", "total": 1, "entry": [{"resource": {"resourceType": "Patient", "id": "1"}}], } route = respx.get("http://test-fhir.com/fhir/Patient").mock( return_value=httpx.Response(200, json=bundle) ) params = {"name": "Smith", "gender": "female"} result = await server.search_fhir_resources.fn("Patient", params) assert route.called request = route.calls.last.request assert "name" in str(request.url) assert "Smith" in str(request.url) @pytest.mark.asyncio async def test_search_permission_denied(self): """Test that search operations are blocked when FHIR_ALLOW_READ is false.""" with patch.object(server, "FHIR_ALLOW_READ", False): result = await server.search_fhir_resources.fn("Patient") assert "Error" in result assert "not allowed" in result.lower() @pytest.mark.asyncio @respx.mock async def test_search_with_builder_id(self): """Test searching with builder_id parameter.""" with patch.object(server, "FHIR_ALLOW_READ", True): with patch.object(server, "FHIR_BASE_URL", "http://test-fhir.com/fhir"): bundle = { "resourceType": "Bundle", "type": "searchset", "total": 1, "entry": [{"resource": {"resourceType": "Patient", "id": "1"}}], } route = respx.get("http://test-fhir.com/fhir/Patient").mock( return_value=httpx.Response(200, json=bundle) ) result = await server.search_fhir_resources.fn("Patient", None, {"Zus-Account": "builder-123"}) assert route.called assert "Found 1 Patient" in result # Verify that the Zus-Account header is set correctly request = route.calls.last.request assert request.headers["Zus-Account"] == "builder-123" @pytest.mark.asyncio @respx.mock async def test_search_without_builder_id(self): """Test searching without builder_id parameter.""" with patch.object(server, "FHIR_ALLOW_READ", True): with patch.object(server, "FHIR_BASE_URL", "http://test-fhir.com/fhir"): bundle = { "resourceType": "Bundle", "type": "searchset", "total": 1, "entry": [{"resource": {"resourceType": "Patient", "id": "1"}}], } route = respx.get("http://test-fhir.com/fhir/Patient").mock( return_value=httpx.Response(200, json=bundle) ) result = await server.search_fhir_resources.fn("Patient") assert route.called assert "Found 1 Patient" in result # Verify that the Zus-Account header is NOT set request = route.calls.last.request assert "Zus-Account" not in request.headers class TestGetFHIRConfig: """Tests for get_fhir_config tool.""" @pytest.mark.asyncio async def test_config_display(self): """Test that configuration is correctly displayed.""" with patch.object(server, "FHIR_BASE_URL", "http://custom-fhir.com/fhir"): with patch.object(server, "FHIR_ALLOW_READ", True): with patch.object(server, "FHIR_ALLOW_WRITE", False): with patch.object(server, "FHIR_AUTH_TOKEN", "secret-token"): with patch.object(server, "_allowed_methods_set", None): result = await server.get_fhir_config.fn() assert "http://custom-fhir.com/fhir" in result assert "Read Allowed: True" in result assert "Write Allowed: False" in result assert "Configured" in result @pytest.mark.asyncio async def test_config_with_allowed_methods(self): """Test configuration display with HTTP methods configured.""" with patch.object(server, "FHIR_BASE_URL", "http://test.com/fhir"): with patch.object(server, "_allowed_methods_set", {"GET", "POST"}): result = await server.get_fhir_config.fn() assert "GET, POST" in result assert "overrides" in result.lower() class TestHTTPMethodsConfiguration: """Tests for HTTP methods configuration (FHIR_ALLOWED_METHODS).""" @pytest.mark.asyncio async def test_allowed_methods_overrides_write_permission( self, valid_patient_resource ): """Test that FHIR_ALLOWED_METHODS takes precedence over FHIR_ALLOW_WRITE.""" with patch("server.validate_fhir_resource", return_value=(True, None)): with patch.object(server, "compiled_validator", Mock()): with patch.object(server, "FHIR_ALLOW_WRITE", True): # Write is allowed with patch.object( server, "_allowed_methods_set", {"GET"} ): # But only GET in methods result = await server.write_fhir_resource.fn( valid_patient_resource ) assert "Error" in result assert "POST is not allowed" in result assert "GET" in result @pytest.mark.asyncio async def test_allowed_methods_permits_specific_write_method( self, valid_patient_resource ): """Test that specific write methods can be allowed via FHIR_ALLOWED_METHODS.""" with patch("server.validate_fhir_resource", return_value=(True, None)): with patch.object(server, "compiled_validator", Mock()): with patch.object(server, "FHIR_ALLOW_WRITE", False): # Write disabled with patch.object( server, "_allowed_methods_set", {"POST"} ): # But POST allowed with patch.object( server, "FHIR_BASE_URL", "http://test.com/fhir" ): with patch.object(server, "FHIR_ALLOW_READ", False): import respx with respx.mock: respx.post("http://test.com/fhir/Patient").mock( return_value=httpx.Response( 201, json={"id": "123"} ) ) result = await server.write_fhir_resource.fn( valid_patient_resource ) assert "Success" in result @pytest.mark.asyncio async def test_allowed_methods_blocks_put_but_allows_post( self, valid_patient_with_id, valid_patient_resource ): """Test that PUT can be blocked while POST is allowed.""" with patch("server.validate_fhir_resource", return_value=(True, None)): with patch.object(server, "compiled_validator", Mock()): with patch.object( server, "_allowed_methods_set", {"POST"} ): # Only POST with patch.object(server, "FHIR_BASE_URL", "http://test.com/fhir"): with patch.object(server, "FHIR_ALLOW_READ", False): # PUT should fail result_put = await server.write_fhir_resource.fn( valid_patient_with_id ) assert "Error" in result_put assert "PUT is not allowed" in result_put # POST should succeed import respx with respx.mock: respx.post("http://test.com/fhir/Patient").mock( return_value=httpx.Response(201, json={"id": "123"}) ) result_post = await server.write_fhir_resource.fn( valid_patient_resource ) assert "Success" in result_post @pytest.mark.asyncio async def test_allowed_methods_overrides_read_permission(self): """Test that FHIR_ALLOWED_METHODS overrides FHIR_ALLOW_READ.""" with patch.object(server, "FHIR_ALLOW_READ", True): # Read allowed with patch.object( server, "_allowed_methods_set", {"POST"} ): # But only POST in methods result = await server.read_fhir_resource.fn("Patient", "123") assert "Error" in result assert "GET is not allowed" in result assert "POST" in result class TestGetPatientZusUpid: """Tests for get_patient_zus_upid tool.""" @pytest.fixture def patient_with_zus_upid(self): """Sample Patient resource with Zus UPID identifier.""" return { "resourceType": "Patient", "id": "patient-123", "name": [{"family": "Smith", "given": ["John"]}], "identifier": [ { "system": "https://zusapi.com/fhir/identifier/universal-id", "value": "zus-upid-12345" }, { "system": "http://hospital.example.org/patient-ids", "value": "hospital-123" } ] } @pytest.fixture def patient_without_zus_upid(self): """Sample Patient resource without Zus UPID identifier.""" return { "resourceType": "Patient", "id": "patient-456", "name": [{"family": "Doe", "given": ["Jane"]}], "identifier": [ { "system": "http://hospital.example.org/patient-ids", "value": "hospital-456" } ] } @pytest.mark.asyncio @respx.mock async def test_get_zus_upid_success(self, patient_with_zus_upid): """Test successful retrieval of Zus UPID.""" with patch.object(server, "FHIR_BASE_URL", "http://test.com/fhir"): with patch.object(server, "FHIR_ALLOW_READ", True): with patch("zus_extensions.FHIR_BASE_URL", "http://test.com/fhir"): # Mock FHIR search response bundle_response = { "resourceType": "Bundle", "total": 1, "entry": [ { "resource": patient_with_zus_upid } ] } respx.get("http://test.com/fhir/Patient").mock( return_value=httpx.Response(200, json=bundle_response) ) result = await server.get_patient_zus_upid.fn("John", "Smith") assert "Zus UPID: zus-upid-12345" in result @pytest.mark.asyncio @respx.mock async def test_get_zus_upid_with_builder_id(self, patient_with_zus_upid): """Test successful retrieval of Zus UPID with builder ID filter.""" with patch.object(server, "FHIR_BASE_URL", "http://test.com/fhir"): with patch.object(server, "FHIR_ALLOW_READ", True): with patch("zus_extensions.FHIR_BASE_URL", "http://test.com/fhir"): # Mock FHIR search response bundle_response = { "resourceType": "Bundle", "total": 1, "entry": [ { "resource": patient_with_zus_upid } ] } respx.get("http://test.com/fhir/Patient").mock( return_value=httpx.Response(200, json=bundle_response) ) result = await server.get_patient_zus_upid.fn("John", "Smith", "builder-123") assert "Zus UPID: zus-upid-12345" in result @pytest.mark.asyncio @respx.mock async def test_get_zus_upid_no_patients_found(self): """Test when no patients are found.""" with patch.object(server, "FHIR_BASE_URL", "http://test.com/fhir"): with patch.object(server, "FHIR_ALLOW_READ", True): with patch("zus_extensions.FHIR_BASE_URL", "http://test.com/fhir"): # Mock empty FHIR search response bundle_response = { "resourceType": "Bundle", "total": 0, "entry": [] } respx.get("http://test.com/fhir/Patient").mock( return_value=httpx.Response(200, json=bundle_response) ) result = await server.get_patient_zus_upid.fn("John", "Smith") assert "No Patient found with name 'John Smith'" in result @pytest.mark.asyncio @respx.mock async def test_get_zus_upid_no_zus_upid_identifier(self, patient_without_zus_upid): """Test when patient found but no Zus UPID identifier.""" with patch.object(server, "FHIR_BASE_URL", "http://test.com/fhir"): with patch.object(server, "FHIR_ALLOW_READ", True): with patch("zus_extensions.FHIR_BASE_URL", "http://test.com/fhir"): # Mock FHIR search response bundle_response = { "resourceType": "Bundle", "total": 1, "entry": [ { "resource": patient_without_zus_upid } ] } respx.get("http://test.com/fhir/Patient").mock( return_value=httpx.Response(200, json=bundle_response) ) result = await server.get_patient_zus_upid.fn("Jane", "Doe") assert "No Zus UPID found for Patient(s) with name 'Jane Doe'" in result @pytest.mark.asyncio @respx.mock async def test_get_zus_upid_multiple_patients(self): """Test when multiple patients with Zus UPIDs are found.""" with patch.object(server, "FHIR_BASE_URL", "http://test.com/fhir"): with patch.object(server, "FHIR_ALLOW_READ", True): with patch("zus_extensions.FHIR_BASE_URL", "http://test.com/fhir"): # Mock FHIR search response with multiple patients bundle_response = { "resourceType": "Bundle", "total": 2, "entry": [ { "resource": { "resourceType": "Patient", "id": "patient-1", "name": [{"family": "Smith", "given": ["John"]}], "identifier": [ { "system": "https://zusapi.com/fhir/identifier/universal-id", "value": "zus-upid-1" } ] } }, { "resource": { "resourceType": "Patient", "id": "patient-2", "name": [{"family": "Smith", "given": ["Johnny"]}], "identifier": [ { "system": "https://zusapi.com/fhir/identifier/universal-id", "value": "zus-upid-2" } ] } } ] } respx.get("http://test.com/fhir/Patient").mock( return_value=httpx.Response(200, json=bundle_response) ) result = await server.get_patient_zus_upid.fn("John", "Smith") assert "Zus UPID: zus-upid-1 (Best match: John Smith)" in result assert "zus-upid-1" in result assert "zus-upid-2" in result @pytest.mark.asyncio async def test_get_zus_upid_read_not_allowed(self): """Test when read operations are not allowed.""" with patch.object(server, "FHIR_ALLOW_READ", False): result = await server.get_patient_zus_upid.fn("John", "Smith") assert "Error" in result assert "Read operations are not allowed" in result @pytest.mark.asyncio async def test_get_zus_upid_method_not_allowed(self): """Test when GET method is not allowed.""" with patch.object(server, "FHIR_ALLOW_READ", True): with patch.object(server, "_allowed_methods_set", {"POST"}): result = await server.get_patient_zus_upid.fn("John", "Smith") assert "Error" in result assert "GET is not allowed" in result assert "POST" in result @pytest.mark.asyncio @respx.mock async def test_get_zus_upid_name_matching_best_match(self): """Test name matching when multiple patients found with clear best match.""" with patch.object(server, "FHIR_BASE_URL", "http://test.com/fhir"): with patch.object(server, "FHIR_ALLOW_READ", True): with patch("zus_extensions.FHIR_BASE_URL", "http://test.com/fhir"): # Mock FHIR search response with multiple patients bundle_response = { "resourceType": "Bundle", "total": 3, "entry": [ { "resource": { "resourceType": "Patient", "id": "patient-1", "name": [{"family": "Smith", "given": ["John"]}], # Exact match "identifier": [ { "system": "https://zusapi.com/fhir/identifier/universal-id", "value": "zus-upid-1" } ] } }, { "resource": { "resourceType": "Patient", "id": "patient-2", "name": [{"family": "Smith", "given": ["Johnny"]}], # Partial match "identifier": [ { "system": "https://zusapi.com/fhir/identifier/universal-id", "value": "zus-upid-2" } ] } }, { "resource": { "resourceType": "Patient", "id": "patient-3", "name": [{"family": "Smith", "given": ["Jonathan"]}], # No match "identifier": [ { "system": "https://zusapi.com/fhir/identifier/universal-id", "value": "zus-upid-3" } ] } } ] } respx.get("http://test.com/fhir/Patient").mock( return_value=httpx.Response(200, json=bundle_response) ) result = await server.get_patient_zus_upid.fn("John", "Smith") # Should return the best match (exact match) assert "Zus UPID: zus-upid-1" in result assert "Best match: John Smith" in result assert "Other matches found:" in result assert "zus-upid-2" in result # Johnny Smith should be mentioned @pytest.mark.asyncio @respx.mock async def test_get_zus_upid_name_matching_no_good_match(self): """Test when multiple patients found but no clear name match.""" with patch.object(server, "FHIR_BASE_URL", "http://test.com/fhir"): with patch.object(server, "FHIR_ALLOW_READ", True): with patch("zus_extensions.FHIR_BASE_URL", "http://test.com/fhir"): # Mock FHIR search response with patients that don't match well bundle_response = { "resourceType": "Bundle", "total": 2, "entry": [ { "resource": { "resourceType": "Patient", "id": "patient-1", "name": [{"family": "Johnson", "given": ["Mike"]}], # No match "identifier": [ { "system": "https://zusapi.com/fhir/identifier/universal-id", "value": "zus-upid-1" } ] } }, { "resource": { "resourceType": "Patient", "id": "patient-2", "name": [{"family": "Williams", "given": ["Jane"]}], # No match "identifier": [ { "system": "https://zusapi.com/fhir/identifier/universal-id", "value": "zus-upid-2" } ] } } ] } respx.get("http://test.com/fhir/Patient").mock( return_value=httpx.Response(200, json=bundle_response) ) result = await server.get_patient_zus_upid.fn("John", "Smith") # Should return all patients since no good match assert "Found 2 Patient(s) with Zus UPID(s) (no clear name match):" in result assert "zus-upid-1" in result assert "zus-upid-2" in result @pytest.mark.asyncio @respx.mock async def test_get_zus_upid_name_matching_partial_match(self): """Test name matching with partial matches.""" with patch.object(server, "FHIR_BASE_URL", "http://test.com/fhir"): with patch.object(server, "FHIR_ALLOW_READ", True): with patch("zus_extensions.FHIR_BASE_URL", "http://test.com/fhir"): # Mock FHIR search response with partial matches bundle_response = { "resourceType": "Bundle", "total": 2, "entry": [ { "resource": { "resourceType": "Patient", "id": "patient-1", "name": [{"family": "Smith", "given": ["Johnny"]}], # Partial match "identifier": [ { "system": "https://zusapi.com/fhir/identifier/universal-id", "value": "zus-upid-1" } ] } }, { "resource": { "resourceType": "Patient", "id": "patient-2", "name": [{"family": "Smith", "given": ["Johnson"]}], # Partial match "identifier": [ { "system": "https://zusapi.com/fhir/identifier/universal-id", "value": "zus-upid-2" } ] } } ] } respx.get("http://test.com/fhir/Patient").mock( return_value=httpx.Response(200, json=bundle_response) ) result = await server.get_patient_zus_upid.fn("John", "Smith") # Should return the best partial match (Johnny matches John) assert "Zus UPID: zus-upid-1" in result assert "Best match: Johnny Smith" in result def test_calculate_name_similarity_exact_match(self): """Test name similarity calculation with exact match.""" patient_name = {"family": "Smith", "given": ["John"]} score = server.calculate_name_similarity(patient_name, "John", "Smith") assert score == 1.0 def test_calculate_name_similarity_partial_match(self): """Test name similarity calculation with partial match.""" patient_name = {"family": "Smith", "given": ["Johnny"]} score = server.calculate_name_similarity(patient_name, "John", "Smith") assert abs(score - 0.88) < 0.001 # 0.6 (family) + 0.4 * 0.7 (partial given) def test_calculate_name_similarity_no_match(self): """Test name similarity calculation with no match.""" patient_name = {"family": "Johnson", "given": ["Mike"]} score = server.calculate_name_similarity(patient_name, "John", "Smith") assert score == 0.0 def test_calculate_name_similarity_family_only_match(self): """Test name similarity calculation with family name only match.""" patient_name = {"family": "Smith", "given": ["Mike"]} score = server.calculate_name_similarity(patient_name, "John", "Smith") assert score == 0.6 # Only family name matches def test_calculate_name_similarity_empty_name(self): """Test name similarity calculation with empty name.""" patient_name = {} score = server.calculate_name_similarity(patient_name, "John", "Smith") assert score == 0.0

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mrosata/mcp-fhir'

If you have feedback or need assistance with the MCP directory API, please join our Discord server