diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 0e2893b7c..1c4a92d3e 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 @@ -220,9 +217,6 @@ jobs: - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.10 - - 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 5e0f17827..70919204e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,18 +46,11 @@ Pre-commit still works for this project because `prek` and `pre-commit` share th ### 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 0eeb9ca45..3b7863bf9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,76 @@ 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 0.1.7", + "generic-array 0.14.7", +] + +[[package]] +name = "aead" +version = "0.6.0-rc.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b657e772794c6b04730ea897b66a058ccd866c16d1967da05eeeecec39043fe" +dependencies = [ + "crypto-common 0.2.2", + "inout 0.2.2", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher 0.4.4", + "cpufeatures 0.2.17", +] + +[[package]] +name = "aes" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138" +dependencies = [ + "cipher 0.5.2", + "cpubits", + "cpufeatures 0.3.0", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead 0.5.2", + "aes 0.8.4", + "cipher 0.4.4", + "ctr 0.9.2", + "ghash 0.5.1", + "subtle", +] + +[[package]] +name = "aes-gcm" +version = "0.11.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22c0c90bbe8d4f77c3ca9ddabe41a1f8382d6fc1f7cea89459d0f320371f972" +dependencies = [ + "aead 0.6.0-rc.10", + "aes 0.9.1", + "cipher 0.5.2", + "ctr 0.10.1", + "ghash 0.6.0", + "subtle", +] + [[package]] name = "ahash" version = "0.8.12" @@ -45,8 +115,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "390515b47251185fa076ac92a7a582d9d383b03e13cef0c801e7670cf928229b" dependencies = [ "cfg-if", - "indexmap", - "schemars", + "indexmap 2.14.0", + "schemars 1.2.1", "serde", "serde_json", "thiserror 2.0.18", @@ -231,6 +301,22 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "astral-tokio-tar" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb50a7aae84a03bf55b067832bc376f4961b790c97e64d3eacee97d389b90277" +dependencies = [ + "filetime", + "futures-core", + "libc", + "portable-atomic", + "rustc-hash", + "tokio", + "tokio-stream", + "xattr", +] + [[package]] name = "async-channel" version = "1.9.0" @@ -496,6 +582,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "base16ct" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" + [[package]] name = "base64" version = "0.22.1" @@ -508,6 +600,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 0.12.2", + "sha2 0.10.9", +] + [[package]] name = "bitflags" version = "2.13.0" @@ -546,7 +649,7 @@ 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]] @@ -558,6 +661,24 @@ dependencies = [ "hybrid-array", ] +[[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]] +name = "block-padding" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b" +dependencies = [ + "hybrid-array", +] + [[package]] name = "blocking" version = "1.6.2" @@ -571,6 +692,99 @@ dependencies = [ "piper", ] +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher 0.4.4", +] + +[[package]] +name = "bollard" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee04c4c84f1f811b017f2fbb7dd8815c976e7ca98593de9c1e2afad0f636bff4" +dependencies = [ + "async-stream", + "base64", + "bitflags", + "bollard-buildkit-proto", + "bollard-stubs", + "bytes", + "futures-core", + "futures-util", + "hex", + "home", + "http 1.4.2", + "http-body-util", + "hyper", + "hyper-named-pipe", + "hyper-rustls", + "hyper-util", + "hyperlocal", + "log", + "num", + "pin-project-lite", + "rand 0.9.4", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "serde", + "serde_derive", + "serde_json", + "serde_urlencoded", + "thiserror 2.0.18", + "time", + "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.52.1-rc.29.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0a8ca8799131c1837d1282c3f81f31e76ceb0ce426e04a7fe1ccee3287c066" +dependencies = [ + "base64", + "bollard-buildkit-proto", + "bytes", + "prost", + "serde", + "serde_json", + "serde_repr", + "time", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + [[package]] name = "bstr" version = "1.12.1" @@ -625,6 +839,24 @@ 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 0.4.4", +] + +[[package]] +name = "cbc" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2dc9ee5f88d11e0beb842c88b33c8a5cf0d1329c4b19494af42b07dbfe8896" +dependencies = [ + "cipher 0.5.2", +] + [[package]] name = "cc" version = "1.2.65" @@ -641,11 +873,28 @@ 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.10.0" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher 0.4.4", + "cpufeatures 0.2.17", +] + +[[package]] +name = "chacha20" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" dependencies = [ "cfg-if", "cpufeatures 0.3.0", @@ -703,6 +952,27 @@ dependencies = [ "half", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.7", + "inout 0.1.4", +] + +[[package]] +name = "cipher" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", + "inout 0.2.2", +] + [[package]] name = "clap" version = "4.6.1" @@ -842,6 +1112,18 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "constant_time_eq" version = "0.4.2" @@ -935,7 +1217,7 @@ dependencies = [ "http-body-util", "humantime", "idna", - "indexmap", + "indexmap 2.14.0", "is_terminal_polyfill", "lettre", "mime", @@ -947,16 +1229,19 @@ dependencies = [ "redis", "reqwest", "rustversion", - "schemars", + "schemars 1.2.1", "sea-query", "sea-query-sqlx", "serde", "serde_json", + "serde_repr", "serde_urlencoded", "sqlx", "subtle", "swagger-ui-redist", "tempfile", + "testcontainers", + "testcontainers-modules", "thiserror 2.0.18", "time", "tokio", @@ -1047,9 +1332,9 @@ dependencies = [ "http 1.4.2", "http-body", "http-body-util", - "indexmap", + "indexmap 2.14.0", "insta", - "schemars", + "schemars 1.2.1", "serde", "serde_html_form", "serde_json", @@ -1078,6 +1363,12 @@ dependencies = [ "trybuild", ] +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -1123,7 +1414,7 @@ dependencies = [ "ciborium", "clap", "criterion-plot", - "itertools", + "itertools 0.13.0", "num-traits", "oorandom", "page_size", @@ -1144,7 +1435,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" dependencies = [ "cast", - "itertools", + "itertools 0.13.0", ] [[package]] @@ -1187,13 +1478,30 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "crypto-bigint" +version = "0.7.0-rc.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96dacf199529fb801ae62a9aafdc01b189e9504c0d1ee1512a4c16bcd8666a93" +dependencies = [ + "cpubits", + "ctutils", + "getrandom 0.4.3", + "hybrid-array", + "num-traits", + "rand_core 0.10.1", + "serdect", + "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", ] @@ -1203,7 +1511,38 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ + "getrandom 0.4.3", "hybrid-array", + "rand_core 0.10.1", +] + +[[package]] +name = "crypto-primes" +version = "0.7.0-pre.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6081ce8b60c0e533e2bba42771b94eb6149052115f4179744d5779883dc98583" +dependencies = [ + "crypto-bigint", + "libm", + "rand_core 0.10.1", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher 0.4.4", +] + +[[package]] +name = "ctr" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baaca1c4b237092596f64d571e9db6ce4109c4ef9742e27590f1709594461f21" +dependencies = [ + "cipher 0.5.2", ] [[package]] @@ -1213,6 +1552,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" dependencies = [ "cmov", + "subtle", +] + +[[package]] +name = "curve25519-dalek" +version = "5.0.0-pre.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335f1947f241137a14106b6f5acc5918a5ede29c9d71d3f2cb1678d5075d9fc3" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest 0.11.3", + "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]] @@ -1284,6 +1651,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" + [[package]] name = "deadpool" version = "0.13.0" @@ -1315,16 +1688,38 @@ dependencies = [ ] [[package]] -name = "deranged" -version = "0.5.8" +name = "delegate" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370" dependencies = [ - "serde_core", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "derive_builder" +name = "der" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b" +dependencies = [ + "const-oid 0.10.2", + "pem-rfc7468 1.0.0", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "serde_core", +] + +[[package]] +name = "derive_builder" version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" @@ -1396,6 +1791,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", + "const-oid 0.9.6", "crypto-common 0.1.7", "subtle", ] @@ -1407,6 +1803,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer 0.12.1", + "const-oid 0.10.2", "crypto-common 0.2.2", "ctutils", ] @@ -1428,6 +1825,17 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aeda16ab4059c5fd2a83f2b9c9e9c981327b18aa8e3b313f7e6563799d4f093e" +[[package]] +name = "docker_credential" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29547a1dc60885a552306986316bc9701ba120c1a8db6769fa68691529ad373d" +dependencies = [ + "base64", + "serde", + "serde_json", +] + [[package]] name = "dotenvy" version = "0.15.7" @@ -1458,6 +1866,47 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "ecdsa" +version = "0.17.0-rc.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91bbdd377139884fafcad8dc43a760a3e1e681aa26db910257fa6535b70e1829" +dependencies = [ + "der", + "digest 0.11.3", + "elliptic-curve", + "rfc6979", + "signature", + "spki", + "zeroize", +] + +[[package]] +name = "ed25519" +version = "3.0.0-rc.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6e914c7c52decb085cea910552e24c63ac019e3ab8bf001ff736da9a9d9d890" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "3.0.0-pre.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053618a4c3d3bc24f188aa660ae75a46eeab74ef07fb415c61431e5e7cd4749b" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.10.1", + "serde", + "sha2 0.11.0", + "signature", + "subtle", + "zeroize", +] + [[package]] name = "either" version = "1.16.0" @@ -1467,6 +1916,29 @@ dependencies = [ "serde", ] +[[package]] +name = "elliptic-curve" +version = "0.14.0-rc.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde7860544606d222fd6bd6d9f9a0773321bf78072a637e1d560a058c0031978" +dependencies = [ + "base16ct", + "crypto-bigint", + "crypto-common 0.2.2", + "digest 0.11.3", + "hkdf", + "hybrid-array", + "once_cell", + "pem-rfc7468 1.0.0", + "pkcs8", + "rand_core 0.10.1", + "rustcrypto-ff", + "rustcrypto-group", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "email-encoding" version = "0.4.1" @@ -1507,6 +1979,18 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" +[[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" @@ -1610,7 +2094,7 @@ name = "example-json" version = "0.1.0" dependencies = [ "cot", - "schemars", + "schemars 1.2.1", "serde", ] @@ -1678,6 +2162,33 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +[[package]] +name = "ferroid" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee93edf3c501f0035bbeffeccfed0b79e14c311f12195ec0e661e114a0f60da4" +dependencies = [ + "portable-atomic", + "rand 0.10.1", + "web-time", +] + +[[package]] +name = "fiat-crypto" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" + +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -1775,6 +2286,7 @@ checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -1867,6 +2379,7 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -1887,6 +2400,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "generic-array" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e55f16dcf0e9c00efbe2e655ffe45fc98e7066b52bc92f8a79e64060a79351" +dependencies = [ + "generic-array 0.14.7", + "rustversion", + "typenum", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -1924,6 +2448,25 @@ dependencies = [ "rand_core 0.10.1", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval 0.6.2", +] + +[[package]] +name = "ghash" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eecf2d5dc9b66b732b97707a0210906b1d30523eb773193ab777c0c84b3e8d5" +dependencies = [ + "polyval 0.7.1", +] + [[package]] name = "gimli" version = "0.32.3" @@ -1955,12 +2498,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d9e3df7f0222ce5184154973d247c591d9aadc28ce7a73c6cd31100c9facff6" dependencies = [ "codemap", - "indexmap", + "indexmap 2.14.0", "lasso", "once_cell", "phf 0.11.3", ] +[[package]] +name = "h2" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.4.2", + "indexmap 2.14.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "2.7.1" @@ -1972,6 +2534,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" @@ -2035,13 +2603,28 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-literal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1" + [[package]] name = "hkdf" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" dependencies = [ - "hmac", + "hmac 0.13.0", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", ] [[package]] @@ -2053,6 +2636,15 @@ dependencies = [ "digest 0.11.3", ] +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "http" version = "0.2.12" @@ -2121,7 +2713,10 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" dependencies = [ + "ctutils", + "subtle", "typenum", + "zeroize", ] [[package]] @@ -2134,6 +2729,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2", "http 1.4.2", "http-body", "httparse", @@ -2145,6 +2741,49 @@ 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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http 1.4.2", + "hyper", + "hyper-util", + "rustls", + "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" @@ -2184,6 +2823,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" @@ -2391,6 +3045,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.14.0" @@ -2403,6 +3068,26 @@ dependencies = [ "serde_core", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding 0.3.3", + "generic-array 0.14.7", +] + +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "block-padding 0.4.2", + "hybrid-array", +] + [[package]] name = "insta" version = "1.48.0" @@ -2429,6 +3114,46 @@ dependencies = [ "serde_json", ] +[[package]] +name = "internal-russh-forked-ssh-key" +version = "0.6.18+upstream-0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25f8a978272e3cbdf4768f7363eb1c8e1e6ba63c52a3ed05e29e222da4aec7cb" +dependencies = [ + "argon2", + "bcrypt-pbkdf", + "crypto-bigint", + "ecdsa", + "ed25519-dalek", + "hex", + "hmac 0.13.0", + "p256", + "p384", + "p521", + "rand_core 0.10.1", + "rsa", + "sec1", + "sha1 0.11.0", + "sha2 0.11.0", + "signature", + "ssh-cipher", + "ssh-encoding", + "subtle", + "zeroize", +] + +[[package]] +name = "internal-russh-num-bigint" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8e22120c32fb4d19ec55fba35015f57095cd95a2e3b732e44457f5915b2ee8" +dependencies = [ + "num-integer", + "num-traits", + "rand 0.10.1", + "rand_core 0.10.1", +] + [[package]] name = "ipnet" version = "2.12.0" @@ -2450,6 +3175,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.18" @@ -2523,16 +3257,36 @@ dependencies = [ ] [[package]] -name = "kv-log-macro" -version = "1.0.7" +name = "keccak" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" dependencies = [ - "log", + "cfg-if", + "cpufeatures 0.3.0", ] [[package]] -name = "lasso" +name = "kem" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01737161ba802849cfd486b5bd209d38ba4943494c249a8126005170c7621edd" +dependencies = [ + "crypto-common 0.2.2", + "rand_core 0.10.1", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "lasso" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e14eda50a3494b3bf7b9ce51c52434a761e383d7238ce1dd5dcec2fbc13e9fb" @@ -2670,6 +3424,12 @@ dependencies = [ "digest 0.11.3", ] +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + [[package]] name = "memchr" version = "2.8.2" @@ -2712,6 +3472,19 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "ml-kem" +version = "0.3.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8198b5db27ac9773534c371751a59dc18aec8b80aa141e69abfdd1dec2e3f78c" +dependencies = [ + "hybrid-array", + "kem", + "module-lattice", + "rand_core 0.10.1", + "sha3", +] + [[package]] name = "mockall" version = "0.14.0" @@ -2738,6 +3511,17 @@ dependencies = [ "syn", ] +[[package]] +name = "module-lattice" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c61b87c9683ab7cb1c6871d261ad5479b6b10ceb52c4352aaca3b5d35a8febe" +dependencies = [ + "ctutils", + "hybrid-array", + "num-traits", +] + [[package]] name = "multer" version = "3.1.0" @@ -2772,6 +3556,18 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nom" version = "8.0.0" @@ -2790,12 +3586,76 @@ dependencies = [ "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]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[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.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "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" @@ -2842,6 +3702,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.81" @@ -2885,6 +3751,47 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "p256" +version = "0.14.0-rc.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "018bfbb86e05fd70a83e985921241035ee09fcd369c4a2c3680b389a01d2ad28" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primefield", + "primeorder", + "sha2 0.11.0", +] + +[[package]] +name = "p384" +version = "0.14.0-rc.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c91df688211f5957dbe2ab599dcbcaade8d6d3cdc15c5b350d350d7d07ce423" +dependencies = [ + "ecdsa", + "elliptic-curve", + "fiat-crypto", + "primefield", + "primeorder", + "sha2 0.11.0", +] + +[[package]] +name = "p521" +version = "0.14.0-rc.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de6cd9451de522549d36cc78a1b45a699a3d55a872e8ea0c8f0318e502d99e2c" +dependencies = [ + "base16ct", + "ecdsa", + "elliptic-curve", + "primefield", + "primeorder", + "sha2 0.11.0", +] + [[package]] name = "page_size" version = "0.6.0" @@ -2895,6 +3802,26 @@ dependencies = [ "winapi", ] +[[package]] +name = "pageant" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3a5ae18f65a85c67a77d18d42d3606c07948e3c17c1e5f74852b26589e88a5" +dependencies = [ + "base16ct", + "byteorder", + "bytes", + "delegate", + "futures", + "log", + "rand 0.10.1", + "sha2 0.11.0", + "thiserror 2.0.18", + "tokio", + "windows", + "windows-strings", +] + [[package]] name = "parking" version = "2.2.1" @@ -2924,6 +3851,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" @@ -2947,6 +3899,44 @@ dependencies = [ "subtle", ] +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest 0.10.7", + "hmac 0.12.1", +] + +[[package]] +name = "pbkdf2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629" +dependencies = [ + "digest 0.11.3", + "hmac 0.13.0", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "pem-rfc7468" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -2961,7 +3951,7 @@ checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset", "hashbrown 0.15.5", - "indexmap", + "indexmap 2.14.0", ] [[package]] @@ -3063,6 +4053,26 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -3086,6 +4096,45 @@ dependencies = [ "futures-io", ] +[[package]] +name = "pkcs1" +version = "0.8.0-rc.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "986d2e952779af96ea048f160fd9194e1751b4faea78bcf3ceb456efe008088e" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkcs5" +version = "0.8.0-rc.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5a777c6e26664bc9504b3ce3f6133f8f20d9071f130a4f9fcbd3186959d8dd6" +dependencies = [ + "aes 0.9.1", + "aes-gcm 0.11.0-rc.3", + "cbc 0.2.1", + "der", + "pbkdf2 0.13.0", + "rand_core 0.10.1", + "scrypt", + "sha2 0.11.0", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.11.0-rc.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12922b6296c06eb741b02d7b5161e3aaa22864af38dfa025a1a3ba3f68c84577" +dependencies = [ + "der", + "pkcs5", + "rand_core 0.10.1", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.33" @@ -3134,6 +4183,46 @@ 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 0.2.17", + "opaque-debug", + "universal-hash 0.5.1", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash 0.5.1", +] + +[[package]] +name = "polyval" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfc63250416fea14f5749b90725916a6c903f599d51cb635aa7a52bfd03eede" +dependencies = [ + "cpubits", + "cpufeatures 0.3.0", + "universal-hash 0.6.1", +] + +[[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.5" @@ -3195,6 +4284,29 @@ dependencies = [ "syn", ] +[[package]] +name = "primefield" +version = "0.14.0-rc.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93401c13cc7ff24684571cfca9d3cf9ebabfaf3d4b7b9963ade41ec54da196b5" +dependencies = [ + "crypto-bigint", + "crypto-common 0.2.2", + "rand_core 0.10.1", + "rustcrypto-ff", + "subtle", + "zeroize", +] + +[[package]] +name = "primeorder" +version = "0.14.0-rc.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c5c8a39bcd764bfedf456e8d55e115fe86dda3e0f555371849f2a41cbc9706" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro-crate" version = "3.5.0" @@ -3213,11 +4325,43 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a" +dependencies = [ + "prost", +] + [[package]] name = "quote" -version = "1.0.45" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] @@ -3265,7 +4409,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ - "chacha20", + "chacha20 0.10.1", "getrandom 0.4.3", "rand_core 0.10.1", ] @@ -3436,6 +4580,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "rfc6979" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5236ce872cac07e0fb3969b0cbf468c7d2f37d432f1b627dcb7b8d34563fb0c3" +dependencies = [ + "hmac 0.13.0", + "subtle", +] + [[package]] name = "ring" version = "0.17.14" @@ -3456,6 +4610,127 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "323c417e1d9665a65b263ec744ba09030cfb277e9daa0b018a4ab62e57bc8189" +[[package]] +name = "rsa" +version = "0.10.0-rc.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb9fd8c1edd9e6a2693623baf0fe77ff05ce022a5d7746900ffc38a15c233de" +dependencies = [ + "const-oid 0.10.2", + "crypto-bigint", + "crypto-primes", + "digest 0.11.3", + "pkcs1", + "pkcs8", + "rand_core 0.10.1", + "sha2 0.11.0", + "signature", + "zeroize", +] + +[[package]] +name = "russh" +version = "0.60.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "324b92f459d3e42da294e14e8eb150d2215fcfb7c966838bc1127cd68bc05a0d" +dependencies = [ + "aead 0.6.0-rc.10", + "aes 0.8.4", + "aes 0.9.1", + "aes-gcm 0.11.0-rc.3", + "bitflags", + "block-padding 0.3.3", + "byteorder", + "bytes", + "cbc 0.1.2", + "cbc 0.2.1", + "cipher 0.5.2", + "crypto-bigint", + "ctr 0.10.1", + "ctr 0.9.2", + "curve25519-dalek", + "data-encoding", + "delegate", + "der", + "digest 0.10.7", + "ecdsa", + "ed25519-dalek", + "elliptic-curve", + "enum_dispatch", + "futures", + "generic-array 1.4.3", + "getrandom 0.2.17", + "ghash 0.6.0", + "hex-literal", + "hkdf", + "hmac 0.12.1", + "hmac 0.13.0", + "inout 0.1.4", + "internal-russh-forked-ssh-key", + "internal-russh-num-bigint", + "keccak", + "log", + "md5", + "ml-kem", + "module-lattice", + "num-bigint", + "p256", + "p384", + "p521", + "pageant", + "pbkdf2 0.12.2", + "pbkdf2 0.13.0", + "pkcs5", + "pkcs8", + "polyval 0.7.1", + "rand 0.10.1", + "rand_core 0.10.1", + "ring", + "russh-cryptovec", + "russh-util", + "salsa20", + "scrypt", + "sec1", + "sha1 0.10.6", + "sha1 0.11.0", + "sha2 0.10.9", + "sha2 0.11.0", + "sha3", + "signature", + "spki", + "ssh-encoding", + "subtle", + "thiserror 2.0.18", + "tokio", + "typenum", + "universal-hash 0.6.1", + "zeroize", +] + +[[package]] +name = "russh-cryptovec" +version = "0.60.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37cb4d0360bdd8935392a306d8b5edb539cc455b30e8bf13dd213a0cf7879b40" +dependencies = [ + "log", + "nix", + "ssh-encoding", + "windows-sys 0.61.2", +] + +[[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" @@ -3477,6 +4752,27 @@ dependencies = [ "semver", ] +[[package]] +name = "rustcrypto-ff" +version = "0.14.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd2a8adb347447693cd2ba0d218c4b66c62da9b0a5672b17b981e4291ec65ff6" +dependencies = [ + "rand_core 0.10.1", + "subtle", +] + +[[package]] +name = "rustcrypto-group" +version = "0.14.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "369f9b61aa45933c062c9f6b5c3c50ab710687eca83dd3802653b140b43f85ed" +dependencies = [ + "rand_core 0.10.1", + "rustcrypto-ff", + "subtle", +] + [[package]] name = "rustix" version = "1.1.4" @@ -3492,9 +4788,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.40" +version = "0.23.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" dependencies = [ "log", "once_cell", @@ -3576,6 +4872,16 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "salsa20" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f874456e72520ff1375a06c588eaf074b0f01f9e9e1aada45bd9b7954a6e42c" +dependencies = [ + "cfg-if", + "cipher 0.5.2", +] + [[package]] name = "same-file" version = "1.0.6" @@ -3594,6 +4900,18 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "schemars" version = "1.2.1" @@ -3601,7 +4919,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" dependencies = [ "dyn-clone", - "indexmap", + "indexmap 2.14.0", "ref-cast", "schemars_derive", "serde", @@ -3626,6 +4944,18 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scrypt" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87af57419b594aa23fa95f09f0e06d80d84ba01c26148c43844cad6ff4485f0" +dependencies = [ + "cfg-if", + "pbkdf2 0.13.0", + "salsa20", + "sha2 0.11.0", +] + [[package]] name = "sea-query" version = "1.0.1" @@ -3641,8 +4971,22 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eaa419cdb9157da1361186b1959983eb2ea0dcb9a3c69dc45c449ecb2af8fef" dependencies = [ - "sea-query", - "sqlx", + "sea-query", + "sqlx", +] + +[[package]] +name = "sec1" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d" +dependencies = [ + "base16ct", + "ctutils", + "der", + "hybrid-array", + "subtle", + "zeroize", ] [[package]] @@ -3722,7 +5066,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0946d52b4b7e28823148aebbeceb901012c595ad737920d504fa8634bb099e6f" dependencies = [ "form_urlencoded", - "indexmap", + "indexmap 2.14.0", "serde_core", ] @@ -3750,6 +5094,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.1.1" @@ -3771,6 +5126,59 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" +dependencies = [ + "base64", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serdect" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + [[package]] name = "sha1" version = "0.11.0" @@ -3804,6 +5212,16 @@ dependencies = [ "digest 0.11.3", ] +[[package]] +name = "sha3" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1" +dependencies = [ + "digest 0.11.3", + "keccak", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -3829,6 +5247,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" +dependencies = [ + "digest 0.11.3", + "rand_core 0.10.1", +] + [[package]] name = "simd_cesu8" version = "1.1.1" @@ -3891,6 +5319,16 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spki" +version = "0.8.0-rc.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8baeff88f34ed0691978ec34440140e1572b68c7dd4a495fd14a3dc1944daa80" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "sqlx" version = "0.9.0" @@ -3924,7 +5362,7 @@ dependencies = [ "futures-util", "hashbrown 0.16.1", "hashlink", - "indexmap", + "indexmap 2.14.0", "log", "memchr", "percent-encoding", @@ -3993,11 +5431,11 @@ dependencies = [ "either", "futures-core", "futures-util", - "generic-array", + "generic-array 0.14.7", "log", "percent-encoding", "serde", - "sha1", + "sha1 0.11.0", "sha2 0.11.0", "sqlx-core", "thiserror 2.0.18", @@ -4023,7 +5461,7 @@ dependencies = [ "futures-util", "hex", "hkdf", - "hmac", + "hmac 0.13.0", "itoa", "log", "md-5", @@ -4065,6 +5503,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 0.8.4", + "aes-gcm 0.10.3", + "cbc 0.1.2", + "chacha20 0.9.1", + "cipher 0.4.4", + "ctr 0.9.2", + "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 0.7.0", + "sha2 0.10.9", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -4097,6 +5564,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" @@ -4188,6 +5678,47 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" +[[package]] +name = "testcontainers" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfd5785b5483672915ed5fe3cddf9f546802779fc1eceff0a6fb7321fac81c1e" +dependencies = [ + "astral-tokio-tar", + "async-trait", + "bollard", + "bytes", + "docker_credential", + "either", + "etcetera", + "ferroid", + "futures", + "http 1.4.2", + "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.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5985fde5befe4ffa77a052e035e16c2da86e8bae301baa9f9904ad3c494d357" +dependencies = [ + "testcontainers", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -4239,9 +5770,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.49" +version = "0.3.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711a53c2d47bbd818258c498c8dbfe186a2526c631495cfe7e078567f86b8469" +checksum = "85c17d80feb7334b40c484e45ed1a5273dfd8bfda537c3be2e74a06a6686f327" dependencies = [ "deranged", "num-conv", @@ -4259,9 +5790,9 @@ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.29" +version = "0.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71c652a3727a9cbb9a02f707f530b618ce00d0ccd762009c8c23bd191df3c17d" +checksum = "dcef1a61bdb119096e153208ec5cbec23944ce8bca13be5c7f60c634f7403935" dependencies = [ "num-conv", "time-core", @@ -4388,7 +5919,7 @@ version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ - "indexmap", + "indexmap 2.14.0", "serde_core", "serde_spanned", "toml_datetime 0.7.5+spec-1.1.0", @@ -4403,7 +5934,7 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" dependencies = [ - "indexmap", + "indexmap 2.14.0", "serde_core", "serde_spanned", "toml_datetime 1.1.1+spec-1.1.0", @@ -4436,7 +5967,7 @@ version = "0.25.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ - "indexmap", + "indexmap 2.14.0", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", "winnow 1.0.3", @@ -4457,6 +5988,46 @@ version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" +[[package]] +name = "tonic" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef" +dependencies = [ + "async-trait", + "axum", + "base64", + "bytes", + "h2", + "http 1.4.2", + "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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0" +dependencies = [ + "bytes", + "prost", + "tonic", +] + [[package]] name = "tower" version = "0.5.3" @@ -4465,11 +6036,15 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", + "indexmap 2.14.0", "pin-project-lite", + "slab", "sync_wrapper", "tokio", + "tokio-util", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -4673,9 +6248,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "trybuild" -version = "1.0.116" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c635f0191bd3a2941013e5062667100969f8c4e9cd787c14f977265d73616e" +checksum = "0710d4dfbeae4f9c390baa784c49858a7468fa433f3fe5d0ec5ebef651cf59f9" dependencies = [ "dissimilar", "glob", @@ -4744,12 +6319,59 @@ 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 0.1.7", + "subtle", +] + +[[package]] +name = "universal-hash" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96" +dependencies = [ + "crypto-common 0.2.2", + "ctutils", +] + [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" +dependencies = [ + "base64", + "log", + "percent-encoding", + "rustls", + "rustls-pki-types", + "ureq-proto", + "utf8-zero", +] + +[[package]] +name = "ureq-proto" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c" +dependencies = [ + "base64", + "http 1.4.2", + "httparse", + "log", +] + [[package]] name = "url" version = "2.5.8" @@ -4763,6 +6385,12 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "utf8-zero" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -4916,6 +6544,16 @@ 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" @@ -4982,6 +6620,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" @@ -4995,6 +6654,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" @@ -5023,6 +6693,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" @@ -5075,6 +6755,15 @@ dependencies = [ "windows_x86_64_msvc", ] +[[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.52.6" @@ -5156,6 +6845,16 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + [[package]] name = "xxhash-rust" version = "0.8.15" diff --git a/Cargo.toml b/Cargo.toml index ba1cf1703..c1b20542b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -137,6 +137,7 @@ serde = "1" serde_html_form = { version = "0.4", default-features = false } serde_json = "1" serde_path_to_error = "0.1.20" +serde_repr = "0.1.20" serde_urlencoded = "0.7" sqlx = { version = "0.9", default-features = false } subtle = { version = "2", default-features = false } @@ -144,6 +145,8 @@ swagger-ui-redist = { version = "0.1" } syn = { version = "2", default-features = false } sync_wrapper = "1" tempfile = "3" +testcontainers = { version = "0.27", default-features = false } +testcontainers-modules = { version = "0.15", default-features = false } thiserror = "2" time = { version = "0.3.46", default-features = false } tokio = { version = "1.52", default-features = false } diff --git a/clippy.toml b/clippy.toml index 938f67e3f..3561b339d 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 0e2c7dcf5..000000000 --- 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 e36330359..0bc41f043 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 018ccc7cb..261ce45a4 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-test/src/lib.rs b/cot-test/src/lib.rs index 7224b2405..6eb5a27de 100644 --- a/cot-test/src/lib.rs +++ b/cot-test/src/lib.rs @@ -74,14 +74,21 @@ impl DocTestProject { let cargo_toml_path = project_path.join("Cargo.toml"); let mut cargo_toml = fs::read_to_string(&cargo_toml_path).expect("failed to read Cargo.toml"); + + let mut additional_deps = String::new(); + additional_deps.push_str("serde = { version = \"1\", features = [\"derive\"] }\n"); + additional_deps.push_str("schemars = \"1\"\n"); + additional_deps.push_str("askama = { version = \"0.16\", features = [\"std\"] }\n"); + additional_deps.push_str("async-trait = \"0.1\"\n"); + cargo_toml = cargo_toml.replace( "features = [\"full\"]", - "features = [\"full\", \"openapi\", \"swagger-ui\"]", + "features = [\"full\", \"openapi\", \"swagger-ui\", \"test\"]", + ); + cargo_toml = cargo_toml.replace( + "[dependencies]", + &format!("[dependencies]\n{additional_deps}"), ); - cargo_toml.push_str("serde = { version = \"1\", features = [\"derive\"] }\n"); - cargo_toml.push_str("schemars = \"1\"\n"); - cargo_toml.push_str("askama = { version = \"0.16\", features = [\"std\"] }\n"); - cargo_toml.push_str("async-trait = \"0.1\"\n"); // Add empty workspace info to prevent Cargo from trying to build the entire // workspace when running tests diff --git a/cot/Cargo.toml b/cot/Cargo.toml index 936529294..b20067bd8 100644 --- a/cot/Cargo.toml +++ b/cot/Cargo.toml @@ -54,10 +54,13 @@ sea-query = { workspace = true, optional = true } sea-query-sqlx = { workspace = true, features = ["with-chrono"], optional = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, optional = true } +serde_repr = { workspace = true, optional = true } sqlx = { workspace = true, features = ["runtime-tokio", "chrono"], optional = true } 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"] } @@ -96,13 +99,15 @@ ignored = [ "idna", # time requires version 0.3.35 to work with the latest versions of Rust, but we don't use it directly "time", + # Used indirectly by `testcontainers`, but it causes dependency issues if minimal dependency versions are used + "serde_repr", # used indirectly by `clap`, but it causes dependency issues if minimal dependency versions are used "is_terminal_polyfill", ] [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-sqlx", "dep:sqlx"] email = ["dep:lettre", "dep:idna"] @@ -115,7 +120,7 @@ openapi = ["json", "cot_core/schemars", "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", "dep:serde_repr"] [lib] bench = false diff --git a/cot/src/cache/store/redis.rs b/cot/src/cache/store/redis.rs index 305f8b6d1..de71a7d9e 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 6fc5c539a..0d4bc0b8a 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 b123cc191..20422d91a 100644 --- a/cot/src/test.rs +++ b/cot/src/test.rs @@ -7,15 +7,17 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; 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; +use cot_core::impl_into_cot_error; +use testcontainers::runners::AsyncRunner; +use testcontainers::{ContainerAsync, ImageExt}; +#[cfg(feature = "mysql")] +use testcontainers_modules::mariadb::Mariadb; +#[cfg(feature = "postgres")] +use testcontainers_modules::postgres::Postgres; #[cfg(feature = "redis")] -use deadpool_redis::Connection; -#[cfg(feature = "redis")] -use redis::AsyncCommands; +use testcontainers_modules::redis::Redis; +use thiserror::Error; use tokio::net::TcpListener; use tokio::sync::oneshot; use tower::Service; @@ -28,8 +30,8 @@ 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; +#[cfg(feature = "cache")] +use crate::config::CacheUrl; use crate::config::ProjectConfig; #[cfg(feature = "cache")] use crate::config::Timeout; @@ -986,9 +988,29 @@ impl TestDatabase { /// # Ok(()) /// # } /// ``` + #[cfg(feature = "postgres")] 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 { + 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) + })?; + ( + 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}"); @@ -1007,6 +1029,7 @@ impl TestDatabase { TestDatabaseKind::Postgres { db_url, db_name: test_database_name, + _container: container, }, )) } @@ -1054,9 +1077,27 @@ impl TestDatabase { /// # Ok(()) /// # } /// ``` + #[cfg(feature = "mysql")] 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 { + 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))?; + ( + 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}"); @@ -1075,6 +1116,7 @@ impl TestDatabase { TestDatabaseKind::MySql { db_url, db_name: test_database_name, + _container: container, }, )) } @@ -1227,15 +1269,28 @@ impl TestDatabase { self.database.close().await?; match &self.kind { TestDatabaseKind::Sqlite => {} - TestDatabaseKind::Postgres { db_url, db_name } => { + #[cfg(feature = "postgres")] + 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 } => { + #[cfg(feature = "mysql")] + TestDatabaseKind::MySql { + db_url, db_name, .. + } => { let database = Database::new(format!("{db_url}/mysql")).await?; database.raw(&format!("DROP DATABASE {db_name}")).await?; @@ -1257,11 +1312,21 @@ 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 }, + #[cfg(feature = "postgres")] + Postgres { + db_url: String, + db_name: String, + _container: Option>>, + }, + #[cfg(feature = "mysql")] + MySql { + db_url: String, + db_name: String, + _container: Option>>, + }, } /// A test migration. @@ -1476,6 +1541,7 @@ impl TestServerBuilder { #[derive(Debug)] pub struct TestServer { address: SocketAddr, + host: Option, channel_send: oneshot::Sender<()>, server_handle: tokio::task::JoinHandle<()>, project: PhantomData T>, @@ -1509,6 +1575,7 @@ impl TestServer { Self { address, + host: None, channel_send: send, server_handle, project: PhantomData, @@ -1552,7 +1619,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 /// @@ -1575,7 +1643,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) @@ -1618,188 +1688,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>>, }, } @@ -1823,7 +1859,7 @@ enum CacheKind { /// # } /// ``` #[cfg(feature = "cache")] -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct TestCache { cache: Cache, kind: CacheKind, @@ -1864,25 +1900,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 @@ -1900,39 +1923,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; + let (container, url) = run_redis_container().await?; + let url = CacheUrl::from(url); - // switch to the allocation DB to perform initialization - set_current_db(&mut conn, db_num - 1).await; - - 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. @@ -1981,9 +1984,68 @@ 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)> { + 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 46867a0af..ea6fc1900 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 ab5239fa5..03a7d3d6b 100644 --- a/justfile +++ b/justfile @@ -59,20 +59,20 @@ 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 alias td := test-docs