rustauth-core 0.2.0

Core types and primitives for RustAuth.
Documentation
use rustauth_core::db::{
    auth_schema, AuthSchemaOptions, DbField, DbFieldType, IdGeneration, IdPolicy, RateLimitStorage,
    TableOptions,
};
use rustauth_core::error::RustAuthError;

#[test]
fn auth_schema_uses_plural_table_names_by_default() {
    let schema = auth_schema(AuthSchemaOptions::default());

    assert_eq!(
        schema.table("user").map(|table| table.name.as_str()),
        Some("users")
    );
    assert_eq!(
        schema.table("account").map(|table| table.name.as_str()),
        Some("accounts")
    );
    assert_eq!(
        schema.table("session").map(|table| table.name.as_str()),
        Some("sessions")
    );
    assert_eq!(
        schema
            .table("verification")
            .map(|table| table.name.as_str()),
        Some("verifications")
    );
}

#[test]
fn auth_schema_uses_snake_case_column_names_by_default() {
    let schema = auth_schema(AuthSchemaOptions::default());

    assert_eq!(
        schema
            .table("account")
            .and_then(|table| table.field("refresh_token_expires_at"))
            .map(|field| field.name.as_str()),
        Some("refresh_token_expires_at")
    );
    assert_eq!(
        schema
            .table("account")
            .and_then(|table| table.field("access_token_expires_at"))
            .map(|field| field.name.as_str()),
        Some("access_token_expires_at")
    );
}

#[test]
fn auth_schema_uses_custom_refresh_token_expiry_field_name() {
    let schema = auth_schema(AuthSchemaOptions {
        account: TableOptions::default().with_field_name(
            "refresh_token_expires_at",
            "custom_refresh_token_expires_at",
        ),
        ..AuthSchemaOptions::default()
    });

    assert_eq!(
        schema
            .table("account")
            .and_then(|table| table.field("refresh_token_expires_at"))
            .map(|field| field.name.as_str()),
        Some("custom_refresh_token_expires_at")
    );
}

#[test]
fn auth_schema_keeps_access_and_refresh_expiry_overrides_separate() {
    let schema = auth_schema(AuthSchemaOptions {
        account: TableOptions::default()
            .with_field_name("access_token_expires_at", "custom_access_token_expires_at")
            .with_field_name(
                "refresh_token_expires_at",
                "custom_refresh_token_expires_at",
            ),
        ..AuthSchemaOptions::default()
    });

    assert_eq!(
        schema
            .table("account")
            .and_then(|table| table.field("access_token_expires_at"))
            .map(|field| field.name.as_str()),
        Some("custom_access_token_expires_at")
    );
    assert_eq!(
        schema
            .table("account")
            .and_then(|table| table.field("refresh_token_expires_at"))
            .map(|field| field.name.as_str()),
        Some("custom_refresh_token_expires_at")
    );
}

#[test]
fn auth_schema_merges_additional_verification_fields() {
    let schema = auth_schema(AuthSchemaOptions {
        verification: TableOptions::default()
            .with_field("new_field", DbField::new("new_field", DbFieldType::String)),
        ..AuthSchemaOptions::default()
    });

    assert_eq!(
        schema
            .table("verification")
            .and_then(|table| table.field("new_field"))
            .map(|field| field.name.as_str()),
        Some("new_field")
    );
}

#[test]
fn auth_schema_excludes_verification_table_when_secondary_storage_is_configured() {
    let schema = auth_schema(AuthSchemaOptions {
        has_secondary_storage: true,
        ..AuthSchemaOptions::default()
    });

    assert!(schema.table("verification").is_none());
}

#[test]
fn auth_schema_includes_verification_table_when_store_in_database_is_true() {
    let schema = auth_schema(AuthSchemaOptions {
        has_secondary_storage: true,
        store_verification_in_database: true,
        ..AuthSchemaOptions::default()
    });

    assert!(schema.table("verification").is_some());
}

