Refactor helper functions in tests of hagrid::web module into fixtures.

Fixtures allow for push-style cascading dependency resolution with
ability to override some of the dependent fixture's arguments when needed.
This is very nice as it gives a lot of flexibility and suitable in a lot
of case. So, single fixture based approach can be applied to most of the
tests.

In addition, this result in good reusability of test code.

If you feel overwelmed and confused with complex argument list of test
functions try to read links from "Documentation" section (see below).

But here is the primer.

    #[fixture]
    pub fn configuration(
        base_uri: &str,
        base_uri_onion: &str,
    ) -> (TempDir, :🚀:figment::Figment) {
        // ...
    }

Attribute #[fixture] turn function into fixture which means now it can
be injected into tests or other fixtures by "configuration" argument name.

"base_uri", "base_uri_onion" are other fixtures which are injected into
"configuration".

    #[fixture]
    pub fn rocket(
        #[from(configuration)] (tmpdir, config): (TempDir, :🚀:figment::Figment),
    ) -> (TempDir, :🚀:Rocket<:🚀:Build>) {
        // ...
    }

As we destructuring results of "configuration" fixture injection into
"rocket" fixture the name of the fixture can't be inferenced from
parameters name, so we have to give a hint with #[from(configuration)]
which fixture has to be called.

    #[rstest]
    fn hkp_add_two(
        base_uri: &str,
        #[from(cert)] (_, tpk_0): (&str, Cert),
        #[with(alt_cert_name())]
        #[from(cert)]
        (_, tpk_1): (&str, Cert),
        #[from(client)] (tmpdir, client): (TempDir, Client),
    ) {
        // ...
    }

This is probably the most trickies function signature, but let brake it
down.

There are 4 fixtures injected into "hkp_add_two" test function:
- base_uri
- cert
- cert
- client

I think at this point of explanation syntax for "base_uri" and "client"
fixturex should be clear.

So,
    #[from(cert)] (_, tpk_0): (&str, Cert),
    #[with(alt_cert_name())] #[from(cert)] (_, tpk_1): (&str, Cert),

calls to "cert" fixture which takes "cert_name" argument. Here is the
definition:

    #[fixture]
    pub fn cert<'a>(cert_name: &'a str) -> (&'a str, Cert) {
        // ...
    }

For the "hkp_add_two" test we need two certificates, so first one is
generated with default name which is returned by "cert_name" fixture,
but for the second with #[with(alt_cert_name())] we say that we want to
use "alt_cert_name" fixture for cert_name argument, so the certificate
is generated with "bar@invalid.example.com" name.

And parenthis for destructing of the results of injection as "cert"
fixture returns tuple of (cert_name, cert).

Documentation:
- https://crates.io/crates/rstest
- https://docs.rs/rstest/0.26.1/rstest/attr.fixture.html

NOTE: probably this changes made translation generation in tests more unstable.
It was already unstable, but it looks like the use of fixtures make a
bit more unstable. So, in case of test failures, just run them several
times. I'll take a look and modules compilation order later once move
tests into modules with SUT's functionality.

Changes:
- Turn the following helper functions from hagrid::web::tests
  module to fixtures:
  - configuration()
  - rocket()
  - client()
- Panic immediately in the fixtures and don't bubble up the error like
  it was in helper functions. Bubbling up errors does not make sense as
  we panic with expect later in tests code. So, we duplicated code
  (.unwrap()) calls) and make return signatures more complex.
- Inject "base_uri" and "base_uri_onion" into "configuration" fixture as
  after turning it into fixture we can do that.
- Remove BASE_URI_ONION constant as it is not used. Now "base_uri_onion"
  fixture is used instead.
- Refactor all the tests which used mentioned above helper functions to
  use fixture.
This commit is contained in:
Zeke Fast
2025-09-07 23:38:53 +02:00
committed by Vincent Breitmoser
parent 0d868ce27e
commit 76ec3eed82
2 changed files with 139 additions and 131 deletions

View File

