Files
fhir2padnext/test_fhir_to_pad_converter.py
Alexander Domene 8650bd09a3 added tests
2025-10-27 08:19:13 +01:00

1009 lines
36 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Comprehensive test suite for FHIR to PADneXt converter.
Run with:
pytest test_fhir_to_pad_converter.py -v
pytest test_fhir_to_pad_converter.py -v --cov=. --cov-report=html
"""
import pytest
import json
import xml.etree.ElementTree as ET
from datetime import datetime
from pathlib import Path
from typing import Dict, Any, List
# Import modules to test
from utils import (
parse_iso_date,
format_iso_date,
get_ref_id,
ensure_text,
collect_effective_dates,
validate_file_path,
validate_output_path,
validate_directory_path
)
from validation import (
validate_temporal_consistency,
validate_codes,
run_validation
)
from translator import CodeTranslator
from fhir_to_pad_converter import (
validate_fhir_json,
compute_fhir_stats,
group_entries,
claim_item_to_position,
find_resource_by_ref,
claim_to_rechnung_header,
validate_ziffer,
get_with_placeholder,
validate_pad_xml,
compute_pad_stats,
build_pad_xml,
PAD_NS
)
# ============================================================================
# FIXTURES
# ============================================================================
@pytest.fixture
def sample_fhir_bundle():
"""Minimal valid FHIR bundle for testing."""
return {
"resourceType": "Bundle",
"type": "collection",
"entry": [
{
"resource": {
"resourceType": "Patient",
"id": "patient-1",
"name": [{"family": "Test", "given": ["John"]}],
"birthDate": "1980-01-01",
"gender": "male"
}
},
{
"resource": {
"resourceType": "Encounter",
"id": "encounter-1",
"status": "finished",
"subject": {"reference": "Patient/patient-1"},
"period": {
"start": "2024-01-01T10:00:00+00:00",
"end": "2024-01-01T12:00:00+00:00"
}
}
},
{
"resource": {
"resourceType": "Observation",
"id": "obs-1",
"status": "final",
"code": {
"coding": [{
"system": "http://loinc.org",
"code": "12345-6",
"display": "Test Observation"
}]
},
"subject": {"reference": "Patient/patient-1"},
"encounter": {"reference": "Encounter/encounter-1"},
"effectiveDateTime": "2024-01-01T10:30:00+00:00"
}
}
]
}
@pytest.fixture
def sample_claim_bundle():
"""FHIR bundle with Claim resource."""
return {
"resourceType": "Bundle",
"type": "collection",
"entry": [
{
"resource": {
"resourceType": "Patient",
"id": "patient-1",
"name": [{"family": "Smith", "given": ["Jane"]}],
"birthDate": "1990-05-15",
"gender": "female"
}
},
{
"resource": {
"resourceType": "Organization",
"id": "provider-org-1",
"name": "Test Hospital"
}
},
{
"resource": {
"resourceType": "Organization",
"id": "insurer-org-1",
"name": "Test Insurance"
}
},
{
"resource": {
"resourceType": "Claim",
"id": "claim-1",
"status": "active",
"patient": {"reference": "Patient/patient-1"},
"provider": {"reference": "Organization/provider-org-1"},
"insurer": {"reference": "Organization/insurer-org-1"},
"created": "2024-01-15T10:00:00+00:00",
"diagnosis": [{
"diagnosisCodeableConcept": {
"coding": [{
"system": "http://hl7.org/fhir/sid/icd-10",
"code": "Z00.0",
"display": "General examination"
}]
}
}],
"item": [
{
"sequence": 1,
"productOrService": {
"coding": [{
"system": "http://test.de/goa",
"code": "1",
"display": "Consultation"
}]
},
"servicedDate": "2024-01-15"
}
]
}
}
]
}
@pytest.fixture
def sample_header_config():
"""Sample header configuration."""
return {
"nachrichtentyp_version": "1.0",
"rechnungsersteller_name": "Test Clinic",
"rechnungsersteller_kundennr": "12345",
"rechnungsersteller_strasse": "Main St 1",
"rechnungsersteller_plz": "12345",
"rechnungsersteller_ort": "Berlin",
"leistungserbringer_id": "LE001",
"leistungserbringer_vorname": "Dr. Max",
"leistungserbringer_name": "Mustermann",
"behandelter_vorname": "John",
"behandelter_name": "Doe",
"behandelter_gebdatum": "1980-01-01",
"behandelter_geschlecht": "m",
"versicherter_vorname": "John",
"versicherter_name": "Doe",
"versicherter_gebdatum": "1980-01-01",
"versicherter_geschlecht": "m",
"behandlungsart": "0",
"vertragsart": "1",
"diagnose_text": "Test diagnosis",
"diagnose_datum": "2024-01-01"
}
@pytest.fixture
def sample_placeholder_config():
"""Sample placeholder configuration."""
return {
"rechnungsersteller": {
"name": "PLACEHOLDER_CLINIC",
"plz": "00000",
"ort": "PLACEHOLDER_CITY",
"strasse": "PLACEHOLDER_STREET"
},
"leistungserbringer": {
"vorname": "PLACEHOLDER_FIRSTNAME",
"name": "PLACEHOLDER_LASTNAME"
},
"behandelter": {
"anrede": "Ohne Anrede",
"vorname": "PLACEHOLDER_PATIENT",
"name": "PLACEHOLDER_PATIENT",
"gebdatum": "1900-01-01",
"geschlecht": "u"
},
"goziffer": {
"ziffer": "99999",
"datum": "1900-01-01"
}
}
# ============================================================================
# UTILS.PY TESTS
# ============================================================================
class TestUtils:
"""Tests for utility functions."""
def test_parse_iso_date_valid_with_z(self):
"""Test parsing ISO date with Z suffix."""
result = parse_iso_date("2024-01-01T10:00:00Z")
assert result is not None
assert result.year == 2024
assert result.month == 1
assert result.day == 1
def test_parse_iso_date_valid_with_timezone(self):
"""Test parsing ISO date with timezone."""
result = parse_iso_date("2024-01-01T10:00:00+02:00")
assert result is not None
assert result.year == 2024
def test_parse_iso_date_invalid(self):
"""Test parsing invalid date returns None."""
assert parse_iso_date("not-a-date") is None
assert parse_iso_date("") is None
assert parse_iso_date(None) is None
def test_parse_iso_date_edge_cases(self):
"""Test edge cases for date parsing."""
# Just date without time
result = parse_iso_date("2024-01-01")
assert result is not None
assert result.year == 2024
def test_format_iso_date(self):
"""Test formatting datetime to ISO date string."""
dt = datetime(2024, 1, 15, 10, 30, 45)
result = format_iso_date(dt)
assert result == "2024-01-15"
def test_get_ref_id_valid(self):
"""Test extracting ID from valid reference."""
assert get_ref_id("Patient/123") == "123"
assert get_ref_id("Encounter/abc-def") == "abc-def"
assert get_ref_id("Organization/org-1") == "org-1"
def test_get_ref_id_invalid(self):
"""Test handling invalid references."""
assert get_ref_id("NoSlash") is None
assert get_ref_id("") is None
assert get_ref_id(None) is None
assert get_ref_id("Patient/") is None
def test_ensure_text_with_element(self):
"""Test extracting text from XML element."""
elem = ET.Element("test")
elem.text = " content "
assert ensure_text(elem) == "content"
def test_ensure_text_none(self):
"""Test ensure_text with None returns default."""
assert ensure_text(None) == ""
assert ensure_text(None, "default") == "default"
def test_collect_effective_dates(self):
"""Test collecting dates from resource."""
resource = {
"effectiveDateTime": "2024-01-01T10:00:00Z",
"issued": "2024-01-02T10:00:00Z",
"meta": {
"lastUpdated": "2024-01-03T10:00:00Z"
}
}
dates = collect_effective_dates(resource)
assert len(dates) == 3
assert all(isinstance(d, datetime) for d in dates)
def test_collect_effective_dates_empty(self):
"""Test collecting dates from resource with no dates."""
resource = {"resourceType": "Patient"}
dates = collect_effective_dates(resource)
assert len(dates) == 0
# ============================================================================
# VALIDATION.PY TESTS
# ============================================================================
class TestValidation:
"""Tests for validation functions."""
def test_validate_temporal_consistency_valid(self):
"""Test validation passes for valid dates."""
resources = [{
"id": "test-1",
"effectiveDateTime": "2024-01-01T10:00:00Z"
}]
warnings = validate_temporal_consistency(resources)
assert len(warnings) == 0
def test_validate_temporal_consistency_future_date(self):
"""Test validation warns for future dates."""
resources = [{
"id": "test-1",
"effectiveDateTime": "2099-01-01T10:00:00Z"
}]
warnings = validate_temporal_consistency(resources)
assert len(warnings) > 0
assert "future" in warnings[0].lower()
def test_validate_codes(self):
"""Test code validation (currently stub)."""
resources = [{"id": "test-1"}]
warnings = validate_codes(resources)
assert isinstance(warnings, list)
def test_run_validation(self):
"""Test running all validations."""
resources = [{
"id": "test-1",
"effectiveDateTime": "2024-01-01T10:00:00Z"
}]
warnings = run_validation(resources)
assert isinstance(warnings, list)
# ============================================================================
# TRANSLATOR.PY TESTS
# ============================================================================
class TestCodeTranslator:
"""Tests for code translation."""
def test_translator_init(self):
"""Test translator initialization."""
translator = CodeTranslator()
assert translator.maps == {}
def test_translator_parse_concept_map(self):
"""Test parsing a concept map."""
translator = CodeTranslator()
concept_map = {
"group": [{
"source": "http://loinc.org",
"target": "http://test.de/goa",
"element": [
{
"code": "12345-6",
"target": [{"code": "001"}]
}
]
}]
}
translator._parse_concept_map(concept_map)
assert "http://loinc.org" in translator.maps
result = translator.translate("http://loinc.org", "12345-6")
assert result == "001"
def test_translator_no_match(self):
"""Test translation returns None when no match."""
translator = CodeTranslator()
result = translator.translate("http://unknown.org", "12345")
assert result is None
# ============================================================================
# FHIR VALIDATION TESTS
# ============================================================================
class TestFhirValidation:
"""Tests for FHIR validation functions."""
def test_validate_fhir_json_valid_bundle(self, sample_fhir_bundle):
"""Test validation of valid FHIR bundle."""
ok, messages = validate_fhir_json(sample_fhir_bundle)
assert ok is True
assert len(messages) > 0
def test_validate_fhir_json_invalid_resource_type(self):
"""Test validation fails for wrong resourceType."""
bundle = {"resourceType": "NotABundle"}
ok, messages = validate_fhir_json(bundle)
assert ok is False
assert any("Bundle" in m for m in messages)
def test_validate_fhir_json_missing_type(self):
"""Test validation warns for missing bundle type."""
bundle = {"resourceType": "Bundle", "entry": []}
ok, messages = validate_fhir_json(bundle)
assert ok is False
def test_validate_fhir_json_invalid_entry(self):
"""Test validation handles invalid entry."""
bundle = {
"resourceType": "Bundle",
"type": "collection",
"entry": "not-a-list"
}
ok, messages = validate_fhir_json(bundle)
assert ok is False
def test_compute_fhir_stats(self, sample_fhir_bundle):
"""Test computing FHIR statistics."""
stats = compute_fhir_stats(sample_fhir_bundle)
assert stats["bundle_type"] == "collection"
assert stats["total_entries"] == 3
assert "Patient" in stats["resource_type_counts"]
assert stats["resource_type_counts"]["Patient"] == 1
# ============================================================================
# GROUPING TESTS
# ============================================================================
class TestGrouping:
"""Tests for resource grouping logic."""
def test_group_entries_by_encounter(self, sample_fhir_bundle):
"""Test grouping by encounter when no Claims."""
groups = group_entries(sample_fhir_bundle)
assert len(groups) > 0
# Should have one group for (patient-1, encounter-1)
assert ("patient-1", "encounter-1") in groups
def test_group_entries_by_claim(self, sample_claim_bundle):
"""Test grouping by claim when Claims present."""
groups = group_entries(sample_claim_bundle)
assert len(groups) > 0
# Should have one group for (patient-1, claim-1)
assert ("patient-1", "claim-1") in groups
def test_group_entries_empty_bundle(self):
"""Test grouping empty bundle."""
bundle = {"resourceType": "Bundle", "type": "collection", "entry": []}
groups = group_entries(bundle)
assert len(groups) == 0
# ============================================================================
# CLAIM MAPPING TESTS
# ============================================================================
class TestClaimMapping:
"""Tests for Claim-to-PAD mapping functions."""
def test_claim_item_to_position(self):
"""Test converting Claim item to position."""
item = {
"sequence": 1,
"productOrService": {
"coding": [{
"code": "12345",
"display": "Test Service"
}]
},
"servicedDate": "2024-01-15"
}
position = claim_item_to_position(item)
assert position["id"] == 1
assert position["ziffer"] == "12345"
assert position["text"] == "Test Service"
assert position["datum"] == "2024-01-15"
assert position["anzahl"] == "1"
def test_claim_item_to_position_missing_fields(self):
"""Test converting Claim item with missing fields."""
item = {"sequence": 2}
position = claim_item_to_position(item)
assert position["id"] == 2
assert position["ziffer"] == ""
assert position["text"] == ""
def test_find_resource_by_ref(self, sample_claim_bundle):
"""Test finding resource by reference."""
resource = find_resource_by_ref(sample_claim_bundle, "Patient/patient-1")
assert resource is not None
assert resource["resourceType"] == "Patient"
assert resource["id"] == "patient-1"
def test_find_resource_by_ref_not_found(self, sample_claim_bundle):
"""Test finding non-existent resource."""
resource = find_resource_by_ref(sample_claim_bundle, "Patient/does-not-exist")
assert resource is None
def test_find_resource_by_ref_invalid_format(self, sample_claim_bundle):
"""Test finding resource with invalid reference format."""
resource = find_resource_by_ref(sample_claim_bundle, "InvalidReference")
assert resource is None
def test_claim_to_rechnung_header(self, sample_claim_bundle):
"""Test extracting header info from Claim."""
claim = sample_claim_bundle["entry"][3]["resource"]
header = claim_to_rechnung_header(claim, sample_claim_bundle)
assert "behandelter_vorname" in header
assert header["behandelter_name"] == "Smith"
assert header["behandelter_gebdatum"] == "1990-05-15"
assert header["leistungserbringer_name"] == "Test Hospital"
assert header["empfaenger_name"] == "Test Insurance"
# ============================================================================
# PLACEHOLDER & VALIDATION TESTS
# ============================================================================
class TestPlaceholders:
"""Tests for placeholder and validation functions."""
def test_validate_ziffer_valid(self):
"""Test validating valid ziffer code."""
auto_filled = []
result = validate_ziffer("12345", "99999", "test.ziffer", auto_filled)
assert result == "12345"
assert len(auto_filled) == 0
def test_validate_ziffer_empty(self):
"""Test validating empty ziffer uses placeholder."""
auto_filled = []
result = validate_ziffer("", "99999", "test.ziffer", auto_filled)
assert result == "99999"
assert len(auto_filled) == 1
assert "99999" in auto_filled[0]
def test_validate_ziffer_too_long(self):
"""Test validating ziffer that's too long gets truncated."""
auto_filled = []
result = validate_ziffer("123456789", "99999", "test.ziffer", auto_filled)
assert result == "12345678"
assert len(result) == 8
assert len(auto_filled) == 1
assert "truncated" in auto_filled[0].lower()
def test_get_with_placeholder_has_value(self):
"""Test get_with_placeholder with existing value."""
auto_filled = []
result = get_with_placeholder("RealValue", "Placeholder", "test.field", auto_filled)
assert result == "RealValue"
assert len(auto_filled) == 0
def test_get_with_placeholder_uses_placeholder(self):
"""Test get_with_placeholder uses placeholder for empty value."""
auto_filled = []
result = get_with_placeholder("", "Placeholder", "test.field", auto_filled)
assert result == "Placeholder"
assert len(auto_filled) == 1
assert "Placeholder" in auto_filled[0]
# ============================================================================
# XML BUILDING TESTS
# ============================================================================
class TestXmlBuilding:
"""Tests for PAD XML building functions."""
def test_build_pad_xml_basic(self, sample_fhir_bundle, sample_header_config, sample_placeholder_config):
"""Test building basic PAD XML."""
root, warnings, header, auto_filled = build_pad_xml(
sample_fhir_bundle,
header_cfg=sample_header_config,
placeholder_cfg=sample_placeholder_config
)
assert root is not None
assert root.tag == f"{{{PAD_NS}}}rechnungen"
assert root.get("anzahl") is not None
def test_build_pad_xml_with_claim(self, sample_claim_bundle, sample_header_config, sample_placeholder_config):
"""Test building PAD XML with Claim resource."""
root, warnings, header, auto_filled = build_pad_xml(
sample_claim_bundle,
header_cfg=sample_header_config,
placeholder_cfg=sample_placeholder_config
)
assert root is not None
# Check that claim data overrode config
assert header["behandelter_name"] == "Smith"
assert header["leistungserbringer_name"] == "Test Hospital"
def test_build_pad_xml_namespace(self, sample_fhir_bundle, sample_header_config):
"""Test PAD XML has correct namespace."""
root, _, _, _ = build_pad_xml(sample_fhir_bundle, header_cfg=sample_header_config)
assert root.get("xmlns") == PAD_NS
# ============================================================================
# PAD VALIDATION TESTS
# ============================================================================
class TestPadValidation:
"""Tests for PAD XML validation functions."""
def test_validate_pad_xml_well_formed(self, sample_fhir_bundle, sample_header_config):
"""Test validation of well-formed XML."""
root, _, _, _ = build_pad_xml(sample_fhir_bundle, header_cfg=sample_header_config)
ok, messages = validate_pad_xml(root)
assert ok is True
assert any("well-formed" in m for m in messages)
def test_compute_pad_stats(self, sample_fhir_bundle, sample_header_config):
"""Test computing PAD statistics."""
root, _, _, _ = build_pad_xml(sample_fhir_bundle, header_cfg=sample_header_config)
stats = compute_pad_stats(root)
assert "rechnungen_declared" in stats
assert "rechnungen_actual" in stats
assert "goziffer_count" in stats
assert isinstance(stats["warnings"], list)
# ============================================================================
# INTEGRATION TESTS
# ============================================================================
class TestIntegration:
"""End-to-end integration tests."""
def test_full_conversion_encounter_based(self, sample_fhir_bundle, sample_header_config, sample_placeholder_config):
"""Test complete conversion workflow (encounter-based)."""
# Validate input
fhir_ok, _ = validate_fhir_json(sample_fhir_bundle)
assert fhir_ok
# Compute stats
stats = compute_fhir_stats(sample_fhir_bundle)
assert stats["total_entries"] == 3
# Build XML
root, warnings, header, auto_filled = build_pad_xml(
sample_fhir_bundle,
header_cfg=sample_header_config,
placeholder_cfg=sample_placeholder_config
)
assert root is not None
# Validate output
pad_ok, _ = validate_pad_xml(root)
assert pad_ok
# Compute output stats
pad_stats = compute_pad_stats(root)
assert pad_stats["goziffer_count"] > 0
def test_full_conversion_claim_based(self, sample_claim_bundle, sample_header_config, sample_placeholder_config):
"""Test complete conversion workflow (claim-based)."""
# Validate input
fhir_ok, _ = validate_fhir_json(sample_claim_bundle)
assert fhir_ok
# Build XML
root, warnings, header, auto_filled = build_pad_xml(
sample_claim_bundle,
header_cfg=sample_header_config,
placeholder_cfg=sample_placeholder_config
)
assert root is not None
# Check claim data was used
assert header["behandelter_name"] == "Smith"
# Validate output
pad_ok, _ = validate_pad_xml(root)
assert pad_ok
def test_conversion_with_missing_data(self, sample_placeholder_config):
"""Test conversion with incomplete FHIR data uses placeholders."""
# Minimal bundle with missing fields
minimal_bundle = {
"resourceType": "Bundle",
"type": "collection",
"entry": [{
"resource": {
"resourceType": "Patient",
"id": "p1"
# Missing name, birthDate, etc.
}
}]
}
root, warnings, header, auto_filled = build_pad_xml(
minimal_bundle,
header_cfg={},
placeholder_cfg=sample_placeholder_config
)
# Should have many auto-filled fields
assert len(auto_filled) > 0
# XML should still be valid
assert root is not None
# ============================================================================
# EDGE CASE TESTS
# ============================================================================
class TestEdgeCases:
"""Tests for edge cases and error conditions."""
def test_empty_bundle(self):
"""Test handling empty bundle."""
bundle = {"resourceType": "Bundle", "type": "collection", "entry": []}
ok, messages = validate_fhir_json(bundle)
assert ok is True
def test_bundle_with_null_entries(self):
"""Test handling bundle with None entries."""
bundle = {
"resourceType": "Bundle",
"type": "collection",
"entry": [None, {"resource": {"resourceType": "Patient", "id": "p1"}}]
}
groups = group_entries(bundle)
# Should handle None gracefully
assert isinstance(groups, dict)
def test_resource_without_subject(self):
"""Test handling resource without subject reference."""
bundle = {
"resourceType": "Bundle",
"type": "collection",
"entry": [{
"resource": {
"resourceType": "Observation",
"id": "obs-1",
"status": "final"
# No subject field
}
}]
}
stats = compute_fhir_stats(bundle)
assert stats["entries_missing_subject"] == 1
def test_claim_with_empty_items(self):
"""Test handling Claim with empty items array."""
item = {"sequence": 1} # Missing productOrService
position = claim_item_to_position(item)
assert position["ziffer"] == ""
assert position["text"] == ""
def test_reference_with_multiple_slashes(self):
"""Test handling malformed reference with multiple slashes."""
# Current implementation will only split on first slash
ref_id = get_ref_id("http://example.com/fhir/Patient/123")
# Should return the last part or None
assert ref_id is not None or ref_id is None # Either is acceptable
def test_date_parsing_various_formats(self):
"""Test parsing various date formats."""
dates = [
"2024-01-01",
"2024-01-01T10:00:00",
"2024-01-01T10:00:00Z",
"2024-01-01T10:00:00+02:00",
"2024-01-01T10:00:00.123456Z"
]
for date_str in dates:
result = parse_iso_date(date_str)
assert result is not None or result is None # Should not crash
# ============================================================================
# PERFORMANCE TESTS (Optional)
# ============================================================================
class TestPerformance:
"""Basic performance tests."""
def test_large_bundle_grouping(self):
"""Test grouping with larger bundle."""
# Create bundle with 100 observations
entries = []
for i in range(100):
entries.append({
"resource": {
"resourceType": "Observation",
"id": f"obs-{i}",
"status": "final",
"subject": {"reference": f"Patient/patient-{i % 10}"},
"encounter": {"reference": f"Encounter/enc-{i % 10}"},
"effectiveDateTime": "2024-01-01T10:00:00Z"
}
})
bundle = {
"resourceType": "Bundle",
"type": "collection",
"entry": entries
}
# Should complete quickly
import time
start = time.time()
groups = group_entries(bundle)
elapsed = time.time() - start
assert elapsed < 1.0 # Should take less than 1 second
assert len(groups) == 10 # 10 unique (patient, encounter) combinations
# ============================================================================
# INPUT VALIDATION TESTS
# ============================================================================
class TestInputValidation:
"""Tests for input path validation."""
def test_validate_file_path_existing_file(self, tmp_path):
"""Test validation of existing file."""
# Create a temporary file
test_file = tmp_path / "test.json"
test_file.write_text('{"test": true}')
# Should succeed
result = validate_file_path(str(test_file), must_exist=True)
assert Path(result).exists()
assert Path(result).is_absolute()
def test_validate_file_path_nonexistent_file(self, tmp_path):
"""Test validation fails for nonexistent file."""
test_file = tmp_path / "nonexistent.json"
# Should raise FileNotFoundError
with pytest.raises(FileNotFoundError):
validate_file_path(str(test_file), must_exist=True)
def test_validate_file_path_empty_path(self):
"""Test validation fails for empty path."""
with pytest.raises(ValueError, match="File path cannot be empty"):
validate_file_path("")
def test_validate_file_path_none_path(self):
"""Test validation fails for None path."""
with pytest.raises(ValueError, match="File path must be a string"):
validate_file_path(None)
def test_validate_output_path_creates_directory(self, tmp_path):
"""Test output path validation creates parent directory."""
output_file = tmp_path / "new_dir" / "output.xml"
# Should succeed and create parent directory
result = validate_output_path(str(output_file))
assert Path(result).parent.exists()
assert Path(result).parent.is_dir()
def test_validate_output_path_existing_file_overwrite(self, tmp_path):
"""Test output path with existing file (overwrite=True)."""
output_file = tmp_path / "existing.xml"
output_file.write_text('<test/>')
# Should succeed (overwrite allowed by default)
result = validate_output_path(str(output_file), overwrite=True)
assert Path(result).exists()
def test_validate_output_path_existing_file_no_overwrite(self, tmp_path):
"""Test output path fails with existing file when overwrite=False."""
output_file = tmp_path / "existing.xml"
output_file.write_text('<test/>')
# Should raise FileExistsError
with pytest.raises(FileExistsError):
validate_output_path(str(output_file), overwrite=False)
def test_validate_directory_path_existing(self, tmp_path):
"""Test directory validation for existing directory."""
result = validate_directory_path(str(tmp_path), must_exist=True)
assert Path(result).is_dir()
assert Path(result).is_absolute()
def test_validate_directory_path_create(self, tmp_path):
"""Test directory validation creates directory."""
new_dir = tmp_path / "new_directory"
result = validate_directory_path(str(new_dir), must_exist=False, create=True)
assert Path(result).exists()
assert Path(result).is_dir()
def test_validate_directory_path_nonexistent(self, tmp_path):
"""Test directory validation fails for nonexistent directory."""
new_dir = tmp_path / "nonexistent_dir"
with pytest.raises(FileNotFoundError):
validate_directory_path(str(new_dir), must_exist=True, create=False)
def test_validate_directory_path_file_not_directory(self, tmp_path):
"""Test directory validation fails when path is a file."""
test_file = tmp_path / "file.txt"
test_file.write_text("content")
with pytest.raises(NotADirectoryError):
validate_directory_path(str(test_file), must_exist=True)
# ============================================================================
# CONFIG VALIDATION TESTS
# ============================================================================
class TestConfigValidation:
"""Tests for configuration validation."""
def test_validate_placeholder_config(self):
"""Test placeholder config validation."""
try:
from config_schemas import validate_placeholder_config
# Valid config
valid_config = {
"rechnungsersteller": {
"name": "Test",
"plz": "12345",
"ort": "Berlin",
"strasse": "Main St"
},
"leistungserbringer": {
"vorname": "Dr. John",
"name": "Doe"
},
"goziffer": {
"go": "EBM",
"ziffer": "12345",
"datum": "2024-01-01"
}
}
warnings = validate_placeholder_config(valid_config)
assert isinstance(warnings, list)
except ImportError:
pytest.skip("config_schemas module not available")
def test_validate_invalid_placeholder_config(self):
"""Test placeholder config validation with invalid data."""
try:
from config_schemas import validate_placeholder_config
# Invalid config - missing required fields
invalid_config = {
"rechnungsersteller": {
"name": "Test"
# Missing required fields
}
}
with pytest.raises(ValueError):
validate_placeholder_config(invalid_config)
except ImportError:
pytest.skip("config_schemas module not available")
def test_validate_mapping_config(self):
"""Test mapping config validation."""
try:
from config_schemas import validate_mapping_config
# Valid mapping config
valid_config = {
"resources": {
"Observation": {
"target": "goziffer",
"fields": {
"ziffer": {
"source": "code.coding[0].code"
}
}
}
}
}
warnings = validate_mapping_config(valid_config)
assert isinstance(warnings, list)
except ImportError:
pytest.skip("config_schemas module not available")
# ============================================================================
# RUN CONFIGURATION
# ============================================================================
if __name__ == "__main__":
# Run with: python test_fhir_to_pad_converter.py
pytest.main([__file__, "-v", "--tb=short"])