mangadex_api/v5/oauth/
refresh_token.rs1use derive_builder::Builder;
51use mangadex_api_schema::v5::oauth::OAuthTokenResponse;
52use mangadex_api_schema::v5::AuthTokens;
53use mangadex_api_types::oauth::GrantTypeSupported;
54use reqwest::Method;
55use serde::Serialize;
56#[cfg(not(test))]
57use url::Url;
58
59use crate::v5::HttpClientRef;
60use crate::Result;
61
62#[cfg_attr(
66 feature = "deserializable-endpoint",
67 derive(serde::Deserialize, getset::Getters, getset::Setters)
68)]
69#[derive(Debug, Clone, Builder)]
70#[builder(
71 setter(into, strip_option),
72 build_fn(error = "crate::error::BuilderError")
73)]
74#[non_exhaustive]
75pub struct RefreshTokens {
76 #[doc(hidden)]
78 #[cfg_attr(feature = "deserializable-endpoint", serde(skip))]
79 #[builder(pattern = "immutable")]
80 #[cfg_attr(feature = "deserializable-endpoint", getset(set = "pub", get = "pub"))]
81 pub http_client: HttpClientRef,
82}
83
84#[derive(Clone, Serialize)]
85struct RefreshTokenBody {
86 grant_type: GrantTypeSupported,
87 refresh_token: String,
88 client_id: String,
89 client_secret: String,
90}
91
92impl RefreshTokens {
93 pub async fn send(&mut self) -> Result<OAuthTokenResponse> {
94 let res = {
95 let client = self.http_client.read().await;
96 let client_info = client
97 .get_client_info()
98 .ok_or(crate::error::Error::MissingClientInfo)?;
99 let auth_tokens = client
100 .get_tokens()
101 .ok_or(crate::error::Error::MissingTokens)?;
102 let params = RefreshTokenBody {
103 grant_type: GrantTypeSupported::RefreshToken,
104 refresh_token: auth_tokens.refresh.to_owned(),
105 client_id: client_info.client_id.to_owned(),
106 client_secret: client_info.client_secret.to_owned(),
107 };
108 #[cfg(test)]
109 let res = client
110 .client
111 .request(
112 Method::POST,
113 client
114 .base_url
115 .join("/realms/mangadex/protocol/openid-connect/token")?,
116 )
117 .form(¶ms)
118 .send()
119 .await?;
120 #[cfg(not(test))]
121 let res = client
122 .client
123 .request(
124 Method::POST,
125 Url::parse(crate::AUTH_URL)?
126 .join("/realms/mangadex/protocol/openid-connect/token")?,
127 )
128 .form(¶ms)
129 .send()
130 .await?;
131 if res.status().is_client_error() || res.status().is_server_error() {
132 return Err(super::OAuthError::handle_resp(res).await);
133 }
134 res.json::<OAuthTokenResponse>().await?
135 };
136 {
137 let auth_tokens: AuthTokens = From::from(res.clone());
138 let mut client = self.http_client.write().await;
139 client.set_auth_tokens(&auth_tokens);
140 };
141 Ok(res)
142 }
143}
144
145builder_send! {
146 #[builder] RefreshTokensBuilder,
147 OAuthTokenResponse
148}
149
150#[cfg(test)]
151mod tests {
152 use mangadex_api_schema::v5::oauth::ClientInfo;
153 use mangadex_api_types::oauth::GrantTypeSupported;
154 use serde_json::json;
155 use url::Url;
156 use wiremock::matchers::{body_string, header, method, path};
157 use wiremock::{Mock, MockServer, ResponseTemplate};
158
159 use crate::v5::oauth::refresh_token::RefreshTokenBody;
160 use crate::v5::AuthTokens;
161 use crate::{HttpClient, MangaDexClient};
162 use serde_urlencoded::to_string;
163
164 #[tokio::test]
165 async fn refresh_token_fires_a_request_to_base_url() -> anyhow::Result<()> {
166 let mock_server = MockServer::start().await;
167 let http_client: HttpClient = HttpClient::builder()
168 .base_url(Url::parse(&mock_server.uri())?)
169 .build()?;
170 let mangadex_client = MangaDexClient::new_with_http_client(http_client);
171
172 let client_info: ClientInfo = non_exhaustive::non_exhaustive!(ClientInfo {
173 client_id: "someClientId".to_string(),
174 client_secret: "someClientSecret".to_string(),
175 });
176
177 mangadex_client.set_client_info(&client_info).await?;
178
179 let auth_tokens = non_exhaustive::non_exhaustive!(AuthTokens {
180 session: "sessiontoken".to_string(),
181 refresh: "refreshtoken".to_string(),
182 });
183
184 mangadex_client.set_auth_tokens(&auth_tokens).await?;
185
186 let response_body = json!({
187 "access_token": auth_tokens.session.clone(),
188 "expires_in": 900,
189 "refresh_expires_in": 2414162,
190 "refresh_token": auth_tokens.refresh.clone(),
191 "token_type": "Bearer",
192 "not-before-policy": 0,
193 "session_state": "c176499d-6e8d-4ddf-ad59-6d922be66431",
194 "scope": "groups email profile",
195 "client_type": "personal"
196 });
197 let expected_body: String = to_string(RefreshTokenBody {
198 grant_type: GrantTypeSupported::RefreshToken,
199 refresh_token: auth_tokens.refresh.to_owned(),
200 client_id: client_info.client_id.clone(),
201 client_secret: client_info.client_secret.clone(),
202 })?;
203
204 Mock::given(method("POST"))
205 .and(path(r"/realms/mangadex/protocol/openid-connect/token"))
206 .and(header("Content-Type", "application/x-www-form-urlencoded"))
207 .and(body_string(expected_body))
208 .respond_with(ResponseTemplate::new(200).set_body_json(response_body))
209 .expect(1)
210 .mount(&mock_server)
211 .await;
212
213 let _ = mangadex_client.oauth().refresh().send().await?;
214
215 assert_eq!(
216 mangadex_client.http_client.read().await.get_tokens(),
217 Some(&auth_tokens)
218 );
219
220 Ok(())
221 }
222}