From 72ca78aae7dd4afd43b2cef35127d719623112ed Mon Sep 17 00:00:00 2001 From: Abhijit Roy Date: Sat, 2 Nov 2024 03:19:23 +0530 Subject: [PATCH 1/2] Add users.http --- api-requests/users.http | 109 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 api-requests/users.http diff --git a/api-requests/users.http b/api-requests/users.http new file mode 100644 index 0000000..5fca8c1 --- /dev/null +++ b/api-requests/users.http @@ -0,0 +1,109 @@ +@host=https://dummyjson.com/users + +### +# @name GetAllUsers +GET {{host}} +Accept: application/json +Content-Type: application/json + +### +# @name LoginUserGetTokens +POST {{host}}/login +Accept: application/json +Content-Type: application/json + +{ + "username": "emilys", + "password": "emilyspass", + "expiresInMins": 30 +} + +### +# @name GetCurrentAuthenticatedUser +@YOUR_ACCESS_TOKEN={{LoginUserGetTokens.response.body.accessToken}} +GET {{host}}/me +Accept: application/json +Content-Type: application/json +Authorization: Bearer {{YOUR_ACCESS_TOKEN}} + +### +# @name GetUserById +@id=1 +GET {{host}}/{{id}} +Accept: application/json +Content-Type: application/json + +### +# @name SearchUsers +GET {{host}}/search?q=john +Accept: application/json +Content-Type: application/json + +### +# @name FilterUsers +GET {{host}}/filter?key=hair.color&value=Brown +Accept: application/json +Content-Type: application/json + +### +# @name LimitSkipUsers +GET {{host}}?limit=3&skip=10&select=firstName,age +Accept: application/json +Content-Type: application/json + +### +# @name SortUsers +GET {{host}}/?sortBy=age&order=desc +Accept: application/json +Content-Type: application/json + +### +# @name GetUserCartsByUserId +@id=6 +GET {{host}}/{{id}}/carts +Accept: application/json +Content-Type: application/json + +### +# @name GetUserPostsByUserId +@id=6 +GET {{host}}/{{id}}/posts +Accept: application/json +Content-Type: application/json + +### +# @name GetUserTodosByUserId +@id=6 +GET {{host}}/{{id}}/todos +Accept: application/json +Content-Type: application/json + +### +# @name AddUser +POST {{host}}/add +Accept: application/json +Content-Type: application/json + +{ + "firstName": "John", + "lastName": "Doe", + "age": 25 +} + +### +# @name UpdateUser +PUT {{host}}/{{id}} +Accept: application/json +Content-Type: application/json + +{ + "lastName": "Owais", + "age": 27 +} + +### +# @name DeleteUser +@id=6 +DELETE {{host}}/{{id}} +Accept: application/json +Content-Type: application/json From 2c18f4cf03d01648f4c3caf12704e1a26536fbc9 Mon Sep 17 00:00:00 2001 From: Abhijit Roy Date: Sat, 2 Nov 2024 15:20:25 +0530 Subject: [PATCH 2/2] Add Users module with tests --- src/auth.rs | 50 ++++++------ src/lib.rs | 2 + src/users.rs | 217 +++++++++++++++++++++++++++++++++++++++++++++++++ tests/auth.rs | 6 +- tests/users.rs | 136 +++++++++++++++++++++++++++++++ 5 files changed, 385 insertions(+), 26 deletions(-) create mode 100644 src/users.rs create mode 100644 tests/users.rs diff --git a/src/auth.rs b/src/auth.rs index 6823e11..d3cc1d9 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -16,9 +16,8 @@ static AUTH_BASE_URL: Lazy = Lazy::new(|| format!("{}/auth", API_BASE_UR pub struct LoginRequest { pub username: String, pub password: String, - /// Expiration time in minutes #[serde(rename = "expiresInMins")] - pub expires_in_mins: u32, + pub expires_in_mins: Option, } /// Login response @@ -41,35 +40,40 @@ pub struct LoginResponse { pub refresh_token: String, } -#[derive(Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug)] pub struct User { pub id: u32, + #[serde(flatten)] + pub other_fields: AddUserPayload, +} + +#[derive(Serialize, Deserialize, Debug, Default)] +pub struct AddUserPayload { #[serde(rename = "firstName")] - pub first_name: String, + pub first_name: Option, #[serde(rename = "lastName")] - pub last_name: String, + pub last_name: Option, #[serde(rename = "maidenName")] - pub maiden_name: String, - pub age: u8, - pub gender: String, - pub email: String, - pub phone: String, - pub username: String, - pub password: String, + pub maiden_name: Option, + pub age: Option, + pub gender: Option, + pub email: Option, + pub phone: Option, + pub username: Option, + pub password: Option, #[serde(rename = "birthDate")] - pub birth_date: String, - pub image: String, + pub birth_date: Option, + pub image: Option, #[serde(rename = "bloodGroup")] - pub blood_group: String, - pub height: f32, - pub weight: f32, + pub blood_group: Option, + pub height: Option, + pub weight: Option, #[serde(rename = "eyeColor")] - pub eye_color: String, - pub hair: Hair, - // TODO: Other fields + pub eye_color: Option, + pub hair: Option, + // TODO: Add other fields } - -#[derive(Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug)] pub struct Hair { pub color: String, #[serde(rename = "type")] @@ -91,7 +95,7 @@ impl DummyJsonClient { &self, username: &str, password: &str, - expires_in_mins: u32, + expires_in_mins: Option, ) -> Result { let payload: LoginRequest = LoginRequest { username: username.to_string(), diff --git a/src/lib.rs b/src/lib.rs index 04e3edd..022d83b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ mod products; mod quotes; mod recipes; mod todos; +mod users; pub use auth::*; pub use carts::*; @@ -16,6 +17,7 @@ pub use quotes::*; pub use recipes::*; use reqwest::Client; pub use todos::*; +pub use users::*; const API_BASE_URL: &str = "https://dummyjson.com"; diff --git a/src/users.rs b/src/users.rs new file mode 100644 index 0000000..3750d35 --- /dev/null +++ b/src/users.rs @@ -0,0 +1,217 @@ +use crate::{ + AddUserPayload, AllTodos, DummyJsonClient, GetAllCartsResponse, GetAllPosts, LoginRequest, + LoginResponse, User, API_BASE_URL, +}; +use once_cell::sync::Lazy; +use serde::Deserialize; + +static USERS_BASE_URL: Lazy = Lazy::new(|| format!("{}/users", API_BASE_URL)); + +#[derive(Deserialize, Debug)] +pub struct GetAllUsersResponse { + pub users: Vec, + pub total: u32, + pub skip: u32, + pub limit: u32, +} + +#[derive(Deserialize, Debug)] +pub struct DeleteUserResponse { + #[serde(flatten)] + pub user: User, + #[serde(rename = "isDeleted")] + pub is_deleted: bool, + #[serde(rename = "deletedOn")] + pub deleted_on: String, +} + +impl DummyJsonClient { + /// Get all users + pub async fn get_all_users(&self) -> Result { + self.client + .get(USERS_BASE_URL.as_str()) + .send() + .await? + .json::() + .await + } + + /// Login User and get tokens + pub async fn login_user( + &self, + username: &str, + password: &str, + expires_in_mins: Option, + ) -> Result { + let payload: LoginRequest = LoginRequest { + username: username.to_string(), + password: password.to_string(), + expires_in_mins, + }; + self.client + .post(format!("{}/login", USERS_BASE_URL.as_str())) + .json(&payload) + .send() + .await? + .json::() + .await + } + + /// Get current authenticated user + pub async fn get_current_authenticated_user( + &self, + access_token: &str, + ) -> Result { + self.client + .get(format!("{}/me", USERS_BASE_URL.as_str())) + .header("Authorization", format!("Bearer {}", access_token)) + .send() + .await? + .json::() + .await + } + + /// Get user by id + pub async fn get_user_by_id(&self, id: u32) -> Result { + self.client + .get(format!("{}/{}", USERS_BASE_URL.as_str(), id)) + .send() + .await? + .json::() + .await + } + + /// Search users with name + pub async fn search_users(&self, query: &str) -> Result { + self.client + .get(format!("{}/search?q={}", USERS_BASE_URL.as_str(), query)) + .send() + .await? + .json::() + .await + } + + /// Filter users by key and value + pub async fn filter_users( + &self, + key: &str, + value: &str, + ) -> Result { + self.client + .get(format!("{}/filter?key={}&value={}", USERS_BASE_URL.as_str(), key, value)) + .send() + .await? + .json::() + .await + } + + /// Limit, skip and select users + pub async fn limit_skip_select_users( + &self, + limit: u32, + skip: u32, + selects: &str, + ) -> Result { + self.client + .get(format!( + "{}/?limit={}&skip={}&selects={}", + USERS_BASE_URL.as_str(), + limit, + skip, + selects + )) + .send() + .await? + .json::() + .await + } + + /// Sort users by key and order + pub async fn sort_users( + &self, + key: &str, + order: &str, + ) -> Result { + self.client + .get(format!("{}/?sortBy={}&order={}", USERS_BASE_URL.as_str(), key, order)) + .send() + .await? + .json::() + .await + } + + /// Get user carts by user id + pub async fn get_user_carts_by_user_id( + &self, + user_id: u32, + ) -> Result { + self.client + .get(format!("{}/{}/carts", USERS_BASE_URL.as_str(), user_id)) + .send() + .await? + .json::() + .await + } + + /// Get user posts by user id + pub async fn get_user_posts_by_user_id( + &self, + user_id: u32, + ) -> Result { + self.client + .get(format!("{}/{}/posts", USERS_BASE_URL.as_str(), user_id)) + .send() + .await? + .json::() + .await + } + + /// Get user todos by user id + pub async fn get_user_todos_by_user_id( + &self, + user_id: u32, + ) -> Result { + self.client + .get(format!("{}/{}/todos", USERS_BASE_URL.as_str(), user_id)) + .send() + .await? + .json::() + .await + } + + /// Add user + pub async fn add_user(&self, user: &AddUserPayload) -> Result { + self.client + .post(format!("{}/add", USERS_BASE_URL.as_str())) + .json(&user) + .send() + .await? + .json::() + .await + } + + /// Update user + pub async fn update_user( + &self, + id: u32, + user: &AddUserPayload, + ) -> Result { + self.client + .put(format!("{}/{}", USERS_BASE_URL.as_str(), id)) + .json(&user) + .send() + .await? + .json::() + .await + } + + /// Delete user + pub async fn delete_user(&self, id: u32) -> Result { + self.client + .delete(format!("{}/{}", USERS_BASE_URL.as_str(), id)) + .send() + .await? + .json::() + .await + } +} diff --git a/tests/auth.rs b/tests/auth.rs index b3f70c6..189c8e2 100644 --- a/tests/auth.rs +++ b/tests/auth.rs @@ -5,7 +5,7 @@ mod auth { #[tokio::test] async fn login() { let client = DummyJsonClient::default(); - let response = client.login("emilys", "emilyspass", 30).await; + let response = client.login("emilys", "emilyspass", Some(30)).await; assert!(response.is_ok()); println!("{:#?}", response.unwrap()); } @@ -13,7 +13,7 @@ mod auth { #[tokio::test] async fn get_user() { let client = DummyJsonClient::default(); - let response = client.login("emilys", "emilyspass", 30).await; + let response = client.login("emilys", "emilyspass", Some(30)).await; let access_token = response.unwrap().access_token; let response = client.get_user(&access_token).await; assert!(response.is_ok()); @@ -23,7 +23,7 @@ mod auth { #[tokio::test] async fn refresh_auth_session() { let client = DummyJsonClient::default(); - let response = client.login("emilys", "emilyspass", 30).await; + let response = client.login("emilys", "emilyspass", Some(30)).await; let refresh_token = response.unwrap().refresh_token; let response = client.refresh_auth_session(&refresh_token, 30).await; assert!(response.is_ok()); diff --git a/tests/users.rs b/tests/users.rs new file mode 100644 index 0000000..a4f4e36 --- /dev/null +++ b/tests/users.rs @@ -0,0 +1,136 @@ +mod users { + use dummy_json_rs::{AddUserPayload, DummyJsonClient}; + + #[tokio::test] + async fn get_all_users() { + let client = DummyJsonClient::default(); + let response = client.get_all_users().await; + assert!(response.is_ok()); + println!("{:#?}", response.unwrap()); + } + + #[tokio::test] + async fn login_user() { + let client = DummyJsonClient::default(); + let response = client.login_user("emilys", "emilyspass", None).await; + assert!(response.is_ok()); + println!("{:#?}", response.unwrap()); + } + + #[tokio::test] + async fn login_user_with_expires_in_mins() { + let client = DummyJsonClient::default(); + let response = client.login_user("emilys", "emilyspass", Some(30)).await; + assert!(response.is_ok()); + println!("{:#?}", response.unwrap()); + } + + #[tokio::test] + async fn get_current_authenticated_user() { + let client = DummyJsonClient::default(); + let response = client.login_user("emilys", "emilyspass", None).await; + let access_token = response.unwrap().access_token; + let response = client.get_current_authenticated_user(&access_token).await; + assert!(response.is_ok()); + println!("{:#?}", response.unwrap()); + } + + #[tokio::test] + async fn get_user_by_id() { + let client = DummyJsonClient::default(); + let response = client.get_user_by_id(1).await; + assert!(response.is_ok()); + println!("{:#?}", response.unwrap()); + } + + #[tokio::test] + async fn search_users() { + let client = DummyJsonClient::default(); + let response = client.search_users("john").await; + assert!(response.is_ok()); + println!("{:#?}", response.unwrap()); + } + + #[tokio::test] + async fn filter_users() { + let client = DummyJsonClient::default(); + let response = client.filter_users("hair.color", "Brown").await; + assert!(response.is_ok()); + println!("{:#?}", response.unwrap()); + } + + #[tokio::test] + async fn limit_skip_select_users() { + let client = DummyJsonClient::default(); + let response = client.limit_skip_select_users(3, 10, "firstName,age").await; + assert!(response.is_ok()); + println!("{:#?}", response.unwrap()); + } + + #[tokio::test] + async fn sort_users() { + let client = DummyJsonClient::default(); + let response = client.sort_users("age", "desc").await; + assert!(response.is_ok()); + println!("{:#?}", response.unwrap()); + } + + #[tokio::test] + async fn get_user_carts_by_user_id() { + let client = DummyJsonClient::default(); + let response = client.get_user_carts_by_user_id(6).await; + assert!(response.is_ok()); + println!("{:#?}", response.unwrap()); + } + + #[tokio::test] + async fn get_user_posts_by_user_id() { + let client = DummyJsonClient::default(); + let response = client.get_user_posts_by_user_id(6).await; + assert!(response.is_ok()); + println!("{:#?}", response.unwrap()); + } + + #[tokio::test] + async fn get_user_todos_by_user_id() { + let client = DummyJsonClient::default(); + let response = client.get_user_todos_by_user_id(6).await; + assert!(response.is_ok()); + println!("{:#?}", response.unwrap()); + } + + #[tokio::test] + async fn add_user() { + let client = DummyJsonClient::default(); + let payload = AddUserPayload { + first_name: Some("John".to_string()), + last_name: Some("Doe".to_string()), + age: Some(25), + ..Default::default() + }; + let response = client.add_user(&payload).await; + assert!(response.is_ok()); + println!("{:#?}", response.unwrap()); + } + + #[tokio::test] + async fn update_user() { + let client = DummyJsonClient::default(); + let payload = AddUserPayload { + last_name: Some("Owais".to_string()), + age: Some(27), + ..Default::default() + }; + let response = client.update_user(6, &payload).await; + assert!(response.is_ok()); + println!("{:#?}", response.unwrap()); + } + + #[tokio::test] + async fn delete_user() { + let client = DummyJsonClient::default(); + let response = client.delete_user(6).await; + assert!(response.is_ok()); + println!("{:#?}", response.unwrap()); + } +}