diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index a075d013..207c19f8 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -40,8 +40,6 @@ jobs: continue-on-error: true - name: Format run: cargo fmt --all -- --check - - name: check +api-mocks - run: cargo check --features api-mocks --locked - name: check +otlp-trace run: cargo check --features otlp-trace --locked - name: check cli +default +admin diff --git a/Cargo.lock b/Cargo.lock index d179efe5..90ec1d83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -222,31 +222,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "async-channel" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-compat" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f68a707c1feb095d8c07f8a65b9f506b117d30af431cab89374357de7c11461b" -dependencies = [ - "futures-core", - "futures-io", - "once_cell", - "pin-project-lite", - "tokio", -] - [[package]] name = "async-compression" version = "0.4.6" @@ -263,23 +238,13 @@ dependencies = [ "zstd-safe", ] -[[package]] -name = "async-dup" -version = "1.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2886ab563af5038f79ec016dd7b87947ed138b794e8dd64992962c9cca0411" -dependencies = [ - "async-lock", - "futures-io", -] - [[package]] name = "async-lock" version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ - "event-listener 5.4.1", + "event-listener", "event-listener-strategy", "pin-project-lite", ] @@ -317,15 +282,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "async_cell" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447ab28afbb345f5408b120702a44e5529ebf90b1796ec76e9528df8e288e6c2" -dependencies = [ - "loom", -] - [[package]] name = "atoi" version = "2.0.0" @@ -675,12 +631,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfg-if" version = "1.0.0" @@ -803,16 +753,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" -[[package]] -name = "colored" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" -dependencies = [ - "lazy_static", - "windows-sys 0.59.0", -] - [[package]] name = "colored" version = "3.1.1" @@ -822,16 +762,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "combine" -version = "4.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" -dependencies = [ - "bytes", - "memchr", -] - [[package]] name = "concurrent-queue" version = "2.5.0" @@ -925,16 +855,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -1096,19 +1016,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "dashmap" -version = "5.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" -dependencies = [ - "cfg-if", - "hashbrown 0.14.3", - "lock_api", - "once_cell", - "parking_lot_core", -] - [[package]] name = "data-encoding" version = "2.9.0" @@ -1244,6 +1151,7 @@ dependencies = [ "thiserror 2.0.18", "time", "tokio", + "tokio-rustls", "tokio-util", "tower 0.5.3", "tower-http", @@ -1253,16 +1161,6 @@ dependencies = [ "tracing-log", "tracing-stackdriver", "tracing-subscriber", - "trillium", - "trillium-api", - "trillium-client", - "trillium-http", - "trillium-logger", - "trillium-macros 0.0.6", - "trillium-router", - "trillium-rustls", - "trillium-testing", - "trillium-tokio", "typenum", "url", "uuid", @@ -1276,7 +1174,7 @@ dependencies = [ "anyhow", "base64 0.22.1", "clap", - "colored 3.1.1", + "colored", "const_format", "divviup-client", "email_address", @@ -1302,6 +1200,7 @@ dependencies = [ name = "divviup-client" version = "0.5.0-pre.1" dependencies = [ + "axum 0.8.9", "base64 0.22.1", "divviup-api", "divviup-client", @@ -1322,10 +1221,6 @@ dependencies = [ "thiserror 2.0.18", "time", "tokio", - "trillium", - "trillium-http", - "trillium-testing", - "trillium-tokio", "url", "uuid", ] @@ -1460,17 +1355,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "event-listener" -version = "4.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - [[package]] name = "event-listener" version = "5.4.1" @@ -1488,7 +1372,7 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ - "event-listener 5.4.1", + "event-listener", "pin-project-lite", ] @@ -1675,17 +1559,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "futures-rustls" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb" -dependencies = [ - "futures-io", - "rustls", - "rustls-pki-types", -] - [[package]] name = "futures-sink" version = "0.3.31" @@ -1716,21 +1589,6 @@ dependencies = [ "slab", ] -[[package]] -name = "generator" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" -dependencies = [ - "cc", - "cfg-if", - "libc", - "log", - "rustversion", - "windows-link 0.2.1", - "windows-result", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -2085,7 +1943,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots 0.26.1", + "webpki-roots", ] [[package]] @@ -2493,28 +2351,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - [[package]] name = "jobserver" version = "0.1.32" @@ -2612,19 +2448,6 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" -[[package]] -name = "loom" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" -dependencies = [ - "cfg-if", - "generator", - "scoped-tls", - "tracing", - "tracing-subscriber", -] - [[package]] name = "lru-slab" version = "0.1.2" @@ -2931,12 +2754,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" -[[package]] -name = "openssl-probe" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" - [[package]] name = "opentelemetry" version = "0.27.1" @@ -3247,15 +3064,6 @@ dependencies = [ "portable-atomic", ] -[[package]] -name = "portpicker" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be97d76faf1bfab666e1375477b23fde79eccf0276e9b63b92a39d676a889ba9" -dependencies = [ - "rand 0.8.5", -] - [[package]] name = "powerfmt" version = "0.2.0" @@ -3724,7 +3532,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 0.26.1", + "webpki-roots", "windows-registry", ] @@ -3782,26 +3590,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "rlimit" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3560f70f30a0f16d11d01ed078a07740fe6b489667abc7c7b029155d9f21c3d8" -dependencies = [ - "libc", -] - -[[package]] -name = "routefinder" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0971d3c8943a6267d6bd0d782fdc4afa7593e7381a92a3df950ff58897e066b5" -dependencies = [ - "memchr", - "smartcow", - "smartstring", -] - [[package]] name = "rsa" version = "0.9.10" @@ -3884,18 +3672,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustls-native-certs" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" -dependencies = [ - "openssl-probe", - "rustls-pki-types", - "schannel", - "security-framework", -] - [[package]] name = "rustls-pemfile" version = "2.2.0" @@ -3915,33 +3691,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustls-platform-verifier" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" -dependencies = [ - "core-foundation", - "core-foundation-sys", - "jni", - "log", - "once_cell", - "rustls", - "rustls-native-certs", - "rustls-platform-verifier-android", - "rustls-webpki", - "security-framework", - "security-framework-sys", - "webpki-root-certs", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls-platform-verifier-android" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84e217e7fdc8466b5b35d30f8c0a30febd29173df4a3a0c2115d306b9c4117ad" - [[package]] name = "rustls-webpki" version = "0.103.13" @@ -3966,30 +3715,6 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schannel" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.2.0" @@ -4180,29 +3905,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "security-framework" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" -dependencies = [ - "bitflags 2.11.0", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "semver" version = "1.0.21" @@ -4335,16 +4037,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "signal-hook" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" -dependencies = [ - "libc", - "signal-hook-registry", -] - [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -4354,18 +4046,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signal-hook-tokio" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213241f76fb1e37e27de3b6aa1b068a2c333233b59cca6634f634b80a27ecf1e" -dependencies = [ - "futures-core", - "libc", - "signal-hook", - "tokio", -] - [[package]] name = "signature" version = "2.2.0" @@ -4382,12 +4062,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" -[[package]] -name = "size" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fed904c7fb2856d868b92464fc8fa597fce366edea1a9cbfaa8cb5fe080bd6d" - [[package]] name = "slab" version = "0.4.9" @@ -4497,7 +4171,7 @@ dependencies = [ "crc", "crossbeam-queue", "either", - "event-listener 5.4.1", + "event-listener", "futures-core", "futures-intrusive", "futures-io", @@ -4522,7 +4196,7 @@ dependencies = [ "tracing", "url", "uuid", - "webpki-roots 0.26.1", + "webpki-roots", ] [[package]] @@ -4698,7 +4372,7 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66c82d03d16a1e591756e978782ce4bc4300f83048b57d44c5600dafa7337019" dependencies = [ - "event-listener 5.4.1", + "event-listener", "futures-lite", "pin-project-lite", ] @@ -4819,9 +4493,13 @@ dependencies = [ name = "test-support" version = "0.5.0-pre.1" dependencies = [ + "async-trait", + "axum 0.8.9", "base64 0.22.1", + "bytes", "divviup-api", "fastrand", + "http-body-util", "pretty_assertions", "querystrong", "rand 0.8.5", @@ -4833,16 +4511,11 @@ dependencies = [ "thiserror 2.0.18", "time", "tokio", + "tokio-util", + "tower 0.5.3", "tracing", "tracing-log", "tracing-subscriber", - "trillium", - "trillium-client", - "trillium-http", - "trillium-macros 0.0.6", - "trillium-rustls", - "trillium-testing", - "trillium-tokio", "url", "uuid", ] @@ -5364,44 +5037,6 @@ dependencies = [ "trillium-http", ] -[[package]] -name = "trillium-api" -version = "0.2.0-rc.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ac0b2a746f70736c3b1c78078d361d71a1705121f2b134a791d50de2a4f78d" -dependencies = [ - "log", - "mime", - "serde", - "serde_json", - "serde_path_to_error", - "thiserror 1.0.69", - "trillium", - "trillium-macros 0.0.6", -] - -[[package]] -name = "trillium-client" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6977db194636278cb7b7397887066885e35a040c7e16312c10e29780372a6c5" -dependencies = [ - "crossbeam-queue", - "dashmap", - "encoding_rs", - "futures-lite", - "httparse", - "log", - "memchr", - "mime", - "serde", - "serde_json", - "size", - "thiserror 1.0.69", - "trillium-http", - "trillium-server-common", -] - [[package]] name = "trillium-http" version = "0.3.17" @@ -5411,43 +5046,17 @@ dependencies = [ "encoding_rs", "futures-lite", "hashbrown 0.14.3", - "http", "httparse", "httpdate", "log", "memchr", "mime", - "serde", "smallvec", "smartcow", "smartstring", "stopper", "thiserror 1.0.69", - "trillium-macros 0.0.6", -] - -[[package]] -name = "trillium-logger" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da5e9b6c08a27d991b4a9c73dd7276c6f181fd4dee7218723b0fbd7fca3a1659" -dependencies = [ - "colored 2.2.0", - "log", - "size", - "time", - "trillium", -] - -[[package]] -name = "trillium-macros" -version = "0.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916054381183f0cfed7604bf7de2044a760624a50d26eef5492468fb73083bbb" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "trillium-macros", ] [[package]] @@ -5461,89 +5070,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "trillium-router" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a7aed20d63101d7dcd165fd047141423009a7f4ccfc75db5b875312d8127dbe" -dependencies = [ - "log", - "routefinder", - "trillium", -] - -[[package]] -name = "trillium-rustls" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0501210eedcb4eae5d4a48c7855632f4be7fac5f57c7c772f5b1e598d6db53ff" -dependencies = [ - "futures-rustls", - "log", - "rustls-pemfile", - "rustls-platform-verifier", - "trillium-server-common", - "webpki-roots 1.0.6", -] - -[[package]] -name = "trillium-server-common" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96faa60ceaf4b575886eb7d2ad3df4371acf67e0a4489585cccd4ff18966103" -dependencies = [ - "async-trait", - "async_cell", - "event-listener 4.0.3", - "futures-lite", - "log", - "pin-project-lite", - "rlimit", - "trillium", - "trillium-http", - "url", -] - -[[package]] -name = "trillium-testing" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6c5d4d9d6f6844131f166cbe4d779894f09f9b846945ab2024272bcd6e198c" -dependencies = [ - "async-channel", - "async-dup", - "cfg-if", - "dashmap", - "fastrand", - "futures-lite", - "once_cell", - "portpicker", - "trillium", - "trillium-http", - "trillium-macros 0.0.6", - "trillium-server-common", - "trillium-tokio", - "url", -] - -[[package]] -name = "trillium-tokio" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "160f07d12cc798d7de6a65e2279bb445f0606ecc4a9c594315ffff556ab7968b" -dependencies = [ - "async-compat", - "log", - "signal-hook", - "signal-hook-tokio", - "tokio", - "tokio-stream", - "trillium", - "trillium-http", - "trillium-macros 0.0.5", - "trillium-server-common", -] - [[package]] name = "try-lock" version = "0.2.5" @@ -5712,16 +5238,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - [[package]] name = "want" version = "0.3.1" @@ -5842,15 +5358,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webpki-root-certs" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "webpki-roots" version = "0.26.1" @@ -5860,15 +5367,6 @@ dependencies = [ "rustls-pki-types", ] -[[package]] -name = "webpki-roots" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "whoami" version = "1.5.1" @@ -5895,15 +5393,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -5960,15 +5449,6 @@ dependencies = [ "windows-link 0.1.3", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -5987,15 +5467,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -6005,21 +5476,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.48.5" @@ -6068,12 +5524,6 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -6092,12 +5542,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -6116,12 +5560,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -6152,12 +5590,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -6176,12 +5608,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -6200,12 +5626,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -6224,12 +5644,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" diff --git a/Cargo.toml b/Cargo.toml index 769f9126..b154810c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,13 +26,7 @@ default-run = "divviup_api_bin" [features] default = [] -api-mocks = ["dep:trillium-testing"] integration-testing = [] -# Enables a non-production axum middleware that reads an `X-Integration-Testing-User` -# header and injects the decoded [`User`] into request extensions. Strictly for -# use by the test harness (`test-support`); never enable in deployed builds. -# TODO: fold into `integration-testing` in Part 9 (test-support rewrite). -test-header-injection = [] otlp-trace = ["opentelemetry/trace", "opentelemetry-otlp", "opentelemetry_sdk/trace"] [dependencies] @@ -70,7 +64,6 @@ time = { version = "0.3.41", features = ["serde", "serde-well-known"] } tokio = { version = "1.47.1", features = ["full"] } tokio-util = { version = "0.7", features = ["rt"] } tracing = "0.1.41" -trillium = "0.2.20" tracing-chrome = "0.7.2" tracing-log = "0.2.0" tracing-stackdriver = "0.10.0" @@ -80,15 +73,6 @@ tracing-subscriber = { version = "0.3.19", features = [ "std", "fmt", ] } -trillium-api = { version = "0.2.0-rc.12", default-features = false } -trillium-client = { version = "0.6.2", features = ["json"] } -trillium-http = { version = "0.3.14", features = ["http-compat-1", "serde"] } -trillium-logger = "0.4.5" -trillium-macros = "0.0.6" -trillium-router = "0.4.1" -trillium-rustls = "0.9.0" -trillium-testing = { version = "0.7.0", optional = true } -trillium-tokio = "0.4.0" typenum = "1.18.0" url = "2.5.2" uuid = { version = "1.16.0", features = ["v4", "fast-rng", "serde"] } @@ -120,6 +104,7 @@ features = [ rcgen = "0.14.3" regex = "1.11.1" test-support.workspace = true +tokio-rustls = "0.26" [build-dependencies] rustc_version = "0.4.1" diff --git a/client/Cargo.toml b/client/Cargo.toml index 11924041..605832d4 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -31,13 +31,10 @@ num-bigint = "0.4.6" num-rational = "0.4.2" [dev-dependencies] +axum = "0.8" divviup-api.workspace = true fastrand = "2.4.1" futures-lite = "2.6.1" test-support.workspace = true tokio = { version = "1", features = ["net"] } -trillium = "0.2.20" -trillium-http = "0.3.14" -trillium-testing = { version = "0.7.0", features = ["tokio"] } -trillium-tokio = "0.4.0" divviup-client = { path = ".", features = ["admin"] } diff --git a/client/tests/integration/basic_client_behavior.rs b/client/tests/integration/basic_client_behavior.rs index 9f22fe71..337a2fbc 100644 --- a/client/tests/integration/basic_client_behavior.rs +++ b/client/tests/integration/basic_client_behavior.rs @@ -16,13 +16,17 @@ async fn default_headers( ); assert_eq!( - log.request_headers.get_str(KnownHeaderName::Accept), + log.request_headers + .get(headers::ACCEPT) + .and_then(|v| v.to_str().ok()), Some(CONTENT_TYPE) ); assert!(log .request_headers - .get_str(KnownHeaderName::Authorization) + .get(headers::AUTHORIZATION) + .unwrap() + .to_str() .unwrap() .starts_with("Bearer ")); } diff --git a/client/tests/integration/harness.rs b/client/tests/integration/harness.rs index 87b3859d..42cc612e 100644 --- a/client/tests/integration/harness.rs +++ b/client/tests/integration/harness.rs @@ -1,28 +1,20 @@ pub use divviup_client::DivviupClient; use std::{future::Future, net::Ipv6Addr, process::Termination}; -use test_support::tracing::install_test_trace_subscriber; use tokio::net::TcpListener; -use trillium_http::Stopper; use url::Url; pub use std::sync::Arc; pub use test_support::*; -async fn spawn_test_server(app: impl trillium::Handler) -> (Url, Stopper) { +async fn spawn_test_server(router: axum::Router) -> Url { let listener = TcpListener::bind((Ipv6Addr::LOCALHOST, 0)).await.unwrap(); let port = listener.local_addr().unwrap().port(); - let stopper = Stopper::new(); - - tokio::spawn( - trillium_tokio::config() - .without_signals() - .with_stopper(stopper.clone()) - .with_prebound_server(listener) - .run_async(app), - ); - - let url = Url::parse(&format!("http://[::1]:{port}/")).unwrap(); - (url, stopper) + tokio::spawn(async move { + axum::serve(listener, router) + .await + .expect("test server error"); + }); + Url::parse(&format!("http://[::1]:{port}/")).unwrap() } pub fn with_configured_client(f: F) -> Out @@ -42,13 +34,18 @@ where Fut: Future + Send + 'static, Out: Termination, { - with_client_logs(move |app, _api_logs| async move { - install_test_trace_subscriber(); - let client_logs = ClientLogs::default(); + with_client_logs(move |app, client_logs| async move { + tracing::install_test_trace_subscriber(); + let router = app + .router() + .clone() + .layer(axum::middleware::from_fn_with_state( + client_logs.clone(), + client_logs::client_logs_middleware, + )); + let base_url = spawn_test_server(router).await; let app = Arc::new(app); - let (base_url, _stopper) = spawn_test_server((client_logs.clone(), app.clone())).await; - let account = fixtures::account(&app).await; let (api_token, token) = ApiToken::build(&account); api_token.insert(app.db()).await.unwrap(); diff --git a/deny.toml b/deny.toml index a37781a9..8fd00996 100644 --- a/deny.toml +++ b/deny.toml @@ -5,12 +5,10 @@ all-features = true [advisories] version = 2 ignore = [ - # All these must be retired when we finish migrating to Axum - "RUSTSEC-2025-0056", # Used by console-subscriber and trillium - "RUSTSEC-2025-0141", # Used by async-session and trillium - "RUSTSEC-2024-0437", # Used by trillium and old prometheus - "RUSTSEC-2025-0134", # Used by trillium and old requwest (which has to be old b/c trillium) - "RUSTSEC-2026-0097", # Used by trillium and old prometheus + "RUSTSEC-2025-0056", # Used by console-subscriber + "RUSTSEC-2024-0437", # protobuf 2.28.0 via opentelemetry-prometheus → prometheus + "RUSTSEC-2025-0134", # rustls-pemfile 2.2.0 via reqwest + "RUSTSEC-2026-0097", # rand 0.8.5 via prometheus ] [bans] diff --git a/src/api_mocks.rs b/src/api_mocks.rs index f221847d..4706d084 100644 --- a/src/api_mocks.rs +++ b/src/api_mocks.rs @@ -1,11 +1,11 @@ -use crate::handler::origin_router; -use trillium::Handler; -use trillium_http::KnownHeaderName; -use trillium_logger::{ - formatters::{method, request_header, status, url}, - logger, +use axum::{ + extract::{Request, State}, + response::IntoResponse, + Router, }; -use trillium_macros::Handler; +use std::{collections::HashMap, sync::Arc}; +use tower::ServiceExt; +use url::Url; pub mod aggregator_api; pub mod auth0; @@ -17,26 +17,68 @@ fn random_chars(n: usize) -> String { .collect() } -#[derive(Handler, Debug)] -pub struct ApiMocks(Box); +#[derive(Clone)] +struct HostMap { + routers: HashMap, + fallback: Router, +} + +#[derive(Debug)] +pub struct ApiMocks { + router: Router, +} impl ApiMocks { pub fn new(postmark_url: &str, auth0_url: &str) -> Self { - Self(Box::new(( - logger().with_formatter(( - "[mock] ", - method, - " ", - request_header(KnownHeaderName::Host), - " ", - url, - " ", - status, - )), - origin_router() - .with_handler(postmark_url, postmark::mock()) - .with_handler(auth0_url, auth0::mock(auth0_url)), - aggregator_api::mock(), - ))) + let mut routers = HashMap::new(); + routers.insert(extract_host(postmark_url), postmark::mock()); + routers.insert(extract_host(auth0_url), auth0::mock(auth0_url)); + + let host_map = Arc::new(HostMap { + routers, + fallback: aggregator_api::mock(), + }); + + let router = Router::new() + .fallback(dispatch_by_host) + .with_state(host_map); + + Self { router } + } + + pub fn into_router(self) -> Router { + self.router } } + +// NB: Url::host_str() strips IPv6 brackets, but the Host header keeps them. +// Current callers only pass hostname URLs, so this doesn't bite today. +fn extract_host(url: &str) -> String { + Url::parse(url) + .ok() + .and_then(|u| { + u.host_str().map(|h| match u.port() { + Some(p) => format!("{h}:{p}"), + None => h.to_string(), + }) + }) + .unwrap_or_default() + .to_lowercase() +} + +async fn dispatch_by_host(State(host_map): State>, req: Request) -> impl IntoResponse { + let host = req + .headers() + .get("host") + .and_then(|h| h.to_str().ok()) + .unwrap_or("") + .to_lowercase(); + + let router = host_map + .routers + .get(&host) + .unwrap_or(&host_map.fallback) + .clone(); + + router.oneshot(req).await.into_response() +} diff --git a/src/api_mocks/aggregator_api.rs b/src/api_mocks/aggregator_api.rs index 6102d950..4e6054b7 100644 --- a/src/api_mocks/aggregator_api.rs +++ b/src/api_mocks/aggregator_api.rs @@ -7,54 +7,43 @@ use crate::{ }, entity::aggregator::{Feature, Features}, }; +use axum::{ + extract::{Path, Query, Request}, + http::{header, StatusCode}, + middleware::{self, Next}, + response::{IntoResponse, Response}, + routing, Json, Router, +}; use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; -use querystrong::QueryStrong; use rand::random; +use serde::Deserialize; use sha2::{Digest, Sha256}; use std::iter::repeat_with; -use trillium::{Conn, Handler, Status}; -use trillium_api::{api, Json, State}; -use trillium_http::KnownHeaderName; -use trillium_router::{router, RouterConnExt}; use uuid::Uuid; pub const BAD_BEARER_TOKEN: &str = "badbearertoken"; -pub fn mock() -> impl Handler { - ( - bearer_token_check, - router() - .get( - "/", - api(|_: &mut Conn, _: ()| async move { - Json(AggregatorApiConfig { - dap_url: format!("https://dap.{}.example", random_chars(5)) - .parse() - .unwrap(), - role: random(), - vdafs: Default::default(), - query_types: Default::default(), - protocol: random(), - features: Features::from_iter([Feature::TokenHash]), - }) - }), - ) - .post("/tasks", api(post_task)) - .get("/tasks/:task_id", api(get_task)) - .patch("/tasks/:task_id", api(patch_task)) - .get("/task_ids", api(task_ids)) - .delete("/tasks/:task_id", Status::Ok) - .get( - "/tasks/:task_id/metrics/uploads", - api(get_task_upload_metrics), - ), - ) +pub fn mock() -> Router { + Router::new() + .route("/", routing::get(get_config)) + .route("/tasks", routing::post(post_task)) + .route( + "/tasks/{task_id}", + routing::get(get_task).patch(patch_task).delete(delete_task), + ) + .route("/task_ids", routing::get(task_ids)) + .route( + "/tasks/{task_id}/metrics/uploads", + routing::get(get_task_upload_metrics), + ) + .layer(middleware::from_fn(bearer_token_check)) } -async fn bearer_token_check(conn: Conn) -> Conn { - let token_is_valid = conn - .request_headers() - .get_str(KnownHeaderName::Authorization) +async fn bearer_token_check(request: Request, next: Next) -> Response { + let token_is_valid = request + .headers() + .get(header::AUTHORIZATION) + .and_then(|v| v.to_str().ok()) .is_some_and(|s| match s.split_once(' ') { Some(("Bearer", BAD_BEARER_TOKEN)) => false, Some(("Bearer", _)) => true, @@ -62,13 +51,26 @@ async fn bearer_token_check(conn: Conn) -> Conn { }); if token_is_valid { - conn + next.run(request).await } else { - conn.with_status(Status::Unauthorized).halt() + StatusCode::UNAUTHORIZED.into_response() } } -async fn get_task_upload_metrics(_: &mut Conn, (): ()) -> Json { +async fn get_config() -> Json { + Json(AggregatorApiConfig { + dap_url: format!("https://dap.{}.example", random_chars(5)) + .parse() + .unwrap(), + role: random(), + vdafs: Default::default(), + query_types: Default::default(), + protocol: random(), + features: Features::from_iter([Feature::TokenHash]), + }) +} + +async fn get_task_upload_metrics() -> Json { Json(TaskUploadMetrics { interval_collected: fastrand::u64(..1000), report_decode_failure: fastrand::u64(..1000), @@ -81,8 +83,7 @@ async fn get_task_upload_metrics(_: &mut Conn, (): ()) -> Json Json { - let task_id = conn.param("task_id").unwrap(); +async fn get_task(Path(task_id): Path) -> Json { Json(TaskResponse { task_id: task_id.parse().unwrap(), peer_aggregator_endpoint: "https://_".parse().unwrap(), @@ -103,15 +104,14 @@ async fn get_task(conn: &mut Conn, (): ()) -> Json { }) } -async fn post_task( - _: &mut Conn, - Json(task_create): Json, -) -> (State, Json) { - (State(task_create.clone()), Json(task_response(task_create))) +async fn post_task(Json(task_create): Json) -> Json { + Json(task_response(task_create)) } -async fn patch_task(conn: &mut Conn, Json(patch): Json) -> Json { - let task_id = conn.param("task_id").unwrap(); +async fn patch_task( + Path(task_id): Path, + Json(patch): Json, +) -> Json { Json(TaskResponse { task_id: task_id.parse().unwrap(), peer_aggregator_endpoint: "https://_".parse().unwrap(), @@ -132,6 +132,10 @@ async fn patch_task(conn: &mut Conn, Json(patch): Json) -> Json StatusCode { + StatusCode::OK +} + pub fn task_response(task_create: TaskCreate) -> TaskResponse { let task_id = TaskId::try_from( Sha256::digest(URL_SAFE_NO_PAD.decode(task_create.vdaf_verify_key).unwrap()).as_slice(), @@ -167,27 +171,30 @@ pub fn random_hpke_config() -> HpkeConfig { ) } -async fn task_ids(conn: &mut Conn, (): ()) -> Result, Status> { - let query = - QueryStrong::parse_strict(conn.querystring()).map_err(|_| Status::InternalServerError)?; - match query.get_str("pagination_token") { - None => Ok(Json(TaskIds { +#[derive(Deserialize)] +struct TaskIdsQuery { + pagination_token: Option, +} + +async fn task_ids(Query(query): Query) -> Json { + match query.pagination_token.as_deref() { + None => Json(TaskIds { task_ids: repeat_with(|| Uuid::new_v4().to_string()) .take(10) .collect(), pagination_token: Some("second".into()), - })), + }), - Some("second") => Ok(Json(TaskIds { + Some("second") => Json(TaskIds { task_ids: repeat_with(|| Uuid::new_v4().to_string()) .take(10) .collect(), pagination_token: Some("last".into()), - })), + }), - _ => Ok(Json(TaskIds { + _ => Json(TaskIds { task_ids: repeat_with(|| Uuid::new_v4().to_string()).take(5).collect(), pagination_token: None, - })), + }), } } diff --git a/src/api_mocks/auth0.rs b/src/api_mocks/auth0.rs index be273d07..bb9b153a 100644 --- a/src/api_mocks/auth0.rs +++ b/src/api_mocks/auth0.rs @@ -1,33 +1,36 @@ use super::random_chars; use crate::{clients::auth0_client::Token, User}; +use axum::{routing, Json, Router}; use serde_json::json; -use trillium::Handler; -use trillium_api::{Halt, Json}; -use trillium_router::router; -pub fn mock(auth0_url: &str) -> impl Handler { - ( - router() - .get("/userinfo", Json(User::for_integration_testing())) - .post( - "/oauth/token", +pub fn mock(auth0_url: &str) -> Router { + let auth0_url = auth0_url.to_owned(); + Router::new() + .route( + "/userinfo", + routing::get(|| async { Json(User::for_integration_testing()) }), + ) + .route( + "/oauth/token", + routing::post(|| async { Json(Token { access_token: "access token".into(), expires_in: 60, scope: "".into(), token_type: "bearer".into(), - }), - ) - .post( - "/api/v2/users", - Json(json!({ "user_id": random_chars(10) })), - ) - .post( - "/api/v2/tickets/password-change", + }) + }), + ) + .route( + "/api/v2/users", + routing::post(|| async { Json(json!({ "user_id": random_chars(10) })) }), + ) + .route( + "/api/v2/tickets/password-change", + routing::post(move || async move { Json(json!({ "ticket": format!("{auth0_url}/password_tickets/{}", random_chars(10)) - })), - ), - Halt, - ) + })) + }), + ) } diff --git a/src/api_mocks/postmark.rs b/src/api_mocks/postmark.rs index 43a6c18a..5c97f274 100644 --- a/src/api_mocks/postmark.rs +++ b/src/api_mocks/postmark.rs @@ -1,7 +1,9 @@ +use axum::{routing, Json, Router}; use serde_json::json; -use trillium::Handler; -use trillium_api::{Halt, Json}; -use trillium_router::router; -pub fn mock() -> impl Handler { - (router().post("/email/withTemplate", Json(json!({}))), Halt) + +pub fn mock() -> Router { + Router::new().route( + "/email/withTemplate", + routing::post(|| async { Json(json!({})) }), + ) } diff --git a/src/clients.rs b/src/clients.rs index c0f8c7a8..ff2224bc 100644 --- a/src/clients.rs +++ b/src/clients.rs @@ -37,6 +37,10 @@ impl HttpClient { } } + pub(crate) fn reqwest_client(&self) -> &reqwest::Client { + &self.inner + } + pub fn with_base(mut self, url: impl Into) -> Self { let mut url = url.into(); if !url.path().ends_with('/') { @@ -139,7 +143,7 @@ impl HttpClient { #[derive(Debug)] pub struct HttpStatusNotSuccess { - pub method: String, + pub method: Method, pub url: Url, pub status: Option, pub body: String, @@ -164,12 +168,18 @@ pub enum ClientError { /// and convert non-success into [`ClientError`]. #[async_trait::async_trait] pub trait ResponseExt: Sized { - async fn success_or_client_error(self) -> Result; + async fn success_or_client_error( + self, + method: Method, + ) -> Result; } #[async_trait::async_trait] impl ResponseExt for reqwest::Response { - async fn success_or_client_error(self) -> Result { + async fn success_or_client_error( + self, + method: Method, + ) -> Result { let status = self.status(); if status.is_success() { Ok(self) @@ -178,7 +188,7 @@ impl ResponseExt for reqwest::Response { let body = self.text().await.unwrap_or_default(); Err(ClientError::HttpStatusNotSuccess(Box::new( HttpStatusNotSuccess { - method: String::new(), + method, url, status: Some(status), body, diff --git a/src/clients/aggregator_client.rs b/src/clients/aggregator_client.rs index 5740ac31..2717bfa0 100644 --- a/src/clients/aggregator_client.rs +++ b/src/clients/aggregator_client.rs @@ -4,7 +4,7 @@ use crate::{ handler::Error, }; use api_types::TaskAggregationJobMetrics; -use axum::http::header; +use axum::http::{header, Method}; use janus_messages::Time as JanusTime; use serde::{de::DeserializeOwned, Serialize}; use url::Url; @@ -42,7 +42,7 @@ impl AggregatorClient { .header(header::ACCEPT, CONTENT_TYPE) .send() .await? - .success_or_client_error() + .success_or_client_error(Method::GET) .await? .json() .await @@ -113,7 +113,7 @@ impl AggregatorClient { .get(path) .send() .await? - .success_or_client_error() + .success_or_client_error(Method::GET) .await? .json() .await @@ -131,7 +131,7 @@ impl AggregatorClient { .json(body) .send() .await? - .success_or_client_error() + .success_or_client_error(Method::POST) .await? .json() .await @@ -149,7 +149,7 @@ impl AggregatorClient { .json(body) .send() .await? - .success_or_client_error() + .success_or_client_error(Method::PATCH) .await? .json() .await diff --git a/src/clients/auth0_client.rs b/src/clients/auth0_client.rs index 7d447b05..88940011 100644 --- a/src/clients/auth0_client.rs +++ b/src/clients/auth0_client.rs @@ -1,5 +1,5 @@ use async_lock::RwLock; -use axum::http::{header, StatusCode}; +use axum::http::{header, Method, StatusCode}; use educe::Educe; use rand::distributions::{Alphanumeric, DistString}; use serde::{de::DeserializeOwned, Serialize}; @@ -154,7 +154,7 @@ impl Auth0Client { })) .send() .await? - .success_or_client_error() + .success_or_client_error(Method::POST) .await? .json() .await?; @@ -184,7 +184,7 @@ impl Auth0Client { .json(json) .send() .await? - .success_or_client_error() + .success_or_client_error(Method::POST) .await? .json() .await @@ -201,7 +201,7 @@ impl Auth0Client { .header(header::AUTHORIZATION, format!("Bearer {token}")) .send() .await? - .success_or_client_error() + .success_or_client_error(Method::GET) .await? .json() .await diff --git a/src/clients/postmark_client.rs b/src/clients/postmark_client.rs index 6f8cde49..1525066c 100644 --- a/src/clients/postmark_client.rs +++ b/src/clients/postmark_client.rs @@ -2,7 +2,7 @@ use crate::{ clients::{ClientError, HttpClient, ResponseExt}, Config, }; -use axum::http::{header, HeaderName}; +use axum::http::{header, HeaderName, Method}; use email_address::EmailAddress; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::{json, Value}; @@ -77,7 +77,7 @@ impl PostmarkClient { .json(json) .send() .await? - .success_or_client_error() + .success_or_client_error(Method::POST) .await? .json() .await diff --git a/src/handler.rs b/src/handler.rs index 1ce06637..2d9d05f2 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -6,13 +6,8 @@ pub(crate) mod custom_mime_types; pub(crate) mod error; pub(crate) mod extract; pub(crate) mod oauth2; -// TODO: remove origin_router in Part 9/10 (used by api_mocks) -pub(crate) mod origin_router; pub(crate) mod session_store; -// TODO: remove proxy in Part 9 (only kept for DivviupApi test shim) -pub(crate) mod proxy; - use crate::{ clients::{Auth0Client, HttpClient}, routes::{axum_routes, health_check}, @@ -24,23 +19,15 @@ use axum::{ routing, }; use cors::axum_cors_layer; -use error::ErrorHandler; use oauth2::OauthClient; -// TODO: remove proxy + trillium imports in Part 9 (test-support rewrite) -use proxy::AxumProxy; use session_store::axum_session_layer; -use std::{net::Ipv6Addr, sync::Arc}; -use tokio::net::TcpListener; -use tokio_util::sync::CancellationToken; +use std::sync::Arc; use tower::ServiceBuilder; use tower_http::{ compression::CompressionLayer, set_header::SetResponseHeaderLayer, trace::TraceLayer, }; -use trillium::{Handler, Info}; -use trillium_macros::Handler; pub use error::Error; -pub use origin_router::origin_router; /// Shared state for the Axum application. #[derive(Clone, Debug)] @@ -124,9 +111,14 @@ pub async fn build_app(config: Config) -> BuiltApp { client: config.client.clone(), }; - // TODO(Part 9): add OpenTelemetry HTTP metrics middleware. The deleted + // TODO(Part 10): add OpenTelemetry HTTP metrics middleware. The deleted // trillium-opentelemetry handler provided http.server.* histograms and // optional OTLP per-request spans; TraceLayer only emits tracing events. + // + // TODO(Part 10): restore X-Forwarded-For / Forwarded header propagation. + // The deleted trillium-forwarding::Forwarding::trust_always() updated the + // peer IP from proxy headers so trace spans logged the client IP. Without + // it, TraceLayer records the proxy/load-balancer IP instead. let middleware = ServiceBuilder::new() .layer(TraceLayer::new_for_http()) .layer(DefaultBodyLimit::max(MAX_REQUEST_BODY_SIZE)) @@ -141,9 +133,6 @@ pub async fn build_app(config: Config) -> BuiltApp { #[cfg(feature = "integration-testing")] let middleware = middleware.layer(axum::middleware::from_fn(inject_integration_testing_user)); - #[cfg(feature = "test-header-injection")] - let middleware = middleware.layer(axum::middleware::from_fn(inject_test_header_user)); - #[cfg(assets)] let middleware = middleware.layer(axum::middleware::from_fn_with_state( assets::AssetConfig::new(&config.api_url, &config.app_url), @@ -162,109 +151,8 @@ pub async fn build_app(config: Config) -> BuiltApp { BuiltApp { router, db, config } } -// --------------------------------------------------------------------------- -// Test-only shim: DivviupApi -// -// test-support constructs a DivviupApi, calls .db()/.config()/.init(), and -// passes it to trillium_testing's .run_async(&app). This shim keeps that -// working by spawning the Axum router on a loopback port and proxying via -// AxumProxy. -// -// TODO: remove in Part 9 (test-support rewrite) -// --------------------------------------------------------------------------- - -#[derive(Handler, Debug)] -pub struct DivviupApi { - #[handler(except = init)] - handler: Box, - db: Db, - config: Arc, -} - -impl DivviupApi { - pub async fn init(&mut self, info: &mut Info) { - *info.server_description_mut() = format!("divviup-api {}", env!("CARGO_PKG_VERSION")); - self.handler.init(info).await - } - - pub async fn new(config: Config) -> Self { - let app = build_app(config).await; - - let axum_listener = TcpListener::bind((Ipv6Addr::LOCALHOST, 0)) - .await - .expect("failed to bind Axum listener on IPv6 loopback"); - let axum_addr = axum_listener - .local_addr() - .expect("failed to get Axum listener address"); - tokio::spawn(async move { - if let Err(e) = axum::serve(axum_listener, app.router).await { - log::error!("axum server error: {e}"); - } - }); - - let proxy = AxumProxy::new(axum_addr); - - Self { - handler: Box::new(( - #[cfg(feature = "test-header-injection")] - inject_test_user_trillium, - proxy, - ErrorHandler, - )), - db: app.db, - config: app.config, - } - } - - pub fn db(&self) -> &Db { - &self.db - } - - pub fn config(&self) -> &Config { - &self.config - } - - pub fn crypter(&self) -> &crate::Crypter { - &self.config.crypter - } -} - -// TODO: remove in Part 9 (test-support rewrite) -// NOTE: the CancellationToken created here is never cancelled, so workers -// spawned from this Queue would run forever. This is fine because callers -// only use perform_one_queue_job(), never spawn_workers(). -impl From<&DivviupApi> for crate::Queue { - fn from(app: &DivviupApi) -> Self { - Self::new(app.db(), app.config(), CancellationToken::new()) - } -} - -impl AsRef for DivviupApi { - fn as_ref(&self) -> &Db { - &self.db - } -} - -/// Trillium-side test shim: if a `User` was injected via `.with_state()` -/// (legacy test pattern), serialize it into the `X-Integration-Testing-User` -/// header so the proxy forwards it to Axum. -// TODO: remove in Part 9 (test-support rewrite — tests will set the header directly) -#[cfg(feature = "test-header-injection")] -async fn inject_test_user_trillium(mut conn: trillium::Conn) -> trillium::Conn { - if let Some(json) = conn - .state::() - .and_then(|u| serde_json::to_string(u).ok()) - { - conn.request_headers_mut() - .insert("x-integration-testing-user", json); - } - conn -} - -/// Axum middleware that unconditionally injects an admin -/// [`User`](crate::User) into every request. This is the Axum equivalent of -/// the Trillium `state(User::for_integration_testing())` that was in the old -/// `api()` handler chain. +/// Axum middleware that injects an admin [`User`](crate::User) into every +/// request that doesn't already have one in extensions. /// /// Only compiled under `--features integration-testing` (enabled by /// `compose.dev.override.yaml`). Never compiled into deployed builds. @@ -273,29 +161,10 @@ async fn inject_integration_testing_user( mut request: axum::extract::Request, next: axum::middleware::Next, ) -> axum::response::Response { - request - .extensions_mut() - .insert(crate::User::for_integration_testing()); - next.run(request).await -} - -/// Axum middleware that reads an `X-Integration-Testing-User` header and -/// injects the decoded [`User`](crate::User) into request extensions. -/// Used by `test-support` to impersonate specific users in tests. -/// -/// Only compiled under `--features test-header-injection` (enabled by -/// `test-support`). Never compiled into deployed builds. -#[cfg(feature = "test-header-injection")] -async fn inject_test_header_user( - mut request: axum::extract::Request, - next: axum::middleware::Next, -) -> axum::response::Response { - if let Some(user) = request - .headers() - .get("x-integration-testing-user") - .and_then(|v| serde_json::from_slice::(v.as_bytes()).ok()) - { - request.extensions_mut().insert(user); + if request.extensions().get::().is_none() { + request + .extensions_mut() + .insert(crate::User::for_integration_testing()); } next.run(request).await } diff --git a/src/handler/error.rs b/src/handler/error.rs index f12dd957..3e6a5a30 100644 --- a/src/handler/error.rs +++ b/src/handler/error.rs @@ -7,16 +7,6 @@ use serde_json::json; use std::sync::Arc; use validator::ValidationErrors; -// TODO: remove in Part 9 (test-support rewrite) — ErrorHandler is only kept -// in the DivviupApi test shim's handler tuple. -pub struct ErrorHandler; -#[trillium::async_trait] -impl trillium::Handler for ErrorHandler { - async fn run(&self, conn: trillium::Conn) -> trillium::Conn { - conn - } -} - /// API-layer errors surfaced by extractors and content-type negotiation. #[derive(thiserror::Error, Debug, Clone)] pub enum ApiError { diff --git a/src/handler/oauth2.rs b/src/handler/oauth2.rs index 0099911a..44aa6342 100644 --- a/src/handler/oauth2.rs +++ b/src/handler/oauth2.rs @@ -235,7 +235,7 @@ impl OauthClient { .set_token_uri(TokenUrl::from_url(config.token_url.clone())) .set_redirect_uri(RedirectUrl::from_url(config.redirect_url.clone())); - let reqwest_client = reqwest::Client::new(); + let reqwest_client = config.http_client.reqwest_client().clone(); Self(Arc::new(OauthClientInner { oauth_config: config.clone(), diff --git a/src/handler/origin_router.rs b/src/handler/origin_router.rs deleted file mode 100644 index 261ed456..00000000 --- a/src/handler/origin_router.rs +++ /dev/null @@ -1,72 +0,0 @@ -// TODO: remove in Part 9/10 (kept for api_mocks) -use std::collections::HashMap; -use trillium::{Conn, Handler, Info}; - -fn origin_host(conn: &Conn) -> String { - conn.inner().host().unwrap_or_default().to_lowercase() -} - -#[derive(Default, Debug)] -pub struct OriginRouter { - map: HashMap>, -} - -impl OriginRouter { - fn handler(&self, conn: &Conn) -> Option<&dyn Handler> { - let host = origin_host(conn); - self.map.get(&host).map(|boxed_handler| &**boxed_handler) - } - - /// Construct a new OriginRouter. - pub fn new() -> Self { - Self::default() - } - - /// Add a handler to this origin router at the specified exact origin, returning self. - /// See also [`add_handler`]. - pub fn with_handler(mut self, origin: &str, handler: impl Handler) -> Self { - self.add_handler(origin, handler); - self - } - - /// Add a handler to this origin router at the specified exact origin. - /// See also [`with_handler`] for chainability. - pub fn add_handler(&mut self, origin: &str, handler: impl Handler) { - let key = origin - .to_lowercase() - .trim_end_matches('/') - .trim_start_matches("https://") - .trim_start_matches("http://") - .to_owned(); - self.map.insert(key, Box::new(handler)); - } -} - -#[trillium::async_trait] -impl Handler for OriginRouter { - async fn run(&self, conn: Conn) -> Conn { - if let Some(handler) = self.handler(&conn) { - handler.run(conn).await - } else { - conn - } - } - - async fn before_send(&self, conn: Conn) -> Conn { - if let Some(handler) = self.handler(&conn) { - handler.before_send(conn).await - } else { - conn - } - } - - async fn init(&mut self, info: &mut Info) { - for value in self.map.values_mut() { - value.init(info).await - } - } -} - -pub fn origin_router() -> OriginRouter { - OriginRouter::new() -} diff --git a/src/handler/proxy.rs b/src/handler/proxy.rs deleted file mode 100644 index 98ddee8d..00000000 --- a/src/handler/proxy.rs +++ /dev/null @@ -1,131 +0,0 @@ -//! Temporary reverse proxy handler that forwards unmatched Trillium requests to -//! the local Axum server. This exists only during the incremental migration and -//! will be removed once all routes have been moved to Axum. - -use reqwest::{ - header::{HeaderName, CONNECTION, HOST, TE, TRAILER, TRANSFER_ENCODING}, - redirect, -}; -use std::net::SocketAddr; -use trillium::{Conn, Handler, Status}; - -/// A Trillium [`Handler`] that proxies unhalted requests to a local Axum server. -#[derive(Debug)] -pub struct AxumProxy { - upstream: String, - client: reqwest::Client, -} - -impl AxumProxy { - pub fn new(addr: SocketAddr) -> Self { - Self { - upstream: format!("http://[::1]:{}", addr.port()), - client: reqwest::Client::builder() - .no_proxy() - .redirect(redirect::Policy::none()) - .build() - .expect("failed to build proxy HTTP client"), - } - } -} - -/// Hop-by-hop headers that should not be forwarded through the proxy (RFC 7230 §6.1). -const UNPROXYABLE_HEADERS: [HeaderName; 5] = [HOST, TRANSFER_ENCODING, CONNECTION, TE, TRAILER]; - -#[trillium::async_trait] -impl Handler for AxumProxy { - async fn run(&self, mut conn: Conn) -> Conn { - // Only proxy requests that haven't been handled by earlier handlers. - if conn.status().is_some() || conn.is_halted() { - return conn; - } - - let method = conn.method(); - let path = conn.path(); - let querystring = conn.querystring(); - - let url = if querystring.is_empty() { - format!("{}{}", self.upstream, path) - } else { - format!("{}{}?{}", self.upstream, path, querystring) - }; - - let reqwest_method = match reqwest::Method::from_bytes(method.as_ref().as_bytes()) { - Ok(m) => m, - Err(_) => return conn.with_status(Status::BadRequest).halt(), - }; - - let mut builder = self.client.request(reqwest_method, &url); - - // Preserve the original Host for downstream host-based routing (e.g. assets). - if let Some(original_host) = conn.request_headers().get_str("host") { - builder = builder.header("x-forwarded-host", original_host); - } - - // Forward request headers, filtering out hop-by-hop headers. - for (name, values) in conn.request_headers() { - let header_name = match HeaderName::from_bytes(name.as_ref().as_bytes()) { - Ok(h) => h, - Err(_) => continue, - }; - if UNPROXYABLE_HEADERS.contains(&header_name) { - continue; - } - for value in values.iter() { - if let Some(s) = value.as_str() { - builder = builder.header(&header_name, s); - } - } - } - - // Forward the request body. Note: no size limit is enforced here; - // the Trillium API layer (trillium-api) enforces a 1 MiB limit before - // requests reach this handler, so it's fine for the migration window. - let body = conn.request_body().await.read_bytes().await; - match body { - Ok(bytes) if !bytes.is_empty() => { - builder = builder.body(bytes); - } - Err(e) => { - log::error!("axum proxy error reading request body: {e}"); - return conn.with_status(Status::BadRequest).halt(); - } - _ => {} - } - - let resp = match builder.send().await { - Ok(resp) => resp, - Err(e) => { - log::error!("axum proxy error: {e}"); - return conn.with_status(Status::BadGateway).halt(); - } - }; - - let status = resp.status().as_u16(); - let resp_headers = resp.headers().clone(); - let body = match resp.bytes().await { - Ok(b) => b, - Err(e) => { - log::error!("axum proxy error reading response: {e}"); - return conn.with_status(Status::BadGateway).halt(); - } - }; - - let mut conn = conn.with_status(status).halt(); - - for (name, value) in resp_headers.iter() { - if UNPROXYABLE_HEADERS.contains(name) { - continue; - } - if let Ok(v) = value.to_str() { - conn.response_headers_mut() - .append(name.as_str().to_owned(), v.to_owned()); - } - } - - // This copies the response body; we could avoid it by streaming via - // resp.into_body() + Body::new_streaming, but it's not worth the extra - // plumbing for a temporary shim. - conn.with_body(body.to_vec()) - } -} diff --git a/src/lib.rs b/src/lib.rs index 06dbf9b5..b00d6231 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,7 @@ pub use config::{Config, ConfigError, FeatureFlags}; pub use crypter::Crypter; pub use db::Db; pub use handler::{ - build_app, custom_mime_types::DIVVIUP_API_MEDIA_TYPE, AxumAppState, BuiltApp, DivviupApi, Error, + build_app, custom_mime_types::DIVVIUP_API_MEDIA_TYPE, AxumAppState, BuiltApp, Error, }; pub use opentelemetry; pub use permissions::{AdminPermissionsActor, Permissions, PermissionsActor}; diff --git a/src/queue/job.rs b/src/queue/job.rs index b7ebde86..cb432c0c 100644 --- a/src/queue/job.rs +++ b/src/queue/job.rs @@ -57,7 +57,7 @@ impl From for JobError { fn from(value: ClientError) -> Self { match value { ClientError::HttpStatusNotSuccess(e) => Self::HttpStatusNotSuccess { - method: e.method, + method: e.method.to_string(), url: e.url, status: e.status.map(|s| s.as_u16()), body: e.body, diff --git a/src/user.rs b/src/user.rs index a5089b47..45950939 100644 --- a/src/user.rs +++ b/src/user.rs @@ -80,15 +80,14 @@ impl User { Db: FromRef, S: Send + Sync, { - // Cache: return early if already extracted and admin-populated. - // Cleanup after Trillium is removed. + // Return early if already extracted and admin-populated if let Some(user) = parts.extensions.get::() { if user.admin.is_some() { return Ok(Some(user.clone())); } } - // Get a mutable user, preferring extensions and falling back to session. + // Prefer extensions, fall back to session. let mut user = if let Some(user) = parts.extensions.remove::() { user } else { diff --git a/test-support/Cargo.toml b/test-support/Cargo.toml index 30cdac5b..68a10124 100644 --- a/test-support/Cargo.toml +++ b/test-support/Cargo.toml @@ -6,12 +6,13 @@ publish = false license.workspace = true [dependencies] +async-trait = "0.1" +axum = "0.8" +bytes = "1" fastrand = "2.4.1" +http-body-util = "0.1" time = "0.3.47" -trillium = "0.2.20" -trillium-macros = "0.0.6" -trillium-testing = { version = "0.7.0", features = ["tokio"] } -divviup-api = { workspace = true, features = ["test-header-injection"] } +divviup-api = { workspace = true } serde = "1.0.228" serde_json = "1.0.149" thiserror = "2.0.18" @@ -23,12 +24,10 @@ tracing-subscriber = { version = "0.3.23", features = [ "std", "fmt", ] } -trillium-client = { version = "0.6.2", features = ["json"] } -trillium-http = "0.3.14" -trillium-rustls = "0.9.0" -trillium-tokio = "0.4.0" reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json"] } -tokio = { version = "1", features = ["net"] } +tokio = { version = "1", features = ["net", "rt"] } +tokio-util = { version = "0.7", features = ["rt"] } +tower = { version = "0.5", features = ["util"] } url = "2.5.8" uuid = { version = "1.20.0", features = ["v4", "fast-rng", "serde"] } sea-orm = { version = "1.1.20", features = ["sqlx-sqlite"] } diff --git a/test-support/src/api_mocks.rs b/test-support/src/api_mocks.rs index 2c18e514..4fbdbfaf 100644 --- a/test-support/src/api_mocks.rs +++ b/test-support/src/api_mocks.rs @@ -1,10 +1,10 @@ -use super::{ClientLogs, AUTH0_URL, POSTMARK_URL}; -use trillium_macros::Handler; +use super::{client_logs::client_logs_middleware, ClientLogs, AUTH0_URL, POSTMARK_URL}; +use axum::{middleware, Router}; +use divviup_api::api_mocks::ApiMocks as DivviupApiMocks; -#[derive(Handler, Debug)] +#[derive(Debug)] pub struct ApiMocks { - #[handler] - handler: (ClientLogs, divviup_api::api_mocks::ApiMocks), + router: Router, client_logs: ClientLogs, } @@ -18,11 +18,14 @@ impl ApiMocks { pub fn new() -> Self { let client_logs = ClientLogs::default(); + let inner = DivviupApiMocks::new(POSTMARK_URL, AUTH0_URL); + let router = inner.into_router().layer(middleware::from_fn_with_state( + client_logs.clone(), + client_logs_middleware, + )); + Self { - handler: ( - client_logs.clone(), - divviup_api::api_mocks::ApiMocks::new(POSTMARK_URL, AUTH0_URL), - ), + router, client_logs, } } @@ -30,4 +33,8 @@ impl ApiMocks { pub fn client_logs(&self) -> ClientLogs { self.client_logs.clone() } + + pub fn into_router(self) -> Router { + self.router + } } diff --git a/test-support/src/client_logs.rs b/test-support/src/client_logs.rs index ea2423d4..96f00b94 100644 --- a/test-support/src/client_logs.rs +++ b/test-support/src/client_logs.rs @@ -1,84 +1,49 @@ +use axum::{ + body::Body, + extract::{Request, State}, + http::{HeaderMap, Method, StatusCode}, + middleware::Next, + response::Response, +}; use divviup_api::clients::ORIGINAL_URL_HEADER; +use http_body_util::BodyExt; use serde::Deserialize; use std::{ - fmt::{Display, Formatter, Result}, + fmt::{Display, Formatter, Result as FmtResult}, sync::{Arc, RwLock}, }; -use trillium::{async_trait, Body, Conn, Handler, Headers, Method, StateSet, Status}; use url::Url; #[derive(Debug, Clone)] pub struct LoggedConn { pub url: Url, pub method: Method, + pub request_headers: HeaderMap, + pub request_body: Option, pub response_body: Option, - pub response_status: Status, - pub request_headers: Headers, - pub response_headers: Headers, - pub state: Arc, + pub response_status: StatusCode, + pub response_headers: HeaderMap, } impl LoggedConn { pub fn response_json<'a: 'de, 'de, T: Deserialize<'de>>(&'a self) -> T { serde_json::from_str(self.response_body.as_ref().unwrap()).expect("deserialization error") } -} -impl From<&mut Conn> for LoggedConn { - fn from(conn: &mut Conn) -> Self { - let url = conn - .request_headers() - .get_str(ORIGINAL_URL_HEADER.as_str()) - .and_then(|s| Url::parse(s).ok()) - .unwrap_or_else(|| { - Url::parse(&format!( - "{}://{}{}{}", - if conn.is_secure() { "https" } else { "http" }, - conn.inner().host().unwrap(), - conn.path(), - match conn.querystring() { - "" => "".into(), - q => format!("?{q}"), - } - )) - .unwrap() - }); - - let state = Arc::new(std::mem::take(conn.inner_mut().state_mut())); - - let request_headers = match state.get() { - Some(OriginalRequestHeaders(headers)) => headers.clone(), - None => conn.request_headers().clone(), - }; - - Self { - url, - state, - method: conn.method(), - response_body: conn - .inner() - .response_body() - .and_then(Body::static_bytes) - .map(|s| String::from_utf8_lossy(s).to_string()), - response_status: conn.status().unwrap_or(Status::NotFound), - request_headers, - response_headers: conn.response_headers().clone(), - } + pub fn request_json(&self) -> T { + serde_json::from_str(self.request_body.as_ref().unwrap()).expect("deserialization error") } } impl Display for LoggedConn { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - f.write_fmt(format_args!( - "{} {}: {}", - self.method, self.url, self.response_status - )) + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + write!(f, "{} {}: {}", self.method, self.url, self.response_status) } } #[derive(Debug, Default, Clone)] pub struct ClientLogs { - pub(super) logged_conns: Arc>>, + logged_conns: Arc>>, } impl ClientLogs { @@ -109,20 +74,74 @@ impl ClientLogs { } } -#[derive(Debug)] -struct OriginalRequestHeaders(Headers); - -#[async_trait] -impl Handler for ClientLogs { - async fn run(&self, conn: Conn) -> Conn { - let request_headers = conn.request_headers().clone(); - conn.with_state(OriginalRequestHeaders(request_headers)) - } - async fn before_send(&self, mut conn: Conn) -> Conn { - self.logged_conns - .write() - .unwrap() - .push(LoggedConn::from(&mut conn)); - conn +fn reconstruct_url(req: &Request) -> Url { + if let Some(original) = req + .headers() + .get(ORIGINAL_URL_HEADER.as_str()) + .and_then(|v| v.to_str().ok()) + { + if let Ok(url) = Url::parse(original) { + return url; + } } + + let host = req + .headers() + .get("host") + .and_then(|h| h.to_str().ok()) + .unwrap_or("unknown"); + let mut url = Url::parse(&format!("https://{host}")) + .unwrap_or_else(|e| panic!("reconstruct_url: malformed Host: {host}: {e}")); + url.set_path(req.uri().path()); + url.set_query(req.uri().query()); + url +} + +pub async fn client_logs_middleware( + State(logs): State, + request: Request, + next: Next, +) -> Response { + let method = request.method().clone(); + let request_headers = request.headers().clone(); + let url = reconstruct_url(&request); + + let (parts, body) = request.into_parts(); + let body_bytes = body + .collect() + .await + .expect("failed to read request body") + .to_bytes(); + let request_body = if body_bytes.is_empty() { + None + } else { + Some(String::from_utf8(body_bytes.to_vec()).expect("could not decode body as UTF-8")) + }; + let request = Request::from_parts(parts, Body::from(body_bytes)); + + let response = next.run(request).await; + + let (parts, body) = response.into_parts(); + let response_bytes = body + .collect() + .await + .expect("failed to read response body") + .to_bytes(); + let response_body = if response_bytes.is_empty() { + None + } else { + Some(String::from_utf8(response_bytes.to_vec()).expect("could not decode body as UTF-8")) + }; + + logs.logged_conns.write().unwrap().push(LoggedConn { + url, + method, + request_headers, + request_body, + response_body, + response_status: parts.status, + response_headers: parts.headers.clone(), + }); + + Response::from_parts(parts, Body::from(response_bytes)) } diff --git a/test-support/src/fixtures.rs b/test-support/src/fixtures.rs index d2aa487e..ae5604c8 100644 --- a/test-support/src/fixtures.rs +++ b/test-support/src/fixtures.rs @@ -201,7 +201,10 @@ pub async fn aggregator(app: &DivviupApi, account: Option<&Account>) -> Aggregat pub async fn api_token(app: &DivviupApi, account: &Account) -> (ApiToken, HeaderValue) { let (api_token, token) = ApiToken::build(account); let api_token = api_token.insert(app.db()).await.unwrap(); - (api_token, format!("Bearer {token}").into()) + ( + api_token, + HeaderValue::try_from(format!("Bearer {token}")).unwrap(), + ) } pub async fn admin_token(app: &DivviupApi) -> HeaderValue { diff --git a/test-support/src/lib.rs b/test-support/src/lib.rs index f31a87a8..f6c35946 100644 --- a/test-support/src/lib.rs +++ b/test-support/src/lib.rs @@ -9,10 +9,21 @@ #![allow(clippy::cargo_common_metadata)] #![allow(clippy::multiple_crate_versions)] +use axum::{ + body::Body, + extract::Request, + http::header::{self, HeaderName, HeaderValue}, + middleware::from_fn_with_state, + response::Response, + serve, Router, +}; use divviup_api::{ clients::{aggregator_client::api_types, HttpClient}, + handler::BuiltApp, Config, Crypter, Db, }; +use http_body_util::BodyExt; +use reqwest::Client; use serde::{de::DeserializeOwned, Serialize}; use std::{ error::Error, @@ -20,13 +31,14 @@ use std::{ iter::repeat_with, net::{Ipv6Addr, SocketAddr}, process::Termination, + sync::Arc, }; -use tokio::net::TcpListener; +use tokio::{net::TcpListener, runtime}; +use tokio_util::sync::CancellationToken; +use tower::ServiceExt; use tracing::install_test_trace_subscriber; -use trillium::Handler; -use trillium_http::HeaderValue; -use trillium_testing::TestConn; +pub use axum::http::{header as headers, HeaderMap, Method, StatusCode}; pub use base64::{ engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD}, Engine, @@ -34,7 +46,7 @@ pub use base64::{ pub use divviup_api::{ entity::{self, *}, queue::{Job, Queue}, - DivviupApi, User, + User, }; pub use pretty_assertions::{assert_eq, assert_ne, assert_str_eq}; pub use querystrong::QueryStrong; @@ -45,16 +57,28 @@ pub use sea_orm::{ pub use serde_json::{json, Value}; pub use test_harness::test; pub use time::OffsetDateTime; -pub use trillium::{Conn, KnownHeaderName, Method, Status}; -pub use trillium_testing::prelude::*; pub use url::Url; pub type TestResult = Result<(), Box>; +pub trait IntoTestStatus { + fn into_status(self) -> StatusCode; +} +impl IntoTestStatus for u16 { + fn into_status(self) -> StatusCode { + StatusCode::from_u16(self).expect("invalid status code") + } +} +impl IntoTestStatus for StatusCode { + fn into_status(self) -> StatusCode { + self + } +} + pub mod fixtures; pub mod tracing; -mod client_logs; +pub mod client_logs; pub use client_logs::{ClientLogs, LoggedConn}; mod api_mocks; @@ -87,21 +111,20 @@ pub async fn set_up_schema(db: &Db) { set_up_schema_for(&schema, db, CollectorCredentials).await; } -pub async fn config(api_mocks: impl Handler) -> Config { +pub async fn config(mock_router: Router) -> Config { let listener = TcpListener::bind((Ipv6Addr::LOCALHOST, 0u16)) .await .expect("failed to bind mock server"); let mock_addr = listener.local_addr().unwrap(); let mock_base = format!("http://[::1]:{}", mock_addr.port()); - let stopper = trillium_http::Stopper::new(); - let config_for_server = trillium_tokio::config() - .without_signals() - .with_stopper(stopper) - .with_prebound_server(listener); - tokio::spawn(config_for_server.run_async(api_mocks)); + tokio::spawn(async move { + serve(listener, mock_router) + .await + .expect("mock server error"); + }); - let reqwest_client = reqwest::Client::builder() + let reqwest_client = Client::builder() .no_proxy() .build() .expect("failed to build reqwest client"); @@ -138,27 +161,68 @@ pub async fn config(api_mocks: impl Handler) -> Config { } } -pub async fn with_db(f: F) -> Out -where - F: FnOnce(Db) -> Fut, - Fut: Future, - Out: Termination, -{ - block_on(async move { - let db = Db::connect("sqlite::memory").await; - set_up_schema(&db).await; - f(db).await - }) +#[derive(Debug)] +pub struct DivviupApi { + router: Router, + db: Db, + config: Arc, +} + +impl DivviupApi { + pub fn router(&self) -> &Router { + &self.router + } + + pub fn db(&self) -> &Db { + &self.db + } + + pub fn config(&self) -> &Config { + &self.config + } + + pub fn crypter(&self) -> &Crypter { + &self.config.crypter + } +} + +impl From<&DivviupApi> for Queue { + fn from(app: &DivviupApi) -> Self { + Self::new(app.db(), app.config(), CancellationToken::new()) + } +} + +impl AsRef for DivviupApi { + fn as_ref(&self) -> &Db { + &self.db + } } pub async fn build_test_app() -> (DivviupApi, ClientLogs) { install_test_trace_subscriber(); let api_mocks = ApiMocks::new(); let client_logs = api_mocks.client_logs(); - let mut app = DivviupApi::new(config(api_mocks).await).await; - set_up_schema(app.db()).await; - let mut info = "testing".into(); - app.init(&mut info).await; + let BuiltApp { router, db, config } = + divviup_api::build_app(config(api_mocks.into_router()).await).await; + set_up_schema(&db).await; + let app = DivviupApi { router, db, config }; + (app, client_logs) +} + +/// Build a test app using a custom mock `Router` instead of the default [`ApiMocks`]. +/// The provided router is wrapped with client-log middleware so all outbound +/// aggregator API requests are captured in the returned [`ClientLogs`]. +pub async fn build_test_app_with_mock(mock: Router) -> (DivviupApi, ClientLogs) { + install_test_trace_subscriber(); + let client_logs = ClientLogs::default(); + let mock_with_logs = mock.layer(from_fn_with_state( + client_logs.clone(), + client_logs::client_logs_middleware, + )); + let BuiltApp { router, db, config } = + divviup_api::build_app(config(mock_with_logs).await).await; + set_up_schema(&db).await; + let app = DivviupApi { router, db, config }; (app, client_logs) } @@ -178,7 +242,7 @@ where pub fn with_client_logs(f: F) -> Out where F: FnOnce(DivviupApi, ClientLogs) -> Fut, - Fut: Future + Send + 'static, + Fut: Future, Out: Termination, { block_on(async move { @@ -187,99 +251,307 @@ where }) } +/// Create a single-threaded tokio runtime and run `future` to completion. +/// The `test_harness::test` macro calls harness functions (e.g. `set_up`) +/// from a synchronous context, so this is the async entry point for tests. +pub fn block_on, T>(future: F) -> T { + runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap() + .block_on(future) +} + pub const APP_CONTENT_TYPE: &str = "application/vnd.divviup+json;version=0.1"; -#[macro_export] -macro_rules! assert_not_found { - ($conn:expr) => { - assert_eq!($conn.status().unwrap_or(Status::NotFound), Status::NotFound); - assert_eq!($conn.take_response_body_string().unwrap_or_default(), ""); - }; +#[derive(Debug)] +pub struct TestRequest { + method: Method, + uri: String, + headers: HeaderMap, + body: Vec, + user: Option, } -#[trillium::async_trait] -pub trait TestingJsonExt { - async fn response_json(&mut self) -> T; - fn with_request_json(self, t: T) -> Self; +pub fn get(path: impl Into) -> TestRequest { + TestRequest::new(Method::GET, path) } -#[trillium::async_trait] -impl TestingJsonExt for TestConn { - async fn response_json(&mut self) -> T { - assert_eq!( - self.response_headers() - .get_str(KnownHeaderName::ContentType) - .unwrap(), - APP_CONTENT_TYPE - ); +pub fn post(path: impl Into) -> TestRequest { + TestRequest::new(Method::POST, path) +} - let body = self - .take_response_body() - .expect("no body was set") - .into_bytes() - .await - .expect("could not read body"); +pub fn put(path: impl Into) -> TestRequest { + TestRequest::new(Method::PUT, path) +} + +pub fn patch(path: impl Into) -> TestRequest { + TestRequest::new(Method::PATCH, path) +} + +pub fn delete(path: impl Into) -> TestRequest { + TestRequest::new(Method::DELETE, path) +} + +impl TestRequest { + fn new(method: Method, uri: impl Into) -> Self { + Self { + method, + uri: uri.into(), + headers: HeaderMap::new(), + body: Vec::new(), + user: None, + } + } + + pub fn with_request_header(mut self, name: N, value: V) -> Self + where + N: TryInto, + N::Error: std::fmt::Debug, + V: TryInto, + V::Error: std::fmt::Debug, + { + let name = name.try_into().expect("invalid header name"); + let value = value.try_into().expect("invalid header value"); + self.headers.insert(name, value); + self + } - serde_json::from_slice(&body).expect("could not deserialize body") + pub fn with_request_body(mut self, body: impl Into) -> Self { + self.body = body.into().into_bytes(); + self } - fn with_request_json(self, t: T) -> Self { + pub fn with_request_json(self, t: T) -> Self { self.with_request_body(serde_json::to_string(&t).unwrap()) } + + pub fn with_api_host(self) -> Self { + self.with_request_header(header::HOST, "api.example") + } + + pub fn with_app_host(self) -> Self { + self.with_request_header(header::HOST, "app.example") + } + + pub fn with_api_headers(self) -> Self { + (if self.method == Method::GET { + self + } else { + self.with_request_header(header::CONTENT_TYPE, APP_CONTENT_TYPE) + }) + .with_request_header(header::ACCEPT, APP_CONTENT_TYPE) + .with_api_host() + } + + pub fn with_auth_header(self, token: HeaderValue) -> Self { + self.with_request_header(header::AUTHORIZATION, token) + } + + pub fn with_user(mut self, user: &User) -> Self { + self.user = Some(user.clone()); + self + } + + /// In Part 10, refactor all callers from `.with_state(user)` to `.with_user(&user)`. + pub fn with_state(self, user: User) -> Self { + self.with_user(&user) + } + + pub fn method(&self) -> Method { + self.method.clone() + } + + pub async fn run_async(self, app: &DivviupApi) -> TestResponse { + let body = if self.body.is_empty() { + Body::empty() + } else { + Body::from(self.body) + }; + + let mut builder = Request::builder().method(self.method).uri(&self.uri); + for (name, value) in &self.headers { + builder = builder.header(name, value); + } + let mut request = builder.body(body).expect("failed to build request"); + if let Some(user) = self.user { + request.extensions_mut().insert(user); + } + let response = app + .router + .clone() + .oneshot(request) + .await + .expect("oneshot request failed"); + TestResponse::from_response(response).await + } } -pub trait TestExt { - fn with_api_headers(self) -> Self; - fn with_api_host(self) -> Self; - fn with_app_host(self) -> Self; - fn with_auth_header(self, token: HeaderValue) -> Self; - fn with_user(self, user: &User) -> Self; +#[derive(Debug)] +pub struct TestResponse { + status: StatusCode, + headers: HeaderMap, + body: bytes::Bytes, } -impl TestExt for TestConn { - fn with_api_host(self) -> Self { - self.with_request_header(KnownHeaderName::Host, "api.example") - .secure() +impl TestResponse { + async fn from_response(response: Response) -> Self { + let status = response.status(); + let headers = response.headers().clone(); + let body = response + .into_body() + .collect() + .await + .expect("failed to read response body") + .to_bytes(); + Self { + status, + headers, + body, + } + } + + pub fn status(&self) -> StatusCode { + self.status } - fn with_app_host(self) -> Self { - self.with_request_header(KnownHeaderName::Host, "app.example") - .secure() + pub fn response_headers(&self) -> &HeaderMap { + &self.headers } - fn with_api_headers(self) -> Self { - if self.method() == Method::Get { - self + pub fn response_json(&self) -> T { + assert_eq!( + self.headers + .get(header::CONTENT_TYPE) + .and_then(|v| v.to_str().ok()), + Some(APP_CONTENT_TYPE), + "expected Content-Type {APP_CONTENT_TYPE}" + ); + + serde_json::from_slice(&self.body).expect("could not deserialize response body") + } + + pub fn response_body_string(&self) -> Option { + if self.body.is_empty() { + None } else { - self.with_request_header(KnownHeaderName::ContentType, APP_CONTENT_TYPE) + Some(String::from_utf8(self.body.to_vec()).expect("could not decode body as UTF-8")) } - .with_request_header(KnownHeaderName::Accept, APP_CONTENT_TYPE) - .with_api_host() } - fn with_auth_header(self, token: HeaderValue) -> Self { - self.with_request_header(KnownHeaderName::Authorization, token) + pub fn header_str(&self, name: impl AsRef) -> Option<&str> { + self.headers + .get(name.as_ref()) + .and_then(|v| v.to_str().ok()) } +} - /// Simulate an authenticated session for routes that have migrated to the - /// Axum side. The header is read by an axum middleware gated on the - /// `test-header-injection` feature; it is not forwarded to or honored by - /// anything in production builds. - fn with_user(self, user: &User) -> Self { - self.with_request_header( - "X-Integration-Testing-User", - serde_json::to_string(user).unwrap(), - ) - } +// These are reimplementations of the Trillium assertion macros atop Axum. In Part 10 +// we can decide whether to keep these or refactor the tests, but they're used heavily. + +#[macro_export] +macro_rules! assert_ok { + ($resp:expr) => {{ + let ref __resp = $resp; + assert_eq!(__resp.status(), ::axum::http::StatusCode::OK); + }}; + + ($resp:expr, $body:expr $(, $header_name:expr => $header_val:expr)*) => {{ + let ref __resp = $resp; + assert_eq!(__resp.status(), ::axum::http::StatusCode::OK); + assert_eq!( + __resp.response_body_string().unwrap_or_default(), + $body + ); + $( + assert_eq!( + __resp.header_str($header_name), + Some($header_val), + concat!("expected header ", stringify!($header_name)), + ); + )* + }}; +} + +#[macro_export] +macro_rules! assert_not_found { + ($resp:expr) => {{ + let ref __resp = $resp; + assert_eq!(__resp.status(), ::axum::http::StatusCode::NOT_FOUND); + assert_eq!(__resp.response_body_string().unwrap_or_default(), ""); + }}; +} + +#[macro_export] +macro_rules! assert_response { + ($resp:expr, $status:expr) => {{ + let ref __resp = $resp; + let expected = $crate::IntoTestStatus::into_status($status); + assert_eq!(__resp.status(), expected); + }}; + + ($resp:expr, $status:expr, $body:expr $(, $header_name:expr => $header_val:expr)*) => {{ + let ref __resp = $resp; + let expected = $crate::IntoTestStatus::into_status($status); + assert_eq!(__resp.status(), expected); + assert_eq!( + __resp.response_body_string().unwrap_or_default(), + $body + ); + $( + assert_eq!( + __resp.header_str($header_name), + Some($header_val), + concat!("expected header ", stringify!($header_name)), + ); + )* + }}; +} + +#[macro_export] +macro_rules! assert_status { + ($resp:expr, $status:expr) => {{ + let ref __resp = $resp; + let expected = $crate::IntoTestStatus::into_status($status); + assert_eq!(__resp.status(), expected); + }}; +} + +#[macro_export] +macro_rules! assert_body_contains { + ($resp:expr, $pattern:expr) => {{ + let ref __resp = $resp; + let body = __resp.response_body_string().unwrap_or_default(); + assert!( + body.contains($pattern), + "expected body to contain {:?}, got {:?}", + $pattern, + body + ); + }}; +} + +#[macro_export] +macro_rules! assert_headers { + ($resp:expr, $($header_name:expr => $header_val:expr),+ $(,)?) => {{ + let ref __resp = $resp; + $( + assert_eq!( + __resp.header_str($header_name), + Some($header_val), + concat!("expected header ", stringify!($header_name)), + ); + )+ + }}; } -#[trillium::async_trait] +#[async_trait::async_trait] pub trait Reload: Sized { async fn reload(&self, db: &impl ConnectionTrait) -> Result, DbErr>; } macro_rules! impl_reload { ($model:ty, $entity:ty) => { - #[trillium::async_trait] + #[async_trait::async_trait] impl Reload for $model { async fn reload(&self, db: &impl ConnectionTrait) -> Result, DbErr> { <$entity>::find_by_id(self.id.clone()).one(db).await diff --git a/tests/integration/accounts.rs b/tests/integration/accounts.rs index 011b764b..a35d95c8 100644 --- a/tests/integration/accounts.rs +++ b/tests/integration/accounts.rs @@ -7,14 +7,14 @@ mod index { let (user, account, ..) = fixtures::member(&app).await; let _other_account = fixtures::account(&app).await; - let mut conn = get("/api/accounts") + let resp = get("/api/accounts") .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_ok!(conn); - let accounts: Vec = conn.response_json().await; + assert_ok!(resp); + let accounts: Vec = resp.response_json(); assert_eq!(accounts, vec![account]); Ok(()) } @@ -23,13 +23,13 @@ mod index { async fn as_admin(app: DivviupApi) -> TestResult { let (user, account, ..) = fixtures::admin(&app).await; let other_account = fixtures::account(&app).await; - let mut conn = get("/api/accounts") + let resp = get("/api/accounts") .with_api_headers() .with_state(user) .run_async(&app) .await; - let accounts: Vec = conn.response_json().await; + let accounts: Vec = resp.response_json(); assert_eq!(accounts, vec![account, other_account]); Ok(()) } @@ -39,12 +39,12 @@ mod index { let account = fixtures::admin_account(&app).await; let other_account = fixtures::account(&app).await; let (_, header) = fixtures::api_token(&app, &account).await; - let mut conn = get("/api/accounts") + let resp = get("/api/accounts") .with_api_headers() - .with_request_header(KnownHeaderName::Authorization, header) + .with_request_header(headers::AUTHORIZATION, header) .run_async(&app) .await; - let accounts: Vec = conn.response_json().await; + let accounts: Vec = resp.response_json(); assert_eq!(accounts, vec![account, other_account]); Ok(()) @@ -55,12 +55,12 @@ mod index { let account = fixtures::account(&app).await; fixtures::account(&app).await; let (_, header) = fixtures::api_token(&app, &account).await; - let mut conn = get("/api/accounts") + let resp = get("/api/accounts") .with_api_headers() - .with_request_header(KnownHeaderName::Authorization, header) + .with_request_header(headers::AUTHORIZATION, header) .run_async(&app) .await; - let accounts: Vec = conn.response_json().await; + let accounts: Vec = resp.response_json(); assert_eq!(accounts, vec![account]); Ok(()) @@ -72,12 +72,12 @@ mod index { fixtures::account(&app).await; let (token, header) = fixtures::api_token(&app, &account).await; token.delete(app.db()).await?; - let conn = get("/api/accounts") + let resp = get("/api/accounts") .with_api_headers() - .with_request_header(KnownHeaderName::Authorization, header) + .with_request_header(headers::AUTHORIZATION, header) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } } @@ -88,14 +88,14 @@ mod show { #[test(harness = set_up)] async fn as_a_member(app: DivviupApi) -> TestResult { let (user, account, ..) = fixtures::member(&app).await; - let mut conn = get(format!("/api/accounts/{}", account.id)) + let resp = get(format!("/api/accounts/{}", account.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_ok!(conn); - let account_response: Account = conn.response_json().await; + assert_ok!(resp); + let account_response: Account = resp.response_json(); assert_eq!(account_response, account); Ok(()) @@ -105,13 +105,13 @@ mod show { async fn not_as_a_member(app: DivviupApi) -> TestResult { let (user, ..) = fixtures::member(&app).await; let other_account = fixtures::account(&app).await; - let conn = get(format!("/api/accounts/{}", other_account.id)) + let resp = get(format!("/api/accounts/{}", other_account.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } @@ -121,14 +121,14 @@ mod show { let (user, ..) = fixtures::admin(&app).await; let other_account = fixtures::account(&app).await; - let mut conn = get(format!("/api/accounts/{}", other_account.id)) + let resp = get(format!("/api/accounts/{}", other_account.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_ok!(conn); - let account: Account = conn.response_json().await; + assert_ok!(resp); + let account: Account = resp.response_json(); assert_eq!(account, other_account); @@ -140,12 +140,12 @@ mod show { let account = fixtures::admin_account(&app).await; let other_account = fixtures::account(&app).await; let (_, header) = fixtures::api_token(&app, &account).await; - let mut conn = get(format!("/api/accounts/{}", other_account.id)) + let resp = get(format!("/api/accounts/{}", other_account.id)) .with_api_headers() - .with_request_header(KnownHeaderName::Authorization, header) + .with_request_header(headers::AUTHORIZATION, header) .run_async(&app) .await; - let response: Account = conn.response_json().await; + let response: Account = resp.response_json(); assert_eq!(response, other_account); Ok(()) @@ -156,13 +156,13 @@ mod show { let account = fixtures::account(&app).await; fixtures::account(&app).await; let (_, header) = fixtures::api_token(&app, &account).await; - let mut conn = get(format!("/api/accounts/{}", account.id)) + let resp = get(format!("/api/accounts/{}", account.id)) .with_api_headers() - .with_request_header(KnownHeaderName::Authorization, header) + .with_request_header(headers::AUTHORIZATION, header) .run_async(&app) .await; - let response: Account = conn.response_json().await; + let response: Account = resp.response_json(); assert_eq!(response, account); Ok(()) } @@ -172,12 +172,12 @@ mod show { let account = fixtures::account(&app).await; let other_account = fixtures::account(&app).await; let (_, header) = fixtures::api_token(&app, &account).await; - let conn = get(format!("/api/accounts/{}", other_account.id)) + let resp = get(format!("/api/accounts/{}", other_account.id)) .with_api_headers() - .with_request_header(KnownHeaderName::Authorization, header) + .with_request_header(headers::AUTHORIZATION, header) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } } @@ -187,13 +187,13 @@ mod create { #[test(harness = set_up)] async fn not_logged_in(app: DivviupApi) -> TestResult { - let conn = post("/api/accounts") + let resp = post("/api/accounts") .with_api_headers() .with_request_json(json!({ "name": "some account name" })) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); let accounts = Accounts::find().all(app.db()).await?; assert_eq!(accounts.len(), 0); let memberships = Memberships::find().all(app.db()).await?; @@ -205,14 +205,14 @@ mod create { #[test(harness = set_up)] async fn valid(app: DivviupApi) -> TestResult { let user = fixtures::user(); - let mut conn = post("/api/accounts") + let resp = post("/api/accounts") .with_api_headers() .with_state(user.clone()) .with_request_json(json!({ "name": "some account name" })) .run_async(&app) .await; - assert_response!(conn, 202); - let account: Account = conn.response_json().await; + assert_response!(resp, 202); + let account: Account = resp.response_json(); assert_eq!(account.name, "some account name"); let accounts = Accounts::find().all(app.db()).await?; @@ -230,15 +230,15 @@ mod create { #[test(harness = set_up)] async fn invalid(app: DivviupApi) -> TestResult { let user = fixtures::user(); - let mut conn = post("/api/accounts") + let resp = post("/api/accounts") .with_api_headers() .with_state(user.clone()) .with_request_json(json!({ "name": "" })) .run_async(&app) .await; - assert_response!(conn, 400); - let errors: Value = conn.response_json().await; + assert_response!(resp, 400); + let errors: Value = resp.response_json(); assert!(errors.get("name").is_some()); let accounts = Accounts::find().all(app.db()).await?; assert_eq!(accounts.len(), 0); @@ -251,14 +251,14 @@ mod create { async fn admin_token(app: DivviupApi) -> TestResult { let token = fixtures::admin_token(&app).await; let name = fixtures::random_name(); - let mut conn = post("/api/accounts") + let resp = post("/api/accounts") .with_api_headers() .with_auth_header(token) .with_request_json(json!({ "name": name })) .run_async(&app) .await; - assert_response!(conn, 202); - let account: Account = conn.response_json().await; + assert_response!(resp, 202); + let account: Account = resp.response_json(); assert_eq!(&account.name, &name); assert_eq!(account.reload(app.db()).await?.unwrap().name, name); @@ -270,14 +270,14 @@ mod create { let account = fixtures::account(&app).await; let (_, token) = fixtures::api_token(&app, &account).await; let name = fixtures::random_name(); - let conn = post("/api/accounts") + let resp = post("/api/accounts") .with_api_headers() .with_auth_header(token) .with_request_json(json!({ "name": name })) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } @@ -290,15 +290,15 @@ mod update { async fn as_a_member(app: DivviupApi) -> TestResult { let (user, account, ..) = fixtures::member(&app).await; - let mut conn = patch(format!("/api/accounts/{}", account.id)) + let resp = patch(format!("/api/accounts/{}", account.id)) .with_api_headers() .with_request_json(json!({ "name": "new name" })) .with_state(user) .run_async(&app) .await; - assert_response!(conn, 202); - let response: Account = conn.response_json().await; + assert_response!(resp, 202); + let response: Account = resp.response_json(); assert_eq!(&response.name, "new name"); assert_eq!(account.reload(app.db()).await?.unwrap().name, "new name"); @@ -309,14 +309,14 @@ mod update { async fn not_as_a_member(app: DivviupApi) -> TestResult { let (user, ..) = fixtures::member(&app).await; let other_account = fixtures::account(&app).await; - let conn = patch(format!("/api/accounts/{}", other_account.id)) + let resp = patch(format!("/api/accounts/{}", other_account.id)) .with_api_headers() .with_request_json(json!({ "name": "new name" })) .with_state(user) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } @@ -326,15 +326,15 @@ mod update { let (user, ..) = fixtures::admin(&app).await; let other_account = fixtures::account(&app).await; - let mut conn = patch(format!("/api/accounts/{}", other_account.id)) + let resp = patch(format!("/api/accounts/{}", other_account.id)) .with_api_headers() .with_request_json(json!({ "name": "new name" })) .with_state(user) .run_async(&app) .await; - assert_response!(conn, 202); - let account: Account = conn.response_json().await; + assert_response!(resp, 202); + let account: Account = resp.response_json(); assert_eq!(&account.name, "new name"); assert_eq!(account.reload(app.db()).await?.unwrap().name, "new name"); @@ -345,15 +345,15 @@ mod update { #[test(harness = set_up)] async fn invalid(app: DivviupApi) -> TestResult { let (user, account, ..) = fixtures::member(&app).await; - let mut conn = patch(format!("/api/accounts/{}", account.id)) + let resp = patch(format!("/api/accounts/{}", account.id)) .with_api_headers() .with_request_json(json!({ "name": "" })) .with_state(user) .run_async(&app) .await; - assert_response!(conn, 400); - let errors: Value = conn.response_json().await; + assert_response!(resp, 400); + let errors: Value = resp.response_json(); assert!(errors.get("name").is_some()); Ok(()) @@ -364,15 +364,15 @@ mod update { let account = fixtures::account(&app).await; let (_, header) = fixtures::api_token(&app, &account).await; let name = fixtures::random_name(); - let mut conn = patch(format!("/api/accounts/{}", account.id)) + let resp = patch(format!("/api/accounts/{}", account.id)) .with_api_headers() - .with_request_header(KnownHeaderName::Authorization, header) + .with_request_header(headers::AUTHORIZATION, header) .with_request_json(json!({ "name": &name })) .run_async(&app) .await; - assert_response!(conn, 202); - let response: Account = conn.response_json().await; + assert_response!(resp, 202); + let response: Account = resp.response_json(); assert_eq!(&response.name, &name); assert_eq!(account.reload(app.db()).await?.unwrap().name, name); @@ -386,14 +386,14 @@ mod update { let other_account = fixtures::account(&app).await; let (_, header) = fixtures::api_token(&app, &account).await; let name = fixtures::random_name(); - let conn = patch(format!("/api/accounts/{}", other_account.id)) + let resp = patch(format!("/api/accounts/{}", other_account.id)) .with_api_headers() - .with_request_header(KnownHeaderName::Authorization, header) + .with_request_header(headers::AUTHORIZATION, header) .with_request_json(json!({ "name": &name })) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } @@ -403,15 +403,15 @@ mod update { let other_account = fixtures::account(&app).await; let (_, header) = fixtures::api_token(&app, &account).await; let name = fixtures::random_name(); - let mut conn = patch(format!("/api/accounts/{}", other_account.id)) + let resp = patch(format!("/api/accounts/{}", other_account.id)) .with_api_headers() - .with_request_header(KnownHeaderName::Authorization, header) + .with_request_header(headers::AUTHORIZATION, header) .with_request_json(json!({ "name": &name })) .run_async(&app) .await; - assert_response!(conn, 202); - let response: Account = conn.response_json().await; + assert_response!(resp, 202); + let response: Account = resp.response_json(); assert_eq!(&response.name, &name); assert_eq!(other_account.reload(app.db()).await?.unwrap().name, name); diff --git a/tests/integration/admin_queue.rs b/tests/integration/admin_queue.rs index 5a2d7fdf..a1ca3564 100644 --- a/tests/integration/admin_queue.rs +++ b/tests/integration/admin_queue.rs @@ -10,16 +10,13 @@ mod index { let queue_item = Job::new_invitation_flow(&fixtures::build_membership(&app).await) .insert(app.db()) .await?; - let mut conn = get("/api/admin/queue") + let resp = get("/api/admin/queue") .with_api_headers() .with_state(admin) .run_async(&app) .await; - assert_ok!(conn); - assert_eq!( - conn.response_json::>().await, - vec![queue_item] - ); + assert_ok!(resp); + assert_eq!(resp.response_json::>(), vec![queue_item]); Ok(()) } @@ -31,13 +28,13 @@ mod index { .insert(app.db()) .await?; - let conn = get("/api/admin/queue") + let resp = get("/api/admin/queue") .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_status!(conn, 404); + assert_status!(resp, 404); Ok(()) } @@ -55,24 +52,21 @@ mod index { .with_state(admin.clone()) .run_async(&app) .await - .response_json() - .await; + .response_json(); let pending: Vec = get("/api/admin/queue?status=pending") .with_api_headers() .with_state(admin.clone()) .run_async(&app) .await - .response_json() - .await; + .response_json(); let failed: Vec = get("/api/admin/queue?status=failed") .with_api_headers() .with_state(admin) .run_async(&app) .await - .response_json() - .await; + .response_json(); assert!(success.iter().all(|item| item.status == JobStatus::Success)); assert_eq!(success.len(), 1); diff --git a/tests/integration/aggregator_client.rs b/tests/integration/aggregator_client.rs index db731445..b3ef20be 100644 --- a/tests/integration/aggregator_client.rs +++ b/tests/integration/aggregator_client.rs @@ -12,19 +12,17 @@ async fn get_task_ids(app: DivviupApi, client_logs: ClientLogs) -> TestResult { assert_eq!(task_ids.len(), 25); // two pages of 10 plus a final page of 5 let logs = client_logs.logs(); - assert!(logs.iter().all(|log| { - log.request_headers - .get_str(KnownHeaderName::Accept) - .unwrap() - == "application/vnd.janus.aggregator+json;version=0.1" - })); - - assert!(logs.iter().all(|log| { - log.request_headers - .get_str(KnownHeaderName::Authorization) - .unwrap() - == format!("Bearer {}", aggregator.bearer_token(app.crypter()).unwrap()) - })); + let expected_token = format!("Bearer {}", aggregator.bearer_token(app.crypter()).unwrap()); + for log in &logs { + assert_eq!( + log.request_headers.get(headers::ACCEPT).unwrap(), + "application/vnd.janus.aggregator+json;version=0.1" + ); + assert_eq!( + log.request_headers.get(headers::AUTHORIZATION).unwrap(), + expected_token.as_str() + ); + } let queries = logs.iter().map(|log| log.url.query()).collect::>(); assert_eq!( @@ -48,13 +46,17 @@ async fn get_task_metrics(app: DivviupApi, client_logs: ClientLogs) -> TestResul let log = client_logs.last(); assert_eq!( log.request_headers - .get_str(KnownHeaderName::Accept) + .get(headers::ACCEPT) + .unwrap() + .to_str() .unwrap(), "application/vnd.janus.aggregator+json;version=0.1" ); assert_eq!( log.request_headers - .get_str(KnownHeaderName::Authorization) + .get(headers::AUTHORIZATION) + .unwrap() + .to_str() .unwrap(), &format!("Bearer {}", aggregator.bearer_token(app.crypter()).unwrap()) ); @@ -81,13 +83,17 @@ async fn get_config(app: DivviupApi, client_logs: ClientLogs) -> TestResult { let log = client_logs.last(); assert_eq!( log.request_headers - .get_str(KnownHeaderName::Accept) + .get(headers::ACCEPT) + .unwrap() + .to_str() .unwrap(), "application/vnd.janus.aggregator+json;version=0.1" ); assert_eq!( log.request_headers - .get_str(KnownHeaderName::Authorization) + .get(headers::AUTHORIZATION) + .unwrap() + .to_str() .unwrap(), "Bearer token" ); @@ -109,8 +115,9 @@ async fn get_config_bad_token(app: DivviupApi) -> TestResult { } mod prefixes { - use divviup_api::{clients::aggregator_client::TaskUploadMetrics, handler::origin_router}; - use trillium_router::router; + use divviup_api::clients::aggregator_client::TaskUploadMetrics; + + use axum::Router; use super::{assert_eq, test, *}; @@ -120,23 +127,15 @@ mod prefixes { Fut: std::future::Future>> + Send + 'static, { block_on(async move { - let client_logs = ClientLogs::default(); let prefix = fixtures::random_name(); + let mock = Router::new().nest(&format!("/{prefix}"), aggregator_api::mock()); + let (app, client_logs) = build_test_app_with_mock(mock).await; + let api_url = Url::parse(&format!( - "https://api.{}.divviup.org/{prefix}", + "https://api.{}.divviup.org/{prefix}/", fixtures::random_name() )) .unwrap(); - let api_mocks = ( - trillium_logger::logger(), - client_logs.clone(), - origin_router().with_handler( - &api_url.origin().ascii_serialization(), - router().all(format!("/{prefix}/*"), aggregator_api::mock()), - ), - ); - let mut app = DivviupApi::new(config(api_mocks).await).await; - set_up_schema(app.db()).await; let mut aggregator = fixtures::aggregator(&app, None).await.into_active_model(); aggregator.encrypted_bearer_token = ActiveValue::Set( app.crypter() @@ -148,8 +147,6 @@ mod prefixes { ); aggregator.api_url = ActiveValue::Set(api_url.into()); let aggregator = aggregator.update(app.db()).await.unwrap(); - let mut info = "testing".into(); - app.init(&mut info).await; f(app, client_logs, aggregator).await }) .unwrap() @@ -171,9 +168,9 @@ mod prefixes { .map(|l| l.url.as_ref()) .collect::>(), vec![ - format!("{}/task_ids", aggregator.api_url), - format!("{}/task_ids?pagination_token=second", aggregator.api_url), - format!("{}/task_ids?pagination_token=last", aggregator.api_url) + format!("{}task_ids", aggregator.api_url), + format!("{}task_ids?pagination_token=second", aggregator.api_url), + format!("{}task_ids?pagination_token=last", aggregator.api_url) ] ); assert_eq!(client_logs.logs().len(), 3); @@ -193,10 +190,10 @@ mod prefixes { client_logs.last().response_json::(), metrics ); - assert_eq!(client_logs.last().method, Method::Get); + assert_eq!(client_logs.last().method, Method::GET); assert_eq!( client_logs.last().url.as_ref(), - format!("{}/tasks/fake-task-id/metrics/uploads", aggregator.api_url) + format!("{}tasks/fake-task-id/metrics/uploads", aggregator.api_url) ); assert_eq!(client_logs.logs().len(), 1); Ok(()) diff --git a/tests/integration/aggregators.rs b/tests/integration/aggregators.rs index 7a5a1fdb..307bc769 100644 --- a/tests/integration/aggregators.rs +++ b/tests/integration/aggregators.rs @@ -15,14 +15,14 @@ mod index { let aggregator1 = fixtures::aggregator(&app, Some(&account)).await; let aggregator2 = fixtures::aggregator(&app, Some(&account)).await; - let mut conn = get(format!("/api/accounts/{}/aggregators", account.id)) + let resp = get(format!("/api/accounts/{}/aggregators", account.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_ok!(conn); - let aggregators: Vec = conn.response_json().await; + assert_ok!(resp); + let aggregators: Vec = resp.response_json(); assert_same_json_representation( &aggregators, &vec![shared_aggregator, aggregator1, aggregator2], @@ -40,14 +40,14 @@ mod index { aggregator1.tombstone().update(app.db()).await?; let aggregator2 = fixtures::aggregator(&app, Some(&account)).await; - let mut conn = get(format!("/api/accounts/{}/aggregators", account.id)) + let resp = get(format!("/api/accounts/{}/aggregators", account.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_ok!(conn); - let aggregators: Vec = conn.response_json().await; + assert_ok!(resp); + let aggregators: Vec = resp.response_json(); assert_same_json_representation(&aggregators, &vec![aggregator2]); Ok(()) } @@ -62,14 +62,14 @@ mod index { aggregator1.tombstone().update(app.db()).await?; let aggregator2 = fixtures::aggregator(&app, Some(&account)).await; - let mut conn = get(format!("/api/accounts/{}/aggregators", account.id)) + let resp = get(format!("/api/accounts/{}/aggregators", account.id)) .with_api_headers() .with_state(admin) .run_async(&app) .await; - assert_ok!(conn); - let aggregators: Vec = conn.response_json().await; + assert_ok!(resp); + let aggregators: Vec = resp.response_json(); assert_same_json_representation(&vec![aggregator2], &aggregators); Ok(()) } @@ -83,13 +83,13 @@ mod index { fixtures::aggregator(&app, Some(&account)).await; fixtures::aggregator(&app, Some(&account)).await; - let conn = get(format!("/api/accounts/{}/aggregators", account.id)) + let resp = get(format!("/api/accounts/{}/aggregators", account.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } @@ -103,13 +103,13 @@ mod index { fixtures::aggregator(&app, Some(&account)).await; fixtures::aggregator(&app, Some(&account)).await; - let mut conn = get("/api/accounts/not-an-account/aggregators") + let resp = get("/api/accounts/not-an-account/aggregators") .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_not_found!(conn); + assert_not_found!(resp); Ok(()) } @@ -123,14 +123,14 @@ mod index { let aggregator1 = fixtures::aggregator(&app, Some(&account)).await; let aggregator2 = fixtures::aggregator(&app, Some(&account)).await; - let mut conn = get(format!("/api/accounts/{}/aggregators", account.id)) + let resp = get(format!("/api/accounts/{}/aggregators", account.id)) .with_api_headers() .with_state(admin) .run_async(&app) .await; - assert_ok!(conn); - let aggregators: Vec = conn.response_json().await; + assert_ok!(resp); + let aggregators: Vec = resp.response_json(); assert_same_json_representation( &aggregators, &vec![shared_aggregator, aggregator1, aggregator2], @@ -149,14 +149,14 @@ mod index { let aggregator1 = fixtures::aggregator(&app, Some(&account)).await; let aggregator2 = fixtures::aggregator(&app, Some(&account)).await; - let mut conn = get(format!("/api/accounts/{}/aggregators", account.id)) + let resp = get(format!("/api/accounts/{}/aggregators", account.id)) .with_api_headers() - .with_request_header(KnownHeaderName::Authorization, token) + .with_request_header(headers::AUTHORIZATION, token) .run_async(&app) .await; - assert_ok!(conn); - let aggregators: Vec = conn.response_json().await; + assert_ok!(resp); + let aggregators: Vec = resp.response_json(); assert_same_json_representation( &aggregators, &vec![shared_aggregator, aggregator1, aggregator2], @@ -177,14 +177,14 @@ mod index { let admin_token = fixtures::admin_token(&app).await; - let mut conn = get(format!("/api/accounts/{}/aggregators", account.id)) + let resp = get(format!("/api/accounts/{}/aggregators", account.id)) .with_api_headers() - .with_request_header(KnownHeaderName::Authorization, admin_token) + .with_request_header(headers::AUTHORIZATION, admin_token) .run_async(&app) .await; - assert_ok!(conn); - let aggregators: Vec = conn.response_json().await; + assert_ok!(resp); + let aggregators: Vec = resp.response_json(); assert_same_json_representation( &aggregators, &vec![shared_aggregator, aggregator1, aggregator2], @@ -204,13 +204,13 @@ mod index { fixtures::aggregator(&app, Some(&account)).await; fixtures::aggregator(&app, Some(&account)).await; - let conn = get(format!("/api/accounts/{}/aggregators", account.id)) + let resp = get(format!("/api/accounts/{}/aggregators", account.id)) .with_api_headers() - .with_request_header(KnownHeaderName::Authorization, token) + .with_request_header(headers::AUTHORIZATION, token) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } @@ -230,14 +230,14 @@ mod shared_aggregator_index { let (user, account, ..) = fixtures::member(&app).await; fixtures::aggregator(&app, Some(&account)).await; - let mut conn = get("/api/aggregators") + let resp = get("/api/aggregators") .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_ok!(conn); - let aggregators: Vec = conn.response_json().await; + assert_ok!(resp); + let aggregators: Vec = resp.response_json(); assert_same_json_representation( &aggregators, &vec![shared_aggregator1, shared_aggregator2], @@ -257,14 +257,14 @@ mod shared_aggregator_index { let (_, token) = fixtures::api_token(&app, &account).await; fixtures::aggregator(&app, Some(&account)).await; - let mut conn = get("/api/aggregators") + let resp = get("/api/aggregators") .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_ok!(conn); - let aggregators: Vec = conn.response_json().await; + assert_ok!(resp); + let aggregators: Vec = resp.response_json(); assert_same_json_representation( &aggregators, &vec![shared_aggregator1, shared_aggregator2], @@ -274,12 +274,12 @@ mod shared_aggregator_index { #[test(harness = set_up)] async fn not_logged_in(app: DivviupApi) -> TestResult { - let conn = get("/api/aggregators") + let resp = get("/api/aggregators") .with_api_headers() .run_async(&app) .await; - assert_status!(conn, 403); + assert_status!(resp, 403); Ok(()) } } @@ -294,14 +294,14 @@ mod create { let (user, account, ..) = fixtures::member(&app).await; let new_aggregator = fixtures::new_aggregator(); - let mut conn = post(format!("/api/accounts/{}/aggregators", account.id)) + let resp = post(format!("/api/accounts/{}/aggregators", account.id)) .with_api_headers() .with_state(user) .with_request_json(new_aggregator.clone()) .run_async(&app) .await; - assert_response!(conn, 201); - let aggregator: Aggregator = conn.response_json().await; + assert_response!(resp, 201); + let aggregator: Aggregator = resp.response_json(); let aggregator_config: AggregatorApiConfig = client_logs.last().response_json(); @@ -329,14 +329,14 @@ mod create { let mut new_aggregator = fixtures::new_aggregator(); new_aggregator.is_first_party = Some(true); - let mut conn = post(format!("/api/accounts/{}/aggregators", account.id)) + let resp = post(format!("/api/accounts/{}/aggregators", account.id)) .with_api_headers() .with_state(user) .with_request_json(new_aggregator) .run_async(&app) .await; - assert_response!(conn, 201); - let aggregator: Aggregator = conn.response_json().await; + assert_response!(resp, 201); + let aggregator: Aggregator = resp.response_json(); assert!(!aggregator.is_first_party); assert!(!aggregator.reload(app.db()).await?.unwrap().is_first_party); @@ -347,7 +347,7 @@ mod create { async fn invalid(app: DivviupApi) -> TestResult { let (user, account, ..) = fixtures::member(&app).await; - let mut conn = post(format!("/api/accounts/{}/aggregators", account.id)) + let resp = post(format!("/api/accounts/{}/aggregators", account.id)) .with_api_headers() .with_state(user) .with_request_json(json!({ @@ -357,8 +357,8 @@ mod create { .run_async(&app) .await; - assert_response!(conn, 400); - let error: Value = conn.response_json().await; + assert_response!(resp, 400); + let error: Value = resp.response_json(); assert!(error.get("name").is_some()); assert!(error.get("api_url").is_some()); Ok(()) @@ -370,14 +370,14 @@ mod create { let account = fixtures::account(&app).await; // no membership let aggregator_count_before = Aggregators::find().count(app.db()).await?; - let conn = post(format!("/api/accounts/{}/aggregators", account.id)) + let resp = post(format!("/api/accounts/{}/aggregators", account.id)) .with_api_headers() .with_state(user) .with_request_json(fixtures::new_aggregator()) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); let aggregator_count_after = Aggregators::find().count(app.db()).await?; assert_eq!(aggregator_count_before, aggregator_count_after); @@ -389,14 +389,14 @@ mod create { let user = fixtures::user(); let aggregator_count_before = Aggregators::find().count(app.db()).await?; - let mut conn = post("/api/accounts/does-not-exist/aggregators") + let resp = post("/api/accounts/does-not-exist/aggregators") .with_api_headers() .with_state(user) .with_request_json(fixtures::new_aggregator()) .run_async(&app) .await; - assert_not_found!(conn); + assert_not_found!(resp); let aggregator_count_after = Aggregators::find().count(app.db()).await?; assert_eq!(aggregator_count_before, aggregator_count_after); @@ -407,15 +407,15 @@ mod create { async fn admin_not_member(app: DivviupApi) -> TestResult { let (admin, ..) = fixtures::admin(&app).await; let account = fixtures::account(&app).await; - let mut conn = post(format!("/api/accounts/{}/aggregators", account.id)) + let resp = post(format!("/api/accounts/{}/aggregators", account.id)) .with_api_headers() .with_state(admin) .with_request_json(fixtures::new_aggregator()) .run_async(&app) .await; - assert_response!(conn, 201); - let aggregator: Aggregator = conn.response_json().await; + assert_response!(resp, 201); + let aggregator: Aggregator = resp.response_json(); assert!(!aggregator.is_first_party); let aggregator_from_db = aggregator.reload(app.db()).await?.unwrap(); @@ -428,15 +428,15 @@ mod create { async fn admin_token(app: DivviupApi) -> TestResult { let token = fixtures::admin_token(&app).await; let account = fixtures::account(&app).await; - let mut conn = post(format!("/api/accounts/{}/aggregators", account.id)) + let resp = post(format!("/api/accounts/{}/aggregators", account.id)) .with_api_headers() .with_auth_header(token) .with_request_json(fixtures::new_aggregator()) .run_async(&app) .await; - assert_response!(conn, 201); - let aggregator: Aggregator = conn.response_json().await; + assert_response!(resp, 201); + let aggregator: Aggregator = resp.response_json(); assert!(!aggregator.is_first_party); let aggregator_from_db = aggregator.reload(app.db()).await?.unwrap(); @@ -449,15 +449,15 @@ mod create { async fn member_token(app: DivviupApi) -> TestResult { let account = fixtures::account(&app).await; let (_, token) = fixtures::api_token(&app, &account).await; - let mut conn = post(format!("/api/accounts/{}/aggregators", account.id)) + let resp = post(format!("/api/accounts/{}/aggregators", account.id)) .with_api_headers() .with_auth_header(token) .with_request_json(fixtures::new_aggregator()) .run_async(&app) .await; - assert_response!(conn, 201); - let aggregator: Aggregator = conn.response_json().await; + assert_response!(resp, 201); + let aggregator: Aggregator = resp.response_json(); assert!(!aggregator.is_first_party); let aggregator_from_db = aggregator.reload(app.db()).await?.unwrap(); @@ -472,14 +472,14 @@ mod create { let account = fixtures::account(&app).await; let aggregator_count_before = Aggregators::find().count(app.db()).await?; - let conn = post(format!("/api/accounts/{}/aggregators", account.id)) + let resp = post(format!("/api/accounts/{}/aggregators", account.id)) .with_api_headers() .with_auth_header(token) .with_request_json(fixtures::new_aggregator()) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); let aggregator_count_after = Aggregators::find().count(app.db()).await?; assert_eq!(aggregator_count_before, aggregator_count_after); @@ -494,15 +494,15 @@ mod create { new_aggregator.bearer_token = Some(BAD_BEARER_TOKEN.to_string()); let aggregator_count_before = Aggregators::find().count(app.db()).await?; - let mut conn = post(format!("/api/accounts/{}/aggregators", account.id)) + let resp = post(format!("/api/accounts/{}/aggregators", account.id)) .with_api_headers() .with_state(user) .with_request_json(new_aggregator.clone()) .run_async(&app) .await; - assert_response!(conn, 400); - assert_eq!(client_logs.last().response_status, Status::Unauthorized); - let error: Value = conn.response_json().await; + assert_response!(resp, 400); + assert_eq!(client_logs.last().response_status, StatusCode::UNAUTHORIZED); + let error: Value = resp.response_json(); assert!(error.get("bearer_token").is_some()); let aggregator_count_after = Aggregators::find().count(app.db()).await?; assert_eq!(aggregator_count_before, aggregator_count_after); @@ -518,13 +518,13 @@ mod show { async fn as_member(app: DivviupApi) -> TestResult { let (user, account, ..) = fixtures::member(&app).await; let aggregator = fixtures::aggregator(&app, Some(&account)).await; - let mut conn = get(format!("/api/aggregators/{}", aggregator.id)) + let resp = get(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_ok!(conn); - let response_aggregator: Aggregator = conn.response_json().await; + assert_ok!(resp); + let response_aggregator: Aggregator = resp.response_json(); assert_same_json_representation(&response_aggregator, &aggregator); Ok(()) } @@ -533,13 +533,13 @@ mod show { async fn shared_aggregator(app: DivviupApi) -> TestResult { let (user, _account, ..) = fixtures::member(&app).await; let aggregator = fixtures::aggregator(&app, None).await; - let mut conn = get(format!("/api/aggregators/{}", aggregator.id)) + let resp = get(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_ok!(conn); - let response_aggregator: Aggregator = conn.response_json().await; + assert_ok!(resp); + let response_aggregator: Aggregator = resp.response_json(); assert_same_json_representation(&response_aggregator, &aggregator); Ok(()) } @@ -549,12 +549,12 @@ mod show { let user = fixtures::user(); let account = fixtures::account(&app).await; let aggregator = fixtures::aggregator(&app, Some(&account)).await; - let conn = get(format!("/api/aggregators/{}", aggregator.id)) + let resp = get(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } @@ -563,13 +563,13 @@ mod show { let (admin, ..) = fixtures::admin(&app).await; let account = fixtures::account(&app).await; let aggregator = fixtures::aggregator(&app, Some(&account)).await; - let mut conn = get(format!("/api/aggregators/{}", aggregator.id)) + let resp = get(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_state(admin) .run_async(&app) .await; - assert_ok!(conn); - let response_aggregator: Aggregator = conn.response_json().await; + assert_ok!(resp); + let response_aggregator: Aggregator = resp.response_json(); assert_same_json_representation(&response_aggregator, &aggregator); Ok(()) } @@ -577,12 +577,12 @@ mod show { #[test(harness = set_up)] async fn nonexistant_aggregator(app: DivviupApi) -> TestResult { let user = fixtures::user(); - let mut conn = get("/api/aggregators/some-made-up-id") + let resp = get("/api/aggregators/some-made-up-id") .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_not_found!(conn); + assert_not_found!(resp); Ok(()) } @@ -595,13 +595,13 @@ mod show { .update(app.db()) .await?; - let mut conn = get(format!("/api/aggregators/{}", aggregator.id)) + let resp = get(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_ok!(conn); - let response_aggregator: Aggregator = conn.response_json().await; + assert_ok!(resp); + let response_aggregator: Aggregator = resp.response_json(); assert_same_json_representation(&response_aggregator, &aggregator); Ok(()) } @@ -615,13 +615,13 @@ mod show { .update(app.db()) .await?; - let mut conn = get(format!("/api/aggregators/{}", aggregator.id)) + let resp = get(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_ok!(conn); - let response_aggregator: Aggregator = conn.response_json().await; + assert_ok!(resp); + let response_aggregator: Aggregator = resp.response_json(); assert_same_json_representation(&response_aggregator, &aggregator); Ok(()) } @@ -635,13 +635,13 @@ mod show { .tombstone() .update(app.db()) .await?; - let mut conn = get(format!("/api/aggregators/{}", aggregator.id)) + let resp = get(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_state(admin) .run_async(&app) .await; - assert_ok!(conn); - let response_aggregator: Aggregator = conn.response_json().await; + assert_ok!(resp); + let response_aggregator: Aggregator = resp.response_json(); assert_same_json_representation(&response_aggregator, &aggregator); Ok(()) } @@ -654,13 +654,13 @@ mod show { .tombstone() .update(app.db()) .await?; - let mut conn = get(format!("/api/aggregators/{}", aggregator.id)) + let resp = get(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_state(admin) .run_async(&app) .await; - assert_ok!(conn); - let response_aggregator: Aggregator = conn.response_json().await; + assert_ok!(resp); + let response_aggregator: Aggregator = resp.response_json(); assert_same_json_representation(&response_aggregator, &aggregator); Ok(()) } @@ -670,13 +670,13 @@ mod show { let token = fixtures::admin_token(&app).await; let account = fixtures::account(&app).await; let aggregator = fixtures::aggregator(&app, Some(&account)).await; - let mut conn = get(format!("/api/aggregators/{}", aggregator.id)) + let resp = get(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_ok!(conn); - let response: Aggregator = conn.response_json().await; + assert_ok!(resp); + let response: Aggregator = resp.response_json(); assert_same_json_representation(&aggregator, &response); Ok(()) } @@ -686,13 +686,13 @@ mod show { let account = fixtures::account(&app).await; let (_, token) = fixtures::api_token(&app, &account).await; let aggregator = fixtures::aggregator(&app, Some(&account)).await; - let mut conn = get(format!("/api/aggregators/{}", aggregator.id)) + let resp = get(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_ok!(conn); - let response: Aggregator = conn.response_json().await; + assert_ok!(resp); + let response: Aggregator = resp.response_json(); assert_same_json_representation(&aggregator, &response); Ok(()) } @@ -703,12 +703,12 @@ mod show { let (_, token) = fixtures::api_token(&app, &other_account).await; let account = fixtures::account(&app).await; let aggregator = fixtures::aggregator(&app, Some(&account)).await; - let conn = get(format!("/api/aggregators/{}", aggregator.id)) + let resp = get(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } } @@ -730,23 +730,25 @@ mod update { let new_name = format!("new name {}", fixtures::random_name()); let new_bearer_token = fixtures::random_name(); - let mut conn = patch(format!("/api/aggregators/{}", aggregator.id)) + let resp = patch(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_request_json(json!({ "name": &new_name, "bearer_token": &new_bearer_token })) .with_state(user) .run_async(&app) .await; - assert_ok!(conn); + assert_ok!(resp); assert_eq!(client_logs.logs().len(), 1); assert_eq!( client_logs .last() .request_headers - .get_str(KnownHeaderName::Authorization) + .get(headers::AUTHORIZATION) + .unwrap() + .to_str() .unwrap(), format!("Bearer {new_bearer_token}") ); - let response_aggregator: Aggregator = conn.response_json().await; + let response_aggregator: Aggregator = resp.response_json(); assert_eq!(response_aggregator.name, new_name); let reloaded = aggregator.reload(app.db()).await?.unwrap(); assert_eq!(reloaded.name, new_name); @@ -761,19 +763,21 @@ mod update { let aggregator = fixtures::aggregator(&app, Some(&account)).await; let original_bearer_token = aggregator.encrypted_bearer_token.clone(); - let mut conn = patch(format!("/api/aggregators/{}", aggregator.id)) + let resp = patch(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_request_json(json!({ "bearer_token": &BAD_BEARER_TOKEN })) .with_state(user) .run_async(&app) .await; - assert_status!(conn, 400); + assert_status!(resp, 400); assert_eq!(client_logs.logs().len(), 1); assert_eq!( client_logs .last() .request_headers - .get_str(KnownHeaderName::Authorization) + .get(headers::AUTHORIZATION) + .unwrap() + .to_str() .unwrap(), format!("Bearer {BAD_BEARER_TOKEN}") ); @@ -786,8 +790,8 @@ mod update { .encrypted_bearer_token, original_bearer_token ); - assert_eq!(client_logs.last().response_status, Status::Unauthorized); - let errors: Value = conn.response_json().await; + assert_eq!(client_logs.last().response_status, StatusCode::UNAUTHORIZED); + let errors: Value = resp.response_json(); assert!(errors.get("bearer_token").is_some()); Ok(()) @@ -798,14 +802,14 @@ mod update { let (user, account, ..) = fixtures::member(&app).await; let aggregator = fixtures::aggregator(&app, Some(&account)).await; - let mut conn = patch(format!("/api/aggregators/{}", aggregator.id)) + let resp = patch(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_request_json(json!({ "name": "" })) .with_state(user) .run_async(&app) .await; - assert_response!(conn, 400); - let errors: Value = conn.response_json().await; + assert_response!(resp, 400); + let errors: Value = resp.response_json(); assert!(errors.get("name").is_some()); assert_eq!( @@ -822,14 +826,14 @@ mod update { let account = fixtures::account(&app).await; let aggregator = fixtures::aggregator(&app, Some(&account)).await; - let conn = patch(format!("/api/aggregators/{}", aggregator.id)) + let resp = patch(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_request_json(json!({ "name": "irrelevant" })) .with_state(user) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); assert_eq!( aggregator.reload(app.db()).await?.unwrap().name, aggregator.name // unchanged @@ -842,14 +846,14 @@ mod update { let user = fixtures::user(); let aggregator = fixtures::aggregator(&app, None).await; let old_name = aggregator.name.clone(); - let conn = patch(format!("/api/aggregators/{}", aggregator.id)) + let resp = patch(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_request_json(json!({ "name": "irrelevant" })) .with_state(user) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); assert_eq!(aggregator.reload(app.db()).await?.unwrap().name, old_name); Ok(()) } @@ -860,15 +864,15 @@ mod update { let aggregator = fixtures::aggregator(&app, None).await; let new_name = format!("new name {}", fixtures::random_name()); - let mut conn = patch(format!("/api/aggregators/{}", aggregator.id)) + let resp = patch(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_request_json(json!({ "name": &new_name })) .with_state(admin) .run_async(&app) .await; - assert_ok!(conn); - let response_aggregator: Aggregator = conn.response_json().await; + assert_ok!(resp); + let response_aggregator: Aggregator = resp.response_json(); assert_eq!(response_aggregator.name, new_name); assert_eq!(aggregator.reload(app.db()).await?.unwrap().name, new_name); Ok(()) @@ -881,14 +885,14 @@ mod update { let aggregator = fixtures::aggregator(&app, Some(&account)).await; let new_name = format!("new name {}", fixtures::random_name()); - let mut conn = patch(format!("/api/aggregators/{}", aggregator.id)) + let resp = patch(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_request_json(json!({ "name": &new_name })) .with_state(admin) .run_async(&app) .await; - assert_ok!(conn); - let response_aggregator: Aggregator = conn.response_json().await; + assert_ok!(resp); + let response_aggregator: Aggregator = resp.response_json(); assert_eq!(response_aggregator.name, new_name); assert_eq!(aggregator.reload(app.db()).await?.unwrap().name, new_name); @@ -898,13 +902,13 @@ mod update { #[test(harness = set_up)] async fn nonexistant_aggregator(app: DivviupApi) -> TestResult { let user = fixtures::user(); - let mut conn = patch("/api/aggregators/not-an-aggregator-id") + let resp = patch("/api/aggregators/not-an-aggregator-id") .with_api_headers() .with_request_json(json!({ "name": "irrelevant" })) .with_state(user) .run_async(&app) .await; - assert_not_found!(conn); + assert_not_found!(resp); Ok(()) } @@ -917,14 +921,14 @@ mod update { .update(app.db()) .await?; let name = fixtures::random_name(); - let mut conn = patch(format!("/api/aggregators/{}", aggregator.id)) + let resp = patch(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_request_json(json!({ "name": name })) .with_state(user) .run_async(&app) .await; - assert_ok!(conn); - let response_aggregator: Aggregator = conn.response_json().await; + assert_ok!(resp); + let response_aggregator: Aggregator = resp.response_json(); assert_eq!(response_aggregator.name, name); assert_eq!(aggregator.reload(app.db()).await?.unwrap().name, name); Ok(()) @@ -939,13 +943,13 @@ mod update { .update(app.db()) .await?; let name = fixtures::random_name(); - let conn = patch(format!("/api/aggregators/{}", aggregator.id)) + let resp = patch(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_request_json(json!({ "name": name })) .with_state(user) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } @@ -959,14 +963,14 @@ mod update { .update(app.db()) .await?; let name = fixtures::random_name(); - let mut conn = patch(format!("/api/aggregators/{}", aggregator.id)) + let resp = patch(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_request_json(json!({ "name": name })) .with_state(admin) .run_async(&app) .await; - assert_ok!(conn); - let response_aggregator: Aggregator = conn.response_json().await; + assert_ok!(resp); + let response_aggregator: Aggregator = resp.response_json(); assert_eq!(response_aggregator.name, name); assert_eq!(aggregator.reload(app.db()).await?.unwrap().name, name); Ok(()) @@ -981,15 +985,15 @@ mod update { .update(app.db()) .await?; let name = fixtures::random_name(); - let mut conn = patch(format!("/api/aggregators/{}", aggregator.id)) + let resp = patch(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_request_json(json!({ "name": name })) .with_state(admin) .run_async(&app) .await; - assert_ok!(conn); - let response_aggregator: Aggregator = conn.response_json().await; + assert_ok!(resp); + let response_aggregator: Aggregator = resp.response_json(); assert_eq!(response_aggregator.name, name); assert_eq!(aggregator.reload(app.db()).await?.unwrap().name, name); Ok(()) @@ -1001,15 +1005,15 @@ mod update { let account = fixtures::account(&app).await; let aggregator = fixtures::aggregator(&app, Some(&account)).await; let name = fixtures::random_name(); - let mut conn = patch(format!("/api/aggregators/{}", aggregator.id)) + let resp = patch(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_auth_header(token) .with_request_json(json!({ "name": name })) .run_async(&app) .await; - assert_ok!(conn); - let response_aggregator: Aggregator = conn.response_json().await; + assert_ok!(resp); + let response_aggregator: Aggregator = resp.response_json(); assert_eq!(response_aggregator.name, name); assert_eq!(aggregator.reload(app.db()).await?.unwrap().name, name); Ok(()) @@ -1021,14 +1025,14 @@ mod update { let (_, token) = fixtures::api_token(&app, &account).await; let aggregator = fixtures::aggregator(&app, Some(&account)).await; let name = fixtures::random_name(); - let mut conn = patch(format!("/api/aggregators/{}", aggregator.id)) + let resp = patch(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_auth_header(token) .with_request_json(json!({ "name": name })) .run_async(&app) .await; - assert_ok!(conn); - let response_aggregator: Aggregator = conn.response_json().await; + assert_ok!(resp); + let response_aggregator: Aggregator = resp.response_json(); assert_eq!(response_aggregator.name, name); assert_eq!(aggregator.reload(app.db()).await?.unwrap().name, name); Ok(()) @@ -1041,13 +1045,13 @@ mod update { let account = fixtures::account(&app).await; let aggregator = fixtures::aggregator(&app, Some(&account)).await; let name_before = aggregator.name.clone(); - let conn = patch(format!("/api/aggregators/{}", aggregator.id)) + let resp = patch(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_auth_header(token) .with_request_json(json!({ "name": fixtures::random_name() })) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); assert_eq!( aggregator.reload(app.db()).await?.unwrap().name, name_before @@ -1071,25 +1075,25 @@ mod update { aggregator.features = ActiveValue::Set(Features::from_iter::<[Feature; 0]>([]).into()); let aggregator = aggregator.update(app.db()).await?; - let mut conn = get(format!("/api/aggregators/{}", aggregator.id)) + let resp = get(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_state(user.clone()) .run_async(&app) .await; - assert_ok!(conn); - let response_aggregator: Aggregator = conn.response_json().await; + assert_ok!(resp); + let response_aggregator: Aggregator = resp.response_json(); assert!(response_aggregator.query_types.is_empty()); assert!(response_aggregator.vdafs.is_empty()); assert!(response_aggregator.features.is_empty()); - let mut conn = patch(format!("/api/aggregators/{}", aggregator.id)) + let resp = patch(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_request_json(json!({})) .with_state(user) .run_async(&app) .await; - assert_ok!(conn); - let response_aggregator: Aggregator = conn.response_json().await; + assert_ok!(resp); + let response_aggregator: Aggregator = resp.response_json(); assert_eq!( response_aggregator.query_types, before_aggregator.query_types @@ -1109,12 +1113,12 @@ mod delete { #[ignore] async fn nonexistant_aggregator(app: DivviupApi) -> TestResult { let (user, ..) = fixtures::member(&app).await; - let conn = delete(format!("/api/aggregators/{}", Uuid::new_v4())) + let resp = delete(format!("/api/aggregators/{}", Uuid::new_v4())) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } @@ -1122,12 +1126,12 @@ mod delete { async fn shared_as_admin(app: DivviupApi) -> TestResult { let (admin, ..) = fixtures::admin(&app).await; let aggregator = fixtures::aggregator(&app, None).await; - let conn = delete(format!("/api/aggregators/{}", aggregator.id)) + let resp = delete(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_state(admin) .run_async(&app) .await; - assert_status!(conn, 204); + assert_status!(resp, 204); assert!(aggregator.reload(app.db()).await?.unwrap().is_tombstoned()); Ok(()) @@ -1137,12 +1141,12 @@ mod delete { async fn shared(app: DivviupApi) -> TestResult { let (user, ..) = fixtures::member(&app).await; let aggregator = fixtures::aggregator(&app, None).await; - let conn = delete(format!("/api/aggregators/{}", aggregator.id)) + let resp = delete(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); assert!(!aggregator.reload(app.db()).await?.unwrap().is_tombstoned()); Ok(()) @@ -1152,12 +1156,12 @@ mod delete { async fn as_member(app: DivviupApi) -> TestResult { let (user, account, ..) = fixtures::member(&app).await; let aggregator = fixtures::aggregator(&app, Some(&account)).await; - let conn = delete(format!("/api/aggregators/{}", aggregator.id)) + let resp = delete(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_status!(conn, 204); + assert_status!(resp, 204); assert!(aggregator.reload(app.db()).await?.unwrap().is_tombstoned()); Ok(()) @@ -1168,12 +1172,12 @@ mod delete { let account = fixtures::account(&app).await; let (user, ..) = fixtures::member(&app).await; let aggregator = fixtures::aggregator(&app, Some(&account)).await; - let conn = delete(format!("/api/aggregators/{}", aggregator.id)) + let resp = delete(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); assert!(!aggregator.reload(app.db()).await?.unwrap().is_tombstoned()); Ok(()) @@ -1184,12 +1188,12 @@ mod delete { let (admin, ..) = fixtures::admin(&app).await; let account = fixtures::account(&app).await; let aggregator = fixtures::aggregator(&app, Some(&account)).await; - let conn = delete(format!("/api/aggregators/{}", aggregator.id)) + let resp = delete(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_state(admin) .run_async(&app) .await; - assert_status!(conn, 204); + assert_status!(resp, 204); assert!(aggregator.reload(app.db()).await?.unwrap().is_tombstoned()); Ok(()) @@ -1200,12 +1204,12 @@ mod delete { let token = fixtures::admin_token(&app).await; let account = fixtures::account(&app).await; let aggregator = fixtures::aggregator(&app, Some(&account)).await; - let conn = delete(format!("/api/aggregators/{}", aggregator.id)) + let resp = delete(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_status!(conn, 204); + assert_status!(resp, 204); assert!(aggregator.reload(app.db()).await?.unwrap().is_tombstoned()); Ok(()) } @@ -1215,12 +1219,12 @@ mod delete { let account = fixtures::account(&app).await; let (_, token) = fixtures::api_token(&app, &account).await; let aggregator = fixtures::aggregator(&app, Some(&account)).await; - let conn = delete(format!("/api/aggregators/{}", aggregator.id)) + let resp = delete(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_status!(conn, 204); + assert_status!(resp, 204); assert!(aggregator.reload(app.db()).await?.unwrap().is_tombstoned()); Ok(()) } @@ -1231,12 +1235,12 @@ mod delete { let (_, token) = fixtures::api_token(&app, &other_account).await; let account = fixtures::account(&app).await; let aggregator = fixtures::aggregator(&app, Some(&account)).await; - let conn = delete(format!("/api/aggregators/{}", aggregator.id)) + let resp = delete(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); assert!(!aggregator.reload(app.db()).await?.unwrap().is_tombstoned()); Ok(()) } @@ -1246,12 +1250,12 @@ mod delete { let account = fixtures::account(&app).await; let (_, token) = fixtures::api_token(&app, &account).await; let aggregator = fixtures::aggregator(&app, None).await; - let conn = delete(format!("/api/aggregators/{}", aggregator.id)) + let resp = delete(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); assert!(!aggregator.reload(app.db()).await?.unwrap().is_tombstoned()); Ok(()) } @@ -1260,12 +1264,12 @@ mod delete { async fn admin_token_shared_aggregator(app: DivviupApi) -> TestResult { let token = fixtures::admin_token(&app).await; let aggregator = fixtures::aggregator(&app, None).await; - let conn = delete(format!("/api/aggregators/{}", aggregator.id)) + let resp = delete(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_status!(conn, 204); + assert_status!(resp, 204); assert!(aggregator.reload(app.db()).await?.unwrap().is_tombstoned()); Ok(()) } @@ -1278,15 +1282,15 @@ mod shared_create { async fn as_admin(app: DivviupApi, client_logs: ClientLogs) -> TestResult { let (admin, ..) = fixtures::admin(&app).await; let new_aggregator = fixtures::new_aggregator(); - let mut conn = post("/api/aggregators") + let resp = post("/api/aggregators") .with_request_json(&new_aggregator) .with_api_headers() .with_state(admin) .run_async(&app) .await; - assert_response!(conn, 201); - let aggregator: Aggregator = conn.response_json().await; + assert_response!(resp, 201); + let aggregator: Aggregator = resp.response_json(); let aggregator_config: AggregatorApiConfig = client_logs.last().response_json(); assert!(aggregator.account_id.is_none()); @@ -1314,14 +1318,14 @@ mod shared_create { let (admin, ..) = fixtures::admin(&app).await; let mut new_aggregator = fixtures::new_aggregator(); new_aggregator.is_first_party = Some(true); - let mut conn = post("/api/aggregators") + let resp = post("/api/aggregators") .with_api_headers() .with_state(admin) .with_request_json(new_aggregator) .run_async(&app) .await; - assert_response!(conn, 201); - let aggregator: Aggregator = conn.response_json().await; + assert_response!(resp, 201); + let aggregator: Aggregator = resp.response_json(); assert!(aggregator.is_first_party); assert!(aggregator.reload(app.db()).await?.unwrap().is_first_party); @@ -1333,14 +1337,14 @@ mod shared_create { let (admin, ..) = fixtures::admin(&app).await; let mut new_aggregator = fixtures::new_aggregator(); new_aggregator.is_first_party = Some(false); - let mut conn = post("/api/aggregators") + let resp = post("/api/aggregators") .with_api_headers() .with_state(admin) .with_request_json(new_aggregator) .run_async(&app) .await; - assert_response!(conn, 201); - let aggregator: Aggregator = conn.response_json().await; + assert_response!(resp, 201); + let aggregator: Aggregator = resp.response_json(); assert!(!aggregator.is_first_party); assert!(!aggregator.reload(app.db()).await?.unwrap().is_first_party); @@ -1353,14 +1357,14 @@ mod shared_create { let new_aggregator = fixtures::new_aggregator(); let aggregator_count_before = Aggregators::find().count(app.db()).await?; - let mut conn = post("/api/aggregators") + let resp = post("/api/aggregators") .with_request_json(&new_aggregator) .with_api_headers() .with_state(admin) .run_async(&app) .await; - assert_not_found!(conn); + assert_not_found!(resp); let aggregator_count_after = Aggregators::find().count(app.db()).await?; assert_eq!(aggregator_count_before, aggregator_count_after); @@ -1374,14 +1378,14 @@ mod shared_create { let new_aggregator = fixtures::new_aggregator(); let aggregator_count_before = Aggregators::find().count(app.db()).await?; - let mut conn = post("/api/aggregators") + let resp = post("/api/aggregators") .with_request_json(&new_aggregator) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_not_found!(conn); + assert_not_found!(resp); let aggregator_count_after = Aggregators::find().count(app.db()).await?; assert_eq!(aggregator_count_before, aggregator_count_after); Ok(()) @@ -1392,13 +1396,13 @@ mod shared_create { let token = fixtures::admin_token(&app).await; let new_aggregator = fixtures::new_aggregator(); let aggregator_count_before = Aggregators::find().count(app.db()).await?; - let mut conn = post("/api/aggregators") + let resp = post("/api/aggregators") .with_request_json(&new_aggregator) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - let aggregator: Aggregator = conn.response_json().await; + let aggregator: Aggregator = resp.response_json(); assert_eq!(aggregator.name, new_aggregator.name.unwrap()); let aggregator_count_after = Aggregators::find().count(app.db()).await?; assert_eq!(aggregator_count_after, aggregator_count_before + 1); diff --git a/tests/integration/api_tokens.rs b/tests/integration/api_tokens.rs index dd6f0c7a..8f2f4b5f 100644 --- a/tests/integration/api_tokens.rs +++ b/tests/integration/api_tokens.rs @@ -13,14 +13,14 @@ mod index { let (deleted, _) = fixtures::api_token(&app, &account).await; let _deleted = deleted.tombstone().update(app.db()).await.unwrap(); - let mut conn = get(format!("/api/accounts/{}/api_tokens", account.id)) + let resp = get(format!("/api/accounts/{}/api_tokens", account.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_ok!(conn); + assert_ok!(resp); - let api_tokens: Vec = conn.response_json().await; + let api_tokens: Vec = resp.response_json(); assert_same_json_representation(&api_tokens, &vec![token2, token1]); Ok(()) } @@ -33,13 +33,13 @@ mod index { fixtures::api_token(&app, &account).await; fixtures::api_token(&app, &account).await; - let conn = get(format!("/api/accounts/{}/api_tokens", account.id)) + let resp = get(format!("/api/accounts/{}/api_tokens", account.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } @@ -52,13 +52,13 @@ mod index { fixtures::api_token(&app, &account).await; fixtures::api_token(&app, &account).await; - let mut conn = get("/api/accounts/not-an-account/api_tokens") + let resp = get("/api/accounts/not-an-account/api_tokens") .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_not_found!(conn); + assert_not_found!(resp); Ok(()) } @@ -70,14 +70,14 @@ mod index { let (api_token1, _) = fixtures::api_token(&app, &account).await; let (api_token2, _) = fixtures::api_token(&app, &account).await; - let mut conn = get(format!("/api/accounts/{}/api_tokens", account.id)) + let resp = get(format!("/api/accounts/{}/api_tokens", account.id)) .with_api_headers() .with_state(admin) .run_async(&app) .await; - assert_ok!(conn); - let api_tokens: Vec = conn.response_json().await; + assert_ok!(resp); + let api_tokens: Vec = resp.response_json(); assert_same_json_representation(&api_tokens, &vec![api_token2, api_token1]); Ok(()) } @@ -89,14 +89,14 @@ mod index { let (api_token1, _) = fixtures::api_token(&app, &account).await; let (api_token2, _) = fixtures::api_token(&app, &account).await; - let mut conn = get(format!("/api/accounts/{}/api_tokens", account.id)) + let resp = get(format!("/api/accounts/{}/api_tokens", account.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_ok!(conn); - let api_tokens: Vec = conn.response_json().await; + assert_ok!(resp); + let api_tokens: Vec = resp.response_json(); assert_same_json_representation(&api_tokens, &vec![api_token2, api_token1]); Ok(()) } @@ -107,14 +107,14 @@ mod index { let (api_token1, token) = fixtures::api_token(&app, &account).await; let (api_token2, _) = fixtures::api_token(&app, &account).await; - let mut conn = get(format!("/api/accounts/{}/api_tokens", account.id)) + let resp = get(format!("/api/accounts/{}/api_tokens", account.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_ok!(conn); - let api_tokens: Vec = conn.response_json().await; + assert_ok!(resp); + let api_tokens: Vec = resp.response_json(); assert_same_json_representation( &api_tokens, &vec![api_token2, api_token1.reload(app.db()).await?.unwrap()], @@ -131,12 +131,12 @@ mod index { fixtures::api_token(&app, &account).await; fixtures::api_token(&app, &account).await; - let conn = get(format!("/api/accounts/{}/api_tokens", account.id)) + let resp = get(format!("/api/accounts/{}/api_tokens", account.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } } @@ -148,13 +148,13 @@ mod create { async fn success(app: DivviupApi) -> TestResult { let (user, account, ..) = fixtures::member(&app).await; - let mut conn = post(format!("/api/accounts/{}/api_tokens", account.id)) + let resp = post(format!("/api/accounts/{}/api_tokens", account.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_response!(conn, 201); - let mut api_token: ApiToken = conn.response_json().await; + assert_response!(resp, 201); + let mut api_token: ApiToken = resp.response_json(); let api_token_from_db = api_token.reload(app.db()).await?.unwrap(); let (api_token_from_token, account_from_token) = ApiTokens::load_and_check(&api_token.token.take().unwrap(), app.db()) @@ -173,13 +173,13 @@ mod create { let account = fixtures::account(&app).await; // no membership let api_token_count_before = ApiTokens::find().count(app.db()).await?; - let conn = post(format!("/api/accounts/{}/api_tokens", account.id)) + let resp = post(format!("/api/accounts/{}/api_tokens", account.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); let api_token_count_after = ApiTokens::find().count(app.db()).await?; assert_eq!(api_token_count_before, api_token_count_after); @@ -191,13 +191,13 @@ mod create { let user = fixtures::user(); let api_token_count_before = ApiTokens::find().count(app.db()).await?; - let mut conn = post("/api/accounts/does-not-exist/api_tokens") + let resp = post("/api/accounts/does-not-exist/api_tokens") .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_not_found!(conn); + assert_not_found!(resp); let api_token_count_after = ApiTokens::find().count(app.db()).await?; assert_eq!(api_token_count_before, api_token_count_after); @@ -208,14 +208,14 @@ mod create { async fn admin_not_member(app: DivviupApi) -> TestResult { let (admin, ..) = fixtures::admin(&app).await; let account = fixtures::account(&app).await; - let mut conn = post(format!("/api/accounts/{}/api_tokens", account.id)) + let resp = post(format!("/api/accounts/{}/api_tokens", account.id)) .with_api_headers() .with_state(admin) .run_async(&app) .await; - assert_response!(conn, 201); - let mut api_token: ApiToken = conn.response_json().await; + assert_response!(resp, 201); + let mut api_token: ApiToken = resp.response_json(); let api_token_from_db = api_token.reload(app.db()).await?.unwrap(); let (api_token_from_token, account_from_token) = ApiTokens::load_and_check(&api_token.token.take().unwrap(), app.db()) @@ -232,14 +232,14 @@ mod create { async fn admin_token(app: DivviupApi) -> TestResult { let token = fixtures::admin_token(&app).await; let account = fixtures::account(&app).await; - let mut conn = post(format!("/api/accounts/{}/api_tokens", account.id)) + let resp = post(format!("/api/accounts/{}/api_tokens", account.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_response!(conn, 201); - let mut api_token: ApiToken = conn.response_json().await; + assert_response!(resp, 201); + let mut api_token: ApiToken = resp.response_json(); let api_token_from_db = api_token.reload(app.db()).await?.unwrap(); let (api_token_from_token, account_from_token) = ApiTokens::load_and_check(&api_token.token.take().unwrap(), app.db()) @@ -256,14 +256,14 @@ mod create { async fn member_token(app: DivviupApi) -> TestResult { let account = fixtures::account(&app).await; let (_, token) = fixtures::api_token(&app, &account).await; - let mut conn = post(format!("/api/accounts/{}/api_tokens", account.id)) + let resp = post(format!("/api/accounts/{}/api_tokens", account.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_response!(conn, 201); - let mut api_token: ApiToken = conn.response_json().await; + assert_response!(resp, 201); + let mut api_token: ApiToken = resp.response_json(); let api_token_from_db = api_token.reload(app.db()).await?.unwrap(); let (api_token_from_token, account_from_token) = ApiTokens::load_and_check(&api_token.token.take().unwrap(), app.db()) @@ -283,12 +283,12 @@ mod create { let (_, token) = fixtures::api_token(&app, &other_account).await; let account = fixtures::account(&app).await; let count_before = ApiTokens::find().count(app.db()).await?; - let conn = post(format!("/api/accounts/{}/api_tokens", account.id)) + let resp = post(format!("/api/accounts/{}/api_tokens", account.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); let count_after = ApiTokens::find().count(app.db()).await?; assert_eq!(count_before, count_after); Ok(()) @@ -303,12 +303,12 @@ mod delete { #[test(harness = set_up)] async fn nonexistant_api_token(app: DivviupApi) -> TestResult { let (user, ..) = fixtures::member(&app).await; - let conn = delete(format!("/api/api_tokens/{}", Uuid::new_v4())) + let resp = delete(format!("/api/api_tokens/{}", Uuid::new_v4())) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } @@ -316,12 +316,12 @@ mod delete { async fn as_member(app: DivviupApi) -> TestResult { let (user, account, ..) = fixtures::member(&app).await; let (api_token, _) = fixtures::api_token(&app, &account).await; - let conn = delete(format!("/api/api_tokens/{}", api_token.id)) + let resp = delete(format!("/api/api_tokens/{}", api_token.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_status!(conn, 204); + assert_status!(resp, 204); assert!(api_token.reload(app.db()).await?.unwrap().is_tombstoned()); Ok(()) @@ -332,12 +332,12 @@ mod delete { let account = fixtures::account(&app).await; let (user, ..) = fixtures::member(&app).await; let (api_token, ..) = fixtures::api_token(&app, &account).await; - let conn = delete(format!("/api/api_tokens/{}", api_token.id)) + let resp = delete(format!("/api/api_tokens/{}", api_token.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); assert!(!api_token.reload(app.db()).await?.unwrap().is_tombstoned()); Ok(()) @@ -348,12 +348,12 @@ mod delete { let (admin, ..) = fixtures::admin(&app).await; let account = fixtures::account(&app).await; let (api_token, _) = fixtures::api_token(&app, &account).await; - let conn = delete(format!("/api/api_tokens/{}", api_token.id)) + let resp = delete(format!("/api/api_tokens/{}", api_token.id)) .with_api_headers() .with_state(admin) .run_async(&app) .await; - assert_status!(conn, 204); + assert_status!(resp, 204); assert!(api_token.reload(app.db()).await?.unwrap().is_tombstoned()); Ok(()) @@ -364,12 +364,12 @@ mod delete { let token = fixtures::admin_token(&app).await; let account = fixtures::account(&app).await; let (api_token, _) = fixtures::api_token(&app, &account).await; - let conn = delete(format!("/api/api_tokens/{}", api_token.id)) + let resp = delete(format!("/api/api_tokens/{}", api_token.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_status!(conn, 204); + assert_status!(resp, 204); assert!(api_token.reload(app.db()).await?.unwrap().is_tombstoned()); Ok(()) @@ -380,12 +380,12 @@ mod delete { let account = fixtures::account(&app).await; let (_, token) = fixtures::api_token(&app, &account).await; let (api_token, _) = fixtures::api_token(&app, &account).await; - let conn = delete(format!("/api/api_tokens/{}", api_token.id)) + let resp = delete(format!("/api/api_tokens/{}", api_token.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_status!(conn, 204); + assert_status!(resp, 204); assert!(api_token.reload(app.db()).await?.unwrap().is_tombstoned()); Ok(()) } @@ -396,12 +396,12 @@ mod delete { let (_, token) = fixtures::api_token(&app, &other_account).await; let account = fixtures::account(&app).await; let (api_token, _) = fixtures::api_token(&app, &account).await; - let conn = delete(format!("/api/api_tokens/{}", api_token.id)) + let resp = delete(format!("/api/api_tokens/{}", api_token.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); assert!(!api_token.reload(app.db()).await?.unwrap().is_tombstoned()); Ok(()) } @@ -415,13 +415,13 @@ mod update { #[test(harness = set_up)] async fn nonexistant_api_token(app: DivviupApi) -> TestResult { let (user, ..) = fixtures::member(&app).await; - let conn = patch(format!("/api/api_tokens/{}", Uuid::new_v4())) + let resp = patch(format!("/api/api_tokens/{}", Uuid::new_v4())) .with_request_json(json!({ "name": fixtures::random_name() })) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } @@ -430,14 +430,14 @@ mod update { let (user, account, ..) = fixtures::member(&app).await; let (api_token, _) = fixtures::api_token(&app, &account).await; let name = fixtures::random_name(); - let mut conn = patch(format!("/api/api_tokens/{}", api_token.id)) + let resp = patch(format!("/api/api_tokens/{}", api_token.id)) .with_api_headers() .with_request_json(json!({ "name": name })) .with_state(user) .run_async(&app) .await; - assert_status!(conn, 200); - let response: ApiToken = conn.response_json().await; + assert_status!(resp, 200); + let response: ApiToken = resp.response_json(); assert_eq!(response.name.unwrap(), name); assert_eq!( api_token.reload(app.db()).await?.unwrap().name.unwrap(), @@ -452,14 +452,14 @@ mod update { let account = fixtures::account(&app).await; let (user, ..) = fixtures::member(&app).await; let (api_token, ..) = fixtures::api_token(&app, &account).await; - let conn = patch(format!("/api/api_tokens/{}", api_token.id)) + let resp = patch(format!("/api/api_tokens/{}", api_token.id)) .with_api_headers() .with_request_json(json!({ "name": fixtures::random_name() })) .with_state(user) .run_async(&app) .await; let name_before = api_token.name.clone(); - assert_response!(conn, 403); + assert_response!(resp, 403); assert_eq!(api_token.reload(app.db()).await?.unwrap().name, name_before); Ok(()) @@ -471,14 +471,14 @@ mod update { let account = fixtures::account(&app).await; let (api_token, _) = fixtures::api_token(&app, &account).await; let name = fixtures::random_name(); - let mut conn = patch(format!("/api/api_tokens/{}", api_token.id)) + let resp = patch(format!("/api/api_tokens/{}", api_token.id)) .with_api_headers() .with_request_json(json!({ "name": name })) .with_state(admin) .run_async(&app) .await; - assert_status!(conn, 200); - let response: ApiToken = conn.response_json().await; + assert_status!(resp, 200); + let response: ApiToken = resp.response_json(); assert_eq!(response.name.unwrap(), name); assert_eq!( api_token.reload(app.db()).await?.unwrap().name.unwrap(), @@ -494,14 +494,14 @@ mod update { let account = fixtures::account(&app).await; let (api_token, _) = fixtures::api_token(&app, &account).await; let name = fixtures::random_name(); - let mut conn = patch(format!("/api/api_tokens/{}", api_token.id)) + let resp = patch(format!("/api/api_tokens/{}", api_token.id)) .with_api_headers() .with_request_json(json!({ "name": name })) .with_auth_header(token) .run_async(&app) .await; - assert_status!(conn, 200); - let response: ApiToken = conn.response_json().await; + assert_status!(resp, 200); + let response: ApiToken = resp.response_json(); assert_eq!(response.name.unwrap(), name); assert_eq!( api_token.reload(app.db()).await?.unwrap().name.unwrap(), @@ -516,14 +516,14 @@ mod update { let account = fixtures::account(&app).await; let (api_token, token) = fixtures::api_token(&app, &account).await; let name = fixtures::random_name(); - let mut conn = patch(format!("/api/api_tokens/{}", api_token.id)) + let resp = patch(format!("/api/api_tokens/{}", api_token.id)) .with_api_headers() .with_request_json(json!({ "name": name })) .with_auth_header(token) .run_async(&app) .await; - assert_status!(conn, 200); - let response: ApiToken = conn.response_json().await; + assert_status!(resp, 200); + let response: ApiToken = resp.response_json(); assert_eq!(response.name.unwrap(), name); assert_eq!( api_token.reload(app.db()).await?.unwrap().name.unwrap(), @@ -541,13 +541,13 @@ mod update { let account = fixtures::account(&app).await; let (api_token, _) = fixtures::api_token(&app, &account).await; let name = fixtures::random_name(); - let conn = patch(format!("/api/api_tokens/{}", api_token.id)) + let resp = patch(format!("/api/api_tokens/{}", api_token.id)) .with_api_headers() .with_request_json(json!({ "name": name })) .with_auth_header(token) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); assert!(api_token.reload(app.db()).await?.unwrap().name.is_none()); Ok(()) } diff --git a/tests/integration/assets.rs b/tests/integration/assets.rs index 79419914..cb4a21e3 100644 --- a/tests/integration/assets.rs +++ b/tests/integration/assets.rs @@ -46,23 +46,23 @@ async fn api_url(app: DivviupApi) -> TestResult { #[test(harness = set_up)] async fn missing_asset_is_not_cached(app: DivviupApi) -> TestResult { - let mut conn = get("/assets/does-not-exist.js") + let resp = get("/assets/does-not-exist.js") .with_app_host() .run_async(&app) .await; - assert_not_found!(&mut conn); - assert_headers!(&conn, "cache-control" => "no-cache"); + assert_not_found!(&resp); + assert_headers!(&resp, "cache-control" => "no-cache"); Ok(()) } #[test(harness = set_up)] async fn static_files(app: DivviupApi) -> TestResult { - let mut html_conn = get("/").with_app_host().run_async(&app).await; + let html_conn = get("/").with_app_host().run_async(&app).await; assert_ok!(&html_conn); assert_headers!(&html_conn, "cache-control" => "no-cache"); - let html = html_conn.take_response_body_string().unwrap(); + let html = html_conn.response_body_string().unwrap(); let regex = regex::Regex::new(r#"script type="module" crossorigin src="([^"]+)""#).unwrap(); let js_path = ®ex.captures_iter(&html).next().unwrap()[1]; diff --git a/tests/integration/auth.rs b/tests/integration/auth.rs index 448f2c36..fca3e388 100644 --- a/tests/integration/auth.rs +++ b/tests/integration/auth.rs @@ -1,4 +1,4 @@ -use test_support::{assert_eq, test, *}; +use test_support::{assert_eq, entity::session, test, *}; #[test(harness = set_up)] async fn first_use_of_a_token_updates_last_used_at(app: DivviupApi) -> TestResult { @@ -25,23 +25,23 @@ async fn deleted_token_cannot_authenticate(app: DivviupApi) -> TestResult { let (api_token, token) = fixtures::api_token(&app, &account).await; // Token works before deletion - let conn = get("/api/accounts") + let resp = get("/api/accounts") .with_api_headers() .with_auth_header(token.clone()) .run_async(&app) .await; - assert_ok!(conn); + assert_ok!(resp); // Delete (tombstone) the token api_token.tombstone().update(app.db()).await.unwrap(); // Deleted token should no longer authenticate - let conn = get("/api/accounts") + let resp = get("/api/accounts") .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_status!(conn, 403); + assert_status!(resp, 403); Ok(()) } @@ -51,14 +51,10 @@ mod login { #[test(harness = set_up)] async fn when_not_already_logged_in(app: DivviupApi) -> TestResult { - let conn = get("/login").with_api_host().run_async(&app).await; + let resp = get("/login").with_api_host().run_async(&app).await; let auth_base = app.config().auth_url.join("/authorize")?; - assert_status!(conn, 302); - let location = conn - .inner() - .response_headers() - .get_str(KnownHeaderName::Location) - .unwrap(); + assert_status!(resp, 302); + let location = resp.header_str(headers::LOCATION.as_str()).unwrap(); assert!(location.starts_with(auth_base.as_ref())); let url = Url::parse(location)?; let query = QueryStrong::parse_strict(url.query().unwrap()).unwrap(); @@ -75,33 +71,33 @@ mod login { #[test(harness = set_up)] async fn when_already_logged_in(app: DivviupApi) -> TestResult { let user = fixtures::user(); - let conn = get("/login") + let resp = get("/login") .with_api_host() .with_user(&user) .run_async(&app) .await; - assert_response!(conn, 302, "", "Location" => app.config().app_url.as_ref()); + assert_response!(resp, 302, "", headers::LOCATION => app.config().app_url.as_ref()); Ok(()) } } #[test(harness = set_up)] async fn logout(app: DivviupApi) -> TestResult { - // Session destruction (Set-Cookie clearing the session cookie) is not - // exercised here: the test harness proxy doesn't propagate cookies, so - // we can't round-trip a live session. Cookie clearing is tower-sessions' - // responsibility; this test only asserts the redirect contract. let user = fixtures::user(); - let conn = get("/logout") + let resp = get("/logout") .with_api_host() .with_user(&user) .run_async(&app) .await; - assert_response!(conn, 302); - let location: Url = conn - .response_headers() - .get_str(KnownHeaderName::Location) + assert_response!(resp, 302); + + // session.flush() in the logout handler should delete any session that was + // created during the request — verify no sessions remain in the store. + let session_count = session::Entity::find().count(app.db()).await?; + assert_eq!(session_count, 0, "session should be destroyed after logout"); + let location: Url = resp + .header_str(headers::LOCATION.as_str()) .unwrap() .parse()?; diff --git a/tests/integration/collector_credentials.rs b/tests/integration/collector_credentials.rs index 3ff64df6..b61dab31 100644 --- a/tests/integration/collector_credentials.rs +++ b/tests/integration/collector_credentials.rs @@ -16,7 +16,7 @@ mod index { let deleted = fixtures::collector_credential(&app, &account).await; let _deleted = deleted.tombstone().update(app.db()).await.unwrap(); - let mut conn = get(format!( + let resp = get(format!( "/api/accounts/{}/collector_credentials", account.id )) @@ -24,9 +24,9 @@ mod index { .with_state(user) .run_async(&app) .await; - assert_ok!(conn); + assert_ok!(resp); - let collector_credentials: Vec = conn.response_json().await; + let collector_credentials: Vec = resp.response_json(); assert_same_json_representation( &collector_credentials, &vec![collector_credential1, collector_credential2], @@ -42,7 +42,7 @@ mod index { fixtures::collector_credential(&app, &account).await; fixtures::collector_credential(&app, &account).await; - let conn = get(format!( + let resp = get(format!( "/api/accounts/{}/collector_credentials", account.id )) @@ -51,7 +51,7 @@ mod index { .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } @@ -64,13 +64,13 @@ mod index { fixtures::collector_credential(&app, &account).await; fixtures::collector_credential(&app, &account).await; - let mut conn = get("/api/accounts/not-an-account/collector_credentials") + let resp = get("/api/accounts/not-an-account/collector_credentials") .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_not_found!(conn); + assert_not_found!(resp); Ok(()) } @@ -82,7 +82,7 @@ mod index { let collector_credential1 = fixtures::collector_credential(&app, &account).await; let collector_credential2 = fixtures::collector_credential(&app, &account).await; - let mut conn = get(format!( + let resp = get(format!( "/api/accounts/{}/collector_credentials", account.id )) @@ -91,8 +91,8 @@ mod index { .run_async(&app) .await; - assert_ok!(conn); - let collector_credentials: Vec = conn.response_json().await; + assert_ok!(resp); + let collector_credentials: Vec = resp.response_json(); assert_same_json_representation( &collector_credentials, &vec![collector_credential1, collector_credential2], @@ -107,7 +107,7 @@ mod index { let collector_credential1 = fixtures::collector_credential(&app, &account).await; let collector_credential2 = fixtures::collector_credential(&app, &account).await; - let mut conn = get(format!( + let resp = get(format!( "/api/accounts/{}/collector_credentials", account.id )) @@ -116,8 +116,8 @@ mod index { .run_async(&app) .await; - assert_ok!(conn); - let collector_credentials: Vec = conn.response_json().await; + assert_ok!(resp); + let collector_credentials: Vec = resp.response_json(); assert_same_json_representation( &collector_credentials, &vec![collector_credential1, collector_credential2], @@ -132,7 +132,7 @@ mod index { let collector_credential1 = fixtures::collector_credential(&app, &account).await; let collector_credential2 = fixtures::collector_credential(&app, &account).await; - let mut conn = get(format!( + let resp = get(format!( "/api/accounts/{}/collector_credentials", account.id )) @@ -141,8 +141,8 @@ mod index { .run_async(&app) .await; - assert_ok!(conn); - let collector_credentials: Vec = conn.response_json().await; + assert_ok!(resp); + let collector_credentials: Vec = resp.response_json(); assert_same_json_representation( &collector_credentials, &vec![collector_credential1, collector_credential2], @@ -159,7 +159,7 @@ mod index { fixtures::collector_credential(&app, &account).await; fixtures::collector_credential(&app, &account).await; - let conn = get(format!( + let resp = get(format!( "/api/accounts/{}/collector_credentials", account.id )) @@ -167,7 +167,7 @@ mod index { .with_auth_header(token) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } } @@ -187,7 +187,7 @@ mod create { let (user, account, ..) = fixtures::member(&app).await; let hpke_config = random_hpke_config(); - let mut conn = post(format!( + let resp = post(format!( "/api/accounts/{}/collector_credentials", account.id )) @@ -196,8 +196,8 @@ mod create { .with_request_json(valid_collector_credential_json(hpke_config.clone())) .run_async(&app) .await; - assert_response!(conn, 201); - let mut collector_credential: CollectorCredential = conn.response_json().await; + assert_response!(resp, 201); + let mut collector_credential: CollectorCredential = resp.response_json(); let token = collector_credential.token.take(); assert!(token.is_some()); assert!(collector_credential.token_hash.is_some()); @@ -214,7 +214,7 @@ mod create { let collector_credential_count_before = CollectorCredentials::find().count(app.db()).await?; - let conn = post(format!( + let resp = post(format!( "/api/accounts/{}/collector_credentials", account.id )) @@ -224,7 +224,7 @@ mod create { .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); let collector_credential_count_after = CollectorCredentials::find().count(app.db()).await?; assert_eq!( collector_credential_count_before, @@ -240,14 +240,14 @@ mod create { let collector_credential_count_before = CollectorCredentials::find().count(app.db()).await?; - let mut conn = post("/api/accounts/does-not-exist/collector_credentials") + let resp = post("/api/accounts/does-not-exist/collector_credentials") .with_api_headers() .with_state(user) .with_request_json(valid_collector_credential_json(random_hpke_config())) .run_async(&app) .await; - assert_not_found!(conn); + assert_not_found!(resp); let collector_credential_count_after = CollectorCredentials::find().count(app.db()).await?; assert_eq!( collector_credential_count_before, @@ -261,7 +261,7 @@ mod create { async fn admin_not_member(app: DivviupApi) -> TestResult { let (admin, ..) = fixtures::admin(&app).await; let account = fixtures::account(&app).await; - let mut conn = post(format!( + let resp = post(format!( "/api/accounts/{}/collector_credentials", account.id )) @@ -271,8 +271,8 @@ mod create { .run_async(&app) .await; - assert_response!(conn, 201); - let mut collector_credential: CollectorCredential = conn.response_json().await; + assert_response!(resp, 201); + let mut collector_credential: CollectorCredential = resp.response_json(); let token = collector_credential.token.take(); assert!(token.is_some()); assert!(collector_credential.token_hash.is_some()); @@ -285,7 +285,7 @@ mod create { async fn admin_token(app: DivviupApi) -> TestResult { let token = fixtures::admin_token(&app).await; let account = fixtures::account(&app).await; - let mut conn = post(format!( + let resp = post(format!( "/api/accounts/{}/collector_credentials", account.id )) @@ -295,8 +295,8 @@ mod create { .run_async(&app) .await; - assert_response!(conn, 201); - let mut collector_credential: CollectorCredential = conn.response_json().await; + assert_response!(resp, 201); + let mut collector_credential: CollectorCredential = resp.response_json(); let token = collector_credential.token.take(); assert!(token.is_some()); assert!(collector_credential.token_hash.is_some()); @@ -309,7 +309,7 @@ mod create { async fn member_token(app: DivviupApi) -> TestResult { let account = fixtures::account(&app).await; let (_, token) = fixtures::api_token(&app, &account).await; - let mut conn = post(format!( + let resp = post(format!( "/api/accounts/{}/collector_credentials", account.id )) @@ -319,8 +319,8 @@ mod create { .run_async(&app) .await; - assert_response!(conn, 201); - let mut collector_credential: CollectorCredential = conn.response_json().await; + assert_response!(resp, 201); + let mut collector_credential: CollectorCredential = resp.response_json(); let token = collector_credential.token.take(); assert!(token.is_some()); assert!(collector_credential.token_hash.is_some()); @@ -335,7 +335,7 @@ mod create { let (_, token) = fixtures::api_token(&app, &other_account).await; let account = fixtures::account(&app).await; let count_before = CollectorCredentials::find().count(app.db()).await?; - let conn = post(format!( + let resp = post(format!( "/api/accounts/{}/collector_credentials", account.id )) @@ -344,7 +344,7 @@ mod create { .with_request_json(valid_collector_credential_json(random_hpke_config())) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); let count_after = CollectorCredentials::find().count(app.db()).await?; assert_eq!(count_before, count_after); Ok(()) @@ -359,12 +359,12 @@ mod delete { #[test(harness = set_up)] async fn nonexistant_collector_credential(app: DivviupApi) -> TestResult { let (user, ..) = fixtures::member(&app).await; - let conn = delete(format!("/api/collector_credentials/{}", Uuid::new_v4())) + let resp = delete(format!("/api/collector_credentials/{}", Uuid::new_v4())) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } @@ -372,7 +372,7 @@ mod delete { async fn as_member(app: DivviupApi) -> TestResult { let (user, account, ..) = fixtures::member(&app).await; let collector_credential = fixtures::collector_credential(&app, &account).await; - let conn = delete(format!( + let resp = delete(format!( "/api/collector_credentials/{}", collector_credential.id )) @@ -380,7 +380,7 @@ mod delete { .with_state(user) .run_async(&app) .await; - assert_status!(conn, 204); + assert_status!(resp, 204); assert!(collector_credential .reload(app.db()) .await? @@ -395,7 +395,7 @@ mod delete { let account = fixtures::account(&app).await; let (user, ..) = fixtures::member(&app).await; let collector_credential = fixtures::collector_credential(&app, &account).await; - let conn = delete(format!( + let resp = delete(format!( "/api/collector_credentials/{}", collector_credential.id )) @@ -403,7 +403,7 @@ mod delete { .with_state(user) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); assert!(!collector_credential .reload(app.db()) .await? @@ -418,7 +418,7 @@ mod delete { let (admin, ..) = fixtures::admin(&app).await; let account = fixtures::account(&app).await; let collector_credential = fixtures::collector_credential(&app, &account).await; - let conn = delete(format!( + let resp = delete(format!( "/api/collector_credentials/{}", collector_credential.id )) @@ -426,7 +426,7 @@ mod delete { .with_state(admin) .run_async(&app) .await; - assert_status!(conn, 204); + assert_status!(resp, 204); assert!(collector_credential .reload(app.db()) .await? @@ -441,7 +441,7 @@ mod delete { let token = fixtures::admin_token(&app).await; let account = fixtures::account(&app).await; let collector_credential = fixtures::collector_credential(&app, &account).await; - let conn = delete(format!( + let resp = delete(format!( "/api/collector_credentials/{}", collector_credential.id )) @@ -449,7 +449,7 @@ mod delete { .with_auth_header(token) .run_async(&app) .await; - assert_status!(conn, 204); + assert_status!(resp, 204); assert!(collector_credential .reload(app.db()) .await? @@ -464,7 +464,7 @@ mod delete { let account = fixtures::account(&app).await; let (_, token) = fixtures::api_token(&app, &account).await; let collector_credential = fixtures::collector_credential(&app, &account).await; - let conn = delete(format!( + let resp = delete(format!( "/api/collector_credentials/{}", collector_credential.id )) @@ -472,7 +472,7 @@ mod delete { .with_auth_header(token) .run_async(&app) .await; - assert_status!(conn, 204); + assert_status!(resp, 204); assert!(collector_credential .reload(app.db()) .await? @@ -487,7 +487,7 @@ mod delete { let (_, token) = fixtures::api_token(&app, &other_account).await; let account = fixtures::account(&app).await; let collector_credential = fixtures::collector_credential(&app, &account).await; - let conn = delete(format!( + let resp = delete(format!( "/api/collector_credentials/{}", collector_credential.id )) @@ -495,7 +495,7 @@ mod delete { .with_auth_header(token) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); assert!(!collector_credential .reload(app.db()) .await? @@ -513,13 +513,13 @@ mod update { #[test(harness = set_up)] async fn nonexistant_collector_credential(app: DivviupApi) -> TestResult { let (user, ..) = fixtures::member(&app).await; - let conn = patch(format!("/api/collector_credentials/{}", Uuid::new_v4())) + let resp = patch(format!("/api/collector_credentials/{}", Uuid::new_v4())) .with_request_json(json!({ "name": fixtures::random_name() })) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } @@ -528,7 +528,7 @@ mod update { let (user, account, ..) = fixtures::member(&app).await; let collector_credential = fixtures::collector_credential(&app, &account).await; let name = fixtures::random_name(); - let mut conn = patch(format!( + let resp = patch(format!( "/api/collector_credentials/{}", collector_credential.id )) @@ -537,8 +537,8 @@ mod update { .with_state(user) .run_async(&app) .await; - assert_status!(conn, 200); - let response: CollectorCredential = conn.response_json().await; + assert_status!(resp, 200); + let response: CollectorCredential = resp.response_json(); assert_eq!(response.name.unwrap(), name); assert_eq!( collector_credential @@ -558,7 +558,7 @@ mod update { let account = fixtures::account(&app).await; let (user, ..) = fixtures::member(&app).await; let collector_credential = fixtures::collector_credential(&app, &account).await; - let conn = patch(format!( + let resp = patch(format!( "/api/collector_credentials/{}", collector_credential.id )) @@ -568,7 +568,7 @@ mod update { .run_async(&app) .await; let name_before = collector_credential.name.clone(); - assert_response!(conn, 403); + assert_response!(resp, 403); assert_eq!( collector_credential.reload(app.db()).await?.unwrap().name, name_before @@ -583,7 +583,7 @@ mod update { let account = fixtures::account(&app).await; let collector_credential = fixtures::collector_credential(&app, &account).await; let name = fixtures::random_name(); - let mut conn = patch(format!( + let resp = patch(format!( "/api/collector_credentials/{}", collector_credential.id )) @@ -592,8 +592,8 @@ mod update { .with_state(admin) .run_async(&app) .await; - assert_status!(conn, 200); - let response: CollectorCredential = conn.response_json().await; + assert_status!(resp, 200); + let response: CollectorCredential = resp.response_json(); assert_eq!(response.name.unwrap(), name); assert_eq!( collector_credential @@ -614,7 +614,7 @@ mod update { let account = fixtures::account(&app).await; let collector_credential = fixtures::collector_credential(&app, &account).await; let name = fixtures::random_name(); - let mut conn = patch(format!( + let resp = patch(format!( "/api/collector_credentials/{}", collector_credential.id )) @@ -623,8 +623,8 @@ mod update { .with_auth_header(token) .run_async(&app) .await; - assert_status!(conn, 200); - let response: CollectorCredential = conn.response_json().await; + assert_status!(resp, 200); + let response: CollectorCredential = resp.response_json(); assert_eq!(response.name.unwrap(), name); assert_eq!( collector_credential @@ -645,7 +645,7 @@ mod update { let (_, token) = fixtures::api_token(&app, &account).await; let collector_credential = fixtures::collector_credential(&app, &account).await; let name = fixtures::random_name(); - let mut conn = patch(format!( + let resp = patch(format!( "/api/collector_credentials/{}", collector_credential.id )) @@ -654,8 +654,8 @@ mod update { .with_auth_header(token) .run_async(&app) .await; - assert_status!(conn, 200); - let response: CollectorCredential = conn.response_json().await; + assert_status!(resp, 200); + let response: CollectorCredential = resp.response_json(); assert_eq!(response.name.unwrap(), name); assert_eq!( collector_credential @@ -678,7 +678,7 @@ mod update { let collector_credential = fixtures::collector_credential(&app, &account).await; let name_before = collector_credential.name.clone(); let name = fixtures::random_name(); - let conn = patch(format!( + let resp = patch(format!( "/api/collector_credentials/{}", collector_credential.id )) @@ -687,7 +687,7 @@ mod update { .with_auth_header(token) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); assert_eq!( collector_credential.reload(app.db()).await?.unwrap().name, name_before diff --git a/tests/integration/jobs.rs b/tests/integration/jobs.rs index 515df17c..cf4aaf98 100644 --- a/tests/integration/jobs.rs +++ b/tests/integration/jobs.rs @@ -77,7 +77,7 @@ async fn send_email(app: DivviupApi, client_logs: ClientLogs) -> TestResult { assert!(job.perform(&app.config().into(), app.db()).await?.is_none()); let reset_request = client_logs.logs().last().unwrap().clone(); - assert_eq!(reset_request.method, Method::Post); + assert_eq!(reset_request.method, Method::POST); assert_eq!( reset_request.url, app.config() diff --git a/tests/integration/memberships.rs b/tests/integration/memberships.rs index d0b180d9..cf377791 100644 --- a/tests/integration/memberships.rs +++ b/tests/integration/memberships.rs @@ -8,16 +8,16 @@ mod create { #[test(harness = set_up)] async fn success(app: DivviupApi) -> TestResult { let (user, account, ..) = fixtures::member(&app).await; - let mut conn = post(format!("/api/accounts/{}/memberships", account.id)) + let resp = post(format!("/api/accounts/{}/memberships", account.id)) .with_api_headers() .with_request_json(json!({ "user_email": "someone.else@example.com" })) .with_state(user) .run_async(&app) .await; - assert_response!(conn, 201); + assert_response!(resp, 201); - let membership: Membership = conn.response_json().await; + let membership: Membership = resp.response_json(); assert_eq!(membership.user_email, "someone.else@example.com"); assert_eq!(membership.account_id, account.id); let membership_id = membership.id; @@ -40,21 +40,21 @@ mod create { let membership_count_before = Memberships::find().count(app.db()).await?; - let mut conn = post(format!("/api/accounts/{}/memberships", account.id)) + let resp = post(format!("/api/accounts/{}/memberships", account.id)) .with_api_headers() .with_request_json(json!({ "user_email": other_user.email })) .with_state(user) .run_async(&app) .await; - assert_response!(conn, 200); + assert_response!(resp, 200); assert_eq!( membership_count_before, Memberships::find().count(app.db()).await? ); - let membership: Membership = conn.response_json().await; + let membership: Membership = resp.response_json(); assert_eq!(membership.user_email, other_user.email); assert_eq!(membership.account_id, account.id); assert!(membership.reload(app.db()).await?.is_some()); @@ -67,16 +67,16 @@ mod create { let (user, account, ..) = fixtures::member(&app).await; let membership_count_before = Memberships::find().count(app.db()).await?; - let mut conn = post(format!("/api/accounts/{}/memberships", account.id)) + let resp = post(format!("/api/accounts/{}/memberships", account.id)) .with_api_headers() .with_request_json(json!({ "user_email": "not a valid email" })) .with_state(user) .run_async(&app) .await; - assert_response!(conn, 400); + assert_response!(resp, 400); - let errors: Value = conn.response_json().await; + let errors: Value = resp.response_json(); assert!(errors.get("user_email").is_some()); assert_eq!( @@ -90,26 +90,26 @@ mod create { async fn not_member(app: DivviupApi) -> TestResult { let (user, ..) = fixtures::member(&app).await; let account = fixtures::account(&app).await; - let conn = post(format!("/api/accounts/{}/memberships", account.id)) + let resp = post(format!("/api/accounts/{}/memberships", account.id)) .with_api_headers() .with_request_json(json!({ "user_email": "someone.else@example.com" })) .with_state(user) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } #[test(harness = set_up)] async fn nonexistant_account(app: DivviupApi) -> TestResult { let (user, ..) = fixtures::member(&app).await; - let conn = post("/api/accounts/no-account-with-this-id/memberships") + let resp = post("/api/accounts/no-account-with-this-id/memberships") .with_api_headers() .with_request_json(json!({ "user_email": "someone.else@example.com" })) .with_state(user) .run_async(&app) .await; - assert_eq!(conn.status().unwrap_or(Status::NotFound), 404); + assert_status!(resp, 404); Ok(()) } @@ -117,16 +117,16 @@ mod create { async fn admin_not_member(app: DivviupApi) -> TestResult { let (user, ..) = fixtures::admin(&app).await; let account = fixtures::account(&app).await; - let mut conn = post(format!("/api/accounts/{}/memberships", account.id)) + let resp = post(format!("/api/accounts/{}/memberships", account.id)) .with_api_headers() .with_request_json(json!({ "user_email": "someone.else@example.com" })) .with_state(user) .run_async(&app) .await; - assert_response!(conn, 201); + assert_response!(resp, 201); - let membership: Membership = conn.response_json().await; + let membership: Membership = resp.response_json(); assert_eq!(membership.user_email, "someone.else@example.com"); assert_eq!(membership.account_id, account.id); assert!(membership.reload(app.db()).await?.is_some()); @@ -139,14 +139,14 @@ mod create { let token = fixtures::admin_token(&app).await; let account = fixtures::account(&app).await; let email = format!("{}@example.com", fixtures::random_name()); - let mut conn = post(format!("/api/accounts/{}/memberships", account.id)) + let resp = post(format!("/api/accounts/{}/memberships", account.id)) .with_api_headers() .with_request_json(json!({ "user_email": email })) .with_auth_header(token) .run_async(&app) .await; - assert_response!(conn, 201); - let membership: Membership = conn.response_json().await; + assert_response!(resp, 201); + let membership: Membership = resp.response_json(); assert_eq!(membership.user_email, email); assert_eq!(membership.account_id, account.id); assert!(membership.reload(app.db()).await?.is_some()); @@ -158,14 +158,14 @@ mod create { let account = fixtures::account(&app).await; let (_, token) = fixtures::api_token(&app, &account).await; let email = format!("{}@example.com", fixtures::random_name()); - let mut conn = post(format!("/api/accounts/{}/memberships", account.id)) + let resp = post(format!("/api/accounts/{}/memberships", account.id)) .with_api_headers() .with_request_json(json!({ "user_email": email })) .with_auth_header(token) .run_async(&app) .await; - assert_response!(conn, 201); - let membership: Membership = conn.response_json().await; + assert_response!(resp, 201); + let membership: Membership = resp.response_json(); assert_eq!(membership.user_email, email); assert_eq!(membership.account_id, account.id); assert!(membership.reload(app.db()).await?.is_some()); @@ -179,13 +179,13 @@ mod create { let account = fixtures::account(&app).await; let email = format!("{}@example.com", fixtures::random_name()); let count_before = Memberships::find().count(app.db()).await?; - let conn = post(format!("/api/accounts/{}/memberships", account.id)) + let resp = post(format!("/api/accounts/{}/memberships", account.id)) .with_api_headers() .with_request_json(json!({ "user_email": email })) .with_auth_header(token) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); let count_after = Memberships::find().count(app.db()).await?; assert_eq!(count_before, count_after); Ok(()) @@ -202,13 +202,13 @@ mod index { let (user, account, ..) = fixtures::member(&app).await; fixtures::membership(&app, &account, &fixtures::user()).await; fixtures::membership(&app, &account, &fixtures::user()).await; - let mut conn = get(format!("/api/accounts/{}/memberships", account.id)) + let resp = get(format!("/api/accounts/{}/memberships", account.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_ok!(conn); - let memberships: Vec = conn.response_json().await; + assert_ok!(resp); + let memberships: Vec = resp.response_json(); assert_eq!(memberships.len(), 3); Ok(()) } @@ -217,24 +217,24 @@ mod index { async fn not_member(app: DivviupApi) -> TestResult { let (_, account, ..) = fixtures::member(&app).await; let (user, ..) = fixtures::member(&app).await; - let conn = get(format!("/api/accounts/{}/memberships", account.id)) + let resp = get(format!("/api/accounts/{}/memberships", account.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } #[test(harness = set_up)] async fn nonexistant_account(app: DivviupApi) -> TestResult { let (user, ..) = fixtures::member(&app).await; - let mut conn = get("/api/accounts/not-an-id/memberships") + let resp = get("/api/accounts/not-an-id/memberships") .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_not_found!(conn); + assert_not_found!(resp); Ok(()) } @@ -245,13 +245,13 @@ mod index { fixtures::membership(&app, &account, &fixtures::user()).await; fixtures::membership(&app, &account, &fixtures::user()).await; - let mut conn = get(format!("/api/accounts/{}/memberships", account.id)) + let resp = get(format!("/api/accounts/{}/memberships", account.id)) .with_api_headers() .with_state(admin) .run_async(&app) .await; - assert_ok!(conn); - let memberships: Vec = conn.response_json().await; + assert_ok!(resp); + let memberships: Vec = resp.response_json(); assert_eq!(memberships.len(), 2); Ok(()) @@ -264,13 +264,13 @@ mod index { fixtures::membership(&app, &account, &fixtures::user()).await; fixtures::membership(&app, &account, &fixtures::user()).await; - let mut conn = get(format!("/api/accounts/{}/memberships", account.id)) + let resp = get(format!("/api/accounts/{}/memberships", account.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_ok!(conn); - let memberships: Vec = conn.response_json().await; + assert_ok!(resp); + let memberships: Vec = resp.response_json(); assert_eq!(memberships.len(), 2); Ok(()) } @@ -282,13 +282,13 @@ mod index { fixtures::membership(&app, &account, &fixtures::user()).await; fixtures::membership(&app, &account, &fixtures::user()).await; - let mut conn = get(format!("/api/accounts/{}/memberships", account.id)) + let resp = get(format!("/api/accounts/{}/memberships", account.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_ok!(conn); - let memberships: Vec = conn.response_json().await; + assert_ok!(resp); + let memberships: Vec = resp.response_json(); assert_eq!(memberships.len(), 2); Ok(()) } @@ -301,12 +301,12 @@ mod index { fixtures::membership(&app, &account, &fixtures::user()).await; fixtures::membership(&app, &account, &fixtures::user()).await; - let conn = get(format!("/api/accounts/{}/memberships", account.id)) + let resp = get(format!("/api/accounts/{}/memberships", account.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } } @@ -319,12 +319,12 @@ mod delete { let (user, account, ..) = fixtures::member(&app).await; let other_membership = fixtures::membership(&app, &account, &fixtures::user()).await; fixtures::membership(&app, &account, &fixtures::user()).await; - let conn = delete(format!("/api/memberships/{}", other_membership.id)) + let resp = delete(format!("/api/memberships/{}", other_membership.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_response!(conn, 204); + assert_response!(resp, 204); assert!(other_membership.reload(app.db()).await?.is_none()); Ok(()) @@ -335,12 +335,12 @@ mod delete { let (user, ..) = fixtures::member(&app).await; let account = fixtures::account(&app).await; let other_membership = fixtures::membership(&app, &account, &fixtures::user()).await; - let mut conn = delete(format!("/api/memberships/{}", other_membership.id)) + let resp = delete(format!("/api/memberships/{}", other_membership.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_not_found!(conn); + assert_not_found!(resp); assert!(other_membership.reload(app.db()).await?.is_some()); Ok(()) } @@ -348,24 +348,24 @@ mod delete { #[test(harness = set_up)] async fn nonexistant_id(app: DivviupApi) -> TestResult { let (user, ..) = fixtures::member(&app).await; - let mut conn = delete("/api/memberships/876b2071-9da8-4bda-bd4c-8d42a3ae7d90") + let resp = delete("/api/memberships/876b2071-9da8-4bda-bd4c-8d42a3ae7d90") .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_not_found!(conn); + assert_not_found!(resp); Ok(()) } #[test(harness = set_up)] async fn removing_self(app: DivviupApi) -> TestResult { let (user, _, membership) = fixtures::member(&app).await; - let conn = delete(format!("/api/memberships/{}", membership.id)) + let resp = delete(format!("/api/memberships/{}", membership.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_status!(conn, 403); + assert_status!(resp, 403); Ok(()) } @@ -374,12 +374,12 @@ mod delete { let (user, ..) = fixtures::admin(&app).await; let account = fixtures::account(&app).await; let membership = fixtures::membership(&app, &account, &fixtures::user()).await; - let conn = delete(format!("/api/memberships/{}", membership.id)) + let resp = delete(format!("/api/memberships/{}", membership.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_response!(conn, 204); + assert_response!(resp, 204); assert!(membership.reload(app.db()).await?.is_none()); Ok(()) } @@ -390,12 +390,12 @@ mod delete { let account = fixtures::account(&app).await; let membership = fixtures::membership(&app, &account, &fixtures::user()).await; let count_before = Memberships::find().count(app.db()).await?; - let conn = delete(format!("/api/memberships/{}", membership.id)) + let resp = delete(format!("/api/memberships/{}", membership.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_response!(conn, 204); + assert_response!(resp, 204); assert!(membership.reload(app.db()).await?.is_none()); let count_after = Memberships::find().count(app.db()).await?; assert_eq!(count_before - 1, count_after); @@ -408,12 +408,12 @@ mod delete { let (_, token) = fixtures::api_token(&app, &account).await; let membership = fixtures::membership(&app, &account, &fixtures::user()).await; let count_before = Memberships::find().count(app.db()).await?; - let conn = delete(format!("/api/memberships/{}", membership.id)) + let resp = delete(format!("/api/memberships/{}", membership.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_response!(conn, 204); + assert_response!(resp, 204); assert!(membership.reload(app.db()).await?.is_none()); let count_after = Memberships::find().count(app.db()).await?; assert_eq!(count_before - 1, count_after); @@ -427,12 +427,12 @@ mod delete { let account = fixtures::account(&app).await; let membership = fixtures::membership(&app, &account, &fixtures::user()).await; let count_before = Memberships::find().count(app.db()).await?; - let mut conn = delete(format!("/api/memberships/{}", membership.id)) + let resp = delete(format!("/api/memberships/{}", membership.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_not_found!(conn); + assert_not_found!(resp); assert!(membership.reload(app.db()).await?.is_some()); let count_after = Memberships::find().count(app.db()).await?; assert_eq!(count_before, count_after); diff --git a/tests/integration/tasks.rs b/tests/integration/tasks.rs index d44f84f6..2bd20c9c 100644 --- a/tests/integration/tasks.rs +++ b/tests/integration/tasks.rs @@ -12,13 +12,13 @@ mod index { am.deleted_at = ActiveValue::Set(Some(OffsetDateTime::now_utc())); let _ = am.update(app.db()).await?; - let mut conn = get(format!("/api/accounts/{}/tasks", account.id)) + let resp = get(format!("/api/accounts/{}/tasks", account.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_ok!(conn); - let tasks: Vec = conn.response_json().await; + assert_ok!(resp); + let tasks: Vec = resp.response_json(); assert!(tasks.is_empty()); Ok(()) } @@ -32,14 +32,14 @@ mod index { let task1 = fixtures::task(&app, &account).await; let task2 = fixtures::task(&app, &account).await; - let mut conn = get(format!("/api/accounts/{}/tasks", account.id)) + let resp = get(format!("/api/accounts/{}/tasks", account.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_ok!(conn); - let tasks: Vec = conn.response_json().await; + assert_ok!(resp); + let tasks: Vec = resp.response_json(); assert_eq!(vec![task1, task2], tasks); Ok(()) } @@ -52,13 +52,13 @@ mod index { fixtures::task(&app, &account).await; fixtures::task(&app, &account).await; - let conn = get(format!("/api/accounts/{}/tasks", account.id)) + let resp = get(format!("/api/accounts/{}/tasks", account.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } @@ -70,13 +70,13 @@ mod index { fixtures::task(&app, &account).await; fixtures::task(&app, &account).await; - let mut conn = get("/api/accounts/not-an-account/tasks") + let resp = get("/api/accounts/not-an-account/tasks") .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_not_found!(conn); + assert_not_found!(resp); Ok(()) } @@ -89,14 +89,14 @@ mod index { let task1 = fixtures::task(&app, &account).await; let task2 = fixtures::task(&app, &account).await; - let mut conn = get(format!("/api/accounts/{}/tasks", account.id)) + let resp = get(format!("/api/accounts/{}/tasks", account.id)) .with_api_headers() .with_state(admin) .run_async(&app) .await; - assert_ok!(conn); - let tasks: Vec = conn.response_json().await; + assert_ok!(resp); + let tasks: Vec = resp.response_json(); assert_eq!(vec![task1, task2], tasks); Ok(()) } @@ -108,14 +108,14 @@ mod index { let task1 = fixtures::task(&app, &account).await; let task2 = fixtures::task(&app, &account).await; - let mut conn = get(format!("/api/accounts/{}/tasks", account.id)) + let resp = get(format!("/api/accounts/{}/tasks", account.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_ok!(conn); - let tasks: Vec = conn.response_json().await; + assert_ok!(resp); + let tasks: Vec = resp.response_json(); assert_eq!(vec![task1, task2], tasks); Ok(()) } @@ -127,14 +127,14 @@ mod index { let task1 = fixtures::task(&app, &account).await; let task2 = fixtures::task(&app, &account).await; - let mut conn = get(format!("/api/accounts/{}/tasks", account.id)) + let resp = get(format!("/api/accounts/{}/tasks", account.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_ok!(conn); - let tasks: Vec = conn.response_json().await; + assert_ok!(resp); + let tasks: Vec = resp.response_json(); assert_eq!(vec![task1, task2], tasks); Ok(()) } @@ -148,13 +148,13 @@ mod index { fixtures::task(&app, &account).await; fixtures::task(&app, &account).await; - let conn = get(format!("/api/accounts/{}/tasks", account.id)) + let resp = get(format!("/api/accounts/{}/tasks", account.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } } @@ -188,7 +188,7 @@ mod create { let (leader, helper) = fixtures::aggregator_pair(&app, &account).await; let collector_credential = fixtures::collector_credential(&app, &account).await; - let mut conn = post(format!("/api/accounts/{}/tasks", account.id)) + let resp = post(format!("/api/accounts/{}/tasks", account.id)) .with_api_headers() .with_state(user) .with_request_json(valid_task_json(&collector_credential, &leader, &helper)) @@ -199,8 +199,8 @@ mod create { let [helper_provisioning, leader_provisioning] = &logs[..] else { panic!("expected exactly two requests"); }; - let helper_task_create = helper_provisioning.state.get::().unwrap(); - let leader_task_create = leader_provisioning.state.get::().unwrap(); + let helper_task_create: TaskCreate = helper_provisioning.request_json(); + let leader_task_create: TaskCreate = leader_provisioning.request_json(); assert_eq!( leader_task_create .collector_auth_token_hash @@ -211,8 +211,8 @@ mod create { ); assert!(helper_task_create.collector_auth_token_hash.is_none()); - assert_response!(conn, 201); - let task: Task = conn.response_json().await; + assert_response!(resp, 201); + let task: Task = resp.response_json(); assert_eq!(task.leader_aggregator_id, leader.id); assert_eq!(task.helper_aggregator_id, helper.id); @@ -237,7 +237,7 @@ mod create { leader.features = ActiveValue::Set(Features::default().into()); let leader = leader.update(app.db()).await?; - let mut conn = post(format!("/api/accounts/{}/tasks", account.id)) + let resp = post(format!("/api/accounts/{}/tasks", account.id)) .with_api_headers() .with_state(user) .with_request_json(valid_task_json(&collector_credential, &leader, &helper)) @@ -248,14 +248,14 @@ mod create { let [helper_provisioning, leader_provisioning] = &logs[..] else { panic!("expected exactly two requests"); }; - let helper_task_create = helper_provisioning.state.get::().unwrap(); - let leader_task_create = leader_provisioning.state.get::().unwrap(); + let helper_task_create: TaskCreate = helper_provisioning.request_json(); + let leader_task_create: TaskCreate = leader_provisioning.request_json(); assert!(leader_task_create.collector_auth_token_hash.is_none()); assert!(helper_task_create.collector_auth_token_hash.is_none()); - assert_response!(conn, 201); - let task: Task = conn.response_json().await; + assert_response!(resp, 201); + let task: Task = resp.response_json(); assert_eq!(task.leader_aggregator_id, leader.id); assert_eq!(task.helper_aggregator_id, helper.id); @@ -277,15 +277,15 @@ mod create { let collector_credential = fixtures::collector_credential(&app, &account).await; let leader = leader.tombstone().update(app.db()).await.unwrap(); - let mut conn = post(format!("/api/accounts/{}/tasks", account.id)) + let resp = post(format!("/api/accounts/{}/tasks", account.id)) .with_api_headers() .with_state(user) .with_request_json(valid_task_json(&collector_credential, &leader, &helper)) .run_async(&app) .await; - assert_response!(conn, 400); - let error: Value = conn.response_json().await; + assert_response!(resp, StatusCode::BAD_REQUEST); + let error: Value = resp.response_json(); assert!(error.get("leader_aggregator_id").is_some()); assert!(client_logs.is_empty()); Ok(()) @@ -301,15 +301,15 @@ mod create { let collector_credential = fixtures::collector_credential(&app, &account).await; let helper = helper.tombstone().update(app.db()).await.unwrap(); - let mut conn = post(format!("/api/accounts/{}/tasks", account.id)) + let resp = post(format!("/api/accounts/{}/tasks", account.id)) .with_api_headers() .with_state(user) .with_request_json(valid_task_json(&collector_credential, &leader, &helper)) .run_async(&app) .await; - assert_response!(conn, 400); - let error: Value = conn.response_json().await; + assert_response!(resp, StatusCode::BAD_REQUEST); + let error: Value = resp.response_json(); assert!(error.get("helper_aggregator_id").is_some()); assert!(client_logs.is_empty()); Ok(()) @@ -319,7 +319,7 @@ mod create { async fn invalid(app: DivviupApi) -> TestResult { let (user, account, ..) = fixtures::member(&app).await; - let mut conn = post(format!("/api/accounts/{}/tasks", account.id)) + let resp = post(format!("/api/accounts/{}/tasks", account.id)) .with_api_headers() .with_state(user) .with_request_json(json!({ @@ -332,8 +332,8 @@ mod create { .run_async(&app) .await; - assert_response!(conn, 400); - let error: Value = conn.response_json().await; + assert_response!(resp, StatusCode::BAD_REQUEST); + let error: Value = resp.response_json(); assert!(error.get("vdaf").is_some()); assert!(error.get("min_batch_size").is_some()); assert!(error.get("time_precision_seconds").is_some()); @@ -348,14 +348,14 @@ mod create { let (leader, helper) = fixtures::aggregator_pair(&app, &account).await; let collector_credential = fixtures::collector_credential(&app, &account).await; - let conn = post(format!("/api/accounts/{}/tasks", account.id)) + let resp = post(format!("/api/accounts/{}/tasks", account.id)) .with_api_headers() .with_state(user) .with_request_json(valid_task_json(&collector_credential, &leader, &helper)) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } @@ -367,14 +367,14 @@ mod create { let (leader, helper) = fixtures::aggregator_pair(&app, &account).await; let collector_credential = fixtures::collector_credential(&app, &account).await; - let mut conn = post("/api/accounts/does-not-exist/tasks") + let resp = post("/api/accounts/does-not-exist/tasks") .with_api_headers() .with_state(user) .with_request_json(valid_task_json(&collector_credential, &leader, &helper)) .run_async(&app) .await; - assert_not_found!(conn); + assert_not_found!(resp); Ok(()) } @@ -386,15 +386,15 @@ mod create { let (leader, helper) = fixtures::aggregator_pair(&app, &account).await; let collector_credential = fixtures::collector_credential(&app, &account).await; - let mut conn = post(format!("/api/accounts/{}/tasks", account.id)) + let resp = post(format!("/api/accounts/{}/tasks", account.id)) .with_api_headers() .with_state(admin) .with_request_json(valid_task_json(&collector_credential, &leader, &helper)) .run_async(&app) .await; - assert_response!(conn, 201); - let task: Task = conn.response_json().await; + assert_response!(resp, 201); + let task: Task = resp.response_json(); assert!(task.reload(app.db()).await?.is_some()); Ok(()) } @@ -406,15 +406,15 @@ mod create { let (leader, helper) = fixtures::aggregator_pair(&app, &account).await; let collector_credential = fixtures::collector_credential(&app, &account).await; - let mut conn = post(format!("/api/accounts/{}/tasks", account.id)) + let resp = post(format!("/api/accounts/{}/tasks", account.id)) .with_api_headers() .with_auth_header(token) .with_request_json(valid_task_json(&collector_credential, &leader, &helper)) .run_async(&app) .await; - assert_response!(conn, 201); - let task: Task = conn.response_json().await; + assert_response!(resp, 201); + let task: Task = resp.response_json(); assert!(task.reload(app.db()).await?.is_some()); Ok(()) } @@ -426,16 +426,16 @@ mod create { let (leader, helper) = fixtures::aggregator_pair(&app, &account).await; let collector_credential = fixtures::collector_credential(&app, &account).await; let count_before = Tasks::find().count(app.db()).await?; - let mut conn = post(format!("/api/accounts/{}/tasks", account.id)) + let resp = post(format!("/api/accounts/{}/tasks", account.id)) .with_api_headers() .with_auth_header(token) .with_request_json(valid_task_json(&collector_credential, &leader, &helper)) .run_async(&app) .await; let count_after = Tasks::find().count(app.db()).await?; - assert_response!(conn, 201); + assert_response!(resp, 201); assert_eq!(count_before + 1, count_after); - let task: Task = conn.response_json().await; + let task: Task = resp.response_json(); assert!(task.reload(app.db()).await?.is_some()); Ok(()) } @@ -450,7 +450,7 @@ mod create { let collector_credential = fixtures::collector_credential(&app, &account).await; let count_before = Tasks::find().count(app.db()).await?; - let conn = post(format!("/api/accounts/{}/tasks", account.id)) + let resp = post(format!("/api/accounts/{}/tasks", account.id)) .with_api_headers() .with_auth_header(token) .with_request_json(valid_task_json(&collector_credential, &leader, &helper)) @@ -459,30 +459,27 @@ mod create { let count_after = Tasks::find().count(app.db()).await?; assert_eq!(count_before, count_after); - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } } mod show { use super::{assert_eq, test, *}; - use divviup_api::{ - entity::aggregator::{Feature, Features}, - FeatureFlags, - }; + use divviup_api::entity::aggregator::{Feature, Features}; use time::Duration; #[test(harness = set_up)] async fn as_member(app: DivviupApi) -> TestResult { let (user, account, ..) = fixtures::member(&app).await; let task = fixtures::task(&app, &account).await; - let mut conn = get(format!("/api/tasks/{}", task.id)) + let resp = get(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_ok!(conn); - let response_task: Task = conn.response_json().await; + assert_ok!(resp); + let response_task: Task = resp.response_json(); assert_eq!(response_task, task); Ok(()) } @@ -500,12 +497,12 @@ mod show { leader.update(app.db()).await?; let leader = task.leader_aggregator(app.db()).await?; - let mut conn = get(format!("/api/tasks/{}", task.id)) + let resp = get(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_state(user.clone()) .run_async(&app) .await; - assert_ok!(conn); + assert_ok!(resp); let aggregator_api_request = client_logs.last(); assert_eq!( @@ -517,17 +514,17 @@ mod show { ); let metrics: TaskUploadMetrics = aggregator_api_request.response_json(); - let response_task: Task = conn.response_json().await; + let response_task: Task = resp.response_json(); assert_eq!(metrics, response_task); assert!(response_task.updated_at > task.updated_at); - let mut conn = get(format!("/api/tasks/{}", task.id)) + let resp = get(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - let second_response_task: Task = conn.response_json().await; + let second_response_task: Task = resp.response_json(); assert_eq!(metrics, second_response_task); assert_eq!(second_response_task.updated_at, response_task.updated_at); @@ -535,23 +532,22 @@ mod show { } #[test(harness = with_client_logs)] - async fn metrics_refresh_disabled(app: DivviupApi, client_logs: ClientLogs) -> TestResult { + async fn no_metrics_refresh_without_aggregator_features( + app: DivviupApi, + client_logs: ClientLogs, + ) -> TestResult { let (user, account, ..) = fixtures::member(&app).await; let task = fixtures::task(&app, &account).await; let mut task = task.into_active_model(); task.updated_at = ActiveValue::Set(OffsetDateTime::now_utc() - Duration::minutes(10)); let task = task.update(app.db()).await?; - let conn = get(format!("/api/tasks/{}", task.id)) + let resp = get(format!("/api/tasks/{}", task.id)) .with_api_headers() - .with_state(FeatureFlags { - metrics_refresh_enabled: false, - ssrf_validation_enabled: false, - }) .with_state(user.clone()) .run_async(&app) .await; - assert_ok!(conn); + assert_ok!(resp); // Ensure the aggregator API was never contacted. assert!(client_logs.logs().is_empty()); @@ -564,12 +560,12 @@ mod show { let user = fixtures::user(); let account = fixtures::account(&app).await; let task = fixtures::task(&app, &account).await; - let conn = get(format!("/api/tasks/{}", task.id)) + let resp = get(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } @@ -578,13 +574,13 @@ mod show { let (admin, ..) = fixtures::admin(&app).await; let account = fixtures::account(&app).await; let task = fixtures::task(&app, &account).await; - let mut conn = get(format!("/api/tasks/{}", task.id)) + let resp = get(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_state(admin) .run_async(&app) .await; - assert_ok!(conn); - let response_task: Task = conn.response_json().await; + assert_ok!(resp); + let response_task: Task = resp.response_json(); assert_eq!(response_task, task); Ok(()) } @@ -592,12 +588,12 @@ mod show { #[test(harness = set_up)] async fn nonexistant_task(app: DivviupApi) -> TestResult { let user = fixtures::user(); - let conn = get("/api/tasks/some-made-up-id") + let resp = get("/api/tasks/some-made-up-id") .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } @@ -606,13 +602,13 @@ mod show { let token = fixtures::admin_token(&app).await; let account = fixtures::account(&app).await; let task = fixtures::task(&app, &account).await; - let mut conn = get(format!("/api/tasks/{}", task.id)) + let resp = get(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_ok!(conn); - let response_task: Task = conn.response_json().await; + assert_ok!(resp); + let response_task: Task = resp.response_json(); assert_eq!(response_task, task); Ok(()) } @@ -622,13 +618,13 @@ mod show { let account = fixtures::account(&app).await; let (_, token) = fixtures::api_token(&app, &account).await; let task = fixtures::task(&app, &account).await; - let mut conn = get(format!("/api/tasks/{}", task.id)) + let resp = get(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_ok!(conn); - let response_task: Task = conn.response_json().await; + assert_ok!(resp); + let response_task: Task = resp.response_json(); assert_eq!(response_task, task); Ok(()) } @@ -639,12 +635,12 @@ mod show { let (_, token) = fixtures::api_token(&app, &other_account).await; let account = fixtures::account(&app).await; let task = fixtures::task(&app, &account).await; - let conn = get(format!("/api/tasks/{}", task.id)) + let resp = get(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } } @@ -660,14 +656,14 @@ mod update { let task = fixtures::task(&app, &account).await; let new_name = format!("new name {}", fixtures::random_name()); - let mut conn = patch(format!("/api/tasks/{}", task.id)) + let resp = patch(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_request_json(json!({ "name": &new_name })) .with_state(user) .run_async(&app) .await; - assert_ok!(conn); - let response_task: Task = conn.response_json().await; + assert_ok!(resp); + let response_task: Task = resp.response_json(); assert_eq!(response_task.name, new_name); let task_reload = task.reload(app.db()).await?.unwrap(); @@ -685,14 +681,14 @@ mod update { let (user, account, ..) = fixtures::member(&app).await; let task = fixtures::task(&app, &account).await; - let mut conn = patch(format!("/api/tasks/{}", task.id)) + let resp = patch(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_request_json(json!({ "name": "" })) .with_state(user) .run_async(&app) .await; - assert_response!(conn, 400); - let errors: Value = conn.response_json().await; + assert_response!(resp, StatusCode::BAD_REQUEST); + let errors: Value = resp.response_json(); assert!(errors.get("name").is_some()); assert_eq!( @@ -710,14 +706,14 @@ mod update { // Set the expiration. let now = OffsetDateTime::now_utc(); - let mut conn = patch(format!("/api/tasks/{}", task.id)) + let resp = patch(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_request_json(json!({ "expiration": now.format(&Rfc3339).unwrap() })) .with_state(user.clone()) .run_async(&app) .await; - assert_ok!(conn); - let response_task: Task = conn.response_json().await; + assert_ok!(resp); + let response_task: Task = resp.response_json(); assert_eq!(response_task.expiration, Some(now)); let task_reload = task.reload(app.db()).await?.unwrap(); @@ -731,14 +727,14 @@ mod update { ); // Unset the expiration. - let mut conn = patch(format!("/api/tasks/{}", task.id)) + let resp = patch(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_request_json(json!({ "expiration": null })) .with_state(user.clone()) .run_async(&app) .await; - assert_ok!(conn); - let response_task: Task = conn.response_json().await; + assert_ok!(resp); + let response_task: Task = resp.response_json(); assert_eq!(response_task.expiration, None); let task_reload = task.reload(app.db()).await?.unwrap(); @@ -754,7 +750,7 @@ mod update { // Set both name and expiration. let now = OffsetDateTime::now_utc(); let new_name = format!("new name {}", fixtures::random_name()); - let mut conn = patch(format!("/api/tasks/{}", task.id)) + let resp = patch(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_request_json( json!({ "expiration": now.format(&Rfc3339).unwrap(), "name": new_name}), @@ -762,8 +758,8 @@ mod update { .with_state(user) .run_async(&app) .await; - assert_ok!(conn); - let response_task: Task = conn.response_json().await; + assert_ok!(resp); + let response_task: Task = resp.response_json(); assert_eq!(response_task.expiration, Some(now)); assert_eq!(response_task.name, new_name); let task_reload = task.reload(app.db()).await?.unwrap(); @@ -778,13 +774,13 @@ mod update { let (user, account, ..) = fixtures::member(&app).await; let task = fixtures::task(&app, &account).await; - let conn = patch(format!("/api/tasks/{}", task.id)) + let resp = patch(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_request_json(json!({ "expiration": "1969-01-01T00:00:00Z" })) .with_state(user) .run_async(&app) .await; - assert_eq!(conn.status(), Some(Status::BadRequest)); + assert_response!(resp, StatusCode::BAD_REQUEST); // Task should be unchanged. let task_reload = task.reload(app.db()).await?.unwrap(); @@ -799,14 +795,14 @@ mod update { let account = fixtures::account(&app).await; let task = fixtures::task(&app, &account).await; - let conn = patch(format!("/api/tasks/{}", task.id)) + let resp = patch(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_request_json(json!({ "name": "irrelevant" })) .with_state(user) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); assert_eq!( task.reload(app.db()).await?.unwrap().name, task.name // unchanged @@ -821,14 +817,14 @@ mod update { let task = fixtures::task(&app, &account).await; let new_name = format!("new name {}", fixtures::random_name()); - let mut conn = patch(format!("/api/tasks/{}", task.id)) + let resp = patch(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_request_json(json!({ "name": &new_name })) .with_state(admin) .run_async(&app) .await; - assert_ok!(conn); - let response_task: Task = conn.response_json().await; + assert_ok!(resp); + let response_task: Task = resp.response_json(); assert_eq!(response_task.name, new_name); assert_eq!(task.reload(app.db()).await?.unwrap().name, new_name); Ok(()) @@ -837,13 +833,13 @@ mod update { #[test(harness = set_up)] async fn nonexistant_task(app: DivviupApi) -> TestResult { let user = fixtures::user(); - let conn = patch("/api/tasks/not-a-task-id") + let resp = patch("/api/tasks/not-a-task-id") .with_api_headers() .with_request_json(json!({ "name": "irrelevant" })) .with_state(user) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } @@ -854,14 +850,14 @@ mod update { let task = fixtures::task(&app, &account).await; let new_name = format!("new name {}", fixtures::random_name()); - let mut conn = patch(format!("/api/tasks/{}", task.id)) + let resp = patch(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_request_json(json!({ "name": &new_name })) .with_auth_header(token) .run_async(&app) .await; - assert_ok!(conn); - let response_task: Task = conn.response_json().await; + assert_ok!(resp); + let response_task: Task = resp.response_json(); assert_eq!(response_task.name, new_name); assert_eq!(task.reload(app.db()).await?.unwrap().name, new_name); Ok(()) @@ -874,14 +870,14 @@ mod update { let task = fixtures::task(&app, &account).await; let new_name = format!("new name {}", fixtures::random_name()); - let mut conn = patch(format!("/api/tasks/{}", task.id)) + let resp = patch(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_request_json(json!({ "name": &new_name })) .with_auth_header(token) .run_async(&app) .await; - assert_ok!(conn); - let response_task: Task = conn.response_json().await; + assert_ok!(resp); + let response_task: Task = resp.response_json(); assert_eq!(response_task.name, new_name); assert_eq!(task.reload(app.db()).await?.unwrap().name, new_name); Ok(()) @@ -895,7 +891,7 @@ mod update { let task = fixtures::task(&app, &account).await; let name_before = task.name.clone(); let new_name = format!("new name {}", fixtures::random_name()); - let conn = patch(format!("/api/tasks/{}", task.id)) + let resp = patch(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_request_json(json!({ "name": &new_name })) .with_auth_header(token) @@ -903,15 +899,14 @@ mod update { .await; assert_eq!(task.reload(app.db()).await?.unwrap().name, name_before); - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } } mod delete { - use divviup_api::DivviupApi; + use axum::Router; use janus_messages::Time as JanusTime; - use test_support::tracing::install_test_trace_subscriber; use super::{assert_eq, test, *}; @@ -942,12 +937,12 @@ mod delete { let (user, account, ..) = fixtures::member(&app).await; let task = fixtures::task(&app, &account).await; - let conn = delete(format!("/api/tasks/{}", task.id)) + let resp = delete(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_state(user.clone()) .run_async(&app) .await; - assert_status!(conn, Status::NoContent); + assert_status!(resp, StatusCode::NO_CONTENT); let task_reload = task.reload(app.db()).await?.unwrap(); assert!(task_reload.expiration.is_some()); assert!(task_reload.expiration <= Some(OffsetDateTime::now_utc())); @@ -957,12 +952,12 @@ mod delete { // Ensure we don't do redundant work when deleting again. let client_logs_len = client_logs.len(); - let conn = delete(format!("/api/tasks/{}", task.id)) + let resp = delete(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_status!(conn, Status::NoContent); + assert_status!(resp, StatusCode::NO_CONTENT); let task_reload_2 = task.reload(app.db()).await?.unwrap(); assert_eq!(task_reload.deleted_at, task_reload_2.deleted_at); assert_eq!(client_logs_len, client_logs.len()); @@ -971,30 +966,19 @@ mod delete { #[tokio::test] async fn force() -> TestResult { - install_test_trace_subscriber(); - let client_logs = ClientLogs::default(); - let mut app = DivviupApi::new( - config(( - client_logs.clone(), - // Stub out aggregator API mocks to simulate a FUBAR aggregator. - |conn: Conn| async move { conn.with_status(Status::InternalServerError) }, - )) - .await, - ) - .await; - set_up_schema(app.db()).await; - let mut info = "testing".into(); - app.init(&mut info).await; + // Stub out aggregator API mocks to simulate a FUBAR aggregator. + let mock = Router::new().fallback(|| async { StatusCode::INTERNAL_SERVER_ERROR }); + let (app, client_logs) = build_test_app_with_mock(mock).await; let (user, account, ..) = fixtures::member(&app).await; let task = fixtures::task(&app, &account).await; - let conn = delete(format!("/api/tasks/{}?force=true", task.id)) + let resp = delete(format!("/api/tasks/{}?force=true", task.id)) .with_api_headers() .with_state(user.clone()) .run_async(&app) .await; - assert_status!(conn, Status::NoContent); + assert_status!(resp, StatusCode::NO_CONTENT); let task_reload = task.reload(app.db()).await?.unwrap(); assert!(task_reload.expiration.is_some()); assert!(task_reload.expiration <= Some(OffsetDateTime::now_utc())); @@ -1005,8 +989,8 @@ mod delete { let [leader, helper] = &logs[..] else { panic!("expected exactly two requests"); }; - assert_eq!(leader.response_status, Status::InternalServerError); - assert_eq!(helper.response_status, Status::InternalServerError); + assert_eq!(leader.response_status, StatusCode::INTERNAL_SERVER_ERROR); + assert_eq!(helper.response_status, StatusCode::INTERNAL_SERVER_ERROR); Ok(()) } @@ -1019,12 +1003,12 @@ mod delete { am.expiration = ActiveValue::Set(None); let task = am.update(app.db()).await.unwrap(); - let conn = delete(format!("/api/tasks/{}", task.id)) + let resp = delete(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_state(user.clone()) .run_async(&app) .await; - assert_status!(conn, Status::NoContent); + assert_status!(resp, StatusCode::NO_CONTENT); let task_reload = task.reload(app.db()).await?.unwrap(); assert!(task_reload.expiration.is_some()); assert!(task_reload.expiration <= Some(OffsetDateTime::now_utc())); @@ -1042,12 +1026,12 @@ mod delete { am.expiration = ActiveValue::Set(Some(OffsetDateTime::from_unix_timestamp(0).unwrap())); let task = am.update(app.db()).await.unwrap(); - let conn = delete(format!("/api/tasks/{}", task.id)) + let resp = delete(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_status!(conn, Status::NoContent); + assert_status!(resp, StatusCode::NO_CONTENT); let task_reload = task.reload(app.db()).await?.unwrap(); // Shouldn't re-expire an already expired task. assert_eq!(task_reload.expiration, task.expiration); @@ -1063,12 +1047,12 @@ mod delete { let account = fixtures::account(&app).await; let task = fixtures::task(&app, &account).await; - let conn = delete(format!("/api/tasks/{}", task.id)) + let resp = delete(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); let task_reload = task.reload(app.db()).await?.unwrap(); assert_eq!(task_reload.expiration, task.expiration); assert_eq!(task_reload.deleted_at, None); @@ -1081,12 +1065,12 @@ mod delete { let account = fixtures::account(&app).await; let task = fixtures::task(&app, &account).await; - let conn = delete(format!("/api/tasks/{}", task.id)) + let resp = delete(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_state(admin) .run_async(&app) .await; - assert_status!(conn, Status::NoContent); + assert_status!(resp, StatusCode::NO_CONTENT); let task_reload = task.reload(app.db()).await?.unwrap(); assert!(task_reload.expiration.is_some()); assert!(task_reload.expiration <= Some(OffsetDateTime::now_utc())); @@ -1098,12 +1082,12 @@ mod delete { #[test(harness = set_up)] async fn nonexistant_task(app: DivviupApi) -> TestResult { let user = fixtures::user(); - let conn = delete("/api/tasks/not-a-task-id") + let resp = delete("/api/tasks/not-a-task-id") .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } @@ -1113,12 +1097,12 @@ mod delete { let account = fixtures::account(&app).await; let task = fixtures::task(&app, &account).await; - let conn = delete(format!("/api/tasks/{}", task.id)) + let resp = delete(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_status!(conn, Status::NoContent); + assert_status!(resp, StatusCode::NO_CONTENT); let task_reload = task.reload(app.db()).await?.unwrap(); assert!(task_reload.expiration.is_some()); assert!(task_reload.expiration <= Some(OffsetDateTime::now_utc())); @@ -1133,12 +1117,12 @@ mod delete { let (_, token) = fixtures::api_token(&app, &account).await; let task = fixtures::task(&app, &account).await; - let conn = delete(format!("/api/tasks/{}", task.id)) + let resp = delete(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_status!(conn, Status::NoContent); + assert_status!(resp, StatusCode::NO_CONTENT); let task_reload = task.reload(app.db()).await?.unwrap(); assert!(task_reload.expiration.is_some()); assert!(task_reload.expiration <= Some(OffsetDateTime::now_utc())); @@ -1153,7 +1137,7 @@ mod delete { let (_, token) = fixtures::api_token(&app, &other_account).await; let account = fixtures::account(&app).await; let task = fixtures::task(&app, &account).await; - let conn = delete(format!("/api/tasks/{}", task.id)) + let resp = delete(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) @@ -1162,7 +1146,7 @@ mod delete { let task_reload = task.reload(app.db()).await?.unwrap(); assert_eq!(task_reload.expiration, task.expiration); assert_eq!(task_reload.deleted_at, None); - assert_response!(conn, 403); + assert_response!(resp, 403); Ok(()) } } diff --git a/tests/integration/tls_smoke_test.rs b/tests/integration/tls_smoke_test.rs index 31235559..cf0fe5ea 100644 --- a/tests/integration/tls_smoke_test.rs +++ b/tests/integration/tls_smoke_test.rs @@ -1,45 +1,62 @@ use rcgen::generate_simple_self_signed; -use test_support::{assert_eq, *}; -use tokio::{net::TcpListener, spawn}; -use trillium_client::Client; -use trillium_http::Stopper; -use trillium_rustls::{rustls::RootCertStore, RustlsAcceptor, RustlsConfig}; -use trillium_tokio::ClientConfig; +use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer}; +use std::sync::Arc; +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + net::TcpListener, +}; +use tokio_rustls::TlsAcceptor; #[tokio::test] async fn https_connection() { // Choose aws-lc-rs as the default rustls crypto provider. This is what's currently enabled by // the default Cargo feature. Specifying a default provider here prevents runtime errors if // another dependency also enables the ring feature. - let _ = trillium_rustls::rustls::crypto::aws_lc_rs::default_provider().install_default(); + let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); let self_signed = generate_simple_self_signed(["localhost".into()]).unwrap(); - let stopper = Stopper::new(); - let listener = TcpListener::bind("localhost:0").await.unwrap(); - let local_addr = listener.local_addr().unwrap(); - let server_config = trillium_tokio::config() - .with_acceptor(RustlsAcceptor::from_single_cert( - self_signed.cert.pem().as_bytes(), - self_signed.signing_key.serialize_pem().as_bytes(), - )) - .without_signals() - .with_stopper(stopper.clone()) - .with_prebound_server(listener); - spawn(server_config.run_async(|conn: Conn| async { conn.ok("") })); - - let mut root_store = RootCertStore::empty(); - root_store.add_parsable_certificates([self_signed.cert.der().clone()]); - - let client = Client::new(RustlsConfig::new( - trillium_rustls::rustls::ClientConfig::builder() - .with_root_certificates(root_store) - .with_no_client_auth(), - ClientConfig::default(), + let cert_der = CertificateDer::from(self_signed.cert.der().to_vec()); + let key_der = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from( + self_signed.signing_key.serialize_der(), )); - let url = format!("https://localhost:{}/", local_addr.port()); - let conn = client.get(url).await.unwrap(); - assert_status!(conn, 200); - stopper.stop(); + let mut server_config = rustls::ServerConfig::builder() + .with_no_client_auth() + .with_single_cert(vec![cert_der.clone()], key_der) + .unwrap(); + server_config.alpn_protocols = vec![b"http/1.1".to_vec()]; + let tls_acceptor = TlsAcceptor::from(Arc::new(server_config)); + + // Bind to "localhost" so the resolved address matches the loopback IP that + // reqwest and rcgen use for hostname verification. The hostname in the + // listener doesn't need to match the cert's SAN — only the hostname passed + // to reqwest and rcgen matters — but "localhost" ensures we listen on + // whichever loopback address the OS resolves it to. + let listener = TcpListener::bind("localhost:0").await.unwrap(); + let port = listener.local_addr().unwrap().port(); + + tokio::spawn(async move { + let (tcp_stream, _) = listener.accept().await.unwrap(); + let mut tls_stream = tls_acceptor.accept(tcp_stream).await.unwrap(); + let mut buf = vec![0u8; 4096]; + let _ = tls_stream.read(&mut buf).await.unwrap(); + let response = b"HTTP/1.1 200 OK\r\ncontent-length: 0\r\nconnection: close\r\n\r\n"; + tls_stream.write_all(response).await.unwrap(); + tls_stream.shutdown().await.unwrap(); + }); + + let root_cert = reqwest::tls::Certificate::from_der(cert_der.as_ref()).unwrap(); + let client = reqwest::Client::builder() + .add_root_certificate(root_cert) + .http1_only() + .build() + .unwrap(); + + let resp = client + .get(format!("https://localhost:{port}/")) + .send() + .await + .unwrap(); + assert_eq!(resp.status(), 200); } diff --git a/tests/integration/users.rs b/tests/integration/users.rs index 2606e6bf..7df00f3a 100644 --- a/tests/integration/users.rs +++ b/tests/integration/users.rs @@ -6,13 +6,13 @@ mod get_users_me { #[test(harness = set_up)] async fn as_a_logged_in_user(app: DivviupApi) -> TestResult { let user = fixtures::user(); - let mut conn = get("/api/users/me") + let resp = get("/api/users/me") .with_api_headers() .with_state(user.clone()) .run_async(&app) .await; - let mut response_user: User = conn.response_json().await; + let mut response_user: User = resp.response_json(); assert!(!response_user.is_admin()); response_user.admin = None; // for equality comparison @@ -23,13 +23,13 @@ mod get_users_me { #[test(harness = set_up)] async fn as_an_admin(app: DivviupApi) -> TestResult { let (admin, ..) = fixtures::admin(&app).await; - let mut conn = get("/api/users/me") + let resp = get("/api/users/me") .with_api_headers() .with_state(admin.clone()) .run_async(&app) .await; - let mut response_user: User = conn.response_json().await; + let mut response_user: User = resp.response_json(); assert!(response_user.is_admin()); response_user.admin = None; // for equality comparison @@ -40,13 +40,13 @@ mod get_users_me { #[test(harness = set_up)] async fn as_integration_testing_user(app: DivviupApi) -> TestResult { let user = User::for_integration_testing(); - let mut conn = get("/api/users/me") + let resp = get("/api/users/me") .with_api_headers() .with_state(user.clone()) .run_async(&app) .await; - let response_user: User = conn.response_json().await; + let response_user: User = resp.response_json(); assert!(response_user.is_admin()); assert_eq!(user, response_user);