Watch
0
0
Fork
You've already forked hyperhive
0

tests use mockall for expectation-style doubles, starting with the builder and hive-priv seams

This commit is contained in:
atlas 2026-11-03 13:58:43 +01:00
commit b0a6a28d10

View file

@ -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) {