relay-knowledge 1.1.17

Graph-database-based knowledge graph project.
Documentation
use super::*;
use crate::model_provider::{
    ModelRequestHeader,
    test_support::{echo_request, openai_request, remote_retrieval, test_service},
};

#[tokio::test]
async fn profile_crud_preserves_secrets_and_redacts_responses() {
    let service = test_service("crud");
    let retrieval = ReadModelBackendConfig::local();
    let saved = service
        .save_profile(
            " primary ",
            openai_request("gpt-a", Some("secret-a")),
            &retrieval,
        )
        .await
        .expect("profile should save");

    assert_eq!(saved.default_profile.as_deref(), Some("primary"));
    assert_eq!(saved.profiles[0].base_url, "https://api.example.com/v1");
    assert!(saved.profiles[0].api_key_configured);
    assert_eq!(saved.profiles[0].headers[0].value, None);
    assert!(saved.profiles[0].headers[0].configured);

    let mut routine_update = openai_request("gpt-b", None);
    routine_update.ssl_verify = None;
    routine_update.capabilities = None;
    let updated = service
        .save_profile("primary", routine_update, &retrieval)
        .await
        .expect("profile should update");
    assert_eq!(updated.profiles[0].model, "gpt-b");
    assert!(updated.profiles[0].api_key_configured);
    assert_eq!(updated.profiles[0].ssl_verify, Some(true));
    assert_eq!(updated.profiles[0].capabilities.input.image, Some(true));

    let raw = fs::read_to_string(service.paths.model_profiles_file())
        .await
        .expect("profile file should exist");
    assert!(raw.contains("secret-a"));
    assert!(raw.contains("header-secret"));
    assert!(
        !serde_json::to_string(&updated)
            .unwrap()
            .contains("secret-a")
    );

    let mut redacted_header_update = openai_request("gpt-c", None);
    redacted_header_update.headers = vec![ModelRequestHeader {
        name: "x-extra-secret".to_owned(),
        value: None,
        secret: true,
        configured: true,
    }];
    service
        .save_profile("primary", redacted_header_update, &retrieval)
        .await
        .expect("redacted header update should preserve stored header value");
    let raw = fs::read_to_string(service.paths.model_profiles_file())
        .await
        .expect("profile file should exist");
    assert!(raw.contains("header-secret"));

    let mut clear_key_update = openai_request("gpt-d", None);
    clear_key_update.clear_api_key = true;
    clear_key_update.headers = vec![ModelRequestHeader {
        name: "x-extra-secret".to_owned(),
        value: Some("header-secret".to_owned()),
        secret: true,
        configured: false,
    }];
    let cleared = service
        .save_profile("primary", clear_key_update, &retrieval)
        .await
        .expect("header-auth update should clear stored api key");
    assert!(!cleared.profiles[0].api_key_configured);
    let raw = fs::read_to_string(service.paths.model_profiles_file())
        .await
        .expect("profile file should exist");
    assert!(!raw.contains("secret-a"));
}

#[tokio::test]
async fn first_save_of_runtime_profile_preserves_environment_secret() {
    let service = test_service("runtime-secret");
    let retrieval = remote_retrieval();
    let profiles = service
        .profiles(&retrieval)
        .await
        .expect("runtime profile should load");
    assert_eq!(
        profiles.default_profile.as_deref(),
        Some(DEFAULT_PROFILE_NAME)
    );
    assert!(profiles.profiles[0].api_key_configured);
    assert_eq!(profiles.profiles[0].source, "environment");

    let mut request = openai_request("text-embedding-3-small", None);
    request.base_url = Some("https://api.openai.example/v1".to_owned());
    request.headers.clear();
    request.ssl_verify = None;
    request.capabilities = None;
    let saved = service
        .save_profile(DEFAULT_PROFILE_NAME, request, &retrieval)
        .await
        .expect("runtime profile edit should preserve secret");

    assert_eq!(saved.profiles[0].source, "config");
    assert!(saved.profiles[0].api_key_configured);
    assert_eq!(saved.profiles[0].capabilities.input.text, Some(true));
    let raw = fs::read_to_string(service.paths.model_profiles_file())
        .await
        .expect("profile file should exist");
    assert!(raw.contains("env-secret"));
}

#[tokio::test]
async fn delete_profile_reassigns_default_and_reports_summary() {
    let service = test_service("delete");
    let retrieval = ReadModelBackendConfig::local();
    service
        .save_profile("first", echo_request("echo-a", true), &retrieval)
        .await
        .expect("first profile should save");
    service
        .save_profile("second", echo_request("echo-b", false), &retrieval)
        .await
        .expect("second profile should save");

    let response = service
        .delete_profile("first", &retrieval)
        .await
        .expect("profile should delete");
    assert_eq!(response.default_profile.as_deref(), Some("second"));
    assert_eq!(response.profiles.len(), 1);
    assert!(response.profiles[0].is_default);

    let summary = service.profile_summary(&retrieval).await;
    assert!(summary.loaded);
    assert_eq!(summary.profile_count, 1);
    assert_eq!(summary.default_profile.as_deref(), Some("second"));
}

#[tokio::test]
async fn profile_validation_rejects_invalid_inputs() {
    let service = test_service("validation");
    let retrieval = ReadModelBackendConfig::local();

    assert!(
        service
            .save_profile("bad name", echo_request("echo", true), &retrieval)
            .await
            .is_err()
    );
    let mut missing_auth = openai_request("gpt", None);
    missing_auth.headers.clear();
    assert!(
        service
            .save_profile("missing-auth", missing_auth, &retrieval)
            .await
            .is_err()
    );
    let mut bad_url = openai_request("gpt", Some("secret"));
    bad_url.base_url = Some("ftp://example.test".to_owned());
    assert!(
        service
            .save_profile("bad-url", bad_url, &retrieval)
            .await
            .is_err()
    );
    let mut bad_sampling = echo_request("echo", true);
    bad_sampling.temperature = 3.0;
    assert!(
        service
            .save_profile("bad-sampling", bad_sampling, &retrieval)
            .await
            .is_err()
    );
    let mut duplicate_headers = openai_request("gpt", None);
    duplicate_headers.headers = vec![
        ModelRequestHeader {
            name: "X-Key".to_owned(),
            value: Some("a".to_owned()),
            secret: true,
            configured: false,
        },
        ModelRequestHeader {
            name: "x-key".to_owned(),
            value: Some("b".to_owned()),
            secret: true,
            configured: false,
        },
    ];
    assert!(
        service
            .save_profile("dup-headers", duplicate_headers, &retrieval)
            .await
            .is_err()
    );
    let mut bad_header_name = openai_request("gpt", None);
    bad_header_name.headers = vec![ModelRequestHeader {
        name: "bad header".to_owned(),
        value: Some("secret".to_owned()),
        secret: true,
        configured: false,
    }];
    assert!(
        service
            .save_profile("bad-header-name", bad_header_name, &retrieval)
            .await
            .is_err()
    );
    let mut bad_header_value = openai_request("gpt", None);
    bad_header_value.headers = vec![ModelRequestHeader {
        name: "x-api-key".to_owned(),
        value: Some("line\nbreak".to_owned()),
        secret: true,
        configured: false,
    }];
    assert!(
        service
            .save_profile("bad-header-value", bad_header_value, &retrieval)
            .await
            .is_err()
    );
}