cima-rs 0.0.6

Rust library and CLI providing access to the Spanish AEMPS CIMA REST API and nomenclator XML parser
Documentation
use anyhow::Result;
use cima_rs::{
    CimaClient, MasterDataParams, MasterDataType, SearchClinicalDescriptionParams,
    SearchMedicationsParams, SearchPresentationsParams,
};

/// Helper to create a client for tests
fn create_client() -> Result<CimaClient> {
    CimaClient::new()
}

#[tokio::test]
async fn test_get_medication_by_registration_number() -> Result<()> {
    let client = create_client()?;

    let med = client.get_medication(Some("72112"), None).await?;

    assert_eq!(med.nregistro, "72112");
    assert!(med.name.contains("PARACETAMOL"));
    assert!(!med.presentations.is_empty());

    Ok(())
}

#[tokio::test]
async fn test_get_medication_by_national_code() -> Result<()> {
    let client = create_client()?;

    let med = client.get_medication(None, Some("672442")).await?;

    assert_eq!(med.nregistro, "72112");
    assert!(med.name.contains("PARACETAMOL"));

    Ok(())
}

#[tokio::test]
async fn test_search_medications() -> Result<()> {
    let client = create_client()?;

    let params = SearchMedicationsParams {
        name: Some("paracetamol".to_string()),
        ..Default::default()
    };

    let response = client.search_medications(&params).await?;

    assert!(response.total_rows > 0);
    assert!(!response.results.is_empty());
    assert_eq!(response.page, 1);

    Ok(())
}

#[tokio::test]
async fn test_search_medications_with_filters() -> Result<()> {
    let client = create_client()?;

    let params = SearchMedicationsParams {
        name: Some("ibuprofeno".to_string()),
        commercialized: Some(1), // Only commercialized
        ..Default::default()
    };

    let response = client.search_medications(&params).await?;

    assert!(response.total_rows > 0);
    // Check that at least some results are commercialized
    let has_commercialized = response
        .results
        .iter()
        .any(|med| med.commercialized == Some(true));
    assert!(has_commercialized);

    Ok(())
}

#[tokio::test]
async fn test_get_presentation() -> Result<()> {
    let client = create_client()?;

    let pres = client.get_presentation("672442").await?;

    assert_eq!(pres.cn, "672442");
    assert!(pres.name.contains("PARACETAMOL"));

    Ok(())
}

#[tokio::test]
async fn test_search_presentations() -> Result<()> {
    let client = create_client()?;

    let params = SearchPresentationsParams {
        registration_number: Some("72112".to_string()),
        ..Default::default()
    };

    let response = client.search_presentations(&params).await?;

    assert!(response.total_rows > 0);
    assert!(!response.results.is_empty());
    // Verify we got results (can't check registration_number since it's not in PresentationSummary)

    Ok(())
}

#[tokio::test]
async fn test_get_all_supply_problems() -> Result<()> {
    let client = create_client()?;

    let response = client.get_all_supply_problems().await?;

    assert!(response.total_rows > 0);
    assert_eq!(response.page, 1);
    assert!(!response.results.is_empty());

    Ok(())
}

#[tokio::test]
async fn test_get_supply_problems_by_national_code() -> Result<()> {
    let client = create_client()?;

    // First get all problems to find a CN with issues
    let all_response = client.get_all_supply_problems().await?;

    if let Some(first_problem) = all_response.results.first() {
        let cn = &first_problem.cn;
        let response = client.get_supply_problems(cn).await?;

        assert!(response.total_rows > 0);
        assert!(response.results.iter().all(|p| p.cn == *cn));
    }

    Ok(())
}

#[tokio::test]
async fn test_get_safety_notes() -> Result<()> {
    let client = create_client()?;

    // Use a known medication with safety notes
    let notas = client.get_safety_notes("72112").await?;

    // This medication should have safety notes based on our earlier testing
    assert!(!notas.is_empty());

    Ok(())
}

#[tokio::test]
async fn test_get_informative_materials() -> Result<()> {
    let client = create_client()?;

    // Use the same medication - this now returns a single SafetyMaterial object
    let _material = client.get_informative_materials("72112").await?;

    // Materials returns a single SafetyMaterial object, just verify the call succeeded
    // The object may have empty lists if no materials are available

    Ok(())
}

#[tokio::test]
async fn test_get_master_data_by_name() -> Result<()> {
    let client = create_client()?;

    let params = MasterDataParams {
        name: Some("par".to_string()),
        ..Default::default()
    };

    let response = client
        .get_master_data(MasterDataType::ActiveIngredients, &params)
        .await?;

    assert!(response.total_rows > 0);
    assert!(!response.results.is_empty());
    // Check that results contain "par" in the name
    assert!(
        response
            .results
            .iter()
            .any(|item| item.name.to_lowercase().contains("par"))
    );

    Ok(())
}

#[tokio::test]
async fn test_get_master_data_by_id() -> Result<()> {
    let client = create_client()?;

    let params = MasterDataParams {
        id: Some(12), // PARACETAMOL
        ..Default::default()
    };

    let response = client
        .get_master_data(MasterDataType::ActiveIngredients, &params)
        .await?;

    assert!(response.total_rows > 0);
    assert!(response.results.iter().all(|item| item.id == Some(12)));

    Ok(())
}

#[tokio::test]
async fn test_get_master_data_pharmaceutical_forms() -> Result<()> {
    let client = create_client()?;

    let params = MasterDataParams {
        name: Some("comp".to_string()),
        ..Default::default()
    };

    let response = client
        .get_master_data(MasterDataType::PharmaceuticalForms, &params)
        .await?;

    assert!(response.total_rows > 0);
    assert!(!response.results.is_empty());

    Ok(())
}

#[tokio::test]
async fn test_get_change_log() -> Result<()> {
    let client = create_client()?;

    // Use a recent date to get some changes
    let response = client.get_change_log("01/01/2024", None).await?;

    // There should be MANY changes since 2024
    assert!(response.total_rows > 100);
    assert!(!response.results.is_empty());

    Ok(())
}

#[tokio::test]
#[ignore] // API BUG: Passing registration_number parameter returns 500 error for numeric IDs,
// null response for complex IDs. Verified via curl - this is a CIMA API issue.
async fn test_get_change_log_specific_medication() -> Result<()> {
    let client = create_client()?;

    // This test would work if the API supported the registration_number parameter correctly
    // Workaround: Fetch all and filter client-side
    let response = client
        .get_change_log("01/01/2020", Some(&["72112"]))
        .await?;

    // Filter to only show changes for this specific medication
    assert!(response.results.iter().all(|c| c.nregistro == "72112"));

    Ok(())
}

#[tokio::test]
async fn test_search_clinical_descriptions() -> Result<()> {
    let client = create_client()?;

    // Search by name
    let params = SearchClinicalDescriptionParams {
        name: Some("paracetamol".to_string()),
        ..Default::default()
    };

    let response = client.search_clinical_descriptions(&params).await?;

    // Should get some results for paracetamol
    assert!(response.total_rows > 0);
    assert!(!response.results.is_empty());

    Ok(())
}

#[tokio::test]
async fn test_pagination_works() -> Result<()> {
    let client = create_client()?;

    let params = SearchMedicationsParams {
        name: Some("a".to_string()), // Short name to get many results
        page: Some(2),               // Request second page
        ..Default::default()
    };

    let response = client.search_medications(&params).await?;

    if response.total_rows > response.page_size {
        // If there are multiple pages, verify we got page 2
        assert_eq!(response.page, 2);
    }

    Ok(())
}