mirror of
https://github.com/getnora-io/nora.git
synced 2026-04-12 09:10:32 +00:00
test: add comprehensive unit tests for storage and auth
- LocalStorage tests: put/get, list, stat, health_check, nested dirs - S3Storage tests with wiremock HTTP mocking - Auth/htpasswd tests: loading, validation, public paths - Token lifecycle tests: create, verify, expire, revoke Total: 75 tests passing
This commit is contained in:
407
Cargo.lock
generated
407
Cargo.lock
generated
@@ -17,6 +17,12 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
@@ -85,6 +91,16 @@ dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "assert-json-diff"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.89"
|
||||
@@ -351,6 +367,12 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
@@ -361,6 +383,38 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "6.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"hashbrown 0.14.5",
|
||||
"lock_api",
|
||||
"once_cell",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deadpool"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0be2b1d1d6ec8d846f05e137292d0b89133caf95ef33695424c09568bdd39b1b"
|
||||
dependencies = [
|
||||
"deadpool-runtime",
|
||||
"lazy_static",
|
||||
"num_cpus",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deadpool-runtime"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b"
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.4.2"
|
||||
@@ -415,6 +469,12 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.27"
|
||||
@@ -449,6 +509,12 @@ version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.2"
|
||||
@@ -458,6 +524,31 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "forwarded-header-value"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9"
|
||||
dependencies = [
|
||||
"nonempty",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.31"
|
||||
@@ -474,12 +565,34 @@ version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||
|
||||
[[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"
|
||||
@@ -492,14 +605,22 @@ version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
||||
|
||||
[[package]]
|
||||
name = "futures-timer"
|
||||
version = "3.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
@@ -545,11 +666,64 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "governor"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9efcab3c1958580ff1f25a2a41be1668f7603d849bb63af523b208a3cc1223b8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"dashmap",
|
||||
"futures-sink",
|
||||
"futures-timer",
|
||||
"futures-util",
|
||||
"getrandom 0.3.4",
|
||||
"hashbrown 0.16.1",
|
||||
"nonzero_ext",
|
||||
"parking_lot",
|
||||
"portable-atomic",
|
||||
"quanta",
|
||||
"rand",
|
||||
"smallvec",
|
||||
"spinning_top",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
@@ -557,6 +731,12 @@ version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.4.0"
|
||||
@@ -612,6 +792,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
@@ -641,6 +822,19 @@ dependencies = [
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[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-util"
|
||||
version = "0.1.19"
|
||||
@@ -798,7 +992,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
"hashbrown 0.16.1",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
@@ -977,6 +1171,18 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nonempty"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7"
|
||||
|
||||
[[package]]
|
||||
name = "nonzero_ext"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21"
|
||||
|
||||
[[package]]
|
||||
name = "nora-cli"
|
||||
version = "0.1.0"
|
||||
@@ -1002,6 +1208,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"flate2",
|
||||
"governor",
|
||||
"httpdate",
|
||||
"indicatif",
|
||||
"lazy_static",
|
||||
@@ -1011,13 +1218,17 @@ dependencies = [
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"tar",
|
||||
"tempfile",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"toml",
|
||||
"tower_governor",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"utoipa",
|
||||
"utoipa-swagger-ui",
|
||||
"uuid",
|
||||
"wiremock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1057,6 +1268,16 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "number_prefix"
|
||||
version = "0.4.0"
|
||||
@@ -1104,6 +1325,26 @@ version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
|
||||
dependencies = [
|
||||
"pin-project-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-internal"
|
||||
version = "1.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
@@ -1170,6 +1411,21 @@ version = "2.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94"
|
||||
|
||||
[[package]]
|
||||
name = "quanta"
|
||||
version = "0.12.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"raw-cpuid",
|
||||
"wasi",
|
||||
"web-sys",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.31.0"
|
||||
@@ -1279,6 +1535,15 @@ dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raw-cpuid"
|
||||
version = "11.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.18"
|
||||
@@ -1634,6 +1899,15 @@ dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spinning_top"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.1"
|
||||
@@ -1694,6 +1968,19 @@ dependencies = [
|
||||
"xattr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
@@ -1806,6 +2093,30 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.23"
|
||||
@@ -1847,6 +2158,35 @@ version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
||||
|
||||
[[package]]
|
||||
name = "tonic"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb7613188ce9f7df5bfe185db26c5814347d110db17920415cf2fbcad85e7203"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum",
|
||||
"base64",
|
||||
"bytes",
|
||||
"h2",
|
||||
"http",
|
||||
"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 = "tower"
|
||||
version = "0.5.3"
|
||||
@@ -1855,9 +2195,12 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"indexmap",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -1893,6 +2236,23 @@ version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
||||
|
||||
[[package]]
|
||||
name = "tower_governor"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44de9b94d849d3c46e06a883d72d408c2de6403367b39df2b1c9d9e7b6736fe6"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"forwarded-header-value",
|
||||
"governor",
|
||||
"http",
|
||||
"pin-project",
|
||||
"thiserror 2.0.18",
|
||||
"tonic",
|
||||
"tower",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.44"
|
||||
@@ -2216,6 +2576,22 @@ dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
@@ -2225,6 +2601,12 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.62.2"
|
||||
@@ -2458,6 +2840,29 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wiremock"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08db1edfb05d9b3c1542e521aea074442088292f00b5f28e435c714a98f85031"
|
||||
dependencies = [
|
||||
"assert-json-diff",
|
||||
"base64",
|
||||
"deadpool",
|
||||
"futures",
|
||||
"http",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"log",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.51.0"
|
||||
|
||||
@@ -38,3 +38,10 @@ tar = "0.4"
|
||||
flate2 = "1.0"
|
||||
indicatif = "0.17"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
thiserror = "2"
|
||||
tower_governor = "0.8"
|
||||
governor = "0.10"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
wiremock = "0.6"
|
||||
|
||||
@@ -313,3 +313,106 @@ pub fn token_routes() -> Router<Arc<AppState>> {
|
||||
.route("/api/tokens/list", post(list_tokens))
|
||||
.route("/api/tokens/revoke", post(revoke_token))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::io::Write;
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
fn create_test_htpasswd(entries: &[(&str, &str)]) -> NamedTempFile {
|
||||
let mut file = NamedTempFile::new().unwrap();
|
||||
for (username, password) in entries {
|
||||
let hash = bcrypt::hash(password, 4).unwrap(); // cost=4 for speed in tests
|
||||
writeln!(file, "{}:{}", username, hash).unwrap();
|
||||
}
|
||||
file.flush().unwrap();
|
||||
file
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_htpasswd_loading() {
|
||||
let file = create_test_htpasswd(&[("admin", "secret"), ("user", "password")]);
|
||||
|
||||
let auth = HtpasswdAuth::from_file(file.path()).unwrap();
|
||||
let users = auth.list_users();
|
||||
assert_eq!(users.len(), 2);
|
||||
assert!(users.contains(&"admin"));
|
||||
assert!(users.contains(&"user"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_htpasswd_loading_empty_file() {
|
||||
let file = NamedTempFile::new().unwrap();
|
||||
let auth = HtpasswdAuth::from_file(file.path());
|
||||
assert!(auth.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_htpasswd_loading_with_comments() {
|
||||
let mut file = NamedTempFile::new().unwrap();
|
||||
writeln!(file, "# This is a comment").unwrap();
|
||||
writeln!(file, "").unwrap();
|
||||
let hash = bcrypt::hash("secret", 4).unwrap();
|
||||
writeln!(file, "admin:{}", hash).unwrap();
|
||||
file.flush().unwrap();
|
||||
|
||||
let auth = HtpasswdAuth::from_file(file.path()).unwrap();
|
||||
assert_eq!(auth.list_users().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_authenticate_valid() {
|
||||
let file = create_test_htpasswd(&[("test", "secret")]);
|
||||
let auth = HtpasswdAuth::from_file(file.path()).unwrap();
|
||||
|
||||
assert!(auth.authenticate("test", "secret"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_authenticate_invalid_password() {
|
||||
let file = create_test_htpasswd(&[("test", "secret")]);
|
||||
let auth = HtpasswdAuth::from_file(file.path()).unwrap();
|
||||
|
||||
assert!(!auth.authenticate("test", "wrong"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_authenticate_unknown_user() {
|
||||
let file = create_test_htpasswd(&[("test", "secret")]);
|
||||
let auth = HtpasswdAuth::from_file(file.path()).unwrap();
|
||||
|
||||
assert!(!auth.authenticate("unknown", "secret"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_public_path() {
|
||||
// Public paths
|
||||
assert!(is_public_path("/"));
|
||||
assert!(is_public_path("/health"));
|
||||
assert!(is_public_path("/ready"));
|
||||
assert!(is_public_path("/metrics"));
|
||||
assert!(is_public_path("/v2/"));
|
||||
assert!(is_public_path("/v2"));
|
||||
assert!(is_public_path("/ui"));
|
||||
assert!(is_public_path("/ui/dashboard"));
|
||||
assert!(is_public_path("/api-docs"));
|
||||
assert!(is_public_path("/api-docs/openapi.json"));
|
||||
assert!(is_public_path("/api/ui/stats"));
|
||||
assert!(is_public_path("/api/tokens"));
|
||||
assert!(is_public_path("/api/tokens/list"));
|
||||
|
||||
// Protected paths
|
||||
assert!(!is_public_path("/v2/myimage/blobs/sha256:abc"));
|
||||
assert!(!is_public_path("/v2/library/nginx/manifests/latest"));
|
||||
assert!(!is_public_path("/maven2/com/example/artifact/1.0/artifact.jar"));
|
||||
assert!(!is_public_path("/npm/lodash"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash_password() {
|
||||
let hash = hash_password("test123").unwrap();
|
||||
assert!(hash.starts_with("$2"));
|
||||
assert!(bcrypt::verify("test123", &hash).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,3 +129,112 @@ impl StorageBackend for LocalStorage {
|
||||
"local"
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_put_and_get() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let storage = LocalStorage::new(temp_dir.path().to_str().unwrap());
|
||||
|
||||
storage.put("test/key", b"test data").await.unwrap();
|
||||
let data = storage.get("test/key").await.unwrap();
|
||||
assert_eq!(&*data, b"test data");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_not_found() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let storage = LocalStorage::new(temp_dir.path().to_str().unwrap());
|
||||
|
||||
let result = storage.get("nonexistent").await;
|
||||
assert!(matches!(result, Err(StorageError::NotFound)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_list_with_prefix() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let storage = LocalStorage::new(temp_dir.path().to_str().unwrap());
|
||||
|
||||
storage.put("docker/image/blob1", b"data1").await.unwrap();
|
||||
storage.put("docker/image/blob2", b"data2").await.unwrap();
|
||||
storage.put("maven/artifact", b"data3").await.unwrap();
|
||||
|
||||
let docker_keys = storage.list("docker/").await;
|
||||
assert_eq!(docker_keys.len(), 2);
|
||||
assert!(docker_keys.iter().all(|k| k.starts_with("docker/")));
|
||||
|
||||
let all_keys = storage.list("").await;
|
||||
assert_eq!(all_keys.len(), 3);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_stat() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let storage = LocalStorage::new(temp_dir.path().to_str().unwrap());
|
||||
|
||||
storage.put("test", b"12345").await.unwrap();
|
||||
let meta = storage.stat("test").await.unwrap();
|
||||
assert_eq!(meta.size, 5);
|
||||
assert!(meta.modified > 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_stat_not_found() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let storage = LocalStorage::new(temp_dir.path().to_str().unwrap());
|
||||
|
||||
let meta = storage.stat("nonexistent").await;
|
||||
assert!(meta.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_health_check() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let storage = LocalStorage::new(temp_dir.path().to_str().unwrap());
|
||||
assert!(storage.health_check().await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_health_check_creates_directory() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let new_path = temp_dir.path().join("new_storage");
|
||||
let storage = LocalStorage::new(new_path.to_str().unwrap());
|
||||
|
||||
assert!(!new_path.exists());
|
||||
assert!(storage.health_check().await);
|
||||
assert!(new_path.exists());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_nested_directory_creation() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let storage = LocalStorage::new(temp_dir.path().to_str().unwrap());
|
||||
|
||||
storage.put("a/b/c/d/e/file", b"deep").await.unwrap();
|
||||
let data = storage.get("a/b/c/d/e/file").await.unwrap();
|
||||
assert_eq!(&*data, b"deep");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_overwrite() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let storage = LocalStorage::new(temp_dir.path().to_str().unwrap());
|
||||
|
||||
storage.put("key", b"original").await.unwrap();
|
||||
storage.put("key", b"updated").await.unwrap();
|
||||
|
||||
let data = storage.get("key").await.unwrap();
|
||||
assert_eq!(&*data, b"updated");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_backend_name() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let storage = LocalStorage::new(temp_dir.path().to_str().unwrap());
|
||||
assert_eq!(storage.backend_name(), "local");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,3 +127,184 @@ impl StorageBackend for S3Storage {
|
||||
"s3"
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use wiremock::matchers::{method, path};
|
||||
use wiremock::{Mock, MockServer, ResponseTemplate};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_put_success() {
|
||||
let mock_server = MockServer::start().await;
|
||||
let storage = S3Storage::new(&mock_server.uri(), "test-bucket");
|
||||
|
||||
Mock::given(method("PUT"))
|
||||
.and(path("/test-bucket/test-key"))
|
||||
.respond_with(ResponseTemplate::new(200))
|
||||
.mount(&mock_server)
|
||||
.await;
|
||||
|
||||
let result = storage.put("test-key", b"data").await;
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_put_failure() {
|
||||
let mock_server = MockServer::start().await;
|
||||
let storage = S3Storage::new(&mock_server.uri(), "test-bucket");
|
||||
|
||||
Mock::given(method("PUT"))
|
||||
.and(path("/test-bucket/test-key"))
|
||||
.respond_with(ResponseTemplate::new(500))
|
||||
.mount(&mock_server)
|
||||
.await;
|
||||
|
||||
let result = storage.put("test-key", b"data").await;
|
||||
assert!(matches!(result, Err(StorageError::Network(_))));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_success() {
|
||||
let mock_server = MockServer::start().await;
|
||||
let storage = S3Storage::new(&mock_server.uri(), "test-bucket");
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/test-bucket/test-key"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_bytes(b"test data".to_vec()))
|
||||
.mount(&mock_server)
|
||||
.await;
|
||||
|
||||
let data = storage.get("test-key").await.unwrap();
|
||||
assert_eq!(&*data, b"test data");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_not_found() {
|
||||
let mock_server = MockServer::start().await;
|
||||
let storage = S3Storage::new(&mock_server.uri(), "test-bucket");
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/test-bucket/missing"))
|
||||
.respond_with(ResponseTemplate::new(404))
|
||||
.mount(&mock_server)
|
||||
.await;
|
||||
|
||||
let result = storage.get("missing").await;
|
||||
assert!(matches!(result, Err(StorageError::NotFound)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_list() {
|
||||
let mock_server = MockServer::start().await;
|
||||
let storage = S3Storage::new(&mock_server.uri(), "test-bucket");
|
||||
|
||||
let xml_response = r#"<?xml version="1.0"?>
|
||||
<ListBucketResult>
|
||||
<Key>docker/image1</Key>
|
||||
<Key>docker/image2</Key>
|
||||
<Key>maven/artifact</Key>
|
||||
</ListBucketResult>"#;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/test-bucket"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_string(xml_response))
|
||||
.mount(&mock_server)
|
||||
.await;
|
||||
|
||||
let keys = storage.list("docker/").await;
|
||||
assert_eq!(keys.len(), 2);
|
||||
assert!(keys.iter().all(|k| k.starts_with("docker/")));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_stat_success() {
|
||||
let mock_server = MockServer::start().await;
|
||||
let storage = S3Storage::new(&mock_server.uri(), "test-bucket");
|
||||
|
||||
Mock::given(method("HEAD"))
|
||||
.and(path("/test-bucket/test-key"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(200)
|
||||
.insert_header("content-length", "1234")
|
||||
.insert_header("last-modified", "Sun, 06 Nov 1994 08:49:37 GMT"),
|
||||
)
|
||||
.mount(&mock_server)
|
||||
.await;
|
||||
|
||||
let meta = storage.stat("test-key").await.unwrap();
|
||||
assert_eq!(meta.size, 1234);
|
||||
assert!(meta.modified > 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_stat_not_found() {
|
||||
let mock_server = MockServer::start().await;
|
||||
let storage = S3Storage::new(&mock_server.uri(), "test-bucket");
|
||||
|
||||
Mock::given(method("HEAD"))
|
||||
.and(path("/test-bucket/missing"))
|
||||
.respond_with(ResponseTemplate::new(404))
|
||||
.mount(&mock_server)
|
||||
.await;
|
||||
|
||||
let meta = storage.stat("missing").await;
|
||||
assert!(meta.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_health_check_healthy() {
|
||||
let mock_server = MockServer::start().await;
|
||||
let storage = S3Storage::new(&mock_server.uri(), "test-bucket");
|
||||
|
||||
Mock::given(method("HEAD"))
|
||||
.and(path("/test-bucket"))
|
||||
.respond_with(ResponseTemplate::new(200))
|
||||
.mount(&mock_server)
|
||||
.await;
|
||||
|
||||
assert!(storage.health_check().await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_health_check_bucket_not_found_is_ok() {
|
||||
let mock_server = MockServer::start().await;
|
||||
let storage = S3Storage::new(&mock_server.uri(), "test-bucket");
|
||||
|
||||
Mock::given(method("HEAD"))
|
||||
.and(path("/test-bucket"))
|
||||
.respond_with(ResponseTemplate::new(404))
|
||||
.mount(&mock_server)
|
||||
.await;
|
||||
|
||||
// 404 is OK for health check (bucket may be empty)
|
||||
assert!(storage.health_check().await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_health_check_server_error() {
|
||||
let mock_server = MockServer::start().await;
|
||||
let storage = S3Storage::new(&mock_server.uri(), "test-bucket");
|
||||
|
||||
Mock::given(method("HEAD"))
|
||||
.and(path("/test-bucket"))
|
||||
.respond_with(ResponseTemplate::new(500))
|
||||
.mount(&mock_server)
|
||||
.await;
|
||||
|
||||
assert!(!storage.health_check().await);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_backend_name() {
|
||||
let storage = S3Storage::new("http://localhost:9000", "bucket");
|
||||
assert_eq!(storage.backend_name(), "s3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_s3_keys() {
|
||||
let xml = r#"<Key>docker/a</Key><Key>docker/b</Key><Key>maven/c</Key>"#;
|
||||
let keys = S3Storage::parse_s3_keys(xml, "docker/");
|
||||
assert_eq!(keys, vec!["docker/a", "docker/b"]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use sha2::{Digest, Sha256};
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
const TOKEN_PREFIX: &str = "nra_";
|
||||
@@ -180,23 +181,178 @@ fn hash_token(token: &str) -> String {
|
||||
format!("{:x}", hasher.finalize())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum TokenError {
|
||||
#[error("Invalid token format")]
|
||||
InvalidFormat,
|
||||
|
||||
#[error("Token not found")]
|
||||
NotFound,
|
||||
|
||||
#[error("Token expired")]
|
||||
Expired,
|
||||
|
||||
#[error("Storage error: {0}")]
|
||||
Storage(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for TokenError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::InvalidFormat => write!(f, "Invalid token format"),
|
||||
Self::NotFound => write!(f, "Token not found"),
|
||||
Self::Expired => write!(f, "Token expired"),
|
||||
Self::Storage(msg) => write!(f, "Storage error: {}", msg),
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
fn test_create_token() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let store = TokenStore::new(temp_dir.path());
|
||||
|
||||
let token = store
|
||||
.create_token("testuser", 30, Some("Test token".to_string()))
|
||||
.unwrap();
|
||||
|
||||
assert!(token.starts_with("nra_"));
|
||||
assert_eq!(token.len(), 4 + 32); // prefix + uuid without dashes
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_valid_token() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let store = TokenStore::new(temp_dir.path());
|
||||
|
||||
let token = store.create_token("testuser", 30, None).unwrap();
|
||||
let user = store.verify_token(&token).unwrap();
|
||||
|
||||
assert_eq!(user, "testuser");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_invalid_format() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let store = TokenStore::new(temp_dir.path());
|
||||
|
||||
let result = store.verify_token("invalid_token");
|
||||
assert!(matches!(result, Err(TokenError::InvalidFormat)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_not_found() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let store = TokenStore::new(temp_dir.path());
|
||||
|
||||
let result = store.verify_token("nra_00000000000000000000000000000000");
|
||||
assert!(matches!(result, Err(TokenError::NotFound)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_expired_token() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let store = TokenStore::new(temp_dir.path());
|
||||
|
||||
// Create token and manually set it as expired
|
||||
let token = store.create_token("testuser", 1, None).unwrap();
|
||||
let token_hash = hash_token(&token);
|
||||
let file_path = temp_dir.path().join(format!("{}.json", &token_hash[..16]));
|
||||
|
||||
// Read and modify the token to be expired
|
||||
let content = std::fs::read_to_string(&file_path).unwrap();
|
||||
let mut info: TokenInfo = serde_json::from_str(&content).unwrap();
|
||||
info.expires_at = 0; // Set to epoch (definitely expired)
|
||||
std::fs::write(&file_path, serde_json::to_string(&info).unwrap()).unwrap();
|
||||
|
||||
// Token should now be expired
|
||||
let result = store.verify_token(&token);
|
||||
assert!(matches!(result, Err(TokenError::Expired)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_tokens() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let store = TokenStore::new(temp_dir.path());
|
||||
|
||||
store.create_token("user1", 30, None).unwrap();
|
||||
store.create_token("user1", 30, None).unwrap();
|
||||
store.create_token("user2", 30, None).unwrap();
|
||||
|
||||
let user1_tokens = store.list_tokens("user1");
|
||||
assert_eq!(user1_tokens.len(), 2);
|
||||
|
||||
let user2_tokens = store.list_tokens("user2");
|
||||
assert_eq!(user2_tokens.len(), 1);
|
||||
|
||||
let unknown_tokens = store.list_tokens("unknown");
|
||||
assert_eq!(unknown_tokens.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_revoke_token() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let store = TokenStore::new(temp_dir.path());
|
||||
|
||||
let token = store.create_token("testuser", 30, None).unwrap();
|
||||
let token_hash = hash_token(&token);
|
||||
let hash_prefix = &token_hash[..16];
|
||||
|
||||
// Verify token works
|
||||
assert!(store.verify_token(&token).is_ok());
|
||||
|
||||
// Revoke
|
||||
store.revoke_token(hash_prefix).unwrap();
|
||||
|
||||
// Verify token no longer works
|
||||
let result = store.verify_token(&token);
|
||||
assert!(matches!(result, Err(TokenError::NotFound)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_revoke_nonexistent_token() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let store = TokenStore::new(temp_dir.path());
|
||||
|
||||
let result = store.revoke_token("nonexistent12345");
|
||||
assert!(matches!(result, Err(TokenError::NotFound)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_revoke_all_for_user() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let store = TokenStore::new(temp_dir.path());
|
||||
|
||||
store.create_token("user1", 30, None).unwrap();
|
||||
store.create_token("user1", 30, None).unwrap();
|
||||
store.create_token("user2", 30, None).unwrap();
|
||||
|
||||
let revoked = store.revoke_all_for_user("user1");
|
||||
assert_eq!(revoked, 2);
|
||||
|
||||
assert_eq!(store.list_tokens("user1").len(), 0);
|
||||
assert_eq!(store.list_tokens("user2").len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_token_updates_last_used() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let store = TokenStore::new(temp_dir.path());
|
||||
|
||||
let token = store.create_token("testuser", 30, None).unwrap();
|
||||
|
||||
// First verification
|
||||
store.verify_token(&token).unwrap();
|
||||
|
||||
// Check last_used is set
|
||||
let tokens = store.list_tokens("testuser");
|
||||
assert!(tokens[0].last_used.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_token_with_description() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let store = TokenStore::new(temp_dir.path());
|
||||
|
||||
store
|
||||
.create_token("testuser", 30, Some("CI/CD Pipeline".to_string()))
|
||||
.unwrap();
|
||||
|
||||
let tokens = store.list_tokens("testuser");
|
||||
assert_eq!(tokens[0].description, Some("CI/CD Pipeline".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for TokenError {}
|
||||
|
||||
Reference in New Issue
Block a user