1use 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}