openauth-core 0.0.5

Core types and primitives for OpenAuth.
Documentation
use openauth_core::db::{
    auth_schema, resolve_join_options, transform_count_query, transform_create_query,
    transform_create_query_with_capabilities, transform_delete_many_query,
    transform_find_many_query, transform_find_many_query_with_capabilities, transform_update_query,
    transform_update_query_with_capabilities, AdapterCapabilities, AuthSchemaOptions, Count,
    Create, DbField, DbFieldType, DbValue, DeleteMany, FindMany, JoinOption, JoinRelation,
    RateLimitStorage, TableOptions, Update, Where,
};
use openauth_core::error::OpenAuthError;
use serde_json::json;
use time::OffsetDateTime;

#[test]
fn transform_create_query_maps_logical_names_to_physical_names() -> Result<(), OpenAuthError> {
    let schema = auth_schema(AuthSchemaOptions {
        user: TableOptions::default()
            .with_name("app_users")
            .with_field_name("email", "primary_email"),
        ..AuthSchemaOptions::default()
    });
    let query = Create::new("user")
        .data("email", DbValue::String("a@example.com".to_owned()))
        .select(["id", "email"]);

    let transformed = transform_create_query(&schema, query)?;

    assert_eq!(transformed.model, "app_users");
    assert_eq!(
        transformed.data.get("primary_email"),
        Some(&DbValue::String("a@example.com".to_owned()))
    );
    assert_eq!(
        transformed.select,
        vec!["id".to_owned(), "primary_email".to_owned()]
    );

    Ok(())
}

#[test]
fn transform_find_many_query_maps_where_sort_and_select_fields() -> Result<(), OpenAuthError> {
    let schema = auth_schema(AuthSchemaOptions {
        session: TableOptions::default().with_field_name("user_id", "owner_id"),
        ..AuthSchemaOptions::default()
    });
    let query = FindMany::new("session")
        .where_clause(Where::new("user_id", DbValue::String("user_1".to_owned())))
        .sort_by(openauth_core::db::Sort::new(
            "expires_at",
            openauth_core::db::SortDirection::Asc,
        ))
        .select(["id", "user_id"]);

    let transformed = transform_find_many_query(&schema, query)?;

    assert_eq!(transformed.model, "sessions");
    assert_eq!(transformed.where_clauses[0].field, "owner_id");
    assert_eq!(
        transformed.sort_by.as_ref().map(|sort| sort.field.as_str()),
        Some("expires_at")
    );
    assert_eq!(
        transformed.select,
        vec!["id".to_owned(), "owner_id".to_owned()]
    );

    Ok(())
}

#[test]
fn transform_update_query_maps_update_data_and_where_fields() -> Result<(), OpenAuthError> {
    let schema = auth_schema(AuthSchemaOptions {
        user: TableOptions::default().with_field_name("name", "display_name"),
        ..AuthSchemaOptions::default()
    });
    let query = Update::new("user")
        .where_clause(Where::new(
            "email",
            DbValue::String("a@example.com".to_owned()),
        ))
        .data("name", DbValue::String("Ada".to_owned()));

    let transformed = transform_update_query(&schema, query)?;

    assert_eq!(transformed.model, "users");
    assert_eq!(transformed.where_clauses[0].field, "email");
    assert_eq!(
        transformed.data.get("display_name"),
        Some(&DbValue::String("Ada".to_owned()))
    );

    Ok(())
}

#[test]
fn transform_count_and_delete_many_queries_map_where_fields() -> Result<(), OpenAuthError> {
    let schema = auth_schema(AuthSchemaOptions {
        account: TableOptions::default().with_field_name("account_id", "provider_account_id"),
        ..AuthSchemaOptions::default()
    });

    let count = transform_count_query(
        &schema,
        Count::new("account").where_clause(Where::new(
            "account_id",
            DbValue::String("account_1".to_owned()),
        )),
    )?;
    let delete_many = transform_delete_many_query(
        &schema,
        DeleteMany::new("account").where_clause(Where::new(
            "account_id",
            DbValue::String("account_1".to_owned()),
        )),
    )?;

    assert_eq!(count.where_clauses[0].field, "provider_account_id");
    assert_eq!(delete_many.where_clauses[0].field, "provider_account_id");

    Ok(())
}

