//! Regression: secret names containing path-traversal characters
//! (`/`, `\`, `..`) MUST be rejected at the MCP validation layer
//! BEFORE the vault is opened.
//!
//! Without this check, an MCP client could probe for arbitrary names
//! that ultimately fail at the vault layer — but only after the
//! platform passphrase dialog has already popped on the user's screen.
//! That wastes user attention and leaks a timing oracle.
//!
//! Found while stress-testing the MCP transport — `envseal_revoke
//! "../../etc/passwd"` was hanging on the GUI passphrase popup
//! instead of erroring out immediately.

use envseal_mcp::tools::revoke::{execute, RevokeParams};
use envseal_mcp::tools::store::{execute as store_execute, StoreParams};

#[tokio::test]
async fn revoke_rejects_unix_path_traversal() {
    let err = execute(RevokeParams {
        name: "../../etc/passwd".to_string(),
    })
    .await
    .expect_err("path traversal must be rejected");
    assert!(err.contains("path separators"), "unexpected error: {err}");
}

#[tokio::test]
async fn revoke_rejects_windows_path_traversal() {
    let err = execute(RevokeParams {
        name: r"..\..\Windows\System32".to_string(),
    })
    .await
    .expect_err("backslash path traversal must be rejected");
    assert!(err.contains("path separators"), "unexpected error: {err}");
}

#[tokio::test]
async fn revoke_rejects_dot_dot_segment() {
    let err = execute(RevokeParams {
        name: "valid..name".to_string(),
    })
    .await
    .expect_err(".. anywhere must be rejected (could be path-segment)");
    assert!(
        err.contains("path separators") || err.contains("..'"),
        "unexpected error: {err}"
    );
}

#[tokio::test]
async fn store_rejects_path_traversal() {
    let err = store_execute(StoreParams {
        name: "etc/passwd".to_string(),
        value: "anything".to_string(),
        force: false,
    })
    .await
    .expect_err("forward-slash in name must be rejected");
    assert!(err.contains("path separators"), "unexpected error: {err}");
}

#[tokio::test]
async fn store_rejects_null_byte() {
    let err = store_execute(StoreParams {
        name: "name\0withnull".to_string(),
        value: "anything".to_string(),
        force: false,
    })
    .await
    .expect_err("null byte in name must be rejected");
    assert!(
        err.contains("null bytes") || err.contains("control"),
        "unexpected error: {err}"
    );
}

#[tokio::test]
async fn store_rejects_newline() {
    let err = store_execute(StoreParams {
        name: "name\nwithnewline".to_string(),
        value: "anything".to_string(),
        force: false,
    })
    .await
    .expect_err("newline in name must be rejected");
    assert!(
        err.contains("newlines") || err.contains("control"),
        "unexpected error: {err}"
    );
}
