rustauth-cli 0.2.0

Command-line tools for RustAuth.
Documentation
#![allow(clippy::expect_used)]

use assert_cmd::Command;
use predicates::prelude::*;
use std::fs;
use std::path::Path;

#[cfg(feature = "deadpool-postgres")]
fn write_minimal_cargo_project(dir: &Path) {
    fs::create_dir_all(dir.join("src")).expect("create src dir");
    fs::write(
        dir.join("Cargo.toml"),
        r#"
[package]
name = "rustauth-doctor-test"
version = "0.1.0"
edition = "2021"

[dependencies]
"#,
    )
    .expect("write Cargo.toml");
    fs::write(dir.join("src/main.rs"), "fn main() {}\n").expect("write main.rs");
}

#[test]
fn doctor_reports_missing_production_secret_as_error() {
    let temp = tempfile::tempdir().expect("tempdir");
    fs::write(
        temp.path().join("rustauth.toml"),
        r#"
[project]
framework = "axum"
base_url = "https://app.example.com/api/auth"
base_path = "/api/auth"
production = true

[database]
adapter = "sqlx"
provider = "sqlite"
url_env = "DATABASE_URL"
migrations_dir = "migrations/rustauth"

[security]
secret_env = "RUSTAUTH_SECRET_FOR_TEST"

[plugins]
enabled = []
"#,
    )
    .expect("write config");

    Command::cargo_bin("rustauth")
        .expect("binary")
        .args([
            "doctor",
            "--cwd",
            temp.path().to_str().expect("utf8 path"),
            "--production",
        ])
        .env_remove("RUSTAUTH_SECRET_FOR_TEST")
        .env("DATABASE_URL", "sqlite::memory:")
        .assert()
        .failure()
        .stdout(predicate::str::contains("[ERROR] security.secret"));
}

#[test]
fn info_json_redacts_sensitive_values() {
    let temp = tempfile::tempdir().expect("tempdir");
    fs::write(
        temp.path().join("rustauth.toml"),
        r#"
[project]
framework = "axum"
base_url = "http://localhost:3000/api/auth"
base_path = "/api/auth"
production = false

[database]
adapter = "sqlx"
provider = "sqlite"
url_env = "DATABASE_URL"
migrations_dir = "migrations/rustauth"

[security]
secret_env = "RUSTAUTH_SECRET_FOR_TEST"

[plugins]
enabled = []
"#,
    )
    .expect("write config");

    Command::cargo_bin("rustauth")
        .expect("binary")
        .args([
            "info",
            "--cwd",
            temp.path().to_str().expect("utf8 path"),
            "--json",
        ])
        .env(
            "RUSTAUTH_SECRET_FOR_TEST",
            "super-secret-value-that-should-not-appear",
        )
        .env("DATABASE_URL", "sqlite::memory:")
        .assert()
        .success()
        .stdout(predicate::str::contains("[REDACTED]"))
        .stdout(predicate::str::contains("normalized_provider"))
        .stdout(predicate::str::contains("rustauth_version"))
        .stdout(predicate::str::contains("super-secret-value").not());
}

#[test]
#[cfg(feature = "deadpool-postgres")]
fn doctor_deadpool_postgres_adapter_without_dependency_reports_error() {
    let temp = tempfile::tempdir().expect("tempdir");
    write_minimal_cargo_project(temp.path());
    fs::write(
        temp.path().join("rustauth.toml"),
        r#"
[project]
framework = "axum"
base_url = "http://localhost:3000/api/auth"
base_path = "/api/auth"
production = false

[database]
adapter = "deadpool-postgres"
provider = "postgres"
url_env = "DATABASE_URL"
migrations_dir = "migrations/rustauth"

[security]
secret_env = "RUSTAUTH_SECRET"

[plugins]
enabled = []
"#,
    )
    .expect("write config");

    Command::cargo_bin("rustauth")
        .expect("binary")
        .args([
            "doctor",
            "--cwd",
            temp.path().to_str().expect("utf8 path"),
            "--json",
        ])
        .env_remove("DATABASE_URL")
        .env("RUSTAUTH_SECRET", "RustAuthSecretForCliTests-1234567890!")
        .assert()
        .failure()
        .stdout(predicate::str::contains("database.adapter_mismatch"))
        .stdout(predicate::str::contains("rustauth-deadpool-postgres"));
}

#[test]
fn doctor_magic_link_plugin_does_not_report_cli_feature_disabled() {
    let temp = tempfile::tempdir().expect("tempdir");
    fs::write(
        temp.path().join("rustauth.toml"),
        r#"
[project]
framework = "axum"
base_url = "http://localhost:3000/api/auth"
base_path = "/api/auth"
production = false

[database]
adapter = "sqlx"
provider = "sqlite"
url_env = "DATABASE_URL"
migrations_dir = "migrations/rustauth"

[security]
secret_env = "RUSTAUTH_SECRET"

[plugins]
enabled = ["magic-link"]
"#,
    )
    .expect("write config");

    Command::cargo_bin("rustauth")
        .expect("binary")
        .args([
            "doctor",
            "--cwd",
            temp.path().to_str().expect("utf8 path"),
            "--json",
        ])
        .env("RUSTAUTH_SECRET", "RustAuthSecretForCliTests-1234567890!")
        .env("DATABASE_URL", "sqlite::memory:")
        .assert()
        .success()
        .stdout(predicate::str::contains("plugins.cli_feature_disabled").not());
}

#[test]
#[cfg(not(feature = "passkey"))]
fn doctor_passkey_without_cli_feature_reports_error() {
    let temp = tempfile::tempdir().expect("tempdir");
    fs::write(
        temp.path().join("rustauth.toml"),
        r#"
[project]
framework = "axum"
base_url = "http://localhost:3000/api/auth"
base_path = "/api/auth"
production = false

[database]
adapter = "sqlx"
provider = "sqlite"
url_env = "DATABASE_URL"
migrations_dir = "migrations/rustauth"

[security]
secret_env = "RUSTAUTH_SECRET"

[plugins]
enabled = ["passkey"]
"#,
    )
    .expect("write config");

    Command::cargo_bin("rustauth")
        .expect("binary")
        .args([
            "doctor",
            "--cwd",
            temp.path().to_str().expect("utf8 path"),
            "--json",
        ])
        .env("RUSTAUTH_SECRET", "RustAuthSecretForCliTests-1234567890!")
        .env("DATABASE_URL", "sqlite::memory:")
        .assert()
        .failure()
        .stdout(predicate::str::contains("plugins.cli_feature_disabled"));
}