floe-core 0.4.3

Core library for Floe, a YAML-driven technical ingestion tool.
Documentation
use super::*;

#[test]
fn plain_literal_is_returned_unchanged() {
    let result = expand_env_token("dapi1234567890abcdef", "my_catalog").unwrap();
    assert_eq!(result, "dapi1234567890abcdef");
}

#[test]
fn env_ref_is_expanded_when_var_is_set() {
    std::env::set_var("FLOE_TEST_UNITY_TOKEN", "test-secret");
    let result = expand_env_token("${FLOE_TEST_UNITY_TOKEN}", "my_catalog").unwrap();
    std::env::remove_var("FLOE_TEST_UNITY_TOKEN");
    assert_eq!(result, "test-secret");
}

#[test]
fn env_ref_errors_when_var_is_not_set() {
    std::env::remove_var("FLOE_TEST_UNITY_MISSING");
    let err = expand_env_token("${FLOE_TEST_UNITY_MISSING}", "my_catalog").unwrap_err();
    assert!(
        err.to_string().contains("FLOE_TEST_UNITY_MISSING"),
        "error should name the missing var"
    );
}

#[test]
fn mixed_literal_and_ref_is_rejected() {
    let err = expand_env_token("Bearer ${MY_TOKEN}", "my_catalog").unwrap_err();
    let msg = err.to_string();
    assert!(
        msg.contains("mixing literal text"),
        "error should explain the constraint, got: {msg}"
    );
}

#[test]
fn empty_placeholder_is_rejected() {
    let err = expand_env_token("${}", "my_catalog").unwrap_err();
    assert!(
        err.to_string().contains("invalid placeholder syntax"),
        "got: {err}"
    );
}

#[test]
fn unclosed_brace_is_rejected() {
    let err = expand_env_token("${UNCLOSED", "my_catalog").unwrap_err();
    assert!(
        err.to_string().contains("mixing literal text"),
        "got: {err}"
    );
}

#[test]
fn parse_unity_error_extracts_code_and_message() {
    let body = r#"{"error_code":"RESOURCE_DOES_NOT_EXIST","message":"Table not found"}"#;
    let result = parse_unity_error(body);
    assert_eq!(result, "RESOURCE_DOES_NOT_EXIST: Table not found");
}

#[test]
fn parse_unity_error_falls_back_to_raw_body_on_invalid_json() {
    let body = "Internal Server Error";
    assert_eq!(parse_unity_error(body), "Internal Server Error");
}

#[test]
fn parse_unity_error_uses_unknown_code_when_field_missing() {
    let body = r#"{"message":"Schema not found"}"#;
    let result = parse_unity_error(body);
    assert_eq!(result, "UNKNOWN: Schema not found");
}

fn test_cfg(server_url: &str) -> UnityCatalogConfig {
    UnityCatalogConfig {
        catalog_name: "test_def".to_string(),
        host: server_url.to_string(),
        unity_catalog: "main".to_string(),
        schema: "bronze".to_string(),
        table: "orders".to_string(),
        token: "test-token".to_string(),
        create_schema_if_missing: false,
    }
}

fn block_on<F: std::future::Future>(f: F) -> F::Output {
    tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap()
        .block_on(f)
}

#[test]
fn table_already_registered_at_same_location_returns_ok() {
    let mut server = mockito::Server::new();
    let table_uri = "s3://my-bucket/bronze/orders";
    server
        .mock("GET", "/api/2.1/unity-catalog/tables/main.bronze.orders")
        .with_status(200)
        .with_header("content-type", "application/json")
        .with_body(format!(r#"{{"storage_location":"{table_uri}"}}"#))
        .create();

    let cfg = test_cfg(&server.url());
    block_on(register_unity_table(&cfg, table_uri)).unwrap();
}

#[test]
fn table_registered_at_different_location_returns_error() {
    let mut server = mockito::Server::new();
    server
        .mock("GET", "/api/2.1/unity-catalog/tables/main.bronze.orders")
        .with_status(200)
        .with_header("content-type", "application/json")
        .with_body(r#"{"storage_location":"s3://other-bucket/path"}"#)
        .create();

    let cfg = test_cfg(&server.url());
    let err = block_on(register_unity_table(&cfg, "s3://my-bucket/bronze/orders")).unwrap_err();
    let msg = err.to_string();
    assert!(
        msg.contains("already registered"),
        "error should flag location mismatch, got: {msg}"
    );
}

#[test]
fn table_200_without_storage_location_is_managed_table_collision() {
    let mut server = mockito::Server::new();
    server
        .mock("GET", "/api/2.1/unity-catalog/tables/main.bronze.orders")
        .with_status(200)
        .with_header("content-type", "application/json")
        .with_body(r#"{"name":"orders"}"#)
        .create();

    let cfg = test_cfg(&server.url());
    let err = block_on(register_unity_table(&cfg, "s3://my-bucket/bronze/orders")).unwrap_err();
    let msg = err.to_string();
    assert!(
        msg.contains("storage_location"),
        "error should mention storage_location could not be read, got: {msg}"
    );
}

#[test]
fn table_not_found_creates_it() {
    let mut server = mockito::Server::new();
    server
        .mock("GET", "/api/2.1/unity-catalog/tables/main.bronze.orders")
        .with_status(404)
        .create();
    server
        .mock("POST", "/api/2.1/unity-catalog/tables")
        .with_status(200)
        .create();

    let cfg = test_cfg(&server.url());
    block_on(register_unity_table(&cfg, "s3://my-bucket/bronze/orders")).unwrap();
}

#[test]
fn table_create_with_schema_creation() {
    let mut server = mockito::Server::new();
    server
        .mock("GET", "/api/2.1/unity-catalog/tables/main.bronze.orders")
        .with_status(404)
        .create();
    server
        .mock("GET", "/api/2.1/unity-catalog/schemas/main.bronze")
        .with_status(404)
        .create();
    server
        .mock("POST", "/api/2.1/unity-catalog/schemas")
        .with_status(200)
        .create();
    server
        .mock("POST", "/api/2.1/unity-catalog/tables")
        .with_status(200)
        .create();

    let mut cfg = test_cfg(&server.url());
    cfg.create_schema_if_missing = true;
    block_on(register_unity_table(&cfg, "s3://my-bucket/bronze/orders")).unwrap();
}

#[test]
fn unexpected_get_status_returns_error() {
    let mut server = mockito::Server::new();
    server
        .mock("GET", "/api/2.1/unity-catalog/tables/main.bronze.orders")
        .with_status(403)
        .with_body(r#"{"error_code":"PERMISSION_DENIED","message":"Access denied"}"#)
        .create();

    let cfg = test_cfg(&server.url());
    let err = block_on(register_unity_table(&cfg, "s3://my-bucket/bronze/orders")).unwrap_err();
    let msg = err.to_string();
    assert!(
        msg.contains("403"),
        "error should mention the status, got: {msg}"
    );
    assert!(
        msg.contains("PERMISSION_DENIED"),
        "error should include parsed error code, got: {msg}"
    );
}