fraiseql-cli 2.17.0

CLI tools for FraiseQL v2 - Schema compilation and development utilities
Documentation
#![allow(clippy::unwrap_used, clippy::panic)] // Reason: test code — panics are acceptable

use super::*;

#[test]
fn mask_password_with_credentials() {
    let url = "postgres://user:password@localhost:5432/db";
    let masked = mask_password(url);
    assert!(masked.contains("***"));
    assert!(!masked.contains("password"));
}

#[test]
fn mask_password_without_credentials() {
    let url = "postgres://localhost:5432/db";
    let masked = mask_password(url);
    assert_eq!(masked, url);
}

/// `fraiseql setup` reports and verifies the version the SQL it installs returns.
#[test]
fn helpers_version_is_the_installed_librarys() {
    assert!(
        MUTATION_RESPONSE_SQL.contains(&format!("RETURN '{HELPERS_VERSION}';")),
        "library_version() in the embedded SQL must return {HELPERS_VERSION}"
    );
}

#[test]
fn mutation_response_sql_content_exists() {
    assert!(MUTATION_RESPONSE_SQL.contains("fraiseql.library_version"));
    assert!(MUTATION_RESPONSE_SQL.contains("fraiseql.mutation_ok"));
    assert!(MUTATION_RESPONSE_SQL.contains("fraiseql.mutation_err"));
}

/// Anti-drift guard for the *other* vendored SQL helper. `sql/helpers/mutation_response.sql`
/// at the repo root is the canonical copy; `crates/fraiseql-cli/sql/helpers/mutation_response.sql`
/// is what `include_str!` embeds into the binary. Nothing compared them in a leg that runs:
/// the only check lived in `tools/lint.sh`, an orphaned harness no CI leg and no Makefile
/// target ever invoked (#1055/#990), so the two could diverge silently and `fraiseql setup`
/// would install SQL that no longer matched the file the repo presents as canonical.
///
/// Its sibling — [`changelog_contract_matches_observers_migration`] — already had exactly this
/// guard as a Rust test. This one did not, which is the whole reason the shell check was the
/// only coverage.
#[test]
fn mutation_response_matches_canonical_copy() {
    let canonical_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("../../sql/helpers/mutation_response.sql");
    let canonical = std::fs::read_to_string(&canonical_path)
        .unwrap_or_else(|e| panic!("read {}: {e}", canonical_path.display()));
    assert_eq!(
        MUTATION_RESPONSE_SQL, canonical,
        "the CLI's embedded mutation_response.sql drifted from the canonical \
         sql/helpers/mutation_response.sql — re-copy it"
    );
}

#[test]
fn changelog_contract_sql_content_exists() {
    // The vendored contract installs the table the mutation outbox writes (#569).
    assert!(CHANGELOG_CONTRACT_SQL.contains("core.tb_entity_change_log"));
    assert!(CHANGELOG_CONTRACT_SQL.contains("CREATE TABLE IF NOT EXISTS"));
}

/// #569 anti-drift guard (mandatory, gate #3). The CLI's vendored change-log contract DDL
/// must stay **byte-identical** to the observers migration that OWNS the contract. If this
/// fails, re-copy `crates/fraiseql-observers/migrations/08_create_entity_change_log_contract.sql`
/// into `crates/fraiseql-cli/sql/helpers/entity_change_log_contract.sql`.
#[test]
fn changelog_contract_matches_observers_migration() {
    let migration_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("../fraiseql-observers/migrations/08_create_entity_change_log_contract.sql");
    let migration = std::fs::read_to_string(&migration_path)
        .unwrap_or_else(|e| panic!("read {}: {e}", migration_path.display()));
    assert_eq!(
        CHANGELOG_CONTRACT_SQL, migration,
        "CLI change-log contract DDL drifted from observers migration 08 — re-copy it"
    );
}

/// #390 lockstep: the change-log CHECK constraint must name **exactly** the
/// serialized tokens of [`fraiseql_core::security::ActorType`]. Adding an enum
/// variant without extending `chk_entity_change_log_actor_type` in migration 08
/// (and re-copying the vendored helper) makes this red — otherwise the database
/// would refuse rows the runtime legitimately stamps.
///
/// This tests the vendored copy; [`changelog_contract_matches_observers_migration`]
/// pins it byte-identical to the owning migration, so the check is transitive.
#[test]
fn actor_constraint_covers_every_actor_type_token() {
    let constraint_start = CHANGELOG_CONTRACT_SQL
        .find("chk_entity_change_log_actor_type CHECK")
        .expect("contract DDL carries the actor-type CHECK constraint");
    let constraint = &CHANGELOG_CONTRACT_SQL[constraint_start..];
    let constraint = &constraint[..constraint.find(';').expect("constraint terminated")];

    for actor in fraiseql_core::security::ActorType::ALL {
        let quoted = format!("'{}'", actor.as_str());
        assert!(
            constraint.contains(&quoted),
            "CHECK constraint is missing ActorType token {quoted} — extend migration 08"
        );
    }
    // And nothing beyond the enum: every quoted token in the IN (…) list parses.
    let in_list_start = constraint.find("IN (").expect("constraint uses IN (…)");
    let in_list = &constraint[in_list_start..];
    let in_list = &in_list[..in_list.find(')').expect("IN list closed")];
    for token in in_list.split('\'').skip(1).step_by(2) {
        assert!(
            fraiseql_core::security::ActorType::from_token(token).is_some(),
            "constraint names '{token}', which is not an ActorType — remove it or add the variant"
        );
    }
}