1use std::borrow::Cow;
2
3use chrono::NaiveDate;
4
5use crate::client::Executor;
6
7const TV_PATH: &str = "/tv/changes";
8const MOVIE_PATH: &str = "/movie/changes";
9const PERSON_PATH: &str = "/person/changes";
10
11#[derive(Clone, Debug, Default)]
31pub struct ChangeList {
32 path: &'static str,
33 pub start_date: Option<NaiveDate>,
35 pub end_date: Option<NaiveDate>,
37 pub page: Option<u32>,
39}
40
41impl ChangeList {
42 pub fn tv() -> Self {
43 Self {
44 path: TV_PATH,
45 start_date: None,
46 end_date: None,
47 page: None,
48 }
49 }
50
51 pub fn movie() -> Self {
52 Self {
53 path: MOVIE_PATH,
54 start_date: None,
55 end_date: None,
56 page: None,
57 }
58 }
59
60 pub fn person() -> Self {
61 Self {
62 path: PERSON_PATH,
63 start_date: None,
64 end_date: None,
65 page: None,
66 }
67 }
68
69 pub fn with_start_date(mut self, value: Option<NaiveDate>) -> Self {
70 self.start_date = value;
71 self
72 }
73
74 pub fn with_end_date(mut self, value: Option<NaiveDate>) -> Self {
75 self.end_date = value;
76 self
77 }
78
79 pub fn with_page(mut self, value: Option<u32>) -> Self {
80 self.page = value;
81 self
82 }
83}
84
85impl crate::prelude::Command for ChangeList {
86 type Output = crate::common::PaginatedResult<super::Change>;
87
88 fn path(&self) -> Cow<'static, str> {
89 Cow::Borrowed(self.path)
90 }
91
92 fn params(&self) -> Vec<(&'static str, Cow<'_, str>)> {
93 let mut res = Vec::with_capacity(3);
94 if let Some(ref start_date) = self.start_date {
95 res.push(("start_date", Cow::Owned(start_date.to_string())));
96 }
97 if let Some(ref end_date) = self.end_date {
98 res.push(("end_date", Cow::Owned(end_date.to_string())));
99 }
100 if let Some(page) = self.page {
101 res.push(("page", Cow::Owned(page.to_string())));
102 }
103 res
104 }
105
106 async fn execute<E: Executor>(
107 &self,
108 client: &crate::Client<E>,
109 ) -> Result<Self::Output, crate::error::Error> {
110 client.execute(self.path().as_ref(), self.params()).await
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::ChangeList;
117 use crate::client::Client;
118 use crate::client::reqwest::ReqwestExecutor;
119 use crate::prelude::Command;
120 use chrono::NaiveDate;
121 use mockito::Matcher;
122
123 #[tokio::test]
124 async fn tv_works() {
125 let mut server = mockito::Server::new_async().await;
126 let client = Client::<ReqwestExecutor>::builder()
127 .with_api_key("secret".into())
128 .with_base_url(server.url())
129 .build()
130 .unwrap();
131 let cmd = ChangeList::tv();
132
133 let _m = server
134 .mock("GET", super::TV_PATH)
135 .match_query(Matcher::UrlEncoded("api_key".into(), "secret".into()))
136 .with_status(200)
137 .with_header("content-type", "application/json")
138 .with_body(include_str!("../../assets/tv-all-changes.json"))
139 .create_async()
140 .await;
141 let result = cmd.execute(&client).await.unwrap();
142 assert_eq!(result.page, 1);
143 }
144
145 #[tokio::test]
146 async fn tv_works_with_args() {
147 let mut server = mockito::Server::new_async().await;
148 let client = Client::<ReqwestExecutor>::builder()
149 .with_api_key("secret".into())
150 .with_base_url(server.url())
151 .build()
152 .unwrap();
153
154 let _m = server
155 .mock("GET", super::TV_PATH)
156 .match_query(Matcher::UrlEncoded("api_key".into(), "secret".into()))
157 .match_query(Matcher::AllOf(vec![
158 Matcher::UrlEncoded("api_key".into(), "secret".into()),
159 Matcher::UrlEncoded("start_date".into(), "2015-03-14".into()),
160 Matcher::UrlEncoded("end_date".into(), "2019-03-14".into()),
161 Matcher::UrlEncoded("page".into(), "2".into()),
162 ]))
163 .with_status(200)
164 .with_header("content-type", "application/json")
165 .with_body(include_str!("../../assets/tv-all-changes.json"))
166 .create_async()
167 .await;
168
169 let result = ChangeList::tv()
170 .with_start_date(Some(NaiveDate::from_ymd_opt(2015, 3, 14).unwrap()))
171 .with_end_date(Some(NaiveDate::from_ymd_opt(2019, 3, 14).unwrap()))
172 .with_page(Some(2))
173 .execute(&client)
174 .await
175 .unwrap();
176 assert_eq!(result.page, 1);
177 }
178
179 #[tokio::test]
180 async fn movie_works() {
181 let mut server = mockito::Server::new_async().await;
182 let client = Client::<ReqwestExecutor>::builder()
183 .with_api_key("secret".into())
184 .with_base_url(server.url())
185 .build()
186 .unwrap();
187 let cmd = ChangeList::movie();
188
189 let _m = server
190 .mock("GET", super::MOVIE_PATH)
191 .match_query(Matcher::UrlEncoded("api_key".into(), "secret".into()))
192 .with_status(200)
193 .with_header("content-type", "application/json")
194 .with_body(include_str!("../../assets/movie-all-changes.json"))
195 .create_async()
196 .await;
197 let result = cmd.execute(&client).await.unwrap();
198 assert_eq!(result.page, 1);
199 }
200
201 #[tokio::test]
202 async fn person_works() {
203 let mut server = mockito::Server::new_async().await;
204 let client = Client::<ReqwestExecutor>::builder()
205 .with_api_key("secret".into())
206 .with_base_url(server.url())
207 .build()
208 .unwrap();
209 let cmd = ChangeList::person();
210
211 let _m = server
212 .mock("GET", super::PERSON_PATH)
213 .match_query(Matcher::UrlEncoded("api_key".into(), "secret".into()))
214 .with_status(200)
215 .with_header("content-type", "application/json")
216 .with_body(include_str!("../../assets/movie-all-changes.json"))
217 .create_async()
218 .await;
219 let result = cmd.execute(&client).await.unwrap();
220 assert_eq!(result.page, 1);
221 }
222
223 #[tokio::test]
224 async fn invalid_api_key() {
225 let mut server = mockito::Server::new_async().await;
226 let client = Client::<ReqwestExecutor>::builder()
227 .with_api_key("secret".into())
228 .with_base_url(server.url())
229 .build()
230 .unwrap();
231 let cmd = ChangeList::tv();
232
233 let _m = server
234 .mock("GET", super::TV_PATH)
235 .match_query(Matcher::UrlEncoded("api_key".into(), "secret".into()))
236 .with_status(401)
237 .with_header("content-type", "application/json")
238 .with_body(include_str!("../../assets/invalid-api-key.json"))
239 .create_async()
240 .await;
241 let err = cmd.execute(&client).await.unwrap_err();
242 let server_err = err.as_server_error().unwrap();
243 assert_eq!(server_err.status_code, 7);
244 }
245
246 #[tokio::test]
247 async fn resource_not_found() {
248 let mut server = mockito::Server::new_async().await;
249 let client = Client::<ReqwestExecutor>::builder()
250 .with_api_key("secret".into())
251 .with_base_url(server.url())
252 .build()
253 .unwrap();
254 let cmd = ChangeList::tv();
255
256 let _m = server
257 .mock("GET", super::TV_PATH)
258 .match_query(Matcher::UrlEncoded("api_key".into(), "secret".into()))
259 .with_status(404)
260 .with_header("content-type", "application/json")
261 .with_body(include_str!("../../assets/resource-not-found.json"))
262 .create_async()
263 .await;
264 let err = cmd.execute(&client).await.unwrap_err();
265 let server_err = err.as_server_error().unwrap();
266 assert_eq!(server_err.status_code, 34);
267 }
268}
269
270#[cfg(all(test, feature = "integration"))]
271mod integration_tests {
272 use super::ChangeList;
273 use crate::client::Client;
274 use crate::client::reqwest::ReqwestExecutor;
275 use crate::prelude::Command;
276
277 #[tokio::test]
278 async fn execute_tv() {
279 let secret = std::env::var("TMDB_TOKEN_V3").unwrap();
280 let client = Client::<ReqwestExecutor>::new(secret);
281
282 let result = ChangeList::tv().execute(&client).await.unwrap();
283 assert_eq!(result.page, 1);
284 }
285
286 #[tokio::test]
287 async fn execute_movie() {
288 let secret = std::env::var("TMDB_TOKEN_V3").unwrap();
289 let client = Client::<ReqwestExecutor>::new(secret);
290
291 let result = ChangeList::movie().execute(&client).await.unwrap();
292 assert_eq!(result.page, 1);
293 }
294
295 #[tokio::test]
296 async fn execute_person() {
297 let secret = std::env::var("TMDB_TOKEN_V3").unwrap();
298 let client = Client::<ReqwestExecutor>::new(secret);
299
300 let result = ChangeList::person().execute(&client).await.unwrap();
301 assert_eq!(result.page, 1);
302 }
303}