mangadex_api/v5/manga/draft/id/commit/
post.rs1use derive_builder::Builder;
49use serde::Serialize;
50use uuid::Uuid;
51
52use crate::HttpClientRef;
53use mangadex_api_schema::v5::MangaData;
54
55#[cfg_attr(
56 feature = "deserializable-endpoint",
57 derive(serde::Deserialize, getset::Getters, getset::Setters)
58)]
59#[derive(Debug, Serialize, Clone, Builder, Default)]
60#[serde(rename_all = "camelCase")]
61#[builder(
62 setter(into),
63 build_fn(error = "mangadex_api_types::error::BuilderError")
64)]
65pub struct SubmitMangaDraft {
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 #[builder(pattern = "immutable")]
75 pub manga_id: Uuid,
76
77 pub version: u32,
78}
79
80endpoint! {
81 POST ("/manga/draft/{}/commit/", manga_id),
82 #[body auth] SubmitMangaDraft,
83 #[rate_limited] MangaData,
84 SubmitMangaDraftBuilder
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::{body_json, 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_types::error::Error;
99 use mangadex_api_types::{
100 ContentRating, Demographic, Language, MangaDexDateTime, MangaStatus, ResponseType, Tag,
101 };
102
103 #[tokio::test]
104 async fn submit_manga_draft_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 .auth_tokens(AuthTokens {
109 session: "sessiontoken".to_string(),
110 refresh: "refreshtoken".to_string(),
111 })
112 .build()?;
113 let mangadex_client = MangaDexClient::new_with_http_client(http_client);
114
115 let manga_id = Uuid::new_v4();
116 let tag_id: Uuid = Tag::Action.into();
117 let manga_title = "Test Manga".to_string();
118
119 let datetime = MangaDexDateTime::new(&OffsetDateTime::now_utc());
120
121 let expected_body = json!({
122 "version": 1
123 });
124 let response_body = json!({
125 "result": "ok",
126 "response": "entity",
127 "data": {
128 "id": manga_id,
129 "type": "manga",
130 "attributes": {
131 "title": {
132 "en": manga_title
133 },
134 "altTitles": [],
135 "description": {},
136 "isLocked": false,
137 "links": null,
138 "originalLanguage": "ja",
139 "lastVolume": null,
140 "lastChapter": null,
141 "publicationDemographic": "shounen",
142 "status": "ongoing",
143 "year": null,
144 "contentRating": "safe",
145 "chapterNumbersResetOnNewVolume": true,
146 "availableTranslatedLanguages": ["en"],
147 "tags": [
148 {
149 "id": tag_id,
150 "type": "tag",
151 "attributes": {
152 "name": {
153 "en": "Action"
154 },
155 "description": [],
156 "group": "genre",
157 "version": 1
158 },
159 "relationships": []
160 }
161 ],
162 "state": "submitted",
163 "createdAt": datetime.to_string(),
164 "updatedAt": datetime.to_string(),
165
166 "version": 1
167 },
168 "relationships": []
169 }
170 });
171
172 Mock::given(method("POST"))
173 .and(path_regex(r"/manga/draft/[0-9a-fA-F-]+/commit"))
174 .and(header("Authorization", "Bearer sessiontoken"))
175 .and(header("Content-Type", "application/json"))
176 .and(body_json(expected_body))
177 .respond_with(
178 ResponseTemplate::new(201)
179 .insert_header("x-ratelimit-retry-after", "1698723860")
180 .insert_header("x-ratelimit-limit", "40")
181 .insert_header("x-ratelimit-remaining", "39")
182 .set_body_json(response_body),
183 )
184 .expect(1)
185 .mount(&mock_server)
186 .await;
187
188 let res = mangadex_client
189 .manga()
190 .draft()
191 .id(manga_id)
192 .commit()
193 .post()
194 .version(1_u32)
195 .send()
196 .await?;
197
198 assert_eq!(res.response, ResponseType::Entity);
199 assert_eq!(res.data.id, manga_id);
200 assert_eq!(
201 res.data.attributes.title.get(&Language::English).unwrap(),
202 &manga_title
203 );
204 assert!(res.data.attributes.alt_titles.is_empty());
205 assert!(res.data.attributes.description.is_empty());
206 assert!(!res.data.attributes.is_locked);
207 assert_eq!(res.data.attributes.links, None);
208 assert_eq!(res.data.attributes.original_language, Language::Japanese);
209 assert_eq!(res.data.attributes.last_volume, None);
210 assert_eq!(res.data.attributes.last_chapter, None);
211 assert_eq!(
212 res.data.attributes.publication_demographic.unwrap(),
213 Demographic::Shounen
214 );
215 assert_eq!(res.data.attributes.status, MangaStatus::Ongoing);
216 assert_eq!(res.data.attributes.year, None);
217 assert_eq!(
218 res.data.attributes.content_rating.unwrap(),
219 ContentRating::Safe
220 );
221 assert_eq!(
222 res.data.attributes.tags[0]
223 .attributes
224 .name
225 .get(&Language::English),
226 Some(&"Action".to_string())
227 );
228 assert_eq!(
229 res.data.attributes.created_at.to_string(),
230 datetime.to_string()
231 );
232 assert_eq!(
233 res.data.attributes.updated_at.as_ref().unwrap().to_string(),
234 datetime.to_string()
235 );
236 assert_eq!(res.data.attributes.version, 1);
237
238 Ok(())
239 }
240
241 #[tokio::test]
242 async fn submit_manga_draft_requires_auth() -> anyhow::Result<()> {
243 let mock_server = MockServer::start().await;
244 let http_client: HttpClient = HttpClient::builder()
245 .base_url(Url::parse(&mock_server.uri())?)
246 .build()?;
247 let mangadex_client = MangaDexClient::new_with_http_client(http_client);
248
249 let manga_id = Uuid::new_v4();
250 let error_id = Uuid::new_v4();
251 let response_body = json!({
252 "result": "error",
253 "errors": [{
254 "id": error_id.to_string(),
255 "status": 403,
256 "title": "Forbidden",
257 "detail": "You must be logged in to continue."
258 }]
259 });
260
261 Mock::given(method("POST"))
262 .and(path_regex(r"/manga/draft/[0-9a-fA-F-]+/commit"))
263 .and(header("Content-Type", "application/json"))
264 .respond_with(
265 ResponseTemplate::new(403)
266 .insert_header("x-ratelimit-retry-after", "1698723860")
267 .insert_header("x-ratelimit-limit", "40")
268 .insert_header("x-ratelimit-remaining", "39")
269 .set_body_json(response_body),
270 )
271 .expect(0)
272 .mount(&mock_server)
273 .await;
274
275 let res = mangadex_client
276 .manga()
277 .draft()
278 .id(manga_id)
279 .commit()
280 .post()
281 .version(1_u32)
282 .send()
283 .await
284 .expect_err("expected error");
285
286 match res {
287 Error::MissingTokens => {}
288 _ => panic!("unexpected error: {:#?}", res),
289 }
290
291 Ok(())
292 }
293}