pas-external 0.12.0

Ppoppo Accounts System (PAS) external SDK — OAuth2 PKCE, JWT verification port, Axum middleware, session liveness
Documentation
//! `MemoryPasAuth` — in-memory `PasAuthPort` for boundary testing.
//!
//! Scriptable in FIFO order: `expect_refresh` enqueues expectations
//! consumed in order by `PasAuthPort::refresh`. Argument mismatches
//! panic with a diff. Unconsumed expectations panic on `Drop` so
//! silent test skips are caught.

#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]

use std::collections::VecDeque;
use std::sync::Mutex;

use super::port::{PasAuthPort, PasFailure};
use crate::oauth::TokenResponse;

#[derive(Debug)]
struct RefreshExpect {
    rt_in: String,
    returns: Result<TokenResponse, PasFailure>,
}

/// In-memory PAS auth port for tests. See module docs.
#[derive(Debug, Default)]
pub struct MemoryPasAuth {
    refresh_script: Mutex<VecDeque<RefreshExpect>>,
}

impl MemoryPasAuth {
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Queue an expected `refresh(rt)` call returning `returns`.
    #[must_use]
    pub fn expect_refresh(
        self,
        rt: impl Into<String>,
        returns: Result<TokenResponse, PasFailure>,
    ) -> Self {
        self.refresh_script
            .lock()
            .unwrap()
            .push_back(RefreshExpect { rt_in: rt.into(), returns });
        self
    }
}

impl PasAuthPort for MemoryPasAuth {
    async fn refresh(&self, rt: &str) -> Result<TokenResponse, PasFailure> {
        let exp = self
            .refresh_script
            .lock()
            .unwrap()
            .pop_front()
            .expect("MemoryPasAuth: refresh called with no expect_refresh queued");
        assert_eq!(
            exp.rt_in, rt,
            "MemoryPasAuth: refresh token mismatch (expected={:?}, actual={:?})",
            exp.rt_in, rt
        );
        exp.returns
    }
}

impl Drop for MemoryPasAuth {
    fn drop(&mut self) {
        // Don't panic-on-panic. If a test already failed, surface the
        // original failure rather than masking it with our assertion.
        if std::thread::panicking() {
            return;
        }
        let r = self.refresh_script.get_mut().unwrap();
        if !r.is_empty() {
            panic!(
                "MemoryPasAuth dropped with {} unconsumed refresh expectation(s)",
                r.len()
            );
        }
    }
}