better-auth-api 1.0.0-alpha.5

Plugin implementations for better-auth
use better_auth_core::types::{CreateOrganization, CreateUser, HttpMethod};
use chrono::Duration;

use crate::plugins::organization::{OrganizationConfig, OrganizationPlugin};
use crate::plugins::test_helpers::{
    create_auth_json_request_no_query, create_test_config, create_test_context_with_plugins,
    create_user, create_user_and_session,
};

use super::{get_full_organization_core, handle_create_organization};
use crate::plugins::organization::types::GetFullOrganizationQuery;

fn test_config() -> OrganizationConfig {
    OrganizationConfig {
        allow_user_to_create_organization: true,
        organization_limit: None,
        membership_limit: Some(100),
        creator_role: "owner".to_string(),
        invitation_expires_in: 172800.0,
        invitation_limit: Some(100),
        disable_organization_deletion: false,
        roles: None,
        send_invitation_email: None,
        ..Default::default()
    }
}

fn test_user(email: &str, name: &str) -> CreateUser {
    CreateUser {
        email: Some(email.to_string()),
        name: Some(name.to_string()).into(),
        ..CreateUser::default()
    }
}

#[tokio::test]
async fn create_organization_keeps_current_active_organization_when_requested() {
    let config = test_config();
    let ctx = create_test_context_with_plugins(
        create_test_config(),
        &[&OrganizationPlugin::with_config(config.clone())],
    )
    .await;
    let (user, session) = create_user_and_session(
        &ctx,
        test_user("owner@example.com", "Owner"),
        Duration::hours(1),
    )
    .await;
    let existing = ctx
        .database
        .create_organization(CreateOrganization {
            additional_fields: Default::default(),
            id: None,
            name: "Existing".to_string().into(),
            slug: "existing".to_string().into(),
            logo: None.into(),
            metadata: None.into(),
        })
        .await
        .expect("organization should be created");
    ctx.database
        .update_session_active_organization(
            session.token.typed().unwrap(),
            Some(existing.id.typed().unwrap()),
        )
        .await
        .expect("active organization should update");

    let request = create_auth_json_request_no_query(
        HttpMethod::Post,
        "/organization/create",
        Some(session.token.typed().unwrap()),
        Some(serde_json::json!({
            "name": "Next",
            "slug": "next",
            "keepCurrentActiveOrganization": true
        })),
    );

    handle_create_organization(&request, &ctx, &config)
        .await
        .expect("request should succeed");

    let updated_session = ctx
        .database
        .get_session(session.token.typed().unwrap())
        .await
        .expect("session lookup should succeed")
        .expect("session should exist");
    assert_eq!(
        updated_session
            .active_organization_id
            .typed()
            .unwrap()
            .as_ref(),
        Some(existing.id.typed().unwrap())
    );
    assert_eq!(user.id, session.user_id);
}

#[tokio::test]
async fn create_organization_updates_active_organization_by_default() {
    let config = test_config();
    let ctx = create_test_context_with_plugins(
        create_test_config(),
        &[&OrganizationPlugin::with_config(config.clone())],
    )
    .await;
    let (_, session) = create_user_and_session(
        &ctx,
        test_user("owner2@example.com", "Owner"),
        Duration::hours(1),
    )
    .await;

    let request = create_auth_json_request_no_query(
        HttpMethod::Post,
        "/organization/create",
        Some(session.token.typed().unwrap()),
        Some(serde_json::json!({
            "name": "Created",
            "slug": "created"
        })),
    );

    let response = handle_create_organization(&request, &ctx, &config)
        .await
        .expect("request should succeed");
    let body: serde_json::Value =
        serde_json::from_slice(&response.body.bytes().unwrap()).expect("response should be JSON");
    let created_id = body["id"]
        .as_str()
        .expect("response should contain organization id");

    let updated_session = ctx
        .database
        .get_session(session.token.typed().unwrap())
        .await
        .expect("session lookup should succeed")
        .expect("session should exist");
    assert_eq!(
        updated_session
            .active_organization_id
            .typed()
            .unwrap()
            .as_deref(),
        Some(created_id)
    );
}

#[tokio::test]
async fn get_full_organization_respects_members_limit() {
    let config = test_config();
    let ctx = create_test_context_with_plugins(
        create_test_config(),
        &[&OrganizationPlugin::with_config(config.clone())],
    )
    .await;
    let (user, session) = create_user_and_session(
        &ctx,
        test_user("owner3@example.com", "Owner"),
        Duration::hours(1),
    )
    .await;
    let organization = ctx
        .database
        .create_organization(CreateOrganization {
            additional_fields: Default::default(),
            id: None,
            name: "Team".to_string().into(),
            slug: "team".to_string().into(),
            logo: None.into(),
            metadata: None.into(),
        })
        .await
        .expect("organization should be created");
    ctx.database
        .create_member(better_auth_core::types::CreateMember {
            additional_fields: Default::default(),
            organization_id: organization.id.clone(),
            user_id: user.id.clone(),
            role: config.creator_role.clone().into(),
        })
        .await
        .expect("owner member should be created");

    let extra_user = create_user(&ctx, test_user("member@example.com", "Member")).await;
    ctx.database
        .create_member(better_auth_core::types::CreateMember {
            additional_fields: Default::default(),
            organization_id: organization.id.clone(),
            user_id: extra_user.id.clone(),
            role: "member".into(),
        })
        .await
        .expect("extra member should be created");

    let response = get_full_organization_core(
        &GetFullOrganizationQuery {
            organization_id: Some(organization.id.typed().unwrap().clone()),
            organization_slug: None,
            members_limit: Some(1.0),
        },
        &(user, session).into(),
        &config,
        &ctx,
    )
    .await
    .expect("request should succeed")
    .expect("organization should exist");

    assert_eq!(
        response
            .model_property("members")
            .unwrap()
            .as_array()
            .unwrap()
            .len()
            .expect("member array must be readable"),
        1
    );
}