From 66ad50787b1f937796d3f642c679e74c750b8c6e Mon Sep 17 00:00:00 2001 From: Rabbit0w0 Date: Wed, 11 Sep 2024 23:04:18 +0800 Subject: [PATCH] feat(doc, test): added unit tests, added more information in readme --- .github/workflows/rust.yml | 4 +- .idea/yggdrasil-authenticator.iml | 1 + Cargo.lock | 330 ++++++++++++++++++++++++++++-- Cargo.toml | 4 + README.md | 48 ++++- src/client/client.rs | 16 +- src/lib.rs | 3 +- src/model/auth_user.rs | 4 +- tests/lib_test.rs | 177 ++++++++++++++++ 9 files changed, 560 insertions(+), 27 deletions(-) create mode 100644 tests/lib_test.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 97dde4b..9fd45e0 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -18,5 +18,5 @@ jobs: - uses: actions/checkout@v4 - name: Build run: cargo build --verbose - #- name: Run tests - # run: cargo test --verbose + - name: Run tests + run: cargo test --verbose diff --git a/.idea/yggdrasil-authenticator.iml b/.idea/yggdrasil-authenticator.iml index cf84ae4..bbe0a70 100644 --- a/.idea/yggdrasil-authenticator.iml +++ b/.idea/yggdrasil-authenticator.iml @@ -3,6 +3,7 @@ + diff --git a/Cargo.lock b/Cargo.lock index bb63b09..5ebe942 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,25 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[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 = "atomic-waker" version = "1.1.2" @@ -62,6 +81,12 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.7.1" @@ -83,6 +108,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "colored" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +dependencies = [ + "lazy_static", + "windows-sys 0.48.0", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -287,6 +322,12 @@ version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "hyper" version = "1.4.1" @@ -300,6 +341,7 @@ dependencies = [ "http", "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -401,6 +443,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.158" @@ -413,6 +461,16 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.22" @@ -452,6 +510,30 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "mockito" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b34bd91b9e5c5b06338d392463e1318d683cf82ec3d3af4014609be6e2108d" +dependencies = [ + "assert-json-diff", + "bytes", + "colored", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "log", + "rand", + "regex", + "serde_json", + "serde_urlencoded", + "similar", + "tokio", +] + [[package]] name = "native-tls" version = "0.2.12" @@ -528,6 +610,29 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -572,6 +677,15 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro2" version = "1.0.86" @@ -590,6 +704,74 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + [[package]] name = "reqwest" version = "0.12.7" @@ -722,6 +904,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "security-framework" version = "2.11.1" @@ -795,6 +983,21 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "similar" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" + [[package]] name = "slab" version = "0.4.9" @@ -911,11 +1114,25 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", + "tokio-macros", "windows-sys 0.52.0", ] +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tokio-native-tls" version = "0.3.1" @@ -1146,7 +1363,7 @@ checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" dependencies = [ "windows-result", "windows-strings", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1155,7 +1372,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1165,7 +1382,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ "windows-result", - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", ] [[package]] @@ -1174,7 +1400,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1183,7 +1409,22 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -1192,28 +1433,46 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "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_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -1226,24 +1485,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -1254,9 +1537,32 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" name = "yggdrasil-authenticator" version = "0.1.0" dependencies = [ + "mockito", "reqwest", "serde", "serde_json", + "tokio", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8bc5a72..47ed1f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,7 @@ edition = "2021" reqwest = "0.12.7" serde_json = "1.0.128" serde = { version = "1.0.210", features = ["derive"] } + +[dev-dependencies] +mockito = "1.5.0" +tokio = { version = "1.40.0", features = ["full"] } \ No newline at end of file diff --git a/README.md b/README.md index d8432ec..619a38d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,47 @@ -## Yggdrasil Authenticator +# Yggdrasil Authenticator Library ---- \ No newline at end of file +--- + +This Rust library provides an interface for interacting with Yggdrasil's authentication system. It includes models for handling authentication requests, responses, and errors, as well as a client for sending HTTP requests to authenticate, refresh, validate, and sign out users. + +Implemented Standard: [authlib-injector](https://github.com/yushijinhun/authlib-injector/wiki/Yggdrasil-%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83) + +## Features + +- **AuthClient**: The main client for handling authentication operations. +- **JSON Models**: Structs for serializing/deserializing request and response data, including agents, profiles, users, and errors. +- **Error Handling**: Custom error types for handling authentication failures. + +## Sample Code + +```rust +use yggdrasil_authenticator::client::AuthClient; +use yggdrasil_authenticator::model::{AuthAgent, AuthProfile}; +use std::error::Error; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = AuthClient::new( + "https://myauthserver.com/auth-endpoint".to_string(), // No "/" at the end + None, // No proxy URL + ); + + // Authenticate a user + let agent = AuthAgent::new("Minecraft".to_string(), 1); + let auth_response = client + .authenticate(agent, "username", "password", "client_token", true) + .await?; + + println!("Access Token: {}", auth_response.access_token); + + // Refresh token + let refresh_response = client + .refresh(&auth_response.access_token, &auth_response.client_token, true, None) + .await?; + + println!("New Access Token: {}", refresh_response.access_token); + + Ok(()) +} + +``` \ No newline at end of file diff --git a/src/client/client.rs b/src/client/client.rs index 3e94ab5..3b2d800 100644 --- a/src/client/client.rs +++ b/src/client/client.rs @@ -225,14 +225,16 @@ async fn send_post_request( } if status != reqwest::StatusCode::OK { - // If response is not JSON, throw an error - if !response.starts_with("{") { - return Err(format!("server error: {}", response).into()); + // Handle this error if possible + let error: Result = serde_json::from_str(&response); + return match error { + Ok(auth_error) => { + Err(auth_error.into()) + } + Err(_error) => { + Err(format!("server status: {}, response: {}", status, response).into()) + } } - - // Or parse the error response - let auth_error: AuthError = serde_json::from_str(&response)?; - return Err(auth_error.into()); } Ok(Some(response)) diff --git a/src/lib.rs b/src/lib.rs index 4671df0..f2efe39 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,5 @@ pub mod client { pub mod client; // Contains the client implementation for interacting with the authentication system. } -// Re-exports all models and client modules for easier access from the top level. +// Re-exports all models for easier access from the top level. pub use model::*; -pub use client::*; diff --git a/src/model/auth_user.rs b/src/model/auth_user.rs index 27e0f19..a374990 100644 --- a/src/model/auth_user.rs +++ b/src/model/auth_user.rs @@ -18,8 +18,8 @@ pub struct AuthUserProperty { #[derive(Deserialize)] pub struct AuthUser { /// The unique identifier of the user. - id: String, + pub id: String, /// A list of properties associated with the user. - properties: Vec, + pub properties: Vec, } diff --git a/tests/lib_test.rs b/tests/lib_test.rs new file mode 100644 index 0000000..affcf05 --- /dev/null +++ b/tests/lib_test.rs @@ -0,0 +1,177 @@ +#[cfg(test)] +mod tests { + use mockito::{Matcher, Server}; + use serde_json::json; + use std::error::Error; + use yggdrasil_authenticator::auth_agent::AuthAgent; + use yggdrasil_authenticator::auth_error::AuthError; + use yggdrasil_authenticator::client::client::AuthClient; + + // Helper function to create a test client + fn create_test_client(url: &str) -> AuthClient { + AuthClient::new(url.to_string(), None) + } + + #[tokio::test] + async fn test_authenticate_success() -> Result<(), Box> { + let mut server = Server::new_async().await; + + // Mock a successful authenticate response + let _m = server.mock("POST", "/authenticate") + .match_body(Matcher::Json(json!({ + "agent": { + "name": "Minecraft", + "version": 1 + }, + "username": "testuser", + "password": "password", + "clientToken": "client_token", + "requestUser": false + }))) + .with_status(200) + .with_body(json!({ + "accessToken": "test_access_token", + "clientToken": "client_token", + "availableProfiles": [], + "selectedProfile": null, + "user": null + }).to_string()) + .create(); + + let client = create_test_client(server.url().as_str()); + let agent = AuthAgent::new("Minecraft".to_string(), 1); + + let response = client + .authenticate(agent, "testuser", "password", "client_token", false) + .await?; + + assert_eq!(response.access_token, "test_access_token"); + assert_eq!(response.client_token, "client_token"); + + Ok(()) + } + + #[tokio::test] + async fn test_refresh_success() -> Result<(), Box> { + let mut server = Server::new_async().await; + + // Mock a successful refresh response + let _m = server.mock("POST", "/refresh") + .match_body(Matcher::Json(json!({ + "accessToken": "old_access_token", + "clientToken": "client_token", + "requestUser": false, + }))) + .with_status(200) + .with_body(json!({ + "accessToken": "new_access_token", + "clientToken": "client_token", + "selectedProfile": null, + "user": null + }).to_string()) + .create(); + + let client = create_test_client(server.url().as_str()); + let response = client + .refresh("old_access_token", "client_token", false, None) + .await?; + + assert_eq!(response.access_token, "new_access_token"); + + Ok(()) + } + + #[tokio::test] + async fn test_validate_success() -> Result<(), Box> { + let mut server = Server::new_async().await; + + // Mock a successful validate response (empty body) + let _m = server.mock("POST", "/validate") + .match_body(Matcher::Json(json!({ + "accessToken": "test_access_token" + }))) + .with_status(204) + .create(); + + let client = create_test_client(server.url().as_str()); + let result = client.validate("test_access_token").await; + + assert!(result.is_ok()); + + Ok(()) + } + + #[tokio::test] + async fn test_invalidate_success() -> Result<(), Box> { + let mut server = Server::new_async().await; + + // Mock a successful invalidate response (empty body) + let _m = server.mock("POST", "/invalidate") + .match_body(Matcher::Json(json!({ + "accessToken": "test_access_token", + "clientToken": "client_token" + }))) + .with_status(204) + .create(); + + let client = create_test_client(server.url().as_str()); + let result = client.invalidate("test_access_token", "client_token").await; + + assert!(result.is_ok()); + + Ok(()) + } + + #[tokio::test] + async fn test_signout_success() -> Result<(), Box> { + let mut server = Server::new_async().await; + + // Mock a successful signout response (empty body) + let _m = server.mock("POST", "/signout") + .match_body(Matcher::Json(json!({ + "username": "testuser", + "password": "password" + }))) + .with_status(204) + .create(); + + let client = create_test_client(server.url().as_str()); + let result = client.signout("testuser", "password").await; + + assert!(result.is_ok()); + + Ok(()) + } + + #[tokio::test] + async fn test_auth_error_response() -> Result<(), Box> { + let mut server = Server::new_async().await; + + // Mock a failed authenticate response with error + let _m = server.mock("POST", "/authenticate") + .with_status(403) + .with_body(json!({ + "error": "ForbiddenOperationException", + "errorMessage": "Invalid credentials. Invalid username or password.", + "cause": "User credentials are invalid" + }).to_string()) + .create(); + + let client = create_test_client(server.url().as_str()); + let agent = AuthAgent::new("Minecraft".to_string(), 1); + + let result = client + .authenticate(agent, "invalid_user", "wrong_password", "client_token", true) + .await; + + assert!(result.is_err()); + + if let Err(e) = result { + let auth_error = e.downcast_ref::().unwrap(); + assert_eq!(auth_error.error, "ForbiddenOperationException"); + assert_eq!(auth_error.error_message, "Invalid credentials. Invalid username or password."); + } + + Ok(()) + } +}