#[test]
fn auth_schema_includes_rate_limits_only_for_database_storage() {
    let memory_schema = auth_schema(AuthSchemaOptions::default());
    let database_schema = auth_schema(AuthSchemaOptions {
        rate_limit_storage: RateLimitStorage::Database,
        ..AuthSchemaOptions::default()
    });

    assert!(memory_schema.table("rate_limit").is_none());
    assert_eq!(
        database_schema
            .table("rate_limit")
            .map(|table| table.name.as_str()),
        Some("rate_limits")
    );
}

#[test]
fn auth_schema_resolves_table_name_from_logical_or_database_name() {
    let schema = auth_schema(AuthSchemaOptions {
        user: TableOptions::default().with_name("app_users"),
        ..AuthSchemaOptions::default()
    });

    assert_eq!(schema.table_name("user"), Ok("app_users"));
    assert_eq!(schema.table_name("app_users"), Ok("app_users"));
}

#[test]
fn auth_schema_resolves_field_name_from_logical_or_database_name() {
    let schema = auth_schema(AuthSchemaOptions {
        user: TableOptions::default().with_field_name("email", "primary_email"),
        ..AuthSchemaOptions::default()
    });

    assert_eq!(schema.field_name("user", "email"), Ok("primary_email"));
    assert_eq!(
        schema.field_name("users", "primary_email"),
        Ok("primary_email")
    );
}

#[test]
fn auth_schema_returns_typed_error_for_unknown_table() {
    let schema = auth_schema(AuthSchemaOptions::default());

    assert_eq!(
        schema.table_name("missing"),
        Err(RustAuthError::TableNotFound {
            table: "missing".to_owned()
        })
    );
}

#[test]
fn auth_schema_returns_typed_error_for_unknown_field() {
    let schema = auth_schema(AuthSchemaOptions::default());

    assert_eq!(
        schema.field_name("user", "missing"),
        Err(RustAuthError::FieldNotFound {
            table: "user".to_owned(),
            field: "missing".to_owned()
        })
    );
}

#[test]
fn auth_schema_uses_physical_user_table_in_account_foreign_key() {
    let schema = auth_schema(AuthSchemaOptions {
        user: TableOptions::default().with_name("app_users"),
        ..AuthSchemaOptions::default()
    });

    assert_eq!(
        schema
            .field("account", "user_id")
            .ok()
            .and_then(|field| field.foreign_key.as_ref())
            .map(|foreign_key| foreign_key.table.as_str()),
        Some("app_users")
    );
}

#[test]
fn auth_schema_applies_serial_id_policy_to_core_tables() {
    let schema = auth_schema(AuthSchemaOptions {
        id_policy: IdPolicy::new(IdGeneration::Serial),
        ..AuthSchemaOptions::default()
    });

    assert_eq!(
        schema.field("user", "id").map(|field| &field.field_type),
        Ok(&DbFieldType::Number)
    );
    assert_eq!(
        schema.field("account", "id").map(|field| field.required),
        Ok(false)
    );
    assert_eq!(
        schema
            .field("account", "user_id")
            .map(|field| &field.field_type),
        Ok(&DbFieldType::Number)
    );
    assert_eq!(
        schema
            .field("session", "user_id")
            .map(|field| &field.field_type),
        Ok(&DbFieldType::Number)
    );
}

#[test]
fn auth_schema_marks_uuid_id_optional_when_database_generates_uuid() {
    let schema = auth_schema(AuthSchemaOptions {
        id_policy: IdPolicy::new(IdGeneration::Uuid).with_database_uuid_support(true),
        ..AuthSchemaOptions::default()
    });

    assert_eq!(
        schema.field("user", "id").map(|field| &field.field_type),
        Ok(&DbFieldType::String)
    );
    assert_eq!(
        schema.field("user", "id").map(|field| field.required),
        Ok(false)
    );
}