@@ -182,21 +182,25 @@ pub fn key_to_hkp_index(db: &rocket::State<Sqlite>, i18n: I18n, query: Query) ->
#[cfg(test)]
mod tests {
use super::super::tests::common::*;
use super::super::tests::*;
use ::rocket::local::blocking::Client;
use rstest::rstest;
use rocket::http::ContentType;
use rocket::http::Status;
use sequoia_openpgp::Cert;
use sequoia_openpgp::serialize::Serialize;
use ::rocket::http::ContentType;
use ::rocket::http::Status;
use crate::mail::pop_mail;
use crate::web::tests::*;
use crate::web::tests::common::*;
use sequoia_openpgp::Cert;
use sequoia_openpgp::serialize::Serialize;
use tempfile::TempDir;
#[rstest]
fn hkp(base_uri: &str, #[from(cert)] (cert_name, tpk): (&str, Cert)) {
let (tmpdir, client) = client().unwrap();
fn hkp(
base_uri: &str,
#[from(cert)] (cert_name, tpk): (&str, Cert),
#[from(client)] (tmpdir, client): (TempDir, Client),
) {
let filemail_into = tmpdir.path().join("filemail");
// eprintln!("LEAKING: {:?}", tmpdir);
@@ -270,12 +274,12 @@ mod tests {
#[rstest]
fn hkp_add_two(
base_uri: &str,
#[from(cert)] (cert_name_0, tpk_0): (&str, Cert),
#[from(cert)] (_, tpk_0): (&str, Cert),
#[with(alt_cert_name())]
#[from(cert)]
(cert_name_1, tpk_1): (&str, Cert),
(_, tpk_1): (&str, Cert),
#[from(client)] (tmpdir, client): (TempDir, Client),
) {
let (tmpdir, client) = client().unwrap();
let filemail_into = tmpdir.path().join("filemail");
// Prepare to /pks/add

View File

@@ -296,35 +296,36 @@ pub fn key_to_response_plain(db: &rocket::State<Sqlite>, i18n: I18n, query: Quer
#[cfg(test)]
pub mod tests {
use ::rocket::http::ContentType;
use ::rocket::http::Header;
use ::rocket::http::Status;
use ::rocket::local::blocking::{Client, LocalResponse};
use regex;
use rocket::http::ContentType;
use rocket::http::Header;
use rocket::http::Status;
use rocket::local::blocking::{Client, LocalResponse};
use std::fs;
use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
use tempfile::{TempDir, tempdir};
use std::path::Path;
use tempfile::TempDir;
use sequoia_openpgp::Cert;
use sequoia_openpgp::cert::CertBuilder;
use sequoia_openpgp::parse::Parse;
use sequoia_openpgp::serialize::Serialize;
use super::*;
use crate::app::configure_rocket;
use crate::mail::pop_mail;
use crate::web::tests::common::{base_uri, base_uri_onion};
use rstest::rstest;
use std::time::SystemTime;
use common::*;
pub mod common {
use crate::app::configure_rocket;
use ::rocket::local::blocking::Client;
use rstest::fixture;
use sequoia_openpgp::Cert;
use sequoia_openpgp::cert::CertBuilder;
use std::path::PathBuf;
use tempfile::{TempDir, tempdir};
/// Fake base URI to use in tests.
#[fixture]
@@ -358,24 +359,25 @@ pub mod tests {
(cert_name, tpk)
}
}
const BASE_URI: &str = "http://local.connection";
const BASE_URI_ONION: &str = "http://local.connection.onion";
/// Creates a configuration and empty state dir for testing purposes.
///
/// Note that you need to keep the returned TempDir alive for the
/// duration of your test. To debug the test, mem::forget it to
/// prevent cleanup.
pub fn configuration() -> anyhow::Result<(TempDir, rocket::figment::Figment)> {
let root = tempdir()?;
#[fixture]
pub fn configuration(
base_uri: &str,
base_uri_onion: &str,
) -> (TempDir, ::rocket::figment::Figment) {
let root = tempdir().expect("valid temporary directory for configuration");
let filemail = root.path().join("filemail");
::std::fs::create_dir_all(&filemail)?;
::std::fs::create_dir_all(&filemail).expect("valid temporary directory for filemail");
let base_dir: PathBuf = root.path().into();
let config = rocket::Config::figment()
let config = ::rocket::Config::figment()
.select("staging")
.merge(("root", root.path()))
.merge((
@@ -416,8 +418,8 @@ pub mod tests {
"maintenance_file",
base_dir.join("maintenance").to_str().unwrap(),
))
.merge(("base-URI", BASE_URI))
.merge(("base-URI-Onion", BASE_URI_ONION))
.merge(("base-URI", base_uri))
.merge(("base-URI-Onion", base_uri_onion))
.merge(("from", "from@example.com"))
.merge(("token_secret", "hagrid"))
.merge(("token_validity", 3600u64))
@@ -428,30 +430,36 @@ pub mod tests {
.into_string()
.expect("path is valid UTF8"),
));
Ok((root, config))
(root, config)
}
fn rocket() -> anyhow::Result<(TempDir, rocket::Rocket<rocket::Build>)> {
let (tmpdir, config) = configuration()?;
Ok((tmpdir, configure_rocket(rocket::custom(config))))
#[fixture]
pub fn rocket(
#[from(configuration)] (tmpdir, config): (TempDir, ::rocket::figment::Figment),
) -> (TempDir, ::rocket::Rocket<::rocket::Build>) {
(tmpdir, configure_rocket(::rocket::custom(config)))
}
pub fn client() -> anyhow::Result<(TempDir, Client)> {
let (tmpdir, rocket) = rocket()?;
#[fixture]
pub fn client(
#[from(rocket)] (tmpdir, rocket): (TempDir, ::rocket::Rocket<::rocket::Build>),
) -> (TempDir, Client) {
let client = Client::untracked(rocket).expect("valid rocket instance");
Ok((tmpdir, Client::untracked(rocket)?))
(tmpdir, client)
}
}
pub fn assert_consistency(rocket: &rocket::Rocket<rocket::Orbit>) {
const BASE_URI: &str = "http://local.connection";
pub fn assert_consistency(rocket: &::rocket::Rocket<::rocket::Orbit>) {
let db = rocket.state::<Sqlite>().unwrap();
db.check_consistency().unwrap();
}
#[rstest]
fn about_translation() {
let (_tmpdir, client) = client().expect("valid rocket instance");
fn about_translation(#[from(client)] (_tmpdir, client): (TempDir, Client)) {
// Check that we see the landing page.
let response = client
.get("/about")
@@ -464,9 +472,7 @@ pub mod tests {
}
#[rstest]
fn basics() {
let (_tmpdir, client) = client().expect("valid rocket instance");
fn basics(#[from(client)] (_tmpdir, client): (TempDir, Client)) {
// Check that we see the landing page.
let response = client.get("/").dispatch();
assert_eq!(response.status(), Status::Ok);
@@ -517,9 +523,7 @@ pub mod tests {
}
#[rstest]
fn maintenance() {
let (tmpdir, client) = client().unwrap();
fn maintenance(#[from(client)] (tmpdir, client): (TempDir, Client)) {
let maintenance_path = tmpdir.path().join("maintenance");
let mut file = File::create(&maintenance_path).unwrap();
file.write_all(b"maintenance-message").unwrap();
@@ -573,8 +577,8 @@ pub mod tests {
base_uri: &str,
base_uri_onion: &str,
#[from(cert)] (cert_name, tpk): (&str, Cert),
#[from(client)] (tmpdir, client): (TempDir, Client),
) {
let (tmpdir, client) = client().unwrap();
let filemail_into = tmpdir.path().join("filemail");
// Upload generated key.
@@ -625,8 +629,10 @@ pub mod tests {
}
#[rstest]
fn upload_verify_lang(#[from(cert)] (cert_name, tpk): (&str, Cert)) {
let (tmpdir, client) = client().unwrap();
fn upload_verify_lang(
#[from(cert)] (cert_name, tpk): (&str, Cert),
#[from(client)] (tmpdir, client): (TempDir, Client),
) {
let filemail_into = tmpdir.path().join("filemail");
// Upload it generated key.
@@ -647,9 +653,8 @@ pub mod tests {
#[with(alt_cert_name())]
#[from(cert)]
(cert_name_1, tpk_1): (&str, Cert),
#[from(client)] (_tmpdir, client): (TempDir, Client),
) {
let (_tmpdir, client) = client().expect("valid rocket instance");
// Upload generated keys.
let mut tpk_serialized = Vec::new();
tpk_0.serialize(&mut tpk_serialized).unwrap();
@@ -679,8 +684,8 @@ pub mod tests {
#[with(alt_cert_name())]
#[from(cert)]
(cert_name_2, tpk_2): (&str, Cert),
#[from(client)] (tmpdir, client): (TempDir, Client),
) {
let (tmpdir, client) = client().expect("valid rocket instance");
let filemail_into = tmpdir.path().join("filemail");
// Upload generated keys.
@@ -741,16 +746,18 @@ pub mod tests {
}
#[rstest]
fn upload_no_key() {
let (_tmpdir, client) = client().unwrap();
fn upload_no_key(#[from(client)] (_tmpdir, client): (TempDir, Client)) {
let response = vks_publish_submit_response(&client, b"");
assert_eq!(response.status(), Status::BadRequest);
}
#[rstest]
fn upload_verify_onion(base_uri_onion: &str, #[from(cert)] (cert_name, tpk): (&str, Cert)) {
let (tmpdir, client) = client().unwrap();
fn upload_verify_onion(
base_uri_onion: &str,
#[from(cert)] (cert_name, tpk): (&str, Cert),
#[from(client)] (tmpdir, client): (TempDir, Client),
) {
let filemail_into = tmpdir.path().join("filemail");
// Upload generated key.
@@ -783,9 +790,10 @@ pub mod tests {
}
#[rstest]
fn upload_curl_shortcut(#[from(cert)] (cert_name, tpk): (&str, Cert)) {
let (_tmpdir, client) = client().unwrap();
fn upload_curl_shortcut(
#[from(cert)] (cert_name, tpk): (&str, Cert),
#[from(client)] (_tmpdir, client): (TempDir, Client),
) {
let mut tpk_serialized = Vec::new();
tpk.serialize(&mut tpk_serialized).unwrap();
@@ -796,9 +804,7 @@ pub mod tests {
}
#[rstest]
fn search_invalid() {
let (_tmpdir, client) = client().unwrap();
fn search_invalid(#[from(client)] (_tmpdir, client): (TempDir, Client)) {
check_response(
&client,
"/search?q=0x1234abcd",
@@ -825,9 +831,7 @@ pub mod tests {
);
}
#[rstest]
fn wkd_policy() {
let (_tmpdir, client) = client().unwrap();
fn wkd_policy(#[from(client)] (_tmpdir, client): (TempDir, Client)) {
check_response(
&client,
"/.well-known/openpgpkey/example.org/policy",