better-auth-api 1.0.0-alpha.5

Plugin implementations for better-auth
#![allow(
    clippy::expect_used,
    reason = "Fixture setup and response assertions require successful fallible construction."
)]
use super::*;
use crate::plugins::organization::OrganizationConfig;
use crate::plugins::test_helpers::{create_test_config, create_test_context_with_plugins};
use better_auth_core::{CreateOrganization, CreateUser, user_fields::UserFieldConfig};
use serde_json::json;
use std::sync::{
    Arc,
    atomic::{AtomicUsize, Ordering},
};

#[derive(Default)]
struct CountLimits(AtomicUsize);

#[async_trait::async_trait]
impl MembershipLimitPolicy for CountLimits {
    async fn membership_limit(&self, _: OrganizationUser<'_>) -> AuthResult<usize> {
        self.0.fetch_add(1, Ordering::SeqCst);
        Ok(100)
    }
}

#[tokio::test]
async fn add_member_validates_fields_before_queries_and_limit_callbacks() {
    let calls = Arc::new(CountLimits::default());
    let mut config = OrganizationConfig {
        membership_limit_callback: Some(calls.clone()),
        ..Default::default()
    };
    let mut ctx = create_test_context_with_plugins(
        create_test_config(),
        &[&OrganizationPlugin::with_config(config.clone())],
    )
    .await;
    ctx.extensions.insert(Arc::new(
        better_auth_core::endpoint_dispatch::EndpointDispatcher::<
            better_auth_seaorm::store::__private_test_support::bundled_schema::BundledSchema,
        >::new(Arc::new(Vec::new()), Default::default(), []),
    ));
    let user = ctx
        .database
        .create_user(
            CreateUser::new()
                .with_email("member-input@example.com")
                .with_name("Fixture"),
        )
        .await
        .unwrap();
    let organization = ctx
        .database
        .create_organization(CreateOrganization::new("Input", "member-input"))
        .await
        .unwrap();
    config.schema.member.fields_mut().insert(
        "role".into(),
        UserFieldConfig {
            required: Some(true),
            ..Default::default()
        },
    );
    config.schema.member.fields_mut().insert(
        "label".into(),
        UserFieldConfig {
            required: Some(true),
            ..Default::default()
        },
    );
    let plugin = OrganizationPlugin::with_config(config);
    let valid = AddMemberInput {
        user_id: user.id.into(),
        organization_id: organization.id.into(),
        role: RoleInput::One("member".into()).into(),
        team_id: Default::default(),
        additional_fields: [("label".into(), "valid".into())].into_iter().collect(),
    };
    for (input, message) in [
        (
            AddMemberInput {
                additional_fields: Default::default(),
                ..valid.clone()
            },
            "[body.label] Invalid input: expected string, received undefined",
        ),
        (
            AddMemberInput {
                role: RoleInput::Many(vec!["member".into()]).into(),
                ..valid.clone()
            },
            "[body.role] Invalid input: expected string, received array",
        ),
        (
            AddMemberInput {
                user_id: "missing-user".into(),
                additional_fields: Default::default(),
                ..valid.clone()
            },
            "[body.label] Invalid input: expected string, received undefined",
        ),
    ] {
        let response = plugin
            .add_member(input, None, &ctx)
            .await
            .unwrap_err()
            .to_auth_response()
            .expect("fixture response succeeds");
        assert_eq!(
            response.api_error_status().unwrap(),
            better_auth_core::SchemaValue::Typed(400)
        );
        assert_eq!(
            response.api_error_body().unwrap().json().unwrap().unwrap(),
            json!({
                "code":"VALIDATION_ERROR", "message":message
            })
        );
    }
    assert_eq!(calls.0.load(Ordering::SeqCst), 0);
    let member = plugin.add_member(valid, None, &ctx).await.unwrap();
    assert_eq!(member.role, "member");
    assert_eq!(calls.0.load(Ordering::SeqCst), 1);
}