better-auth-api 1.0.0-alpha.5

Plugin implementations for better-auth
use super::*;
use better_auth_core::store::{EphemeralStore, schema::EntityRole};

pub(super) fn storage(store: &EphemeralStore) -> AuthResult<FieldValue> {
    let mut tables = FieldMap::new();
    for (name, role) in [
        ("user", EntityRole::User),
        ("session", EntityRole::Session),
        ("account", EntityRole::Account),
        ("verification", EntityRole::Verification),
    ] {
        let rows: Vec<FieldValue> = store
            .storage_rows(role)?
            .into_iter()
            .map(Into::into)
            .collect();
        tables.insert(name.into(), rows.into());
    }
    Ok(tables.into())
}

pub(super) struct Before {
    observations: Value,
    events: Vec<Value>,
}

impl Before {
    pub fn capture(state: &State, store: &EphemeralStore) -> AuthResult<Self> {
        let observations = json!({
            "raw": observe::complete(&state.raw, state, "before.raw")?,
            "mapped": observe::complete(&state.mapped, state, "before.mapped")?,
            "storage": observe::complete(&storage(store)?, state, "before.storage")?,
        });
        let events = state.log.take();
        state.tracking.lock().unwrap().run_start = chrono::Utc::now().timestamp_millis();
        state.log.phase("execute");
        Ok(Self {
            observations,
            events,
        })
    }

    pub fn finish(
        self,
        state: &State,
        store: &EphemeralStore,
        value: FieldValue,
        mut outcome: Value,
        headers: Value,
    ) -> AuthResult<Value> {
        let mut events = state.log.take();
        state.log.phase("assertion");
        assertions::execution(state, store, &outcome, &events, &value)?;
        let assertion_events = state.log.take();
        outcome
            .as_object_mut()
            .unwrap()
            .insert("headers".into(), headers);
        let admissions: FieldValue = state.tracking.lock().unwrap().admissions.clone().into();
        let mut after = json!({
            "raw": observe::complete(&state.raw, state, "after.raw")?,
            "mapped": observe::complete(&state.mapped, state, "after.mapped")?,
            "providerResponse": observe::complete(&state.info(), state, "after.provider-response")?,
            "result": if outcome["kind"] == "native" {
                observe::complete(&value, state, "after.result")?
            } else { Value::Null },
            "admissions": observe::complete(&admissions, state, "after.admissions")?,
            "storage": observe::complete(&storage(store)?, state, "after.storage")?,
        });
        if state.scenario.operation == "account-info" {
            assert_eq!(after["storage"], self.observations["storage"]);
        }
        state.log.phase("identity-observation");
        let (info, mapper, subject, admissions) = {
            let tracking = state.tracking.lock().unwrap();
            (
                tracking.info.clone(),
                tracking.mapper_input.clone(),
                tracking.subject_input.clone(),
                tracking.admissions.clone(),
            )
        };
        let identities = json!({
            "providerDataIsRaw": info.as_ref().map(|info| info.model_property("data").map(|data| data.strict_equals(&state.raw))).transpose()?,
            "customResponseIsOriginal": if state.scenario.provider == "social" { info.as_ref().map(|info| info.strict_equals(&state.social_response)) } else { None },
            "mapperIsRaw": mapper.as_ref().map(|value| value.strict_equals(&state.raw)),
            "subjectIsRaw": subject.as_ref().map(|value| value.strict_equals(&state.raw)),
            "admissionProfilesAreRaw": admissions.iter().map(|data| Ok(data.model_property("source")?.model_property("oauth")?.model_property("profile")?.strict_equals(&state.raw))).collect::<AuthResult<Vec<_>>>()?,
        });
        let tracking = state.tracking.lock().unwrap();
        dates::apply(
            &mut events,
            &mut after,
            &state.scenario,
            &tracking.generated_dates,
        );
        let mut sample = serde_json::to_value(&state.scenario)?;
        sample.as_object_mut().unwrap().extend(
            json!({
                "backend": "memory", "before": self.observations, "outcome": outcome,
                "executionEvents": events,
                "assertionEvents": assertion_events, "after": after,
                "observationEvents": {"before": self.events, "after": state.log.take()},
                "identities": identities,
            })
            .as_object()
            .unwrap()
            .clone(),
        );
        Ok(observe::replace_generated(sample, &tracking.replacements))
    }
}

pub(super) fn assert_complete(actual: &Value, expected: &Value, scenario: &str) {
    const LIMIT: usize = 32;
    fn describe(value: &Value) -> String {
        let text = value.to_string();
        let mut characters = text.chars();
        let mut result: String = characters.by_ref().take(512).collect();
        if characters.next().is_some() {
            result.push_str("…");
        }
        result
    }
    fn differences(actual: &Value, expected: &Value, path: &str, output: &mut Vec<String>) {
        if output.len() == LIMIT || actual == expected {
            return;
        }
        match (actual, expected) {
            (Value::Object(actual), Value::Object(expected)) => {
                for (key, expected) in expected {
                    if output.len() == LIMIT {
                        return;
                    }
                    let path = format!("{path}.{key}");
                    if let Some(actual) = actual.get(key) {
                        differences(actual, expected, &path, output);
                    } else {
                        output.push(format!(
                            "{path}: missing actual key; expected {}",
                            describe(expected)
                        ));
                    }
                }
                for (key, actual) in actual {
                    if output.len() == LIMIT {
                        return;
                    }
                    if !expected.contains_key(key) {
                        output.push(format!(
                            "{path}.{key}: unexpected actual key with {}",
                            describe(actual)
                        ));
                    }
                }
            }
            (Value::Array(actual), Value::Array(expected)) => {
                if actual.len() != expected.len() {
                    output.push(format!(
                        "{path}: array length {} != {}",
                        actual.len(),
                        expected.len()
                    ));
                }
                for (index, (actual, expected)) in actual.iter().zip(expected).enumerate() {
                    if output.len() == LIMIT {
                        return;
                    }
                    differences(actual, expected, &format!("{path}[{index}]"), output);
                }
            }
            _ => output.push(format!(
                "{path}: actual {}, expected {}",
                describe(actual),
                describe(expected)
            )),
        }
    }
    if actual != expected {
        let mut output = Vec::new();
        differences(actual, expected, scenario, &mut output);
        panic!(
            "{scenario}: complete case differs (at most {LIMIT} paths shown)\n{}",
            output.join("\n")
        );
    }
}