apollo-errors 0.7.0

Structured error handling with automatic format conversion
Documentation
//! Tests for HTML format output (to_html)

mod common;

use apollo_errors::{CodeCase, Error as ErrorTrait, FieldCase, FormatConfig};
use common::{ErrorWithFields, ErrorWithHelp, ErrorWithRenamedField, SimpleError};
use insta::assert_snapshot;

#[test]
fn test_simple_error_html() {
    let error = SimpleError::Simple;
    let html = error.to_html(FormatConfig::default());
    assert_snapshot!(html, @r#"
    <div class="error">
    <h3 class="error-code">errors::simple</h3>
    <p class="error-message">Something went wrong</p>

    </div>
    "#);
}

#[test]
fn test_error_with_help_html() {
    let error = ErrorWithHelp::ConfigError;
    let html = error.to_html(FormatConfig::default());
    assert_snapshot!(html, @r#"
    <div class="error">
    <h3 class="error-code">config::error</h3>
    <p class="error-message">Configuration error</p>
    <p class="error-help">Check your configuration file</p>
    </div>
    "#);
}

#[test]
fn test_error_with_single_extension_field_html() {
    let error = ErrorWithFields::MissingConfig {
        expected_path: "/etc/app/config.yaml".to_string(),
    };
    let html = error.to_html(FormatConfig::default());
    assert_snapshot!(html, @r###"
    <div class="error">
    <h3 class="error-code">config::missing</h3>
    <p class="error-message">Missing configuration</p>

    <div class="error-extensions">
    <div class="error-field"><span class="field-name">expected_path:</span> <span class="field-value">/etc/app/config.yaml</span></div>
    </div>
    </div>
    "###);
}

#[test]
fn test_error_with_multiple_extension_fields_html() {
    let error = ErrorWithFields::InvalidPort {
        port: 8080,
        config_file: "/etc/server.conf".to_string(),
    };
    let html = error.to_html(FormatConfig::default());
    assert_snapshot!(html, @r###"
    <div class="error">
    <h3 class="error-code">config::invalid_port</h3>
    <p class="error-message">Invalid port</p>

    <div class="error-extensions">
    <div class="error-field"><span class="field-name">port:</span> <span class="field-value">8080</span></div>
    <div class="error-field"><span class="field-name">config_file:</span> <span class="field-value">/etc/server.conf</span></div>
    </div>
    </div>
    "###);
}

#[test]
fn test_error_with_numeric_extension_html() {
    let error = ErrorWithFields::InvalidPort {
        port: 65535,
        config_file: "/app/settings.toml".to_string(),
    };
    let html = error.to_html(FormatConfig::default());
    assert_snapshot!(html, @r###"
    <div class="error">
    <h3 class="error-code">config::invalid_port</h3>
    <p class="error-message">Invalid port</p>

    <div class="error-extensions">
    <div class="error-field"><span class="field-name">port:</span> <span class="field-value">65535</span></div>
    <div class="error-field"><span class="field-name">config_file:</span> <span class="field-value">/app/settings.toml</span></div>
    </div>
    </div>
    "###);
}

#[test]
fn test_html_escapes_dangerous_characters() {
    // Test that HTML special characters in extension fields are escaped
    let error = ErrorWithFields::MissingConfig {
        expected_path: "<script>alert('xss')</script>".to_string(),
    };
    let html = error.to_html(FormatConfig::default());
    // The path should be escaped, not rendered as a script tag
    assert!(html.contains("&lt;script&gt;alert(&#x27;xss&#x27;)&lt;/script&gt;"));
    assert!(!html.contains("<script>"));
}

#[test]
fn test_html_escapes_ampersand_and_quotes() {
    let error = ErrorWithFields::MissingConfig {
        expected_path: "path with & and \"quotes\"".to_string(),
    };
    let html = error.to_html(FormatConfig::default());
    assert!(html.contains("path with &amp; and &quot;quotes&quot;"));
    assert!(!html.contains("path with & and"));
}

#[test]
fn test_html_with_interpolated_message() {
    use common::ConfigStructError;

    let error = ConfigStructError {
        port: 8080,
        config_path: "/etc/config.toml".to_string(),
    };
    let html = error.to_html(FormatConfig::default());
    // Verify the message contains the interpolated port value
    assert!(
        html.contains("Configuration error: invalid port 8080"),
        "HTML message should contain interpolated port value, got: {}",
        html
    );
}

#[test]
fn test_error_with_fields_kebab_case_code_case_screaming_snake_case() {
    let config = FormatConfig {
        field_case: FieldCase::KebabCase,
        code_case: CodeCase::ScreamingSnakeCase,
    };
    let error = ErrorWithFields::InvalidPort {
        port: 8080,
        config_file: "/etc/config.toml".to_string(),
    };
    let html = error.to_html(config);
    assert_snapshot!(html, @r#"
    <div class="error">
    <h3 class="error-code">CONFIG_INVALID_PORT</h3>
    <p class="error-message">Invalid port</p>

    <div class="error-extensions">
    <div class="error-field"><span class="field-name">port:</span> <span class="field-value">8080</span></div>
    <div class="error-field"><span class="field-name">config-file:</span> <span class="field-value">/etc/config.toml</span></div>
    </div>
    </div>
    "#);
}

#[test]
fn test_error_with_fields_pascal_case_code_case_default() {
    let config = FormatConfig {
        field_case: FieldCase::PascalCase,
        ..FormatConfig::default()
    };
    let error = ErrorWithFields::InvalidPort {
        port: 8080,
        config_file: "/etc/config.toml".to_string(),
    };
    let html = error.to_html(config);
    assert_snapshot!(html, @r#"
    <div class="error">
    <h3 class="error-code">config::invalid_port</h3>
    <p class="error-message">Invalid port</p>

    <div class="error-extensions">
    <div class="error-field"><span class="field-name">Port:</span> <span class="field-value">8080</span></div>
    <div class="error-field"><span class="field-name">ConfigFile:</span> <span class="field-value">/etc/config.toml</span></div>
    </div>
    </div>
    "#);
}

#[test]
fn test_error_with_fields_default_case_code_case_screaming_snake_case() {
    let config = FormatConfig {
        code_case: CodeCase::ScreamingSnakeCase,
        ..FormatConfig::default()
    };
    let error = ErrorWithFields::InvalidPort {
        port: 8080,
        config_file: "/etc/config.toml".to_string(),
    };
    let html = error.to_html(config);
    assert_snapshot!(html, @r#"
    <div class="error">
    <h3 class="error-code">CONFIG_INVALID_PORT</h3>
    <p class="error-message">Invalid port</p>

    <div class="error-extensions">
    <div class="error-field"><span class="field-name">port:</span> <span class="field-value">8080</span></div>
    <div class="error-field"><span class="field-name">config_file:</span> <span class="field-value">/etc/config.toml</span></div>
    </div>
    </div>
    "#);
}

#[test]
fn test_renamed_field_pascal_case_screaming_snake_code_html() {
    let error = ErrorWithRenamedField::WithRename {
        my_field: "value".to_string(),
    };
    let config = FormatConfig {
        field_case: FieldCase::PascalCase,
        code_case: CodeCase::ScreamingSnakeCase,
    };
    let html = error.to_html(config);
    assert_snapshot!(html, @r#"
    <div class="error">
    <h3 class="error-code">TEST_RENAMED_FIELD</h3>
    <p class="error-message">Field was renamed</p>

    <div class="error-extensions">
    <div class="error-field"><span class="field-name">MyRenamedField:</span> <span class="field-value">value</span></div>
    </div>
    </div>
    "#);
}