tmdb_api/movie/
changes.rs

1use std::borrow::Cow;
2
3use chrono::NaiveDate;
4
5/// Command to get changes for a movie
6///
7/// ```rust
8/// use tmdb_api::prelude::Command;
9/// use tmdb_api::client::Client;
10/// use tmdb_api::client::reqwest::ReqwestExecutor;
11/// use tmdb_api::movie::changes::MovieChanges;
12///
13/// #[tokio::main]
14/// async fn main() {
15///     let client = Client::<ReqwestExecutor>::new("this-is-my-secret-token".into());
16///     let cmd = MovieChanges::new(1);
17///     let result = cmd.execute(&client).await;
18///     match result {
19///         Ok(res) => println!("found: {:#?}", res),
20///         Err(err) => eprintln!("error: {:?}", err),
21///     };
22/// }
23/// ```
24#[derive(Clone, Debug, Default)]
25pub struct MovieChanges {
26    /// ID of the Movie
27    pub movie_id: u64,
28    /// Filter the results with a start date.
29    pub start_date: Option<NaiveDate>,
30    /// Filter the results with a end date.
31    pub end_date: Option<NaiveDate>,
32    /// The country to filter the alternative titles
33    pub page: Option<u32>,
34}
35
36impl MovieChanges {
37    pub fn new(movie_id: u64) -> Self {
38        Self {
39            movie_id,
40            start_date: None,
41            end_date: None,
42            page: None,
43        }
44    }
45
46    pub fn with_start_date(mut self, value: Option<NaiveDate>) -> Self {
47        self.start_date = value;
48        self
49    }
50
51    pub fn with_end_date(mut self, value: Option<NaiveDate>) -> Self {
52        self.end_date = value;
53        self
54    }
55
56    pub fn with_page(mut self, value: Option<u32>) -> Self {
57        self.page = value;
58        self
59    }
60}
61
62#[derive(Debug, Deserialize, Serialize)]
63pub struct MovieChange {
64    pub key: String,
65    pub items: Vec<MovieChangeItem>,
66}
67
68#[derive(Debug, Deserialize, Serialize)]
69pub struct MovieChangeItem {
70    pub id: String,
71    pub action: String,
72    pub time: chrono::DateTime<chrono::Utc>,
73    pub iso_639_1: String,
74    pub iso_3166_1: String,
75    // TODO handle really dynamic kind of values
76    // pub value: String,
77    // pub original_value: String,
78}
79
80#[derive(Debug, Deserialize, Serialize)]
81pub struct MovieChangesResult {
82    pub changes: Vec<MovieChange>,
83}
84
85impl crate::prelude::Command for MovieChanges {
86    type Output = MovieChangesResult;
87
88    fn path(&self) -> Cow<'static, str> {
89        Cow::Owned(format!("/movie/{}/changes", self.movie_id))
90    }
91
92    fn params(&self) -> Vec<(&'static str, Cow<'_, str>)> {
93        Vec::new()
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::MovieChanges;
100    use crate::client::Client;
101    use crate::client::reqwest::ReqwestExecutor;
102    use crate::prelude::Command;
103    use mockito::Matcher;
104
105    #[tokio::test]
106    async fn it_works() {
107        let mut server = mockito::Server::new_async().await;
108        let client = Client::<ReqwestExecutor>::builder()
109            .with_api_key("secret".into())
110            .with_base_url(server.url())
111            .build()
112            .unwrap();
113
114        let _m = server
115            .mock("GET", "/movie/3/changes")
116            .match_query(Matcher::UrlEncoded("api_key".into(), "secret".into()))
117            .with_status(200)
118            .with_header("content-type", "application/json")
119            .with_body(include_str!("../../assets/movie-single-changes.json"))
120            .create_async()
121            .await;
122
123        let result = MovieChanges::new(3).execute(&client).await.unwrap();
124        assert_eq!(result.changes.len(), 1);
125    }
126
127    #[tokio::test]
128    async fn invalid_api_key() {
129        let mut server = mockito::Server::new_async().await;
130        let client = Client::<ReqwestExecutor>::builder()
131            .with_api_key("secret".into())
132            .with_base_url(server.url())
133            .build()
134            .unwrap();
135
136        let _m = server
137            .mock("GET", "/movie/1/changes")
138            .match_query(Matcher::UrlEncoded("api_key".into(), "secret".into()))
139            .with_status(401)
140            .with_header("content-type", "application/json")
141            .with_body(include_str!("../../assets/invalid-api-key.json"))
142            .create_async()
143            .await;
144
145        let err = MovieChanges::new(1).execute(&client).await.unwrap_err();
146        let server_err = err.as_server_error().unwrap();
147        assert_eq!(server_err.status_code, 7);
148    }
149
150    #[tokio::test]
151    async fn resource_not_found() {
152        let mut server = mockito::Server::new_async().await;
153        let client = Client::<ReqwestExecutor>::builder()
154            .with_api_key("secret".into())
155            .with_base_url(server.url())
156            .build()
157            .unwrap();
158
159        let _m = server
160            .mock("GET", "/movie/1/changes")
161            .match_query(Matcher::UrlEncoded("api_key".into(), "secret".into()))
162            .with_status(404)
163            .with_header("content-type", "application/json")
164            .with_body(include_str!("../../assets/resource-not-found.json"))
165            .create_async()
166            .await;
167
168        let err = MovieChanges::new(1).execute(&client).await.unwrap_err();
169        let server_err = err.as_server_error().unwrap();
170        assert_eq!(server_err.status_code, 34);
171    }
172}
173
174#[cfg(all(test, feature = "integration"))]
175mod integration_tests {
176    use super::MovieChanges;
177    use crate::client::Client;
178    use crate::client::reqwest::ReqwestExecutor;
179    use crate::prelude::Command;
180
181    #[tokio::test]
182    async fn execute() {
183        let secret = std::env::var("TMDB_TOKEN_V3").unwrap();
184        let client = Client::<ReqwestExecutor>::new(secret);
185
186        let result = MovieChanges::new(1)
187            .with_start_date(Some(chrono::NaiveDate::from_ymd_opt(2015, 3, 14).unwrap()))
188            .with_end_date(Some(chrono::NaiveDate::from_ymd_opt(2019, 3, 14).unwrap()))
189            .execute(&client)
190            .await
191            .unwrap();
192        assert!(result.changes.is_empty());
193    }
194}