mangadex_api/v5/manga/draft/id/
get.rs1use derive_builder::Builder;
48use serde::Serialize;
49use uuid::Uuid;
50
51use crate::HttpClientRef;
52use mangadex_api_schema::v5::MangaResponse;
53use mangadex_api_types::ReferenceExpansionResource;
54
55#[cfg_attr(
56 feature = "deserializable-endpoint",
57 derive(serde::Deserialize, getset::Getters, getset::Setters)
58)]
59#[derive(Debug, Serialize, Clone, Builder)]
60#[serde(rename_all = "camelCase")]
61#[builder(
62 setter(into, strip_option),
63 build_fn(error = "mangadex_api_types::error::BuilderError")
64)]
65pub struct GetMangaDraft {
66 #[doc(hidden)]
68 #[serde(skip)]
69 #[builder(pattern = "immutable")]
70 #[cfg_attr(feature = "deserializable-endpoint", getset(set = "pub", get = "pub"))]
71 pub http_client: HttpClientRef,
72
73 #[serde(skip_serializing)]
74 pub manga_id: Uuid,
75
76 #[builder(setter(each = "include"), default)]
77 pub includes: Vec<ReferenceExpansionResource>,
78}
79
80endpoint! {
81 GET ("/manga/draft/{}", manga_id),
82 #[query auth] GetMangaDraft,
83 #[flatten_result] MangaResponse,
84 GetMangaDraftBuilder
85}
86
87#[cfg(test)]
88mod tests {
89 use serde_json::json;
90 use time::OffsetDateTime;
91 use url::Url;
92 use uuid::Uuid;
93 use wiremock::matchers::{header, method, path_regex};
94 use wiremock::{Mock, MockServer, ResponseTemplate};
95
96 use crate::v5::AuthTokens;
97 use crate::{HttpClient, MangaDexClient};
98 use mangadex_api_schema::v5::RelatedAttributes;
99 use mangadex_api_types::error::Error;
100 use mangadex_api_types::{
101 MangaDexDateTime, MangaRelation, ReferenceExpansionResource, RelationshipType,
102 };
103
104 #[tokio::test]
105 async fn get_manga_draft_fires_a_request_to_base_url() -> anyhow::Result<()> {
106 let mock_server = MockServer::start().await;
107 let http_client: HttpClient = HttpClient::builder()
108 .base_url(Url::parse(&mock_server.uri())?)
109 .auth_tokens(AuthTokens {
110 session: "sessiontoken".to_string(),
111 refresh: "refreshtoken".to_string(),
112 })
113 .build()?;
114 let mangadex_client = MangaDexClient::new_with_http_client(http_client);
115
116 let manga_id = Uuid::new_v4();
117
118 let datetime = MangaDexDateTime::new(&OffsetDateTime::now_utc());
119
120 let response_body = json!({
121 "result": "ok",
122 "response": "entity",
123 "data": {
124 "id": manga_id,
125 "type": "manga",
126 "attributes": {
127 "title": {
128 "en": "Test Manga"
129 },
130 "altTitles": [],
131 "description": {},
132 "isLocked": false,
133 "links": {},
134 "originalLanguage": "ja",
135 "lastVolume": "1",
136 "lastChapter": "1",
137 "publicationDemographic": "shoujo",
138 "status": "completed",
139 "year": 2021,
140 "contentRating": "safe",
141 "chapterNumbersResetOnNewVolume": true,
142 "availableTranslatedLanguages": ["en"],
143 "tags": [],
144 "state": "rejected",
145 "version": 1,
146 "createdAt": datetime.to_string(),
147 "updatedAt": datetime.to_string(),
148 },
149 "relationships": [
150 {
151 "id": "a3219a4f-73c0-4213-8730-05985130539a",
152 "type": "manga",
153 "related": "side_story",
154 }
155 ]
156 }
157 });
158
159 Mock::given(method("GET"))
160 .and(path_regex(r"/manga/draft/[0-9a-fA-F-]+"))
161 .and(header("Authorization", "Bearer sessiontoken"))
162 .respond_with(ResponseTemplate::new(200).set_body_json(response_body))
163 .expect(1)
164 .mount(&mock_server)
165 .await;
166
167 let res = mangadex_client
168 .manga()
169 .draft()
170 .id(manga_id)
171 .get()
172 .build()?
173 .send()
174 .await?;
175
176 assert_eq!(res.data.relationships[0].type_, RelationshipType::Manga);
177 assert_eq!(
178 res.data.relationships[0].related,
179 Some(MangaRelation::SideStory)
180 );
181 assert!(res.data.relationships[0].attributes.is_none());
182
183 Ok(())
184 }
185
186 #[tokio::test]
187 async fn get_manga_draft_handles_reference_expansion() -> anyhow::Result<()> {
188 let mock_server = MockServer::start().await;
189 let http_client: HttpClient = HttpClient::builder()
190 .base_url(Url::parse(&mock_server.uri())?)
191 .auth_tokens(AuthTokens {
192 session: "sessiontoken".to_string(),
193 refresh: "refreshtoken".to_string(),
194 })
195 .build()?;
196 let mangadex_client = MangaDexClient::new_with_http_client(http_client);
197
198 let manga_id = Uuid::new_v4();
199
200 let datetime = MangaDexDateTime::new(&OffsetDateTime::now_utc());
201
202 let response_body = json!({
203 "result": "ok",
204 "response": "entity",
205 "data": {
206 "id": manga_id,
207 "type": "manga",
208 "attributes": {
209 "title": {
210 "en": "Test Manga"
211 },
212 "altTitles": [],
213 "description": {},
214 "isLocked": false,
215 "links": {},
216 "originalLanguage": "ja",
217 "lastVolume": "1",
218 "lastChapter": "1",
219 "publicationDemographic": "shoujo",
220 "status": "completed",
221 "year": 2021,
222 "contentRating": "safe",
223 "chapterNumbersResetOnNewVolume": true,
224 "availableTranslatedLanguages": ["en"],
225 "tags": [],
226 "state": "submitted",
227 "version": 1,
228 "createdAt": datetime.to_string(),
229 "updatedAt": datetime.to_string(),
230 },
231 "relationships": [
232 {
233 "id": "fc343004-569b-4750-aba0-05ab35efc17c",
234 "type": "author",
235 "attributes": {
236 "name": "Hologfx",
237 "imageUrl": null,
238 "biography": [],
239 "twitter": null,
240 "pixiv": null,
241 "melonBook": null,
242 "fanBox": null,
243 "booth": null,
244 "nicoVideo": null,
245 "skeb": null,
246 "fantia": null,
247 "tumblr": null,
248 "youtube": null,
249 "website": null,
250 "createdAt": "2021-04-19T21:59:45+00:00",
251 "updatedAt": "2021-04-19T21:59:45+00:00",
252 "version": 1
253 }
254 }
255 ]
256 }
257 });
258
259 Mock::given(method("GET"))
260 .and(path_regex(r"/manga/draft/[0-9a-fA-F-]+"))
261 .and(header("Authorization", "Bearer sessiontoken"))
262 .respond_with(ResponseTemplate::new(200).set_body_json(response_body))
263 .expect(1)
264 .mount(&mock_server)
265 .await;
266
267 let res = mangadex_client
268 .manga()
269 .draft()
270 .id(manga_id)
271 .get()
272 .include(ReferenceExpansionResource::Author)
273 .send()
274 .await?;
275
276 assert_eq!(res.data.relationships[0].type_, RelationshipType::Author);
277 assert!(res.data.relationships[0].related.is_none());
278 match res.data.relationships[0].attributes.as_ref().unwrap() {
279 RelatedAttributes::Author(author) => assert_eq!(author.name, "Hologfx".to_string()),
280 _ => panic!("Expected author RelatedAttributes"),
281 }
282
283 Ok(())
284 }
285
286 #[tokio::test]
287 async fn get_manga_draft_requires_auth() -> anyhow::Result<()> {
288 let mock_server = MockServer::start().await;
289 let http_client: HttpClient = HttpClient::builder()
290 .base_url(Url::parse(&mock_server.uri())?)
291 .build()?;
292 let mangadex_client = MangaDexClient::new_with_http_client(http_client);
293
294 let manga_id = Uuid::new_v4();
295 let error_id = Uuid::new_v4();
296 let response_body = json!({
297 "result": "error",
298 "errors": [{
299 "id": error_id.to_string(),
300 "status": 403,
301 "title": "Forbidden",
302 "detail": "You must be logged in to continue."
303 }]
304 });
305
306 Mock::given(method("GET"))
307 .and(path_regex(r"/manga/draft/[0-9a-fA-F-]+"))
308 .respond_with(ResponseTemplate::new(403).set_body_json(response_body))
309 .expect(0)
310 .mount(&mock_server)
311 .await;
312
313 let res = mangadex_client
314 .manga()
315 .draft()
316 .id(manga_id)
317 .get()
318 .send()
319 .await
320 .expect_err("expected error");
321
322 match res {
323 Error::MissingTokens => {}
324 _ => panic!("unexpected error: {:#?}", res),
325 }
326
327 Ok(())
328 }
329}