#[test]
fn transform_query_returns_typed_error_for_unknown_field() {
    let schema = auth_schema(AuthSchemaOptions::default());
    let query = Create::new("user").data("missing", DbValue::String("value".to_owned()));

    assert_eq!(
        transform_create_query(&schema, query),
        Err(OpenAuthError::FieldNotFound {
            table: "user".to_owned(),
            field: "missing".to_owned()
        })
    );
}

#[test]
fn transform_create_query_converts_booleans_when_adapter_lacks_boolean_support(
) -> Result<(), OpenAuthError> {
    let schema = auth_schema(AuthSchemaOptions::default());
    let capabilities = AdapterCapabilities::new("legacy-sql").without_booleans();
    let query = Create::new("user").data("email_verified", DbValue::Boolean(true));

    let transformed = transform_create_query_with_capabilities(&schema, &capabilities, query)?;

    assert_eq!(
        transformed.data.get("email_verified"),
        Some(&DbValue::Number(1))
    );

    Ok(())
}

#[test]
fn transform_find_many_query_coerces_number_strings_in_where_clauses() -> Result<(), OpenAuthError>
{
    let schema = auth_schema(AuthSchemaOptions {
        rate_limit_storage: RateLimitStorage::Database,
        ..AuthSchemaOptions::default()
    });
    let capabilities = AdapterCapabilities::new("sql");
    let query = FindMany::new("rate_limit")
        .where_clause(Where::new("count", DbValue::String("42".to_owned())));

    let transformed = transform_find_many_query_with_capabilities(&schema, &capabilities, query)?;

    assert_eq!(transformed.where_clauses[0].value, DbValue::Number(42));

    Ok(())
}

#[test]
fn transform_update_query_converts_timestamps_when_adapter_lacks_date_support(
) -> Result<(), OpenAuthError> {
    let schema = auth_schema(AuthSchemaOptions::default());
    let capabilities = AdapterCapabilities::new("legacy-sql").without_dates();
    let timestamp = OffsetDateTime::UNIX_EPOCH;
    let query = Update::new("session")
        .where_clause(Where::new("id", DbValue::String("session_1".to_owned())))
        .data("expires_at", DbValue::Timestamp(timestamp));

    let transformed = transform_update_query_with_capabilities(&schema, &capabilities, query)?;

    assert_eq!(
        transformed.data.get("expires_at"),
        Some(&DbValue::String(timestamp.to_string()))
    );

    Ok(())
}

#[test]
fn transform_create_query_keeps_json_native_when_adapter_supports_json() -> Result<(), OpenAuthError>
{
    let schema = auth_schema(AuthSchemaOptions {
        user: TableOptions::default()
            .with_field("metadata", DbField::new("metadata", DbFieldType::Json)),
        ..AuthSchemaOptions::default()
    });
    let capabilities = AdapterCapabilities::new("postgres").with_json();
    let query = Create::new("user").data("metadata", DbValue::Json(json!({ "tier": "pro" })));

    let transformed = transform_create_query_with_capabilities(&schema, &capabilities, query)?;

    assert_eq!(
        transformed.data.get("metadata"),
        Some(&DbValue::Json(json!({ "tier": "pro" })))
    );

    Ok(())
}

#[test]
fn transform_create_query_stringifies_json_when_adapter_lacks_json_support(
) -> Result<(), OpenAuthError> {
    let schema = auth_schema(AuthSchemaOptions {
        user: TableOptions::default()
            .with_field("metadata", DbField::new("metadata", DbFieldType::Json)),
        ..AuthSchemaOptions::default()
    });
    let capabilities = AdapterCapabilities::new("legacy-sql");
    let query = Create::new("user").data("metadata", DbValue::Json(json!({ "tier": "pro" })));

    let transformed = transform_create_query_with_capabilities(&schema, &capabilities, query)?;

    assert_eq!(
        transformed.data.get("metadata"),
        Some(&DbValue::String("{\"tier\":\"pro\"}".to_owned()))
    );

    Ok(())
}

