Skip to main content

pas_external/pas_port/
memory.rs

1//! `MemoryPasAuth` — in-memory `PasAuthPort` for boundary testing.
2//!
3//! Scriptable in FIFO order: `expect_refresh` enqueues expectations
4//! consumed in order by `PasAuthPort::refresh`. Argument mismatches
5//! panic with a diff. Unconsumed expectations panic on `Drop` so
6//! silent test skips are caught.
7
8#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
9
10use std::collections::VecDeque;
11use std::sync::Mutex;
12
13use super::port::{PasAuthPort, PasFailure};
14use crate::oauth::TokenResponse;
15
16#[derive(Debug)]
17struct RefreshExpect {
18    rt_in: String,
19    returns: Result<TokenResponse, PasFailure>,
20}
21
22/// In-memory PAS auth port for tests. See module docs.
23#[derive(Debug, Default)]
24pub struct MemoryPasAuth {
25    refresh_script: Mutex<VecDeque<RefreshExpect>>,
26}
27
28impl MemoryPasAuth {
29    #[must_use]
30    pub fn new() -> Self {
31        Self::default()
32    }
33
34    /// Queue an expected `refresh(rt)` call returning `returns`.
35    #[must_use]
36    pub fn expect_refresh(
37        self,
38        rt: impl Into<String>,
39        returns: Result<TokenResponse, PasFailure>,
40    ) -> Self {
41        self.refresh_script
42            .lock()
43            .unwrap()
44            .push_back(RefreshExpect { rt_in: rt.into(), returns });
45        self
46    }
47}
48
49impl PasAuthPort for MemoryPasAuth {
50    async fn refresh(&self, rt: &str) -> Result<TokenResponse, PasFailure> {
51        let exp = self
52            .refresh_script
53            .lock()
54            .unwrap()
55            .pop_front()
56            .expect("MemoryPasAuth: refresh called with no expect_refresh queued");
57        assert_eq!(
58            exp.rt_in, rt,
59            "MemoryPasAuth: refresh token mismatch (expected={:?}, actual={:?})",
60            exp.rt_in, rt
61        );
62        exp.returns
63    }
64}
65
66impl Drop for MemoryPasAuth {
67    fn drop(&mut self) {
68        // Don't panic-on-panic. If a test already failed, surface the
69        // original failure rather than masking it with our assertion.
70        if std::thread::panicking() {
71            return;
72        }
73        let r = self.refresh_script.get_mut().unwrap();
74        if !r.is_empty() {
75            panic!(
76                "MemoryPasAuth dropped with {} unconsumed refresh expectation(s)",
77                r.len()
78            );
79        }
80    }
81}