diff --git a/Cargo.lock b/Cargo.lock index 2f16bcc..9974e8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -104,6 +104,28 @@ dependencies = [ "slab", ] +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "async-task" version = "4.7.1" @@ -157,7 +179,7 @@ version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48404d931ab11b3a7a5267291b3b8f3590f09b86181381f8e82a7e562ed832c0" dependencies = [ - "base64", + "base64 0.22.1", "http", "log", "rustls", @@ -307,6 +329,35 @@ dependencies = [ "syn", ] +[[package]] +name = "axum-test" +version = "18.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680e88effaafbb28675074f29cda0e984c984bed5eb513085c17caf7de564225" +dependencies = [ + "anyhow", + "axum", + "bytes 1.10.1", + "bytesize", + "cookie", + "expect-json", + "http", + "http-body-util", + "hyper", + "hyper-util", + "mime", + "pretty_assertions", + "reserve-port", + "rust-multipart-rfc7578_2", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec 1.15.1", + "tokio 1.47.1", + "tower", + "url", +] + [[package]] name = "backtrace" version = "0.3.75" @@ -322,6 +373,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -353,7 +410,7 @@ dependencies = [ "derive_more", "disqualified", "fixedbitset", - "indexmap", + "indexmap 2.11.3", "log", "nonmax", "serde", @@ -531,6 +588,82 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bollard" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899ca34eb6924d6ec2a77c6f7f5c7339e60fd68235eaf91edd5a15f12958bb06" +dependencies = [ + "async-stream", + "base64 0.22.1", + "bitflags 2.9.4", + "bollard-buildkit-proto", + "bollard-stubs", + "bytes 1.10.1", + "chrono", + "futures-core", + "futures-util", + "hex", + "home", + "http", + "http-body-util", + "hyper", + "hyper-named-pipe", + "hyper-rustls", + "hyper-util", + "hyperlocal", + "log", + "num", + "pin-project-lite", + "rand 0.9.2", + "rustls", + "rustls-native-certs", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_derive", + "serde_json", + "serde_repr", + "serde_urlencoded", + "thiserror 2.0.16", + "tokio 1.47.1", + "tokio-stream", + "tokio-util", + "tonic", + "tower-service", + "url", + "winapi 0.3.9", +] + +[[package]] +name = "bollard-buildkit-proto" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b3e79f8bd0f25f32660e3402afca46fd91bebaf135af017326d905651f8107" +dependencies = [ + "prost", + "prost-types", + "tonic", + "ureq", +] + +[[package]] +name = "bollard-stubs" +version = "1.48.3-rc.28.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ea257e555d16a2c01e5593f40b73865cdf12efbceda33c6d14a2d8d1490368" +dependencies = [ + "base64 0.22.1", + "bollard-buildkit-proto", + "bytes 1.10.1", + "chrono", + "prost", + "serde", + "serde_json", + "serde_repr", + "serde_with", +] + [[package]] name = "built" version = "0.7.7" @@ -577,6 +710,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +[[package]] +name = "bytesize" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5c434ae3cf0089ca203e9019ebe529c47ff45cefe8af7c85ecb734ef541822f" + [[package]] name = "c_vec" version = "2.0.0" @@ -745,6 +884,16 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "time", + "version_check", +] + [[package]] name = "cookie-rs" version = "0.4.1" @@ -761,6 +910,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -904,6 +1063,41 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "dashmap" version = "6.1.0" @@ -1016,6 +1210,17 @@ dependencies = [ "const-random", ] +[[package]] +name = "docker_credential" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d89dfcba45b4afad7450a99b39e751590463e45c04728cf555d36bb66940de8" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + [[package]] name = "document-features" version = "0.2.11" @@ -1043,6 +1248,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "either" version = "1.15.0" @@ -1052,6 +1263,15 @@ dependencies = [ "serde", ] +[[package]] +name = "email_address" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" +dependencies = [ + "serde", +] + [[package]] name = "encoding_rs" version = "0.8.35" @@ -1119,6 +1339,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "etcetera" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c7b13d0780cb82722fd59f6f57f925e143427e4a75313a6c77243bf5326ae6" +dependencies = [ + "cfg-if 1.0.3", + "home", + "windows-sys 0.59.0", +] + [[package]] name = "event-listener" version = "5.4.1" @@ -1130,6 +1361,34 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "expect-json" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7519e78573c950576b89eb4f4fe82aedf3a80639245afa07e3ee3d199dcdb29e" +dependencies = [ + "chrono", + "email_address", + "expect-json-macros", + "num", + "serde", + "serde_json", + "thiserror 2.0.16", + "typetag", + "uuid", +] + +[[package]] +name = "expect-json-macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bf7f5979e98460a0eb412665514594f68f366a32b85fa8d7ffb65bb1edee6a0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "exr" version = "1.73.0" @@ -1224,6 +1483,18 @@ dependencies = [ "version_check", ] +[[package]] +name = "filetime" +version = "0.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" +dependencies = [ + "cfg-if 1.0.3", + "libc", + "libredox", + "windows-sys 0.60.2", +] + [[package]] name = "find-msvc-tools" version = "0.1.1" @@ -1329,6 +1600,7 @@ checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -1392,6 +1664,17 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.31" @@ -1413,6 +1696,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -1507,7 +1791,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap", + "indexmap 2.11.3", "slab", "tokio 1.47.1", "tokio-util", @@ -1533,6 +1817,12 @@ dependencies = [ "byteorder", ] +[[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" @@ -1685,6 +1975,21 @@ 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 1.47.1", + "tower-service", + "winapi 0.3.9", +] + [[package]] name = "hyper-rustls" version = "0.27.7" @@ -1737,7 +2042,7 @@ version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" dependencies = [ - "base64", + "base64 0.22.1", "bytes 1.10.1", "futures-channel", "futures-core", @@ -1749,7 +2054,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.6.0", "system-configuration", "tokio 1.47.1", "tower-service", @@ -1757,6 +2062,21 @@ dependencies = [ "windows-registry", ] +[[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 1.47.1", + "tower-service", +] + [[package]] name = "iana-time-zone" version = "0.1.64" @@ -1867,6 +2187,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.1.0" @@ -1928,6 +2254,17 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" +[[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.11.3" @@ -1936,6 +2273,8 @@ checksum = "92119844f513ffa41556430369ab02c295a3578af21cf945caa3e9e0c2481ac3" dependencies = [ "equivalent", "hashbrown 0.15.5", + "serde", + "serde_core", ] [[package]] @@ -1964,6 +2303,15 @@ dependencies = [ "syn", ] +[[package]] +name = "inventory" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e" +dependencies = [ + "rustversion", +] + [[package]] name = "io-uring" version = "0.7.10" @@ -2050,7 +2398,7 @@ version = "9.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" dependencies = [ - "base64", + "base64 0.22.1", "js-sys", "ring", "serde", @@ -2367,7 +2715,7 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework", + "security-framework 2.11.1", "security-framework-sys", "tempfile", ] @@ -2550,7 +2898,7 @@ version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" dependencies = [ - "base64", + "base64 0.22.1", "chrono", "getrandom 0.2.16", "http", @@ -2681,6 +3029,7 @@ dependencies = [ "async-trait", "axum", "axum-cookie", + "axum-test", "bytes 1.10.1", "chrono", "dashmap", @@ -2693,6 +3042,7 @@ dependencies = [ "image", "jsonwebtoken", "oauth2", + "pretty_assertions", "reqwest", "rustls", "s3-tokio", @@ -2700,6 +3050,7 @@ dependencies = [ "serde_json", "sha2", "sqlx", + "testcontainers", "time", "tokio 1.47.1", "tracing", @@ -2763,6 +3114,31 @@ dependencies = [ "windows-targets 0.52.6", ] +[[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 = "paste" version = "1.0.15" @@ -2776,7 +3152,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59ac35caa284c08f3721fb33c2741b5f763decaf42d080c8a6a722154347017e" dependencies = [ "deprecate-until", - "indexmap", + "indexmap 2.11.3", "integer-sqrt", "num-traits", "rustc-hash", @@ -3036,6 +3412,38 @@ dependencies = [ "syn", ] +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes 1.10.1", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools 0.13.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost", +] + [[package]] name = "pxfm" version = "0.1.23" @@ -3083,7 +3491,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2", + "socket2 0.6.0", "thiserror 2.0.16", "tokio 1.47.1", "tracing", @@ -3120,7 +3528,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2", + "socket2 0.6.0", "tracing", "windows-sys 0.59.0", ] @@ -3275,6 +3683,15 @@ version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.5.17" @@ -3284,6 +3701,26 @@ dependencies = [ "bitflags 2.9.4", ] +[[package]] +name = "ref-cast" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "regex" version = "1.11.2" @@ -3319,7 +3756,7 @@ version = "0.12.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" dependencies = [ - "base64", + "base64 0.22.1", "bytes 1.10.1", "encoding_rs", "futures-core", @@ -3357,6 +3794,15 @@ dependencies = [ "webpki-roots 1.0.2", ] +[[package]] +name = "reserve-port" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21918d6644020c6f6ef1993242989bf6d4952d2e025617744f184c02df51c356" +dependencies = [ + "thiserror 2.0.16", +] + [[package]] name = "rgb" version = "0.8.52" @@ -3441,6 +3887,21 @@ dependencies = [ "ordered-multimap", ] +[[package]] +name = "rust-multipart-rfc7578_2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c839d037155ebc06a571e305af66ff9fd9063a6e662447051737e1ac75beea41" +dependencies = [ + "bytes 1.10.1", + "futures-core", + "futures-util", + "http", + "mime", + "rand 0.9.2", + "thiserror 2.0.16", +] + [[package]] name = "rustc-demangle" version = "0.1.26" @@ -3491,6 +3952,27 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework 3.4.0", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "rustls-pki-types" version = "1.12.0" @@ -3551,7 +4033,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd8d44de9045104205a93dcfe3525889df62af2d2db6d95ccb44663733b8832" dependencies = [ "aws-region", - "base64", + "base64 0.22.1", "bytes 1.10.1", "femlol-aws-creds", "futures 0.3.31", @@ -3597,6 +4079,30 @@ dependencies = [ "windows-sys 0.61.0", ] +[[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.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -3635,7 +4141,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.9.4", - "core-foundation", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b369d18893388b345804dc0007963c99b7d665ae71d275812d828c6f089640" +dependencies = [ + "bitflags 2.9.4", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -3726,6 +4245,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 = "0.6.9" @@ -3747,6 +4277,38 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.11.3", + "schemars 0.9.0", + "schemars 1.0.4", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sha1" version = "0.10.6" @@ -3857,6 +4419,16 @@ dependencies = [ "serde", ] +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "socket2" version = "0.6.0" @@ -3925,7 +4497,7 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ - "base64", + "base64 0.22.1", "bytes 1.10.1", "chrono", "crc", @@ -3938,7 +4510,7 @@ dependencies = [ "futures-util", "hashbrown 0.15.5", "hashlink", - "indexmap", + "indexmap 2.11.3", "log", "memchr", "once_cell", @@ -4001,7 +4573,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", - "base64", + "base64 0.22.1", "bitflags 2.9.4", "byteorder", "bytes 1.10.1", @@ -4044,13 +4616,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", - "base64", + "base64 0.22.1", "bitflags 2.9.4", "byteorder", "chrono", "crc", "dotenvy", - "etcetera", + "etcetera 0.8.0", "futures-channel", "futures-core", "futures-util", @@ -4123,6 +4695,35 @@ dependencies = [ "unicode-properties", ] +[[package]] +name = "strsim" +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 = "strum" version = "0.27.2" @@ -4185,7 +4786,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags 2.9.4", - "core-foundation", + "core-foundation 0.9.4", "system-configuration-sys", ] @@ -4231,6 +4832,36 @@ dependencies = [ "windows-sys 0.61.0", ] +[[package]] +name = "testcontainers" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b92bce247dc9260a19808321e11b51ea6a0293d02b48ab1c6578960610cfa2a7" +dependencies = [ + "async-trait", + "bollard", + "bollard-stubs", + "bytes 1.10.1", + "docker_credential", + "either", + "etcetera 0.10.0", + "futures 0.3.31", + "log", + "memchr", + "parse-display", + "pin-project-lite", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.16", + "tokio 1.47.1", + "tokio-stream", + "tokio-tar", + "tokio-util", + "ulid", + "url", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -4403,7 +5034,7 @@ dependencies = [ "pin-project-lite", "signal-hook-registry", "slab", - "socket2", + "socket2 0.6.0", "tokio-macros", "windows-sys 0.59.0", ] @@ -4532,6 +5163,21 @@ dependencies = [ "futures 0.1.31", ] +[[package]] +name = "tokio-tar" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5714c010ca3e5c27114c1cdeb9d14641ace49874aa5626d7149e47aedace75" +dependencies = [ + "filetime", + "futures-core", + "libc", + "redox_syscall 0.3.5", + "tokio 1.47.1", + "tokio-stream", + "xattr", +] + [[package]] name = "tokio-tcp" version = "0.1.4" @@ -4648,13 +5294,42 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap", + "indexmap 2.11.3", "serde", "serde_spanned", "toml_datetime", "winnow", ] +[[package]] +name = "tonic" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e581ba15a835f4d9ea06c55ab1bd4dce26fc53752c69a04aac00703bfb49ba9" +dependencies = [ + "async-trait", + "axum", + "base64 0.22.1", + "bytes 1.10.1", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "prost", + "socket2 0.5.10", + "tokio 1.47.1", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower" version = "0.5.2" @@ -4663,9 +5338,12 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", + "indexmap 2.11.3", "pin-project-lite", + "slab", "sync_wrapper", "tokio 1.47.1", + "tokio-util", "tower-layer", "tower-service", "tracing", @@ -4815,6 +5493,40 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +[[package]] +name = "typetag" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f22b40dd7bfe8c14230cf9702081366421890435b2d625fa92b4acc4c3de6f" +dependencies = [ + "erased-serde", + "inventory", + "once_cell", + "serde", + "typetag-impl", +] + +[[package]] +name = "typetag-impl" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35f5380909ffc31b4de4f4bdf96b877175a016aa2ca98cee39fcfd8c4d53d952" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ulid" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "470dbf6591da1b39d43c14523b2b469c86879a53e8b758c8e090a470fe7b1fbe" +dependencies = [ + "rand 0.9.2", + "web-time", +] + [[package]] name = "uncased" version = "0.9.10" @@ -4863,6 +5575,21 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" +dependencies = [ + "base64 0.22.1", + "log", + "once_cell", + "rustls", + "rustls-pki-types", + "url", + "webpki-roots 0.26.11", +] + [[package]] name = "url" version = "2.5.7" @@ -5340,6 +6067,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.3", +] + [[package]] name = "windows-sys" version = "0.61.0" @@ -5373,13 +6109,30 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link 0.1.3", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + [[package]] name = "windows-threading" version = "0.2.0" @@ -5401,6 +6154,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -5413,6 +6172,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -5425,12 +6190,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -5443,6 +6220,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -5455,6 +6238,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -5467,6 +6256,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -5479,6 +6274,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winnow" version = "0.7.13" @@ -5510,6 +6311,16 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "xattr" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909" +dependencies = [ + "libc", + "rustix", +] + [[package]] name = "yansi" version = "1.0.1" diff --git a/pacman-server/Cargo.toml b/pacman-server/Cargo.toml index 463f647..f4507ae 100644 --- a/pacman-server/Cargo.toml +++ b/pacman-server/Cargo.toml @@ -56,3 +56,6 @@ hyper = { version = "1", features = ["server", "http1"] } hyper-util = { version = "0.1", features = ["server", "tokio", "http1"] } bytes = "1" anyhow = "1" +axum-test = "18.1.0" +pretty_assertions = "1.4.1" +testcontainers = "0.25.0" diff --git a/pacman-server/src/app.rs b/pacman-server/src/app.rs index 9dd24b2..f8d1113 100644 --- a/pacman-server/src/app.rs +++ b/pacman-server/src/app.rs @@ -1,10 +1,12 @@ +use axum::{routing::get, Router}; +use axum_cookie::CookieLayer; use dashmap::DashMap; use jsonwebtoken::{DecodingKey, EncodingKey}; use std::sync::Arc; use tokio::sync::RwLock; use crate::data::pool::PgPool; -use crate::{auth::AuthRegistry, config::Config, image::ImageStorage}; +use crate::{auth::AuthRegistry, config::Config, image::ImageStorage, routes}; #[derive(Debug, Clone)] pub struct Health { @@ -69,3 +71,34 @@ impl AppState { } } } + +/// Create the application router with all routes and middleware +pub fn create_router(app_state: AppState) -> Router { + Router::new() + .route("/", get(|| async { "Hello, World! Visit /auth/github to start OAuth flow." })) + .route("/health", get(routes::health_handler)) + .route("/auth/providers", get(routes::list_providers_handler)) + .route("/auth/{provider}", get(routes::oauth_authorize_handler)) + .route("/auth/{provider}/callback", get(routes::oauth_callback_handler)) + .route("/logout", get(routes::logout_handler)) + .route("/profile", get(routes::profile_handler)) + .with_state(app_state) + .layer(CookieLayer::default()) + .layer(axum::middleware::from_fn(inject_server_header)) +} + +/// Inject the server header into responses +async fn inject_server_header( + req: axum::http::Request, + next: axum::middleware::Next, +) -> Result { + let mut res = next.run(req).await; + res.headers_mut().insert( + axum::http::header::SERVER, + axum::http::HeaderValue::from_static(SERVER_HEADER_VALUE), + ); + Ok(res) +} + +// Constant value for the Server header: "/" +const SERVER_HEADER_VALUE: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); diff --git a/pacman-server/src/config.rs b/pacman-server/src/config.rs index fbeec65..14db6be 100644 --- a/pacman-server/src/config.rs +++ b/pacman-server/src/config.rs @@ -1,7 +1,7 @@ use figment::{providers::Env, value::UncasedStr, Figment}; use serde::{Deserialize, Deserializer}; -#[derive(Debug, Deserialize)] +#[derive(Debug, Clone, Deserialize)] pub struct Config { // Database URL pub database_url: String, diff --git a/pacman-server/src/main.rs b/pacman-server/src/main.rs index 98a78b8..e884f54 100644 --- a/pacman-server/src/main.rs +++ b/pacman-server/src/main.rs @@ -1,6 +1,8 @@ -use crate::{app::AppState, auth::AuthRegistry, config::Config}; -use axum::{routing::get, Router}; -use axum_cookie::CookieLayer; +use crate::{ + app::{create_router, AppState}, + auth::AuthRegistry, + config::Config, +}; use std::time::Instant; use std::{sync::Arc, time::Duration}; use tracing::{info, trace, warn}; @@ -20,9 +22,6 @@ mod logging; mod routes; mod session; -// Constant value for the Server header: "/" -const SERVER_HEADER_VALUE: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); - #[tokio::main] async fn main() { // Load environment variables @@ -55,17 +54,11 @@ async fn main() { h.set_migrations(true); } - let app = Router::new() - .route("/", get(|| async { "Hello, World! Visit /auth/github to start OAuth flow." })) - .route("/health", get(routes::health_handler)) - .route("/auth/providers", get(routes::list_providers_handler)) - .route("/auth/{provider}", get(routes::oauth_authorize_handler)) - .route("/auth/{provider}/callback", get(routes::oauth_callback_handler)) - .route("/logout", get(routes::logout_handler)) - .route("/profile", get(routes::profile_handler)) - .with_state(app_state.clone()) - .layer(CookieLayer::default()) - .layer(axum::middleware::from_fn(inject_server_header)); + // Extract needed parts for health checker before moving app_state + let health_state = app_state.health.clone(); + let db_pool = app_state.db.clone(); + + let app = create_router(app_state); info!(%addr, "Starting HTTP server bind"); let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); @@ -77,8 +70,8 @@ async fn main() { // Spawn background health checker (listens for shutdown via notify) { - let health_state = app_state.health.clone(); - let db_pool = app_state.db.clone(); + let health_state = health_state.clone(); + let db_pool = db_pool.clone(); let notify_for_health = notify.clone(); tokio::spawn(async move { trace!("Health checker task started"); @@ -177,15 +170,3 @@ async fn shutdown_signal() -> Instant { _ = sigterm => { Instant::now() } } } - -async fn inject_server_header( - req: axum::http::Request, - next: axum::middleware::Next, -) -> Result { - let mut res = next.run(req).await; - res.headers_mut().insert( - axum::http::header::SERVER, - axum::http::HeaderValue::from_static(SERVER_HEADER_VALUE), - ); - Ok(res) -} diff --git a/pacman-server/tests/common/mod.rs b/pacman-server/tests/common/mod.rs new file mode 100644 index 0000000..b378142 --- /dev/null +++ b/pacman-server/tests/common/mod.rs @@ -0,0 +1,105 @@ +use axum::Router; +use pacman_server::{ + app::{create_router, AppState}, + auth::AuthRegistry, + config::Config, +}; +use testcontainers::{ + core::{IntoContainerPort, WaitFor}, + runners::AsyncRunner, + ContainerAsync, GenericImage, ImageExt, +}; + +/// Test configuration for integration tests +pub struct TestConfig { + pub database_url: String, + pub _container: ContainerAsync, + pub config: Config, +} + +impl TestConfig { + /// Create a test configuration with a test database + pub async fn new() -> Self { + rustls::crypto::ring::default_provider() + .install_default() + .expect("Failed to install default crypto provider"); + + let (database_url, container) = setup_test_database("testdb", "testuser", "testpass").await; + + let config = Config { + database_url: database_url.clone(), + discord_client_id: "test_discord_client_id".to_string(), + discord_client_secret: "test_discord_client_secret".to_string(), + github_client_id: "test_github_client_id".to_string(), + github_client_secret: "test_github_client_secret".to_string(), + s3_access_key: "test_s3_access_key".to_string(), + s3_secret_access_key: "test_s3_secret_access_key".to_string(), + s3_bucket_name: "test_bucket".to_string(), + s3_public_base_url: "https://test.example.com".to_string(), + port: 0, // Will be set by test server + host: "127.0.0.1".parse().unwrap(), + shutdown_timeout_seconds: 5, + public_base_url: "http://localhost:3000".to_string(), + jwt_secret: "test_jwt_secret_key_for_testing_only".to_string(), + }; + + Self { + database_url, + _container: container, + config, + } + } +} + +/// Set up a test PostgreSQL database using testcontainers +async fn setup_test_database(db: &str, user: &str, password: &str) -> (String, ContainerAsync) { + let container = GenericImage::new("postgres", "15") + .with_exposed_port(5432.tcp()) + .with_wait_for(WaitFor::message_on_stderr("database system is ready to accept connections")) + .with_env_var("POSTGRES_DB", db) + .with_env_var("POSTGRES_USER", user) + .with_env_var("POSTGRES_PASSWORD", password) + .start() + .await + .unwrap(); + + let host = container.get_host().await.unwrap(); + let port = container.get_host_port_ipv4(5432).await.unwrap(); + + ( + format!("postgresql://{user}:{password}@{host}:{port}/{db}?sslmode=disable"), + container, + ) +} + +/// Create a test app state with database and auth registry +pub async fn create_test_app_state(test_config: &TestConfig) -> AppState { + // Create database pool + let db = pacman_server::data::pool::create_pool(&test_config.database_url, 5).await; + + // Run migrations + sqlx::migrate!("./migrations") + .run(&db) + .await + .expect("Failed to run database migrations"); + + // Create auth registry + let auth = AuthRegistry::new(&test_config.config).expect("Failed to create auth registry"); + + // Create app state + let app_state = AppState::new(test_config.config.clone(), auth, db); + + // Set health status to true for tests (migrations and database are both working) + { + let mut health = app_state.health.write().await; + health.set_migrations(true); + health.set_database(true); + } + + app_state +} + +/// Create a test router with the given app state +pub fn create_test_router(app_state: AppState) -> Router { + create_router(app_state) +} diff --git a/pacman-server/tests/oauth_integration.rs b/pacman-server/tests/oauth_integration.rs new file mode 100644 index 0000000..584a037 --- /dev/null +++ b/pacman-server/tests/oauth_integration.rs @@ -0,0 +1,138 @@ +use axum_test::TestServer; +use pretty_assertions::assert_eq; + +mod common; +use common::{create_test_app_state, create_test_router, TestConfig}; + +/// Common setup function for all tests +async fn setup_test_server() -> TestServer { + let test_config = TestConfig::new().await; + let app_state = create_test_app_state(&test_config).await; + let router = create_test_router(app_state); + TestServer::new(router).unwrap() +} + +/// Test basic endpoints functionality +#[tokio::test] +async fn test_basic_endpoints() { + let server = setup_test_server().await; + + // Test root endpoint + let response = server.get("/").await; + assert_eq!(response.status_code(), 200); + assert_eq!(response.text(), "Hello, World! Visit /auth/github to start OAuth flow."); +} + +/// Test health endpoint functionality +#[tokio::test] +async fn test_health_endpoint() { + let server = setup_test_server().await; + + // Test health endpoint - wait for health checker to complete initial run + tokio::time::sleep(tokio::time::Duration::from_millis(250)).await; + + let mut health_ok = false; + let start = tokio::time::Instant::now(); + let timeout = tokio::time::Duration::from_secs(3); + while start.elapsed() < timeout { + let response = server.get("/health").await; + if response.status_code() == 200 { + let health_json: serde_json::Value = response.json(); + if health_json["ok"] == true { + health_ok = true; + break; + } + } + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + } + + assert!(health_ok, "Health endpoint did not return ok=true within 3 seconds"); +} + +/// Test OAuth provider listing and configuration +#[tokio::test] +async fn test_oauth_provider_configuration() { + let server = setup_test_server().await; + + // Test providers list endpoint + let response = server.get("/auth/providers").await; + assert_eq!(response.status_code(), 200); + let providers: Vec = response.json(); + assert_eq!(providers.len(), 2); // Should have GitHub and Discord providers + + // Verify provider structure + let provider_ids: Vec<&str> = providers.iter().map(|p| p["id"].as_str().unwrap()).collect(); + assert!(provider_ids.contains(&"github")); + assert!(provider_ids.contains(&"discord")); + + // Verify provider details + for provider in providers { + let id = provider["id"].as_str().unwrap(); + let name = provider["name"].as_str().unwrap(); + let active = provider["active"].as_bool().unwrap(); + + assert!(active, "Provider {} should be active", id); + + match id { + "github" => assert_eq!(name, "GitHub"), + "discord" => assert_eq!(name, "Discord"), + _ => panic!("Unknown provider: {}", id), + } + } +} + +/// Test OAuth authorization flows +#[tokio::test] +async fn test_oauth_authorization_flows() { + let server = setup_test_server().await; + + // Test OAuth authorize endpoint (should redirect) + let response = server.get("/auth/github").await; + assert_eq!(response.status_code(), 303); // Redirect to GitHub OAuth + + // Test OAuth authorize endpoint for Discord + let response = server.get("/auth/discord").await; + assert_eq!(response.status_code(), 303); // Redirect to Discord OAuth + + // Test unknown provider + let response = server.get("/auth/unknown").await; + assert_eq!(response.status_code(), 400); // Bad request for unknown provider +} + +/// Test OAuth callback handling +#[tokio::test] +async fn test_oauth_callback_handling() { + let server = setup_test_server().await; + + // Test OAuth callback with missing parameters (should fail gracefully) + let response = server.get("/auth/github/callback").await; + assert_eq!(response.status_code(), 400); // Bad request for missing code/state +} + +/// Test session management endpoints +#[tokio::test] +async fn test_session_management() { + let server = setup_test_server().await; + + // Test logout endpoint (should redirect) + let response = server.get("/logout").await; + assert_eq!(response.status_code(), 302); // Redirect to home + + // Test profile endpoint without session (should be unauthorized) + let response = server.get("/profile").await; + assert_eq!(response.status_code(), 401); // Unauthorized without session +} + +/// Test that verifies database operations work correctly +#[tokio::test] +async fn test_database_operations() { + let server = setup_test_server().await; + + // Act: Test health endpoint to verify database connectivity + let response = server.get("/health").await; + + // Assert: Health should be OK, indicating database is connected and migrations ran + assert_eq!(response.status_code(), 200); + let health_json: serde_json::Value = response.json(); + assert_eq!(health_json["ok"], true); +}