mangadex_api/v5/auth/
login.rs1use derive_builder::Builder;
31use serde::Serialize;
32
33use crate::v5::HttpClientRef;
34use mangadex_api_schema::v5::LoginResponse;
35use mangadex_api_types::error::Result;
36use mangadex_api_types::{Password, Username};
37
38#[derive(Debug, Builder, Serialize, Clone)]
42#[serde(rename_all = "camelCase")]
43#[builder(setter(into, strip_option))]
44pub struct Login<'a> {
45 #[doc(hidden)]
47 #[serde(skip)]
48 #[builder(pattern = "immutable")]
49 pub(crate) http_client: HttpClientRef,
50
51 #[serde(skip_serializing_if = "Option::is_none")]
52 #[builder(default)]
53 pub username: Option<Username>,
54
55 #[serde(skip_serializing_if = "Option::is_none")]
56 #[builder(default)]
57 pub email: Option<&'a str>,
58
59 pub password: Password,
60}
61
62impl Login<'_> {
63 pub async fn send(&self) -> Result<LoginResponse> {
64 #[cfg(not(feature = "multi-thread"))]
65 let res = {
66 let res = self.http_client.borrow().send_request(self).await??;
67
68 self.http_client.borrow_mut().set_auth_tokens(&res.token);
69
70 res
71 };
72 #[cfg(feature = "multi-thread")]
73 let res = {
74 let res = self.http_client.lock().await.send_request(self).await??;
75
76 self.http_client.lock().await.set_auth_tokens(&res.token);
77
78 res
79 };
80
81 Ok(res)
82 }
83}
84
85endpoint! {
86 POST "/auth/login",
87 #[body] Login<'_>,
88 #[no_send] Result<LoginResponse>
89}
90
91#[cfg(test)]
92mod tests {
93 use serde_json::json;
94 use url::Url;
95 use wiremock::matchers::{header, method, path};
96 use wiremock::{Mock, MockServer, ResponseTemplate};
97
98 use crate::v5::AuthTokens;
99 use crate::{HttpClient, MangaDexClient};
100 use mangadex_api_types::error::Error;
101 use mangadex_api_types::{Password, Username};
102
103 #[tokio::test]
104 async fn login_fires_a_request_to_base_url() -> anyhow::Result<()> {
105 let mock_server = MockServer::start().await;
106 let http_client: HttpClient = HttpClient::builder()
107 .base_url(Url::parse(&mock_server.uri())?)
108 .build()?;
109 let mangadex_client = MangaDexClient::new_with_http_client(http_client);
110
111 let _expected_body = json!({
112 "username": "myusername",
113 "password": "mypassword"
114 });
115 let response_body = json!({
116 "result": "ok",
117 "token": {
118 "session": "sessiontoken",
119 "refresh": "refreshtoken"
120 }
121 });
122
123 Mock::given(method("POST"))
124 .and(path(r"/auth/login"))
125 .and(header("Content-Type", "application/json"))
126 .respond_with(ResponseTemplate::new(200).set_body_json(response_body))
129 .expect(1)
130 .mount(&mock_server)
131 .await;
132
133 let _ = mangadex_client
134 .auth()
135 .login()
136 .username(Username::parse("myusername")?)
137 .password(Password::parse("mypassword")?)
138 .build()?
139 .send()
140 .await?;
141
142 #[cfg(not(feature = "multi-thread"))]
143 assert_eq!(
144 mangadex_client.http_client.borrow().get_tokens(),
145 Some(&AuthTokens {
146 session: "sessiontoken".to_string(),
147 refresh: "refreshtoken".to_string(),
148 })
149 );
150 #[cfg(feature = "multi-thread")]
151 assert_eq!(
152 mangadex_client.http_client.lock().await.get_tokens(),
153 Some(&AuthTokens {
154 session: "sessiontoken".to_string(),
155 refresh: "refreshtoken".to_string(),
156 })
157 );
158
159 Ok(())
160 }
161
162 #[tokio::test]
163 async fn logout_handles_400() -> anyhow::Result<()> {
164 let mock_server = MockServer::start().await;
165 let http_client: HttpClient = HttpClient::builder()
166 .base_url(Url::parse(&mock_server.uri())?)
167 .build()?;
168 let mangadex_client = MangaDexClient::new_with_http_client(http_client);
169
170 let _expected_body = json!({
171 "username": "myusername",
172 "password": "mypassword"
173 });
174 let response_body = json!({
175 "result": "error",
176 "errors": []
177 });
178
179 Mock::given(method("POST"))
180 .and(path(r"/auth/login"))
181 .and(header("Content-Type", "application/json"))
182 .respond_with(ResponseTemplate::new(400).set_body_json(response_body))
185 .expect(1)
186 .mount(&mock_server)
187 .await;
188
189 let res = mangadex_client
190 .auth()
191 .login()
192 .username(Username::parse("myusername")?)
193 .password(Password::parse("mypassword")?)
194 .build()?
195 .send()
196 .await
197 .expect_err("expected error");
198
199 #[cfg(not(feature = "multi-thread"))]
200 assert_eq!(mangadex_client.http_client.borrow().get_tokens(), None);
201 #[cfg(feature = "multi-thread")]
202 assert_eq!(mangadex_client.http_client.lock().await.get_tokens(), None);
203
204 if let Error::Api(errors) = res {
205 assert_eq!(errors.errors.len(), 0);
206 }
207
208 Ok(())
209 }
210
211 #[tokio::test]
212 async fn logout_handles_401() -> anyhow::Result<()> {
213 let mock_server = MockServer::start().await;
214 let http_client: HttpClient = HttpClient::builder()
215 .base_url(Url::parse(&mock_server.uri())?)
216 .build()?;
217 let mangadex_client = MangaDexClient::new_with_http_client(http_client);
218
219 let _expected_body = json!({
220 "username": "myusername",
221 "password": "mypassword"
222 });
223
224 Mock::given(method("POST"))
225 .and(path(r"/auth/login"))
226 .and(header("Content-Type", "application/json"))
227 .respond_with(ResponseTemplate::new(401))
230 .expect(1)
231 .mount(&mock_server)
232 .await;
233
234 let res = mangadex_client
235 .auth()
236 .login()
237 .username(Username::parse("myusername")?)
238 .password(Password::parse("mypassword")?)
239 .build()?
240 .send()
241 .await
242 .expect_err("expected error");
243
244 #[cfg(not(feature = "multi-thread"))]
245 assert_eq!(mangadex_client.http_client.borrow().get_tokens(), None);
246 #[cfg(feature = "multi-thread")]
247 assert_eq!(mangadex_client.http_client.lock().await.get_tokens(), None);
248
249 match res {
250 Error::RequestError(_) => {}
251 _ => panic!("unexpected error"),
252 }
253
254 Ok(())
255 }
256
257 #[tokio::test]
258 async fn logout_handles_http_503() -> anyhow::Result<()> {
259 let mock_server = MockServer::start().await;
260 let http_client: HttpClient = HttpClient::builder()
261 .base_url(Url::parse(&mock_server.uri())?)
262 .build()?;
263 let mangadex_client = MangaDexClient::new_with_http_client(http_client);
264
265 let _expected_body = json!({
266 "username": "myusername",
267 "password": "mypassword"
268 });
269
270 Mock::given(method("POST"))
271 .and(path(r"/auth/login"))
272 .and(header("Content-Type", "application/json"))
273 .respond_with(ResponseTemplate::new(503))
276 .expect(1)
277 .mount(&mock_server)
278 .await;
279
280 let res = mangadex_client
281 .auth()
282 .login()
283 .username(Username::parse("myusername")?)
284 .password(Password::parse("mypassword")?)
285 .build()?
286 .send()
287 .await
288 .expect_err("expected error");
289
290 #[cfg(not(feature = "multi-thread"))]
291 assert_eq!(mangadex_client.http_client.borrow().get_tokens(), None);
292 #[cfg(feature = "multi-thread")]
293 assert_eq!(mangadex_client.http_client.lock().await.get_tokens(), None);
294
295 match res {
296 Error::ServerError(..) => {}
297 _ => panic!("unexpected error"),
298 }
299
300 Ok(())
301 }
302}