diff --git a/src/contact.rs b/src/contact.rs
index 08f70a367..029f43694 100644
--- a/src/contact.rs
+++ b/src/contact.rs
@@ -1525,9 +1525,9 @@ mod tests {
let t = TestContext::new().await;
assert_eq!(t.is_self_addr("me@me.org").await?, false);
- let addr = t.configure_alice().await;
+ t.configure_addr("you@you.net").await;
assert_eq!(t.is_self_addr("me@me.org").await?, false);
- assert_eq!(t.is_self_addr(&addr).await?, true);
+ assert_eq!(t.is_self_addr("you@you.net").await?, true);
Ok(())
}
diff --git a/src/e2ee.rs b/src/e2ee.rs
index 9236c60a2..47585a066 100644
--- a/src/e2ee.rs
+++ b/src/e2ee.rs
@@ -416,9 +416,8 @@ mod tests {
#[async_std::test]
async fn test_prexisting() {
- let t = TestContext::new().await;
- let test_addr = t.configure_alice().await;
- assert_eq!(ensure_secret_key_exists(&t).await.unwrap(), test_addr);
+ let t = TestContext::new_alice().await;
+ assert_eq!(ensure_secret_key_exists(&t).await.unwrap(), "alice@example.com");
}
#[async_std::test]
diff --git a/src/imex.rs b/src/imex.rs
index 25a3f99e7..2c221f67c 100644
--- a/src/imex.rs
+++ b/src/imex.rs
@@ -936,9 +936,7 @@ mod tests {
#[async_std::test]
async fn test_render_setup_file() {
- let t = TestContext::new().await;
-
- t.configure_alice().await;
+ let t = TestContext::new_alice().await;
let msg = render_setup_file(&t, "hello").await.unwrap();
println!("{}", &msg);
// Check some substrings, indicating things got substituted.
@@ -955,11 +953,10 @@ mod tests {
#[async_std::test]
async fn test_render_setup_file_newline_replace() {
- let t = TestContext::new().await;
+ let t = TestContext::new_alice().await;
t.set_stock_translation(StockMessage::AcSetupMsgBody, "hello\r\nthere".to_string())
.await
.unwrap();
- t.configure_alice().await;
let msg = render_setup_file(&t, "pw").await.unwrap();
println!("{}", &msg);
assert!(msg.contains("
hello
there
"));
@@ -1012,15 +1009,13 @@ mod tests {
#[async_std::test]
async fn test_export_and_import_key() {
- let context = TestContext::new().await;
- context.configure_alice().await;
+ let context = TestContext::new_alice().await;
let blobdir = context.ctx.get_blobdir();
if let Err(err) = imex(&context.ctx, ImexMode::ExportSelfKeys, blobdir).await {
panic!("got error on export: {:?}", err);
}
- let context2 = TestContext::new().await;
- context2.configure_alice().await;
+ let context2 = TestContext::new_alice().await;
if let Err(err) = imex(&context2.ctx, ImexMode::ImportSelfKeys, blobdir).await {
panic!("got error on import: {:?}", err);
}
diff --git a/src/key.rs b/src/key.rs
index 2592eb8d1..9668fcc62 100644
--- a/src/key.rs
+++ b/src/key.rs
@@ -510,8 +510,7 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
#[async_std::test]
async fn test_load_self_existing() {
let alice = alice_keypair();
- let t = TestContext::new().await;
- t.configure_alice().await;
+ let t = TestContext::new_alice().await;
let pubkey = SignedPublicKey::load_self(&t).await.unwrap();
assert_eq!(alice.public, pubkey);
let seckey = SignedSecretKey::load_self(&t).await.unwrap();
diff --git a/src/keyring.rs b/src/keyring.rs
index aba4d2d33..d6e07ce51 100644
--- a/src/keyring.rs
+++ b/src/keyring.rs
@@ -79,8 +79,7 @@ mod tests {
#[async_std::test]
async fn test_keyring_load_self() {
// new_self() implies load_self()
- let t = TestContext::new().await;
- t.configure_alice().await;
+ let t = TestContext::new_alice().await;
let alice = alice_keypair();
let pub_ring: Keyring = Keyring::new_self(&t).await.unwrap();
diff --git a/src/test_utils.rs b/src/test_utils.rs
index e7523a8f7..b3762c20e 100644
--- a/src/test_utils.rs
+++ b/src/test_utils.rs
@@ -48,11 +48,70 @@ type EventSink =
static CONTEXT_NAMES: Lazy>> =
Lazy::new(|| std::sync::RwLock::new(BTreeMap::new()));
+#[derive(Debug, Clone, Default)]
+pub struct TestContextBuilder {
+ key_pair: Option,
+ log_sink: Option>,
+}
+
+impl TestContextBuilder {
+ /// Configures as alice@example.com with fixed secret key.
+ ///
+ /// This is a shortcut for `.with_key_pair(alice_keypair()).
+ pub fn configure_alice(self) -> Self {
+ self.with_key_pair(alice_keypair())
+ }
+
+ /// Configures as bob@example.net with fixed secret key.
+ ///
+ /// This is a shortcut for `.with_key_pair(bob_keypair()).
+ pub fn configure_bob(self) -> Self {
+ self.with_key_pair(bob_keypair())
+ }
+
+ /// Configures the new [`TestContext`] with the provided [`KeyPair`].
+ ///
+ /// This will extract the email address from the key and configure the context with the
+ /// given identity.
+ pub fn with_key_pair(mut self, key_pair: KeyPair) -> Self {
+ self.key_pair = Some(key_pair);
+ self
+ }
+
+ /// Attaches a [`LogSink`] to this [`TestContext`].
+ ///
+ /// This is useful when using multiple [`TestContext`] instances in one test: it allows
+ /// using a single [`LogSink`] for both contexts. This shows the log messages in
+ /// sequence as they occurred rather than all messages from each context in a single
+ /// block.
+ pub fn with_log_sink(mut self, sink: Sender) -> Self {
+ self.log_sink = Some(sink);
+ self
+ }
+
+ /// Builds the [`TestContext`].
+ pub async fn build(self) -> TestContext {
+ let name = self.key_pair.as_ref().map(|key| key.addr.local.clone());
+
+ let test_context = TestContext::new_internal(name, self.log_sink).await;
+
+ if let Some(key_pair) = self.key_pair {
+ test_context
+ .configure_addr(&key_pair.addr.to_string())
+ .await;
+ key::store_self_keypair(&test_context, &key_pair, KeyPairUse::Default)
+ .await
+ .expect("Failed to save key");
+ }
+ test_context
+ }
+}
+
/// A Context and temporary directory.
///
/// The temporary directory can be used to store the SQLite database,
/// see e.g. [test_context] which does this.
-pub(crate) struct TestContext {
+pub struct TestContext {
pub ctx: Context,
pub dir: TempDir,
pub evtracker: EvTracker,
@@ -62,10 +121,12 @@ pub(crate) struct TestContext {
poison_receiver: Receiver,
/// Reference to implicit [`LogSink`] so it is dropped together with the context.
///
- /// Only used if no explicit [`LogSink`] was given during construction. This is a
- /// convenience in case only a single [`TestContext`] is used to avoid dealing with
- /// [`LogSink`]. Never read, thus "dead code", since the only purpose is to control
- /// when Drop is invoked.
+ /// Only used if no explicit `log_sender` is passed into [`TestContext::new_internal`]
+ /// (which is assumed to be the sending end of a [`LogSink`]).
+ ///
+ /// This is a convenience in case only a single [`TestContext`] is used to avoid dealing
+ /// with [`LogSink`]. Never read, thus "dead code", since the only purpose is to
+ /// control when Drop is invoked.
#[allow(dead_code)]
log_sink: Option,
}
@@ -81,6 +142,11 @@ impl fmt::Debug for TestContext {
}
impl TestContext {
+ /// Returns the builder to have more control over creating the context.
+ pub fn builder() -> TestContextBuilder {
+ TestContextBuilder::default()
+ }
+
/// Creates a new [`TestContext`].
///
/// The [Context] will be created and have an SQLite database named "db.sqlite" in the
@@ -89,19 +155,33 @@ impl TestContext {
///
/// [Context]: crate::context::Context
pub async fn new() -> Self {
- Self::new_named(None, None).await
+ Self::new_internal(None, None).await
}
- /// Creates a new [`TestContext`] with a set name used in event logging.
- pub async fn with_name(name: impl Into) -> Self {
- Self::new_named(Some(name.into()), None).await
+ /// Creates a new configured [`TestContext`].
+ ///
+ /// This is a shortcut which automatically calls [`TestContext::configure_alice`] after
+ /// creating the context.
+ pub async fn new_alice() -> Self {
+ Self::builder().configure_alice().build().await
}
- async fn with_name_and_log_sink(name: String, log_sink: Sender) -> Self {
- Self::new_named(Some(name), Some(log_sink)).await
+ /// Creates a new configured [`TestContext`].
+ ///
+ /// This is a shortcut which configures bob@example.net with a fixed key.
+ pub async fn new_bob() -> Self {
+ Self::builder().configure_bob().build().await
}
- async fn new_named(name: Option, log_sender: Option>) -> Self {
+ /// Internal constructor.
+ ///
+ /// `name` is used to identify this context in e.g. log output. This is useful mostly
+ /// when you have multiple [`TestContext`]s in a test.
+ ///
+ /// `log_sender` is assumed to be the sender for a [`LogSink`]. If not supplied a new
+ /// [`LogSink`] will be created so that events are logged to this test when the
+ /// [`TestContext`] is dropped.
+ async fn new_internal(name: Option, log_sender: Option>) -> Self {
let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite");
let id = rand::thread_rng().gen();
@@ -116,9 +196,7 @@ impl TestContext {
let events = ctx.get_event_emitter();
let (log_sender, log_sink) = match log_sender {
- Some(sender) => {
- (Arc::new(RwLock::new(sender)), None)
- }
+ Some(sender) => (Arc::new(RwLock::new(sender)), None),
None => {
let (sender, sink) = LogSink::create();
(Arc::new(RwLock::new(sender)), Some(sink))
@@ -172,45 +250,6 @@ impl TestContext {
}
}
- /// Creates a new configured [`TestContext`].
- ///
- /// This is a shortcut which automatically calls [`TestContext::configure_alice`] after
- /// creating the context.
- pub async fn new_alice() -> Self {
- let t = Self::with_name("alice").await;
- t.configure_alice().await;
- t
- }
-
- pub async fn new_alice_with_log_sink(log_sink: Sender) -> Self {
- let t = Self::with_name_and_log_sink("alice".into(), log_sink).await;
- t.configure_alice().await;
- t
- }
-
- /// Creates a new configured [`TestContext`].
- ///
- /// This is a shortcut which configures bob@example.net with a fixed key.
- pub async fn new_bob() -> Self {
- let t = Self::with_name("bob").await;
- let keypair = bob_keypair();
- t.configure_addr(&keypair.addr.to_string()).await;
- key::store_self_keypair(&t, &keypair, KeyPairUse::Default)
- .await
- .expect("Failed to save Bob's key");
- t
- }
-
- pub async fn new_bob_with_log_sink(log_sink: Sender) -> Self {
- let t = Self::with_name_and_log_sink("bob".into(), log_sink).await;
- let keypair = bob_keypair();
- t.configure_addr(&keypair.addr.to_string()).await;
- key::store_self_keypair(&t, &keypair, KeyPairUse::Default)
- .await
- .expect("Failed to save Bob's key");
- t
- }
-
/// Sets a name for this [`TestContext`] if one isn't yet set.
///
/// This will show up in events logged in the test output.
@@ -236,18 +275,6 @@ impl TestContext {
sinks.push(Box::new(move |evt| Box::pin(sink(evt))));
}
- /// Configure with alice@example.org.
- ///
- /// The context will be fake-configured as the alice user, with a pre-generated secret
- /// key. The email address of the user is returned as a string.
- pub async fn configure_alice(&self) -> String {
- let keypair = alice_keypair();
- self.configure_addr(&keypair.addr.to_string()).await;
- key::store_self_keypair(&self.ctx, &keypair, KeyPairUse::Default)
- .await
- .expect("Failed to save Alice's key");
- keypair.addr.to_string()
- }
/// Configure as a given email address.
///
@@ -561,6 +588,9 @@ impl Drop for TestContext {
///
/// This sink achieves this by printing the events, in the order received, at the time it is
/// dropped. Thus to use you must only make sure this sink is dropped in the test itself.
+///
+/// To use this create an instance using [`LogSink::create`] and then use the
+/// [`TestContextBuilder::with_log_sink`].
pub struct LogSink {
events: Receiver,
}
@@ -839,14 +869,14 @@ mod tests {
#[async_std::test]
async fn test_with_alice() {
- let alice = TestContext::builder().as_alice().build().await;
+ let alice = TestContext::builder().configure_alice().build().await;
alice.ctx.emit_event(EventType::Info("hello".into()));
// panic!("Alice fails");
}
#[async_std::test]
async fn test_with_bob() {
- let bob = TestContext::builder().as_bob().build().await;
+ let bob = TestContext::builder().configure_bob().build().await;
bob.ctx.emit_event(EventType::Info("there".into()));
// panic!("Bob fails");
}
@@ -855,12 +885,12 @@ mod tests {
async fn test_with_both() {
let (log_sender, _log_sink) = LogSink::create();
let alice = TestContext::builder()
- .as_alice()
+ .configure_alice()
.with_log_sink(log_sender.clone())
.build()
.await;
let bob = TestContext::builder()
- .as_bob()
+ .configure_bob()
.with_log_sink(log_sender)
.build()
.await;