From be4bc4ce2c7deee159ee8c3591957678f97c3f3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Ma=C4=87kowski?= Date: Tue, 27 Jan 2026 23:27:13 +0100 Subject: [PATCH] refactor: use TestContainers --- .github/workflows/rust.yml | 6 - CONTRIBUTING.md | 31 +- Cargo.lock | 1677 ++++++++++++++++- Cargo.toml | 2 + README.md | 9 +- clippy.toml | 2 +- compose.yml | 48 - .../src/project_template/Cargo.toml.template | 3 + ...ve_into_response_missing_trait_impl.stderr | 2 +- cot/Cargo.toml | 6 +- cot/src/cache/store/redis.rs | 44 +- cot/src/session/store/redis.rs | 27 +- cot/src/test.rs | 495 ++--- cot/tests/admin.rs | 34 +- justfile | 10 +- 15 files changed, 1962 insertions(+), 434 deletions(-) delete mode 100644 compose.yml diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 6bd46139..97e497bf 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -173,9 +173,6 @@ jobs: - name: Build run: cargo build --all-features --tests - - name: Run the external dependencies - run: docker compose up -d --wait - - name: Test run: cargo nextest run --all-features --run-ignored only @@ -221,9 +218,6 @@ jobs: - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.9 - - name: Run the external dependencies - run: docker compose up -d - - name: Install cargo-llvm-cov uses: taiki-e/install-action@cargo-llvm-cov diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7d85c149..7c49fc21 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,18 +46,11 @@ instructions. This handles formatting of all the files in the repository. ### Tests that use database, cache, or other external resources Some tests use a database, cache, or other external resources. All these tests -are marked with `#[ignore]`, so they are not run by default. +are marked with `#[ignore]`, but they will automatically start the necessary +containers using [Testcontainers](https://testcontainers.org/) if you have +Docker or a similar container runtime installed. -If you want to run the full test suite, it's necessary to run these external -dependencies. For convenience, Cot provides a -[Docker compose file](./compose.yml) in the root of the repository that -contains all the dependencies needed to run the tests. You can run it with: - -```sh -docker compose up -d -``` - -Then, the tests can be run with: +If you want to run the full test suite, you can run: ```sh cargo test --all-features --include-ignored @@ -66,17 +59,11 @@ cargo test --all-features --include-ignored #### End-to-end tests End-to-end tests require a running webdriver server. By default, a Selenium -Grid server is used (included in the `compose.yml` file). You can access the -UI to see the tests running (for example, to debug them) at -`http://localhost:7900/?autoconnect=1&resize=scale&password=secret`. - -Alternatively, instead of using Selenium Grid, you can run the tests with -a local webdriver server. To do this, you need to install and run the -Webdriver implementation of your choice, such as -[geckodriver](https://github.com/mozilla/geckodriver/releases) or -[chromedriver](https://developer.chrome.com/docs/chromedriver/downloads). -After running the webdriver server, you will see the tests running in a -local browser window. +container is automatically started using Testcontainers. + +Alternatively, you can run the tests with a local webdriver server. To do this, +you need to set the `WEBDRIVER_URL` environment variable to the URL of your +local webdriver server (e.g., `http://localhost:4444`). ### Snapshot tests diff --git a/Cargo.lock b/Cargo.lock index 7797efff..0154ae32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,41 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array 0.14.7", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "ahash" version = "0.8.12" @@ -45,8 +80,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6966317188cdfe54c58c0900a195d021294afb3ece9b7073d09e4018dbb1e3a2" dependencies = [ "cfg-if", - "indexmap", - "schemars", + "indexmap 2.13.0", + "schemars 0.9.0", "serde", "serde_json", "thiserror 2.0.18", @@ -119,7 +154,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -130,7 +165,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -222,6 +257,22 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "astral-tokio-tar" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec179a06c1769b1e42e1e2cbe74c7dcdb3d6383c838454d063eaac5bbb7ebbe5" +dependencies = [ + "filetime", + "futures-core", + "libc", + "portable-atomic", + "rustc-hash", + "tokio", + "tokio-stream", + "xattr", +] + [[package]] name = "async-channel" version = "1.9.0" @@ -487,6 +538,18 @@ dependencies = [ "windows-link", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -499,6 +562,17 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "bcrypt-pbkdf" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aeac2e1fe888769f34f05ac343bbef98b14d1ffb292ab69d4608b3abc86f2a2" +dependencies = [ + "blowfish", + "pbkdf2", + "sha2", +] + [[package]] name = "bitflags" version = "2.10.0" @@ -523,7 +597,16 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array", + "generic-array 0.14.7", +] + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array 0.14.7", ] [[package]] @@ -539,6 +622,93 @@ dependencies = [ "piper", ] +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher", +] + +[[package]] +name = "bollard" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87a52479c9237eb04047ddb94788c41ca0d26eaff8b697ecfbb4c32f7fdc3b1b" +dependencies = [ + "async-stream", + "base64 0.22.1", + "bitflags", + "bollard-buildkit-proto", + "bollard-stubs", + "bytes", + "chrono", + "futures-core", + "futures-util", + "hex", + "home", + "http 1.4.0", + "http-body-util", + "hyper", + "hyper-named-pipe", + "hyper-rustls", + "hyper-util", + "hyperlocal", + "log", + "num", + "pin-project-lite", + "rand 0.9.2", + "rustls", + "rustls-native-certs", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_derive", + "serde_json", + "serde_repr", + "serde_urlencoded", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-util", + "tonic", + "tower-service", + "url", + "winapi", +] + +[[package]] +name = "bollard-buildkit-proto" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a885520bf6249ab931a764ffdb87b0ceef48e6e7d807cfdb21b751e086e1ad" +dependencies = [ + "prost", + "prost-types", + "tonic", + "tonic-prost", + "ureq", +] + +[[package]] +name = "bollard-stubs" +version = "1.49.1-rc.28.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5731fe885755e92beff1950774068e0cae67ea6ec7587381536fca84f1779623" +dependencies = [ + "base64 0.22.1", + "bollard-buildkit-proto", + "bytes", + "chrono", + "prost", + "serde", + "serde_json", + "serde_repr", + "serde_with", +] + [[package]] name = "bstr" version = "1.12.1" @@ -584,11 +754,20 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" -version = "1.2.54" +version = "1.2.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ "find-msvc-tools", "shlex", @@ -606,6 +785,23 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "chrono" version = "0.4.43" @@ -667,6 +863,16 @@ dependencies = [ "half", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" version = "4.5.56" @@ -846,6 +1052,17 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core-models" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0940496e5c83c54f3b753d5317daec82e8edac71c33aaa1f666d76f518de2444" +dependencies = [ + "hax-lib", + "pastey", + "rand 0.9.2", +] + [[package]] name = "core_maths" version = "0.1.1" @@ -889,7 +1106,7 @@ dependencies = [ "http-body-util", "humantime", "idna", - "indexmap", + "indexmap 2.13.0", "lettre", "mime", "mime_guess", @@ -900,7 +1117,7 @@ dependencies = [ "redis", "reqwest", "rustversion", - "schemars", + "schemars 0.9.0", "sea-query", "sea-query-binder", "serde", @@ -911,6 +1128,8 @@ dependencies = [ "subtle", "swagger-ui-redist", "tempfile", + "testcontainers", + "testcontainers-modules", "thiserror 2.0.18", "time", "tokio", @@ -989,7 +1208,7 @@ dependencies = [ "http 1.4.0", "http-body", "http-body-util", - "indexmap", + "indexmap 2.13.0", "serde", "serde_html_form", "serde_json", @@ -1054,7 +1273,7 @@ dependencies = [ "ciborium", "clap", "criterion-plot", - "itertools", + "itertools 0.13.0", "num-traits", "oorandom", "page_size", @@ -1075,7 +1294,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed943f81ea2faa8dcecbbfa50164acf95d555afec96a27871663b300e387b2e4" dependencies = [ "cast", - "itertools", + "itertools 0.13.0", ] [[package]] @@ -1118,16 +1337,64 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array 0.14.7", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ - "generic-array", + "generic-array 0.14.7", "typenum", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "darling" version = "0.20.11" @@ -1138,6 +1405,16 @@ dependencies = [ "darling_macro 0.20.11", ] +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", +] + [[package]] name = "darling" version = "0.23.0" @@ -1162,6 +1439,20 @@ dependencies = [ "syn", ] +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + [[package]] name = "darling_core" version = "0.23.0" @@ -1186,6 +1477,17 @@ dependencies = [ "syn", ] +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", + "quote", + "syn", +] + [[package]] name = "darling_macro" version = "0.23.0" @@ -1197,6 +1499,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + [[package]] name = "deadpool" version = "0.12.3" @@ -1228,6 +1536,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "delegate" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "der" version = "0.7.10" @@ -1344,6 +1663,17 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" +[[package]] +name = "docker_credential" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d89dfcba45b4afad7450a99b39e751590463e45c04728cf555d36bb66940de8" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + [[package]] name = "dotenvy" version = "0.15.7" @@ -1375,48 +1705,120 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] -name = "either" -version = "1.15.0" +name = "ecdsa" +version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ - "serde", + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", ] [[package]] -name = "email-encoding" -version = "0.4.1" +name = "ed25519" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9298e6504d9b9e780ed3f7dfd43a61be8cd0e09eb07f7706a945b0072b6670b6" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ - "base64", - "memchr", + "pkcs8", + "signature", ] [[package]] -name = "email_address" -version = "0.2.9" +name = "ed25519-dalek" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.6.4", "serde", + "sha2", + "subtle", + "zeroize", ] [[package]] -name = "encode_unicode" -version = "1.0.0" +name = "either" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] [[package]] -name = "encoding_rs" -version = "0.8.35" +name = "elliptic-curve" +version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array 0.14.7", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "email-encoding" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9298e6504d9b9e780ed3f7dfd43a61be8cd0e09eb07f7706a945b0072b6670b6" +dependencies = [ + "base64 0.22.1", + "memchr", +] + +[[package]] +name = "email_address" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" +dependencies = [ + "serde", +] + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1430,7 +1832,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1444,6 +1846,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "etcetera" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de48cc4d1c1d97a20fd819def54b890cadde72ed3ad0c614822a0a433361be96" +dependencies = [ + "cfg-if", + "windows-sys 0.61.2", +] + [[package]] name = "event-listener" version = "2.5.3" @@ -1499,7 +1911,7 @@ dependencies = [ name = "example-file-upload" version = "0.1.0" dependencies = [ - "base64", + "base64 0.22.1", "cot", ] @@ -1515,7 +1927,7 @@ name = "example-json" version = "0.1.0" dependencies = [ "cot", - "schemars", + "schemars 0.9.0", "serde", ] @@ -1560,7 +1972,7 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d0086bcd59795408c87a04f94b5a8bd62cba2856cfe656c7e6439061d95b760" dependencies = [ - "base64", + "base64 0.22.1", "cookie 0.18.1", "futures-util", "http 1.4.0", @@ -1584,11 +1996,49 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "ferroid" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb330bbd4cb7a5b9f559427f06f98a4f853a137c8298f3bd3f8ca57663e21986" +dependencies = [ + "portable-atomic", + "rand 0.9.2", + "web-time", +] + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "filetime" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +dependencies = [ + "cfg-if", + "libc", + "libredox", +] + [[package]] name = "find-msvc-tools" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fixedbitset" @@ -1666,6 +2116,7 @@ checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -1758,6 +2209,7 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -1777,6 +2229,18 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", +] + +[[package]] +name = "generic-array" +version = "1.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf57c49a95fd1fe24b90b3033bee6dc7e8f1288d51494cb44e627c295e38542" +dependencies = [ + "generic-array 0.14.7", + "rustversion", + "typenum", ] [[package]] @@ -1804,6 +2268,16 @@ dependencies = [ "wasip2", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gimli" version = "0.32.3" @@ -1845,12 +2319,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d9e3df7f0222ce5184154973d247c591d9aadc28ce7a73c6cd31100c9facff6" dependencies = [ "codemap", - "indexmap", + "indexmap 2.13.0", "lasso", "once_cell", "phf 0.11.3", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.4.0", + "indexmap 2.13.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "2.7.1" @@ -1862,6 +2366,12 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.14.5" @@ -1898,6 +2408,43 @@ dependencies = [ "hashbrown 0.15.5", ] +[[package]] +name = "hax-lib" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d9ba66d1739c68e0219b2b2238b5c4145f491ebf181b9c6ab561a19352ae86" +dependencies = [ + "hax-lib-macros", + "num-bigint", + "num-traits", +] + +[[package]] +name = "hax-lib-macros" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ba777a231a58d1bce1d68313fa6b6afcc7966adef23d60f45b8a2b9b688bf1" +dependencies = [ + "hax-lib-macros-types", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "hax-lib-macros-types" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "867e19177d7425140b417cd27c2e05320e727ee682e98368f88b7194e80ad515" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "serde_json", + "uuid", +] + [[package]] name = "heck" version = "0.5.0" @@ -1916,6 +2463,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + [[package]] name = "hkdf" version = "0.12.4" @@ -2015,6 +2568,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2", "http 1.4.0", "http-body", "httparse", @@ -2027,6 +2581,50 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-named-pipe" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" +dependencies = [ + "hex", + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", + "winapi", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http 1.4.0", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "hyper-tls" version = "0.6.0" @@ -2049,7 +2647,7 @@ version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-channel", "futures-core", @@ -2067,6 +2665,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "hyperlocal" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7" +dependencies = [ + "hex", + "http-body-util", + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "iana-time-zone" version = "0.1.65" @@ -2273,6 +2886,17 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.13.0" @@ -2296,6 +2920,16 @@ dependencies = [ "syn", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array 0.14.7", +] + [[package]] name = "insta" version = "1.46.2" @@ -2321,6 +2955,33 @@ dependencies = [ "serde_json", ] +[[package]] +name = "internal-russh-forked-ssh-key" +version = "0.6.11+upstream-0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a77eae781ed6a7709fb15b64862fcca13d886b07c7e2786f5ed34e5e2b9187" +dependencies = [ + "argon2", + "bcrypt-pbkdf", + "ecdsa", + "ed25519-dalek", + "hex", + "hmac", + "p256", + "p384", + "p521", + "rand_core 0.6.4", + "rsa", + "sec1", + "sha1", + "sha2", + "signature", + "ssh-cipher", + "ssh-encoding", + "subtle", + "zeroize", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -2352,6 +3013,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.17" @@ -2425,7 +3095,7 @@ checksum = "9e13e10e8818f8b2a60f52cb127041d388b89f3a96a62be9ceaffa22262fef7f" dependencies = [ "async-std", "async-trait", - "base64", + "base64 0.22.1", "chumsky", "email-encoding", "email_address", @@ -2453,18 +3123,84 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] -name = "libm" -version = "0.2.16" +name = "libcrux-intrinsics" +version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" +checksum = "bc9ee7ef66569dd7516454fe26de4e401c0c62073929803486b96744594b9632" +dependencies = [ + "core-models", + "hax-lib", +] [[package]] -name = "libredox" -version = "0.1.12" +name = "libcrux-ml-kem" +version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "4bb6a88086bf11bd2ec90926c749c4a427f2e59841437dbdede8cde8a96334ab" dependencies = [ - "bitflags", + "hax-lib", + "libcrux-intrinsics", + "libcrux-platform", + "libcrux-secrets", + "libcrux-sha3", + "libcrux-traits", + "rand 0.9.2", + "tls_codec", +] + +[[package]] +name = "libcrux-platform" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db82d058aa76ea315a3b2092f69dfbd67ddb0e462038a206e1dcd73f058c0778" +dependencies = [ + "libc", +] + +[[package]] +name = "libcrux-secrets" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4dbbf6bc9f2bc0f20dc3bea3e5c99adff3bdccf6d2a40488963da69e2ec307" +dependencies = [ + "hax-lib", +] + +[[package]] +name = "libcrux-sha3" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2400bec764d1c75b8a496d5747cffe32f1fb864a12577f0aca2f55a92021c962" +dependencies = [ + "hax-lib", + "libcrux-intrinsics", + "libcrux-platform", + "libcrux-traits", +] + +[[package]] +name = "libcrux-traits" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9adfd58e79d860f6b9e40e35127bfae9e5bd3ade33201d1347459011a2add034" +dependencies = [ + "libcrux-secrets", + "rand 0.9.2", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +dependencies = [ + "bitflags", "libc", "redox_syscall 0.7.0", ] @@ -2541,6 +3277,12 @@ dependencies = [ "digest", ] +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + [[package]] name = "memchr" version = "2.7.6" @@ -2643,6 +3385,18 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nom" version = "8.0.0" @@ -2658,7 +3412,21 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", ] [[package]] @@ -2669,6 +3437,7 @@ checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", + "rand 0.8.5", ] [[package]] @@ -2687,6 +3456,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-conv" version = "0.2.0" @@ -2713,6 +3491,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -2760,6 +3549,12 @@ version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "openssl" version = "0.10.75" @@ -2810,6 +3605,44 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p521" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" +dependencies = [ + "base16ct", + "ecdsa", + "elliptic-curve", + "primeorder", + "rand_core 0.6.4", + "sha2", +] + [[package]] name = "page_size" version = "0.6.0" @@ -2820,6 +3653,25 @@ dependencies = [ "winapi", ] +[[package]] +name = "pageant" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b537f975f6d8dcf48db368d7ec209d583b015713b5df0f5d92d2631e4ff5595" +dependencies = [ + "byteorder", + "bytes", + "delegate", + "futures", + "log", + "rand 0.8.5", + "sha2", + "thiserror 1.0.69", + "tokio", + "windows", + "windows-strings", +] + [[package]] name = "parking" version = "2.2.1" @@ -2849,6 +3701,31 @@ dependencies = [ "windows-link", ] +[[package]] +name = "parse-display" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914a1c2265c98e2446911282c6ac86d8524f495792c38c5bd884f80499c7538a" +dependencies = [ + "parse-display-derive", + "regex", + "regex-syntax", +] + +[[package]] +name = "parse-display-derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae7800a4c974efd12df917266338e79a7a74415173caf7e70aa0a0707345281" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "regex-syntax", + "structmeta", + "syn", +] + [[package]] name = "password-auth" version = "1.0.0" @@ -2872,6 +3749,22 @@ dependencies = [ "subtle", ] +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -2895,7 +3788,7 @@ checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset", "hashbrown 0.15.5", - "indexmap", + "indexmap 2.13.0", ] [[package]] @@ -2958,6 +3851,26 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -2992,6 +3905,21 @@ dependencies = [ "spki", ] +[[package]] +name = "pkcs5" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6" +dependencies = [ + "aes", + "cbc", + "der", + "pbkdf2", + "scrypt", + "sha2", + "spki", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -2999,6 +3927,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ "der", + "pkcs5", + "rand_core 0.6.4", "spki", ] @@ -3050,6 +3980,35 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + [[package]] name = "potential_utf" version = "0.1.4" @@ -3111,6 +4070,15 @@ dependencies = [ "syn", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro-crate" version = "3.4.0" @@ -3120,6 +4088,28 @@ dependencies = [ "toml_edit", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -3129,6 +4119,38 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" +dependencies = [ + "prost", +] + [[package]] name = "psm" version = "0.1.29" @@ -3333,7 +4355,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-core", "http 1.4.0", @@ -3358,6 +4380,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.17.14" @@ -3392,12 +4424,97 @@ dependencies = [ "pkcs1", "pkcs8", "rand_core 0.6.4", + "sha2", "signature", "spki", "subtle", "zeroize", ] +[[package]] +name = "russh" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b4d036bb45d7bbe99dbfef4ec60eaeb614708d22ff107124272f8ef6b54548" +dependencies = [ + "aes", + "bitflags", + "block-padding", + "byteorder", + "bytes", + "cbc", + "ctr", + "curve25519-dalek", + "data-encoding", + "delegate", + "der", + "digest", + "ecdsa", + "ed25519-dalek", + "elliptic-curve", + "enum_dispatch", + "futures", + "generic-array 1.3.5", + "getrandom 0.2.17", + "hex-literal", + "hmac", + "home", + "inout", + "internal-russh-forked-ssh-key", + "libcrux-ml-kem", + "log", + "md5", + "num-bigint", + "p256", + "p384", + "p521", + "pageant", + "pbkdf2", + "pkcs5", + "pkcs8", + "rand 0.8.5", + "rand_core 0.6.4", + "ring", + "russh-cryptovec", + "russh-util", + "sec1", + "sha1", + "sha2", + "signature", + "spki", + "ssh-encoding", + "subtle", + "thiserror 1.0.69", + "tokio", + "typenum", + "zeroize", +] + +[[package]] +name = "russh-cryptovec" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb0ed583ff0f6b4aa44c7867dd7108df01b30571ee9423e250b4cc939f8c6cf" +dependencies = [ + "libc", + "log", + "nix", + "ssh-encoding", + "winapi", +] + +[[package]] +name = "russh-util" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668424a5dde0bcb45b55ba7de8476b93831b4aa2fa6947e145f3b053e22c60b6" +dependencies = [ + "chrono", + "tokio", + "wasm-bindgen", + "wasm-bindgen-futures", +] + [[package]] name = "rustc-demangle" version = "0.1.27" @@ -3429,7 +4546,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -3459,6 +4576,15 @@ dependencies = [ "security-framework 3.5.1", ] +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "rustls-pki-types" version = "1.14.0" @@ -3486,7 +4612,7 @@ dependencies = [ "security-framework 3.5.1", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -3518,6 +4644,15 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + [[package]] name = "same-file" version = "1.0.6" @@ -3543,13 +4678,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" dependencies = [ "dyn-clone", - "indexmap", + "indexmap 2.13.0", "ref-cast", "schemars_derive", "serde", "serde_json", ] +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "schemars_derive" version = "0.9.0" @@ -3568,6 +4715,17 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "pbkdf2", + "salsa20", + "sha2", +] + [[package]] name = "sea-query" version = "0.32.7" @@ -3582,11 +4740,25 @@ dependencies = [ name = "sea-query-binder" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0019f47430f7995af63deda77e238c17323359af241233ec768aba1faea7608" +checksum = "b0019f47430f7995af63deda77e238c17323359af241233ec768aba1faea7608" +dependencies = [ + "chrono", + "sea-query", + "sqlx", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ - "chrono", - "sea-query", - "sqlx", + "base16ct", + "der", + "generic-array 0.14.7", + "pkcs8", + "subtle", + "zeroize", ] [[package]] @@ -3679,7 +4851,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0946d52b4b7e28823148aebbeceb901012c595ad737920d504fa8634bb099e6f" dependencies = [ "form_urlencoded", - "indexmap", + "indexmap 2.13.0", "serde_core", ] @@ -3707,6 +4879,17 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_spanned" version = "1.0.4" @@ -3728,6 +4911,37 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.13.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +dependencies = [ + "darling 0.21.3", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sha1" version = "0.10.6" @@ -3799,9 +5013,9 @@ checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" @@ -3860,7 +5074,7 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "chrono", "crc", @@ -3873,7 +5087,7 @@ dependencies = [ "futures-util", "hashbrown 0.15.5", "hashlink", - "indexmap", + "indexmap 2.13.0", "log", "memchr", "once_cell", @@ -3934,7 +5148,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", - "base64", + "base64 0.22.1", "bitflags", "byteorder", "bytes", @@ -3947,7 +5161,7 @@ dependencies = [ "futures-core", "futures-io", "futures-util", - "generic-array", + "generic-array 0.14.7", "hex", "hkdf", "hmac", @@ -3977,13 +5191,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", - "base64", + "base64 0.22.1", "bitflags", "byteorder", "chrono", "crc", "dotenvy", - "etcetera", + "etcetera 0.8.0", "futures-channel", "futures-core", "futures-util", @@ -4033,6 +5247,35 @@ dependencies = [ "url", ] +[[package]] +name = "ssh-cipher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caac132742f0d33c3af65bfcde7f6aa8f62f0e991d80db99149eb9d44708784f" +dependencies = [ + "aes", + "aes-gcm", + "cbc", + "chacha20", + "cipher", + "ctr", + "poly1305", + "ssh-encoding", + "subtle", +] + +[[package]] +name = "ssh-encoding" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9242b9ef4108a78e8cd1a2c98e193ef372437f8c22be363075233321dd4a15" +dependencies = [ + "base64ct", + "bytes", + "pem-rfc7468", + "sha2", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -4049,7 +5292,6 @@ dependencies = [ "cfg-if", "libc", "psm", - "windows-sys 0.52.0", "windows-sys 0.59.0", ] @@ -4070,6 +5312,29 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "structmeta" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e1575d8d40908d70f6fd05537266b90ae71b15dbbe7a8b7dffa2b759306d329" +dependencies = [ + "proc-macro2", + "quote", + "structmeta-derive", + "syn", +] + +[[package]] +name = "structmeta-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "subtle" version = "2.6.1" @@ -4133,7 +5398,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4161,6 +5426,46 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" +[[package]] +name = "testcontainers" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a81ec0158db5fbb9831e09d1813fe5ea9023a2b5e6e8e0a5fe67e2a820733629" +dependencies = [ + "astral-tokio-tar", + "async-trait", + "bollard", + "bytes", + "docker_credential", + "either", + "etcetera 0.11.0", + "ferroid", + "futures", + "itertools 0.14.0", + "log", + "memchr", + "parse-display", + "pin-project-lite", + "russh", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-util", + "url", +] + +[[package]] +name = "testcontainers-modules" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e75e78ff453128a2c7da9a5d5a3325ea34ea214d4bf51eab3417de23a4e5147" +dependencies = [ + "testcontainers", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -4285,6 +5590,27 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tls_codec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de2e01245e2bb89d6f05801c564fa27624dbd7b1846859876c7dad82e90bf6b" +dependencies = [ + "tls_codec_derive", + "zeroize", +] + +[[package]] +name = "tls_codec_derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tokio" version = "1.49.0" @@ -4362,7 +5688,7 @@ version = "0.9.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" dependencies = [ - "indexmap", + "indexmap 2.13.0", "serde_core", "serde_spanned", "toml_datetime", @@ -4386,7 +5712,7 @@ version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ - "indexmap", + "indexmap 2.13.0", "toml_datetime", "toml_parser", "winnow", @@ -4407,6 +5733,46 @@ version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" +[[package]] +name = "tonic" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a286e33f82f8a1ee2df63f4fa35c0becf4a85a0cb03091a15fd7bf0b402dc94a" +dependencies = [ + "async-trait", + "axum", + "base64 0.22.1", + "bytes", + "h2", + "http 1.4.0", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "socket2", + "sync_wrapper", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-prost" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6c55a2d6a14174563de34409c9f92ff981d006f56da9c6ecd40d9d4a31500b0" +dependencies = [ + "bytes", + "prost", + "tonic", +] + [[package]] name = "tower" version = "0.5.3" @@ -4415,11 +5781,15 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", + "indexmap 2.13.0", "pin-project-lite", + "slab", "sync_wrapper", "tokio", + "tokio-util", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -4507,7 +5877,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "568531ec3dfcf3ffe493de1958ae5662a0284ac5d767476ecdb6a34ff8c6b06c" dependencies = [ "async-trait", - "base64", + "base64 0.22.1", "futures", "http 1.4.0", "parking_lot", @@ -4688,12 +6058,50 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d39cb1dbab692d82a977c0392ffac19e188bd9186a9f32806f0aaa859d75585a" +dependencies = [ + "base64 0.22.1", + "log", + "percent-encoding", + "rustls", + "rustls-pki-types", + "ureq-proto", + "utf-8", + "webpki-roots", +] + +[[package]] +name = "ureq-proto" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f" +dependencies = [ + "base64 0.22.1", + "http 1.4.0", + "httparse", + "log", +] + [[package]] name = "url" version = "2.5.8" @@ -4707,6 +6115,12 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -4719,6 +6133,17 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" @@ -4861,13 +6286,23 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webdriver" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91d53921e1bef27512fa358179c9a22428d55778d2c2ae3c5c37a52b82ce6e92" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "cookie 0.16.2", "http 0.2.12", @@ -4890,6 +6325,15 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "webpki-roots" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "whoami" version = "1.6.1" @@ -4922,7 +6366,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4931,6 +6375,27 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core", +] + [[package]] name = "windows-core" version = "0.62.2" @@ -4944,6 +6409,17 @@ dependencies = [ "windows-strings", ] +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + [[package]] name = "windows-implement" version = "0.60.2" @@ -4972,6 +6448,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core", + "windows-link", +] + [[package]] name = "windows-result" version = "0.4.1" @@ -5107,6 +6593,15 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -5314,6 +6809,16 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + [[package]] name = "yoke" version = "0.7.5" @@ -5363,18 +6868,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.35" +version = "0.8.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdea86ddd5568519879b8187e1cf04e24fce28f7fe046ceecbce472ff19a2572" +checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.35" +version = "0.8.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c15e1b46eff7c6c91195752e0eeed8ef040e391cdece7c25376957d5f15df22" +checksum = "1328722bbf2115db7e19d69ebcc15e795719e2d66b60827c6a69a117365e37a0" dependencies = [ "proc-macro2", "quote", @@ -5407,6 +6912,20 @@ name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "zerotrie" @@ -5465,6 +6984,6 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.17" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" +checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" diff --git a/Cargo.toml b/Cargo.toml index 7d5b93e9..cc55a80b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -137,6 +137,8 @@ swagger-ui-redist = { version = "0.1" } syn = { version = "2", default-features = false } sync_wrapper = "1" tempfile = "3" +testcontainers = { version = "0.26", default-features = false } +testcontainers-modules = { version = "0.14", default-features = false } thiserror = "2" time = { version = "0.3.46", default-features = false } tokio = { version = "1.49", default-features = false } diff --git a/README.md b/README.md index d13af3ac..b78b2f67 100644 --- a/README.md +++ b/README.md @@ -83,14 +83,7 @@ Tests that require using external databases are ignored by default. In order to root of the repository: ```shell -docker compose up -d -cargo test --all-features -- --include-ignored -``` - -You can them execute the following command to stop the database: - -```shell -docker compose down +cargo nextest run --all-features --run-ignored only ``` ## Star History diff --git a/clippy.toml b/clippy.toml index 938f67e3..3561b339 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,2 +1,2 @@ avoid-breaking-exported-api = false -doc-valid-idents = ["PostgreSQL", "MySQL", "SQLite", "OpenAPI", "RESTful", ".."] +doc-valid-idents = ["PostgreSQL", "MySQL", "SQLite", "OpenAPI", "RESTful", "WebDriver", ".."] diff --git a/compose.yml b/compose.yml deleted file mode 100644 index 0e2c7dcf..00000000 --- a/compose.yml +++ /dev/null @@ -1,48 +0,0 @@ -services: - mariadb: - image: docker.io/mariadb:11 - container_name: cot-mariadb - environment: - MARIADB_DATABASE: mysql - MARIADB_USER: cot - MARIADB_PASSWORD: cot - MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: 1 - ports: - - "3306:3306" - healthcheck: - test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] - interval: 5s - timeout: 5s - retries: 5 - - postgres: - image: docker.io/postgres:17-alpine - container_name: cot-postgres - environment: - POSTGRES_USER: cot - POSTGRES_PASSWORD: cot - ports: - - "5432:5432" - healthcheck: - test: ["CMD", "pg_isready"] - interval: 5s - timeout: 5s - retries: 5 - - redis: - image: redis:8-alpine - container_name: cot-redis - command: ["redis-server", "--databases", "100"] - ports: - - "6379:6379" - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 10s - timeout: 5s - retries: 5 - - webdriver: - image: docker.io/selenium/standalone-firefox:4.31.0-20250414 - container_name: cot-webdriver - shm_size: 2gb - network_mode: host diff --git a/cot-cli/src/project_template/Cargo.toml.template b/cot-cli/src/project_template/Cargo.toml.template index e3633035..0bc41f04 100644 --- a/cot-cli/src/project_template/Cargo.toml.template +++ b/cot-cli/src/project_template/Cargo.toml.template @@ -5,3 +5,6 @@ edition = "2024" [dependencies] cot = { {{ cot_source }}, features = ["full"] } + +[dev-dependencies] +cot = { {{ cot_source }}, features = ["test"] } diff --git a/cot-macros/tests/ui/derive_into_response_missing_trait_impl.stderr b/cot-macros/tests/ui/derive_into_response_missing_trait_impl.stderr index 018ccc7c..261ce45a 100644 --- a/cot-macros/tests/ui/derive_into_response_missing_trait_impl.stderr +++ b/cot-macros/tests/ui/derive_into_response_missing_trait_impl.stderr @@ -2,7 +2,7 @@ error[E0599]: no method named `into_response` found for struct `Dummy` in the cu --> tests/ui/derive_into_response_missing_trait_impl.rs:3:10 | 3 | #[derive(IntoResponse)] - | ^^^^^^^^^^^^ method not found in `Dummy` + | ^^^^^^^^^^^^ ... 8 | struct Dummy; | ------------ method `into_response` not found for this struct diff --git a/cot/Cargo.toml b/cot/Cargo.toml index f12f86d3..eb76c568 100644 --- a/cot/Cargo.toml +++ b/cot/Cargo.toml @@ -60,6 +60,8 @@ sqlx = { workspace = true, features = ["runtime-tokio", "chrono"], optional = tr subtle = { workspace = true, features = ["std"] } swagger-ui-redist = { workspace = true, optional = true } thiserror.workspace = true +testcontainers = { workspace = true, optional = true, features = ["host-port-exposure"] } +testcontainers-modules = { workspace = true, optional = true, features = ["postgres", "mariadb", "redis", "selenium"] } time.workspace = true tokio = { workspace = true, features = ["macros", "rt-multi-thread", "signal", "fs", "io-util"] } toml = { workspace = true, features = ["parse", "serde"] } @@ -104,7 +106,7 @@ ignored = [ [features] default = ["sqlite", "postgres", "mysql", "json"] -full = ["default", "fake", "live-reload", "test", "cache", "redis", "email"] +full = ["default", "fake", "live-reload", "cache", "redis", "email"] fake = ["dep:fake"] db = ["dep:sea-query", "dep:sea-query-binder", "dep:sqlx"] email = ["dep:lettre", "dep:chumsky", "dep:idna"] @@ -117,7 +119,7 @@ openapi = ["json", "dep:aide", "dep:schemars"] swagger-ui = ["openapi", "dep:swagger-ui-redist"] live-reload = ["dep:tower-livereload"] cache = ["json"] -test = [] +test = ["dep:testcontainers", "dep:testcontainers-modules"] [lib] bench = false diff --git a/cot/src/cache/store/redis.rs b/cot/src/cache/store/redis.rs index 305f8b6d..de71a7d9 100644 --- a/cot/src/cache/store/redis.rs +++ b/cot/src/cache/store/redis.rs @@ -236,25 +236,27 @@ impl CacheStore for Redis { #[cfg(test)] mod tests { - use std::env; use std::time::Duration; use serde_json::json; + use testcontainers::ContainerAsync; use super::*; use crate::config::Timeout; + use crate::test::run_redis_container; - async fn make_store(db: &str) -> Redis { - let redis_url = - env::var("REDIS_URL").unwrap_or_else(|_| "redis://127.0.0.1:6379/".to_string()); - let mut url = CacheUrl::from(redis_url); - url.inner_mut().set_path(db); + async fn make_store() -> (ContainerAsync, Redis) { + let (container, url) = run_redis_container() + .await + .expect("failed to run redis container"); + + let url = CacheUrl::from(url); let store = Redis::new(&url, 16).expect("failed to create redis store"); store .get_connection() .await .expect("failed to get redis connection"); - store + (container, store) } #[cot::test] @@ -264,9 +266,9 @@ mod tests { } #[cot::test] - #[ignore = "requires a running redis instance"] + #[ignore = "requires external Redis service"] async fn test_insert_and_get() { - let store = make_store("1").await; + let (_container, store) = make_store().await; let key = "test_key".to_string(); let value = json!({"data": 123}); @@ -279,9 +281,9 @@ mod tests { } #[cot::test] - #[ignore = "requires a running redis instance"] + #[ignore = "requires external Redis service"] async fn test_get_after_expiry() { - let store = make_store("1").await; + let (_container, store) = make_store().await; let key = "temp_key__".to_string(); let value = json!({"data": "temporary"}); let short_timeout = Timeout::After(Duration::from_secs(1)); @@ -295,9 +297,9 @@ mod tests { } #[cot::test] - #[ignore = "requires a running redis instance"] + #[ignore = "requires external Redis service"] async fn test_insert_with_expiry_types() { - let store = make_store("1").await; + let (_container, store) = make_store().await; macro_rules! run_expiry { ($idx:expr, $timeout:expr) => { @@ -335,9 +337,9 @@ mod tests { } #[cot::test] - #[ignore = "requires a running redis instance"] + #[ignore = "requires external Redis service"] async fn test_remove() { - let store = make_store("1").await; + let (_container, store) = make_store().await; let key = "test_key_remove".to_string(); let value = json!({"data": 123}); store @@ -350,9 +352,9 @@ mod tests { } #[cot::test] - #[ignore = "requires a running redis instance"] + #[ignore = "requires external Redis service"] async fn test_clear() { - let store = make_store("2").await; + let (_container, store) = make_store().await; store .insert("key1".to_string(), json!(1), Timeout::default()) .await @@ -367,9 +369,9 @@ mod tests { } #[cot::test] - #[ignore = "requires a running redis instance"] + #[ignore = "requires external Redis service"] async fn test_contains_key() { - let store = make_store("1").await; + let (_container, store) = make_store().await; let key = "test_key".to_string(); let value = json!({"data": 123}); store @@ -382,9 +384,9 @@ mod tests { } #[cot::test] - #[ignore = "requires a running redis instance"] + #[ignore = "requires external Redis service"] async fn test_approx_size() { - let store = make_store("3").await; + let (_container, store) = make_store().await; store.clear().await.unwrap(); store .insert("key1".to_string(), json!(1), Timeout::default()) diff --git a/cot/src/session/store/redis.rs b/cot/src/session/store/redis.rs index b6fa15fd..7a2b2ac5 100644 --- a/cot/src/session/store/redis.rs +++ b/cot/src/session/store/redis.rs @@ -234,20 +234,25 @@ impl SessionStore for RedisStore { #[cfg(test)] mod tests { use std::collections::HashMap; - use std::{env, io}; + use std::io; + use cot::test::run_redis_container; + use testcontainers::ContainerAsync; + use testcontainers_modules::redis::Redis; use time::{Duration, OffsetDateTime}; use tower_sessions::session::{Id, Record}; use super::*; use crate::config::CacheUrl; - async fn make_store() -> RedisStore { - let redis_url = - env::var("REDIS_URL").unwrap_or_else(|_| "redis://127.0.0.1:6379".to_string()); - let url = CacheUrl::from(redis_url); + async fn make_store() -> (ContainerAsync, RedisStore) { + let (container, url) = run_redis_container() + .await + .expect("failed to run redis container"); + + let url = CacheUrl::from(url); let store = RedisStore::new(&url).expect("failed to create RedisStore"); store.get_connection().await.expect("get_connection failed"); - store + (container, store) } fn make_record() -> Record { @@ -261,7 +266,7 @@ mod tests { #[cot::test] #[ignore = "requires external Redis service"] async fn test_create_and_load() { - let store = make_store().await; + let (_container, store) = make_store().await; let mut rec = make_record(); store.create(&mut rec).await.expect("create failed"); @@ -272,7 +277,7 @@ mod tests { #[cot::test] #[ignore = "requires external Redis service"] async fn test_save_overwrites() { - let store = make_store().await; + let (_container, store) = make_store().await; let mut rec = make_record(); store.create(&mut rec).await.unwrap(); @@ -287,7 +292,7 @@ mod tests { #[cot::test] #[ignore = "requires external Redis service"] async fn test_save_creates_if_missing() { - let store = make_store().await; + let (_container, store) = make_store().await; let rec = make_record(); store.save(&rec).await.expect("save failed"); @@ -299,7 +304,7 @@ mod tests { #[cot::test] #[ignore = "requires external Redis service"] async fn test_delete() { - let store = make_store().await; + let (_container, store) = make_store().await; let mut rec = make_record(); store.create(&mut rec).await.unwrap(); @@ -313,7 +318,7 @@ mod tests { #[cot::test] #[ignore = "requires external Redis service"] async fn test_create_id_collision() { - let store = make_store().await; + let (_container, store) = make_store().await; let expiry = OffsetDateTime::now_utc() + Duration::minutes(30); let mut r1 = Record { diff --git a/cot/src/test.rs b/cot/src/test.rs index e9448be3..9b8d6a66 100644 --- a/cot/src/test.rs +++ b/cot/src/test.rs @@ -9,13 +9,12 @@ use std::sync::Arc; use async_trait::async_trait; #[cfg(feature = "cache")] use cot::config::CacheUrl; -#[cfg(feature = "redis")] -use cot_core::error::impl_into_cot_error; use cot_core::handler::BoxedHandler; -#[cfg(feature = "redis")] -use deadpool_redis::Connection; -#[cfg(feature = "redis")] -use redis::AsyncCommands; +use cot_core::impl_into_cot_error; +use testcontainers::runners::AsyncRunner; +use testcontainers::{ContainerAsync, ImageExt}; +use testcontainers_modules::redis::Redis; +use thiserror::Error; use tokio::net::TcpListener; use tokio::sync::oneshot; use tower::Service; @@ -28,8 +27,6 @@ use crate::auth::{Auth, AuthBackend, NoAuthBackend, User, UserId}; use crate::cache::Cache; #[cfg(feature = "cache")] use crate::cache::store::memory::Memory; -#[cfg(feature = "redis")] -use crate::cache::store::redis::Redis; use crate::config::ProjectConfig; #[cfg(feature = "cache")] use crate::config::Timeout; @@ -950,8 +947,30 @@ impl TestDatabase { /// # } /// ``` pub async fn new_postgres(test_name: &str) -> Result { - let db_url = std::env::var("POSTGRES_URL") - .unwrap_or_else(|_| "postgresql://cot:cot@localhost".to_string()); + let (db_url, container) = if let Ok(db_url) = std::env::var("POSTGRES_URL") { + (db_url, None) + } else { + use testcontainers_modules::postgres::Postgres; + + const POSTGRES_PORT: u16 = 5432; + + let container = Postgres::default() + .start() + .await + .map_err(|e| TestcontainersError::new("failed to start PostgreSQL container", e))?; + let host_port = container + .get_host_port_ipv4(POSTGRES_PORT) + .await + .map_err(|e| { + TestcontainersError::new("failed to get PostgreSQL container port", e) + })?; + let host_port: u16 = host_port; + ( + format!("postgresql://postgres:postgres@localhost:{host_port}"), + Some(Box::new(container)), + ) + }; + let database = Database::new(format!("{db_url}/postgres")).await?; let test_database_name = format!("test_cot__{test_name}"); @@ -970,6 +989,7 @@ impl TestDatabase { TestDatabaseKind::Postgres { db_url, db_name: test_database_name, + _container: container, }, )) } @@ -1018,8 +1038,28 @@ impl TestDatabase { /// # } /// ``` pub async fn new_mysql(test_name: &str) -> Result { - let db_url = - std::env::var("MYSQL_URL").unwrap_or_else(|_| "mysql://root:@localhost".to_string()); + let (db_url, container) = if let Ok(db_url) = std::env::var("MYSQL_URL") { + (db_url, None) + } else { + use testcontainers_modules::mariadb::Mariadb; + + const MYSQL_PORT: u16 = 3306; + + let container = Mariadb::default() + .start() + .await + .map_err(|e| TestcontainersError::new("failed to start MariaDB container", e))?; + let host_port = container + .get_host_port_ipv4(MYSQL_PORT) + .await + .map_err(|e| TestcontainersError::new("failed to get MariaDB container port", e))?; + let host_port: u16 = host_port; + ( + format!("mysql://root:@localhost:{host_port}"), + Some(Box::new(container)), + ) + }; + let database = Database::new(format!("{db_url}/mysql")).await?; let test_database_name = format!("test_cot__{test_name}"); @@ -1038,6 +1078,7 @@ impl TestDatabase { TestDatabaseKind::MySql { db_url, db_name: test_database_name, + _container: container, }, )) } @@ -1190,15 +1231,26 @@ impl TestDatabase { self.database.close().await?; match &self.kind { TestDatabaseKind::Sqlite => {} - TestDatabaseKind::Postgres { db_url, db_name } => { + TestDatabaseKind::Postgres { + db_url, db_name, .. + } => { let database = Database::new(format!("{db_url}/postgres")).await?; database - .raw(&format!("DROP DATABASE {db_name} WITH (FORCE)")) + .raw(&format!( + "SELECT pg_terminate_backend(pg_stat_activity.pid) \ + FROM pg_stat_activity \ + WHERE pg_stat_activity.datname = '{db_name}' \ + AND pid <> pg_backend_pid()" + )) .await?; + + database.raw(&format!("DROP DATABASE {db_name}")).await?; database.close().await?; } - TestDatabaseKind::MySql { db_url, db_name } => { + TestDatabaseKind::MySql { + db_url, db_name, .. + } => { let database = Database::new(format!("{db_url}/mysql")).await?; database.raw(&format!("DROP DATABASE {db_name}")).await?; @@ -1220,11 +1272,19 @@ impl std::ops::Deref for TestDatabase { } #[cfg(feature = "db")] -#[derive(Debug, Clone)] +#[derive(Debug)] enum TestDatabaseKind { Sqlite, - Postgres { db_url: String, db_name: String }, - MySql { db_url: String, db_name: String }, + Postgres { + db_url: String, + db_name: String, + _container: Option>>, + }, + MySql { + db_url: String, + db_name: String, + _container: Option>>, + }, } /// A test migration. @@ -1439,6 +1499,7 @@ impl TestServerBuilder { #[derive(Debug)] pub struct TestServer { address: SocketAddr, + host: Option, channel_send: oneshot::Sender<()>, server_handle: tokio::task::JoinHandle<()>, project: PhantomData T>, @@ -1472,6 +1533,7 @@ impl TestServer { Self { address, + host: None, channel_send: send, server_handle, project: PhantomData, @@ -1515,7 +1577,8 @@ impl TestServer { /// server (127.0.0.1) and not the public address of the machine. This might /// be a problem if you are making requests from a different machine or a /// Docker container. If you need to override the host returned by this - /// function, you can set the `COT_TEST_SERVER_HOST` environment variable. + /// function, you can set the `COT_TEST_SERVER_HOST` environment variable + /// or call [`Self::set_host`]. /// /// # Examples /// @@ -1538,7 +1601,9 @@ impl TestServer { /// ``` #[must_use] pub fn url(&self) -> String { - if let Ok(host) = std::env::var("COT_TEST_SERVER_HOST") { + if let Some(host) = &self.host { + format!("http://{}:{}", host, self.address.port()) + } else if let Ok(host) = std::env::var("COT_TEST_SERVER_HOST") { format!("http://{}:{}", host, self.address.port()) } else { format!("http://{}", self.address) @@ -1581,188 +1646,154 @@ impl TestServer { } } -/// A guard for running tests serially. +/// A test Webdriver container. /// -/// This is mostly useful for tests that need to modify some global state (e.g. -/// environment variables or current working directory). -#[doc(hidden)] // not part of the public API; used in cot-cli -pub fn serial_guard() -> std::sync::MutexGuard<'static, ()> { - static LOCK: std::sync::OnceLock> = std::sync::OnceLock::new(); - let lock = LOCK.get_or_init(|| std::sync::Mutex::new(())); - match lock.lock() { - Ok(guard) => guard, - Err(poison_error) => { - lock.clear_poison(); - // We can ignore poisoned mutexes because we don't store any data inside - poison_error.into_inner() - } - } +/// This is used to start a Webdriver container for end-to-end tests using +/// Testcontainers. +#[derive(Debug)] +pub struct TestWebDriver { + _container: ContainerAsync, + host_port: u16, } -#[cfg(feature = "redis")] -const POOL_KEY: &str = "cot:test:db_pool"; +impl TestWebDriver { + /// Create a new Webdriver container. + /// + /// # Errors + /// + /// Returns an error if the container could not be started. + pub async fn new() -> Result { + Self::new_impl(None).await + } -#[cfg(feature = "redis")] -async fn get_db_num(conn: &mut Connection) -> usize { - let cfg = redis::cmd("CONFIG") - .arg("GET") - .arg("databases") - .query_async::>(conn) - .await - .expect("Failed to get Redis config"); - cfg.get(1) - .and_then(|s| s.parse::().ok()) - .unwrap_or(16) -} + /// Create a new Webdriver container and expose a host port to it. + /// + /// This is useful if you want to reach a service running on the host from + /// the container (e.g. the Cot server). The host will be reachable at + /// `host.testcontainers.internal`. + /// + /// # Errors + /// + /// Returns an error if the container could not be started. + pub async fn with_host_port_exposure(port: u16) -> Result { + Self::new_impl(Some(port)).await + } -#[cfg(feature = "redis")] -async fn set_current_db(conn: &mut Connection, db_num: usize) { - redis::cmd("SELECT") - .arg(db_num) - .query_async::<()>(conn) - .await - .expect("Failed to select Redis database"); -} + async fn new_impl(port: Option) -> Result { + use testcontainers_modules::selenium::Selenium; -#[cfg(feature = "redis")] -#[derive(Debug, thiserror::Error)] -#[non_exhaustive] -enum RedisDbAllocatorError { - #[error(transparent)] - Io(#[from] std::io::Error), - #[error("Redis error: {0}")] - Redis(String), -} + const WEBDRIVER_PORT: u16 = 4444; -#[cfg(feature = "redis")] -impl_into_cot_error!(RedisDbAllocatorError); + let selenium = Selenium::default(); -#[cfg(feature = "redis")] -#[derive(Debug, Clone)] -struct RedisDbAllocator { - alloc_db: usize, - redis: Redis, -} + let container = if let Some(port) = port { + selenium.with_exposed_host_port(port).start().await + } else { + selenium.start().await + } + .map_err(|e| TestcontainersError::new("failed to start Selenium container", e))?; -#[cfg(feature = "redis")] -type RedisAllocatorResult = std::result::Result; -#[cfg(feature = "redis")] -impl RedisDbAllocator { - fn new(alloc_db: usize, redis: Redis) -> Self { - Self { alloc_db, redis } + let host_port = container + .get_host_port_ipv4(WEBDRIVER_PORT) + .await + .map_err(|e| TestcontainersError::new("failed to get Selenium container port", e))?; + + Ok(Self { + _container: container, + host_port, + }) } - async fn get_conn(&self) -> RedisAllocatorResult { - let conn = self - .redis - .get_connection() - .await - .map_err(|err| RedisDbAllocatorError::Redis(err.to_string()))?; - Ok(conn) + /// Get the Webdriver URL. + #[must_use] + pub fn url(&self) -> String { + format!("http://localhost:{}", self.host_port) } +} - /// Initialize the Redis database allocator. - /// - /// The goal here is to ensure that DB IDs are initialized once. - /// Since we run tests using `nextest`, the tests are run per process. - /// Thus, we run this in a transaction to guarantee a deterministic - /// behavior. - /// - /// On initializing the IDs, we check for the existence of an "init" key in - /// the DB. If the key does not exist, or if the length of the pool list - /// does not match the expected count, we reinitialize the pool by - /// populating it with database indices from 1 to `alloc_db - 1`. - async fn init(&self) -> RedisAllocatorResult> { - const KEY_TIMEOUT_SECS: u64 = 300; - const INIT_KEY: &str = "cot:test:db_pool:initialized"; +/// A test server with a WebDriver instance. +/// +/// This struct wraps a `TestServer` and manages a WebDriver instance for +/// running end-to-end tests. It automatically handles the lifecycle of the +/// WebDriver container or connects to an external WebDriver if configured. +#[derive(Debug)] +pub struct TestServerWithWebDriver { + server: TestServer, + inner: TestServerWithWebDriverImpl, +} - let mut con = self.get_conn().await?; - let last_eligible_db = self.alloc_db - 1; +#[derive(Debug)] +enum TestServerWithWebDriverImpl { + Testcontainers { test_web_driver: Box }, + External { web_driver_url: String }, +} - redis::cmd("WATCH") - .arg(INIT_KEY) - .query_async::(&mut con) - .await - .map_err(|err| RedisDbAllocatorError::Redis(err.to_string()))?; +impl TestServerWithWebDriver { + /// Creates a new `TestServerWithWebDriver` instance. + /// + /// This will start a new WebDriver container (using `testcontainers`) + /// unless the `COT_WEBDRIVER_URL` environment variable is set, in which + /// case it will use the provided URL. + /// + /// # Errors + /// + /// Returns an error if the WebDriver container fails to start. + pub async fn new(server: TestServer) -> Result { + let inner = if let Ok(web_driver_url) = std::env::var("COT_WEBDRIVER_URL") { + TestServerWithWebDriverImpl::External { web_driver_url } + } else { + let test_web_driver = + TestWebDriver::with_host_port_exposure(server.address.port()).await?; + TestServerWithWebDriverImpl::Testcontainers { + test_web_driver: Box::new(test_web_driver), + } + }; - let prev = redis::cmd("GET") - .arg(INIT_KEY) - .query_async::>(&mut con) - .await - .map_err(|err| RedisDbAllocatorError::Redis(err.to_string()))?; + Ok(Self { server, inner }) + } - if prev.is_some() { - redis::cmd("UNWATCH") - .query_async::(&mut con) - .await - .map_err(|err| RedisDbAllocatorError::Redis(err.to_string()))?; - return Ok(prev); + /// Returns the URL of the WebDriver instance. + #[must_use] + pub fn web_driver_url(&self) -> String { + match &self.inner { + TestServerWithWebDriverImpl::Testcontainers { test_web_driver } => { + test_web_driver.url() + } + TestServerWithWebDriverImpl::External { web_driver_url } => web_driver_url.clone(), } + } - // start a transaction so this is atomic across processes - redis::cmd("MULTI") - .query_async::(&mut con) - .await - .map_err(|err| RedisDbAllocatorError::Redis(err.to_string()))?; - - let mut set_cmd = redis::cmd("SET"); - set_cmd.arg(INIT_KEY).arg("1"); - set_cmd.arg("EX").arg(KEY_TIMEOUT_SECS); - set_cmd - .query_async::(&mut con) - .await - .map_err(|err| RedisDbAllocatorError::Redis(err.to_string()))?; - - // delete and reinit IDs - redis::cmd("DEL") - .arg(POOL_KEY) - .query_async::(&mut con) - .await - .map_err(|err| RedisDbAllocatorError::Redis(err.to_string()))?; - - let vals: Vec = (1..=last_eligible_db).map(|i| i.to_string()).collect(); - redis::cmd("RPUSH") - .arg(POOL_KEY) - .arg(vals) - .query_async::(&mut con) - .await - .map_err(|err| RedisDbAllocatorError::Redis(err.to_string()))?; - - // keys should expire after a short while, a double defense against reuse by - // subsequent runs - redis::cmd("EXPIRE") - .arg(POOL_KEY) - .arg(KEY_TIMEOUT_SECS) - .query_async::(&mut con) - .await - .map_err(|err| RedisDbAllocatorError::Redis(err.to_string()))?; - - redis::cmd("EXEC") - .query_async::>>(&mut con) - .await - .map_err(|err| RedisDbAllocatorError::Redis(err.to_string()))?; - Ok(None) + /// Returns the URL of the test server accessible from the WebDriver. + /// + /// If running inside a container, this will use the host's internal + /// address. Otherwise, it uses `localhost`. + #[must_use] + pub fn server_url(&self) -> String { + let host = match &self.inner { + TestServerWithWebDriverImpl::Testcontainers { .. } => "host.testcontainers.internal", + TestServerWithWebDriverImpl::External { .. } => "localhost", + }; + format!("http://{}:{}", host, self.server.address.port()) } - async fn allocate(&self) -> RedisAllocatorResult> { - let mut connection = self.get_conn().await?; + /// Returns a reference to the underlying `TestServer`. + pub fn server(&self) -> &TestServer { + &self.server + } - let db_index: Option = connection - .lpop(POOL_KEY, None) - .await - .map_err(|err| RedisDbAllocatorError::Redis(err.to_string()))?; - Ok(db_index.and_then(|i| i.parse::().ok())) + /// Closes the test server and cleans up resources. + pub async fn close(self) { + self.server.close().await; } } #[cfg(feature = "cache")] -#[derive(Debug, Clone)] +#[derive(Debug)] enum CacheKind { Memory, #[cfg(feature = "redis")] Redis { - #[expect(unused)] - allocator: RedisDbAllocator, + _container: Option>>, }, } @@ -1786,7 +1817,7 @@ enum CacheKind { /// # } /// ``` #[cfg(feature = "cache")] -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct TestCache { cache: Cache, kind: CacheKind, @@ -1827,25 +1858,12 @@ impl TestCache { /// Create a new Redis test cache. /// - /// The Redis URL is read from the `REDIS_URL` environment variable. If not - /// provided, it defaults to `redis://localhost`. - /// - /// Running with redis makes use of an internal allocator that selects what - /// DB a test will run. Every test requires its own database to avoid - /// conflicts. The allocator, by design, will reserve the last database - /// number for allocation purposes, so make sure your Redis instance is - /// configured with at least 2 databases. For example if your redis - /// instance has 16 logical databases, database 15 will be used for - /// allocations, and databases 0-14 will be used for tests. + /// This starts a new Redis container for the test. /// /// # Errors /// /// Returns an error if the Redis cache could not be created. /// - /// # Panics - /// - /// Panics if Redis is not configured with at least 2 databases. - /// /// # Examples /// /// ```no_run @@ -1863,39 +1881,19 @@ impl TestCache { /// ``` #[cfg(feature = "redis")] pub async fn new_redis() -> Result { - let url = std::env::var("REDIS_URL").unwrap_or_else(|_| "redis://localhost".to_string()); - let mut url = CacheUrl::from(url); - - let redis = Redis::new(&url, crate::config::DEFAULT_REDIS_POOL_SIZE)?; - let mut conn = redis.get_connection().await?; - // get the total number of DBs - let db_num = get_db_num(&mut conn).await; - assert!( - db_num > 1, - "Redis must be configured with at least 2 databases for testing" - ); - - let alloc_db = db_num - 1; - - // switch to the allocation DB to perform initialization - set_current_db(&mut conn, db_num - 1).await; + let (container, url) = run_redis_container().await?; + let url = CacheUrl::from(url); - let allocator = RedisDbAllocator::new(alloc_db, redis); - allocator.init().await?; - // get the db number for the current test - let current_db = allocator - .allocate() - .await? - .expect("Failed to allocate a Redis database for testing"); - - // create a new connection to the correct DB - url.inner_mut().set_path(current_db.to_string().as_str()); - let redis = Redis::new(&url, crate::config::DEFAULT_REDIS_POOL_SIZE)?; + let redis = + crate::cache::store::redis::Redis::new(&url, crate::config::DEFAULT_REDIS_POOL_SIZE)?; let cache = Cache::new(redis, Some("test_harness".to_string()), Timeout::default()); - let this = Self::new(cache, CacheKind::Redis { allocator }); - - Ok(this) + Ok(Self::new( + cache, + CacheKind::Redis { + _container: Some(Box::new(container)), + }, + )) } /// Get the cache. @@ -1944,9 +1942,70 @@ impl TestCache { /// ``` pub async fn cleanup(&self) -> Result<()> { #[cfg(feature = "redis")] - if let CacheKind::Redis { allocator: _ } = &self.kind { + if let CacheKind::Redis { .. } = &self.kind { self.cache.clear().await?; } Ok(()) } } + +#[cfg(feature = "redis")] +pub(crate) async fn run_redis_container() -> Result<(ContainerAsync, String)> { + use testcontainers_modules::redis::Redis; + + const REDIS_PORT: u16 = 6379; + + let container = Redis::default() + .with_tag("8-alpine") + .start() + .await + .map_err(|e| TestcontainersError::new("failed to start Redis container", e))?; + let host_port = container + .get_host_port_ipv4(REDIS_PORT) + .await + .map_err(|e| TestcontainersError::new("failed to get Redis container port", e))?; + let url = format!("redis://localhost:{host_port}"); + + Ok((container, url)) +} + +#[derive(Debug, Error)] +#[error("{message}: {inner}")] +struct TestcontainersError { + message: String, + #[source] + inner: testcontainers::core::error::TestcontainersError, +} + +impl TestcontainersError { + #[must_use] + fn new( + message: impl Into, + inner: testcontainers::core::error::TestcontainersError, + ) -> Self { + Self { + message: message.into(), + inner, + } + } +} + +impl_into_cot_error!(TestcontainersError); + +/// A guard for running tests serially. +/// +/// This is mostly useful for tests that need to modify some global state (e.g. +/// environment variables or current working directory). +#[doc(hidden)] // not part of the public API; used in cot-cli +pub fn serial_guard() -> std::sync::MutexGuard<'static, ()> { + static LOCK: std::sync::OnceLock> = std::sync::OnceLock::new(); + let lock = LOCK.get_or_init(|| std::sync::Mutex::new(())); + match lock.lock() { + Ok(guard) => guard, + Err(poison_error) => { + lock.clear_poison(); + // We can ignore poisoned mutexes because we don't store any data inside + poison_error.into_inner() + } + } +} diff --git a/cot/tests/admin.rs b/cot/tests/admin.rs index 46867a0a..ae50c6f4 100644 --- a/cot/tests/admin.rs +++ b/cot/tests/admin.rs @@ -10,7 +10,7 @@ use cot::config::{ use cot::middleware::{AuthMiddleware, SessionMiddleware}; use cot::project::{MiddlewareContext, RegisterAppsContext, RootHandler}; use cot::static_files::StaticFilesMiddleware; -use cot::test::{TestServer, TestServerBuilder}; +use cot::test::{TestServer, TestServerBuilder, TestServerWithWebDriver}; use cot::{App, AppBuilder, Project, ProjectContext}; use fantoccini::{Client, ClientBuilder, Locator}; @@ -70,11 +70,11 @@ impl Project for AdminProject { } } -#[ignore = "This test requires a Webdriver to be running"] +#[ignore = "This test requires a Webdriver"] #[cot::e2e_test] async fn admin_e2e_login() -> Result<(), Box> { let server = TestServerBuilder::new(AdminProject).start().await; - let driver = create_webdriver().await?; + let (driver, server) = create_webdriver(server).await?; login(&server, &driver).await?; @@ -90,13 +90,13 @@ async fn admin_e2e_login() -> Result<(), Box> { Ok(()) } -#[ignore = "This test requires a Webdriver to be running"] +#[ignore = "This test requires a Webdriver"] #[cot::e2e_test] async fn admin_e2e_change_password() -> Result<(), Box> { const NEW_PASSWORD: &str = "test"; let server = TestServerBuilder::new(AdminProject).start().await; - let driver = create_webdriver().await?; + let (driver, server) = create_webdriver(server).await?; login(&server, &driver).await?; @@ -136,17 +136,22 @@ async fn admin_e2e_change_password() -> Result<(), Box> { Ok(()) } -async fn login(server: &TestServer, driver: &Client) -> Result<(), Box> { +async fn login( + server: &TestServerWithWebDriver, + driver: &Client, +) -> Result<(), Box> { login_with(server, driver, DEFAULT_USERNAME, DEFAULT_PASSWORD).await } async fn login_with( - server: &TestServer, + server: &TestServerWithWebDriver, driver: &Client, username: &str, password: &str, ) -> Result<(), Box> { - driver.goto(&format!("{}/admin/", server.url())).await?; + driver + .goto(&format!("{}/admin/", server.server_url())) + .await?; let username_form = driver.find(Locator::Id("username")).await?; username_form.send_keys(username).await?; @@ -158,8 +163,13 @@ async fn login_with( Ok(()) } -async fn create_webdriver() -> Result> { - Ok(ClientBuilder::native() - .connect("http://localhost:4444") - .await?) +async fn create_webdriver( + server: TestServer

, +) -> Result<(Client, TestServerWithWebDriver

), Box> { + let server_with_web_driver = TestServerWithWebDriver::new(server).await?; + + let client = ClientBuilder::native() + .connect(&server_with_web_driver.web_driver_url()) + .await?; + Ok((client, server_with_web_driver)) } diff --git a/justfile b/justfile index 6bc5eb7a..b279a553 100644 --- a/justfile +++ b/justfile @@ -59,17 +59,17 @@ docs-open: alias t := test -test-all: test test-ignored +test: + cargo nextest run --all-features --run-ignored all + cargo test --all-features --doc -alias ta := test-all +alias tni := test-no-ignored -test: +test-no-ignored: cargo nextest run --all-features cargo test --all-features --doc alias ti := test-ignored test-ignored: - docker compose up -d --wait cargo nextest run --all-features --run-ignored only - docker compose down