tests use mockall for expectation-style doubles, starting with the builder and hive-priv seams
This commit is contained in:
parent
0cc7e42945
commit
b0a6a28d10
1 changed files with 182 additions and 59 deletions
|
|
@ -177,7 +177,7 @@ trait Publisher: Clone + Send + Sync + 'static {
|
|||
}
|
||||
|
||||
/// The secrets a request reads from the store, once per request. [`Store`] in
|
||||
/// production; tests answer from memory.
|
||||
/// production; tests answer with canned replies.
|
||||
trait Tokens: Clone + Send + Sync + 'static {
|
||||
/// The forge token config repos are fetched with.
|
||||
fn forge_token(&self) -> impl Future<Output = Result<String>> + Send;
|
||||
|
|
@ -970,57 +970,79 @@ mod tests {
|
|||
Read::Stuck
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct FakeTokens {
|
||||
forge: Read,
|
||||
push: Read,
|
||||
}
|
||||
|
||||
impl Tokens for FakeTokens {
|
||||
async fn forge_token(&self) -> Result<String> {
|
||||
self.forge.get().await
|
||||
}
|
||||
|
||||
async fn push_token(&self) -> Result<String> {
|
||||
self.push.get().await
|
||||
// `Publisher` and `Tokens` have `Clone` as a supertrait, which `automock`
|
||||
// cannot express (a mock is not `Clone`), so the two mocks are declared by
|
||||
// hand with `mock!` as plain structs. The traits are implemented for
|
||||
// `Arc<Mock…>`: every clone the daemon makes shares one set of
|
||||
// expectations, which are checked when the last clone drops.
|
||||
mockall::mock! {
|
||||
Publisher {
|
||||
fn publish(
|
||||
&self,
|
||||
subject: Subject,
|
||||
body: Vec<u8>,
|
||||
) -> impl Future<Output = Result<(), String>> + Send;
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher whose first `failing` publishes fail; the rest are recorded.
|
||||
#[derive(Clone)]
|
||||
struct Sink {
|
||||
sent: tokio::sync::mpsc::UnboundedSender<(String, Value)>,
|
||||
attempts: Arc<std::sync::atomic::AtomicUsize>,
|
||||
failing: usize,
|
||||
impl Publisher for Arc<MockPublisher> {
|
||||
fn publish(
|
||||
&self,
|
||||
subject: Subject,
|
||||
body: Vec<u8>,
|
||||
) -> impl Future<Output = Result<(), String>> + Send {
|
||||
MockPublisher::publish(self, subject, body)
|
||||
}
|
||||
}
|
||||
|
||||
impl Publisher for Sink {
|
||||
async fn publish(&self, subject: Subject, body: Vec<u8>) -> Result<(), String> {
|
||||
if self.attempts.fetch_add(1, Ordering::SeqCst) < self.failing {
|
||||
return Err("the connection is closed".to_owned());
|
||||
}
|
||||
let body = serde_json::from_slice(&body).expect("a JSON reply");
|
||||
self.sent
|
||||
.send((subject.to_string(), body))
|
||||
.expect("test is listening");
|
||||
Ok(())
|
||||
mockall::mock! {
|
||||
Tokens {
|
||||
fn forge_token(&self) -> impl Future<Output = Result<String>> + Send;
|
||||
fn push_token(&self) -> impl Future<Output = Result<String>> + Send;
|
||||
}
|
||||
}
|
||||
|
||||
impl Tokens for Arc<MockTokens> {
|
||||
fn forge_token(&self) -> impl Future<Output = Result<String>> + Send {
|
||||
MockTokens::forge_token(self)
|
||||
}
|
||||
|
||||
fn push_token(&self) -> impl Future<Output = Result<String>> + Send {
|
||||
MockTokens::push_token(self)
|
||||
}
|
||||
}
|
||||
|
||||
struct Rig {
|
||||
sink: Sink,
|
||||
tokens: FakeTokens,
|
||||
sink: Arc<MockPublisher>,
|
||||
tokens: Arc<MockTokens>,
|
||||
}
|
||||
|
||||
/// A rig whose token reads answer `forge` and `push`, and whose first
|
||||
/// `failing` publishes fail; the rest are sent to the returned receiver.
|
||||
fn rig_with(forge: Read, push: Read, failing: usize) -> (Rig, Sent) {
|
||||
let (sent, rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
let sink = Sink {
|
||||
sent,
|
||||
attempts: Arc::default(),
|
||||
failing,
|
||||
let mut sink = MockPublisher::new();
|
||||
sink.expect_publish()
|
||||
.times(failing)
|
||||
.returning(|_, _| Box::pin(async { Err("the connection is closed".to_owned()) }));
|
||||
sink.expect_publish().returning(move |subject, body| {
|
||||
let body = serde_json::from_slice(&body).expect("a JSON reply");
|
||||
sent.send((subject.to_string(), body))
|
||||
.expect("test is listening");
|
||||
Box::pin(async { Ok(()) })
|
||||
});
|
||||
let mut tokens = MockTokens::new();
|
||||
tokens
|
||||
.expect_forge_token()
|
||||
.returning(move || Box::pin(forge.get()));
|
||||
tokens
|
||||
.expect_push_token()
|
||||
.returning(move || Box::pin(push.get()));
|
||||
let rig = Rig {
|
||||
sink: Arc::new(sink),
|
||||
tokens: Arc::new(tokens),
|
||||
};
|
||||
let tokens = FakeTokens { forge, push };
|
||||
(Rig { sink, tokens }, rx)
|
||||
(rig, rx)
|
||||
}
|
||||
|
||||
fn env() -> (Rig, Sent) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue