Skip to main content

sova_grpc/
fake.rs

1//! Fake unary transport for tests.
2
3use crate::error::GrpcError;
4use crate::transport::GrpcTransport;
5use bytes::Bytes;
6use serde_json::Value;
7use sova_core::extend::BoxFuture;
8use std::sync::{Arc, Mutex};
9
10#[derive(Debug, Clone)]
11pub struct GrpcCall {
12    pub method: String,
13    pub body: Bytes,
14    pub base: String,
15}
16
17#[derive(Default)]
18struct Inner {
19    stubs: Vec<(String, Bytes)>,
20    calls: Vec<GrpcCall>,
21}
22
23#[derive(Clone, Default)]
24pub struct FakeGrpc {
25    inner: Arc<Mutex<Inner>>,
26}
27
28impl FakeGrpc {
29    pub fn new() -> Self {
30        Self::default()
31    }
32
33    pub fn stub(self, method: impl Into<String>, body: impl Into<Bytes>) -> Self {
34        self.inner
35            .lock()
36            .unwrap()
37            .stubs
38            .push((method.into(), body.into()));
39        self
40    }
41
42    pub fn stub_json(self, method: impl Into<String>, value: Value) -> Self {
43        let bytes = Bytes::from(serde_json::to_vec(&value).expect("json"));
44        self.stub(method, bytes)
45    }
46
47    pub fn calls(&self) -> Vec<GrpcCall> {
48        self.inner.lock().unwrap().calls.clone()
49    }
50
51    pub fn clear(&self) {
52        self.inner.lock().unwrap().calls.clear();
53    }
54
55    pub fn assert_called(&self) {
56        assert!(
57            !self.inner.lock().unwrap().calls.is_empty(),
58            "FakeGrpc: expected at least one call"
59        );
60    }
61
62    pub fn assert_called_method(&self, method: &str) {
63        let calls = self.calls();
64        assert!(
65            calls.iter().any(|c| c.method == method),
66            "FakeGrpc: no call to `{method}`; calls={:?}",
67            calls.iter().map(|c| &c.method).collect::<Vec<_>>()
68        );
69    }
70}
71
72impl GrpcTransport for FakeGrpc {
73    fn call(&self, base: &str, method: &str, body: Bytes) -> BoxFuture<Result<Bytes, GrpcError>> {
74        let this = self.clone();
75        let base = base.to_string();
76        let method = method.to_string();
77        Box::pin(async move {
78            let mut g = this.inner.lock().unwrap();
79            g.calls.push(GrpcCall {
80                method: method.clone(),
81                body,
82                base,
83            });
84            for (m, resp) in &g.stubs {
85                if m == &method {
86                    return Ok(resp.clone());
87                }
88            }
89            Err(GrpcError::NotFound(method))
90        })
91    }
92}