#[test]
fn transform_create_query_stringifies_arrays_when_adapter_lacks_array_support(
) -> Result<(), OpenAuthError> {
    let schema = auth_schema(AuthSchemaOptions {
        user: TableOptions::default()
            .with_field("roles", DbField::new("roles", DbFieldType::StringArray)),
        ..AuthSchemaOptions::default()
    });
    let capabilities = AdapterCapabilities::new("legacy-sql");
    let query = Create::new("user").data("roles", DbValue::StringArray(vec!["admin".to_owned()]));

    let transformed = transform_create_query_with_capabilities(&schema, &capabilities, query)?;

    assert_eq!(
        transformed.data.get("roles"),
        Some(&DbValue::String("[\"admin\"]".to_owned()))
    );

    Ok(())
}

#[test]
fn resolve_join_options_maps_forward_foreign_key() -> Result<(), OpenAuthError> {
    let schema = auth_schema(AuthSchemaOptions::default());
    let joins = [("account".to_owned(), JoinOption::enabled())]
        .into_iter()
        .collect();

    let resolved = resolve_join_options(&schema, "user", joins, Vec::new(), 100)?;
    let account_join =
        resolved
            .joins
            .get("accounts")
            .ok_or_else(|| OpenAuthError::JoinForeignKeyNotFound {
                base_model: "user".to_owned(),
                join_model: "account".to_owned(),
            })?;

    assert_eq!(account_join.on.from, "id");
    assert_eq!(account_join.on.to, "user_id");
    assert_eq!(account_join.limit, Some(100));
    assert_eq!(account_join.relation, JoinRelation::OneToMany);

    Ok(())
}

#[test]
fn resolve_join_options_maps_reverse_foreign_key_as_one_to_one() -> Result<(), OpenAuthError> {
    let schema = auth_schema(AuthSchemaOptions::default());
    let joins = [("user".to_owned(), JoinOption::enabled())]
        .into_iter()
        .collect();

    let resolved = resolve_join_options(&schema, "account", joins, Vec::new(), 100)?;
    let user_join =
        resolved
            .joins
            .get("users")
            .ok_or_else(|| OpenAuthError::JoinForeignKeyNotFound {
                base_model: "account".to_owned(),
                join_model: "user".to_owned(),
            })?;

    assert_eq!(user_join.on.from, "user_id");
    assert_eq!(user_join.on.to, "id");
    assert_eq!(user_join.limit, Some(1));
    assert_eq!(user_join.relation, JoinRelation::OneToOne);

    Ok(())
}

#[test]
fn resolve_join_options_adds_required_select_field() -> Result<(), OpenAuthError> {
    let schema = auth_schema(AuthSchemaOptions {
        account: TableOptions::default().with_field_name("user_id", "owner_id"),
        ..AuthSchemaOptions::default()
    });
    let joins = [("user".to_owned(), JoinOption::enabled())]
        .into_iter()
        .collect();

    let resolved = resolve_join_options(&schema, "account", joins, vec!["id".to_owned()], 100)?;

    assert_eq!(
        resolved.select,
        vec!["id".to_owned(), "owner_id".to_owned()]
    );

    Ok(())
}

#[test]
fn resolve_join_options_returns_typed_error_without_foreign_key() {
    let schema = auth_schema(AuthSchemaOptions::default());
    let joins = [("verification".to_owned(), JoinOption::enabled())]
        .into_iter()
        .collect();

    assert_eq!(
        resolve_join_options(&schema, "user", joins, Vec::new(), 100),
        Err(OpenAuthError::JoinForeignKeyNotFound {
            base_model: "user".to_owned(),
            join_model: "verification".to_owned()
        })
    );
}