Skip to main content

better_auth_api/plugins/test_utils/
organization.rs

1use super::{TestUtilsApi, random_lower};
2use better_auth_core::{AuthResult, AuthSchema, Member, Organization, SchemaValue};
3use chrono::Utc;
4
5/// Direct organization adapter helpers. Organization application hooks do not run.
6pub struct TestOrganizationApi<'a, S: AuthSchema> {
7    pub(super) api: &'a TestUtilsApi<'a, S>,
8}
9impl<S: AuthSchema> TestOrganizationApi<'_, S> {
10    /// Build a complete organization record without persisting it.
11    pub fn create_organization(&self, mut overrides: Organization) -> AuthResult<Organization> {
12        let id = self.api.generate_id("organization")?;
13        if overrides.id.is_undefined() {
14            overrides.id = id.into();
15        }
16        let name = overrides
17            .name
18            .as_str()
19            .filter(|value| !value.is_empty())
20            .unwrap_or("Test Organization");
21        let mut slug = String::new();
22        let mut previous_space = false;
23        for character in name.to_lowercase().chars() {
24            let space = super::super::helpers::oauth_scope_whitespace(character);
25            if !space {
26                slug.push(character);
27            } else if !previous_space {
28                slug.push('-');
29            }
30            previous_space = space;
31        }
32        slug.push('-');
33        slug.push_str(&random_lower(4));
34        if overrides.name.is_undefined() {
35            overrides.name = "Test Organization".to_owned().into();
36        }
37        if overrides.slug.is_undefined() {
38            overrides.slug = slug.into();
39        }
40        if overrides.logo.is_undefined() {
41            overrides.logo = SchemaValue::Typed(None);
42        }
43        if overrides.metadata.is_undefined() {
44            overrides.metadata = SchemaValue::Typed(None);
45        }
46        if overrides.created_at.is_undefined() {
47            overrides.created_at = Utc::now().into();
48        }
49        Ok(overrides)
50    }
51    /// Insert the supplied record, including its ID and creation date.
52    pub async fn save_organization(&self, organization: Organization) -> AuthResult<Organization> {
53        self.api
54            .auth
55            .database
56            .insert_organization(organization)
57            .await
58    }
59    /// Delete members, invitations, and organization sequentially; a later error does not roll back earlier deletes.
60    pub async fn delete_organization(&self, id: &str) -> AuthResult<()> {
61        self.api.auth.database.delete_organization_records(id).await
62    }
63    /// Insert a membership directly, bypassing route permission and application-hook checks.
64    pub async fn add_member(
65        &self,
66        organization_id: impl Into<String>,
67        user_id: impl Into<String>,
68        role: Option<&str>,
69    ) -> AuthResult<Member> {
70        let member = Member {
71            id: self.api.generate_id("member")?.into(),
72            organization_id: organization_id.into().into(),
73            user_id: user_id.into().into(),
74            role: role
75                .filter(|value| !value.is_empty())
76                .unwrap_or("member")
77                .to_owned()
78                .into(),
79            created_at: Utc::now().into(),
80            additional_fields: Default::default(),
81        };
82        self.api.auth.database.insert_member(member).await
83    }
84}