1mod auth;
123mod client;
124mod endpoints;
125mod error;
126mod types;
127
128pub use auth::{Auth, SessionData};
129pub use client::{CredentialsBuilder, Instagram, InstagramBuilder};
130pub use endpoints::QUERY_HASH;
131pub use error::{Error, Result};
132pub use types::*;
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn build_anonymous_client() {
140 let client = Instagram::builder().build();
141 assert!(client.is_ok());
142 }
143
144 #[test]
145 fn build_with_credentials() {
146 let client = Instagram::builder()
147 .id("test_user")
148 .password("test_pass")
149 .build();
150 assert!(client.is_ok());
151 }
152
153 #[test]
154 fn build_with_session() {
155 let session = SessionData::new("sess123", "csrf456");
156 let client = Instagram::builder().session(session).build();
157 assert!(client.is_ok());
158 }
159
160 #[test]
161 fn session_with_user_id() {
162 let session = SessionData::new("sess123", "csrf456").with_user_id("user789");
163 assert_eq!(session.user_id, Some("user789".into()));
164 }
165
166 #[test]
167 fn custom_user_agent() {
168 let client = Instagram::builder().user_agent("CustomBot/1.0").build();
169 assert!(client.is_ok());
170 }
171
172 #[test]
173 fn pagination_default() {
174 let page: Pagination<Media> = Pagination::default();
175 assert!(page.items.is_empty());
176 assert!(!page.has_next);
177 assert!(page.cursor.is_none());
178 }
179
180 #[test]
181 fn endpoint_urls() {
182 let url = endpoints::profile_url("testuser");
183 assert!(url.contains("testuser"));
184
185 let url = endpoints::user_info_url("12345");
186 assert!(url.contains("12345"));
187
188 let url = endpoints::media_info_url("ABC");
189 assert!(url.contains("ABC"));
190 }
191
192 #[test]
193 fn action_urls() {
194 let url = endpoints::like_url("123");
195 assert!(url.contains("123") && url.contains("like"));
196
197 let url = endpoints::follow_url("456");
198 assert!(url.contains("456") && url.contains("follow"));
199
200 let url = endpoints::comment_url("789");
201 assert!(url.contains("789") && url.contains("comment"));
202 }
203
204 #[test]
205 fn auth_variants() {
206 let auth = Auth::None;
207 assert!(matches!(auth, Auth::None));
208
209 let auth = Auth::Credentials {
210 id: "u".into(),
211 password: "p".into(),
212 };
213 assert!(matches!(auth, Auth::Credentials { .. }));
214
215 let session = SessionData::new("s", "c");
216 let auth = Auth::Session(session);
217 assert!(matches!(auth, Auth::Session(_)));
218 }
219
220 #[test]
221 fn media_type_serialization() {
222 let reel = MediaType::Reel;
223 let json = serde_json::to_string(&reel).unwrap();
224 assert_eq!(json, "\"reel\"");
225
226 let carousel = MediaType::Carousel;
227 let json = serde_json::to_string(&carousel).unwrap();
228 assert_eq!(json, "\"carousel\"");
229 }
230}
231
232#[cfg(test)]
233mod api_tests {
234 use super::*;
235
236 fn client() -> Instagram {
237 Instagram::builder().build().unwrap()
238 }
239
240 async fn auth_client() -> Option<Instagram> {
241 let id = "id";
242 let password = "password";
243
244 let _ig = Instagram::builder().id(id).password(password).build();
245 match &_ig {
246 Ok(_) => println!("Ok"),
247 Err(err) => println!("{:?}", err),
248 }
249 let ig = _ig.ok()?;
250
251 let res = ig.login().await;
252 println!("{:?}", res);
253 res.ok()?;
254 println!("gurt: yo");
255 Some(ig)
256 }
257
258 #[tokio::test]
259 #[ignore]
260 async fn fetch_user_profile() {
261 let ig = client();
262 let user = ig.user("instagram").await.unwrap();
263 assert_eq!(user.username, "instagram");
264 assert!(user.follower_count > 0);
265 }
266
267 #[tokio::test]
268 #[ignore]
269 async fn fetch_media_post() {
270 let ig = client();
271 let media = ig.media("CvIBvWQPxKB").await.unwrap();
272 assert!(!media.id.is_empty());
273 assert!(!media.shortcode.is_empty());
274 }
275
276 #[tokio::test]
277 #[ignore]
278 async fn fetch_reel() {
279 let Some(ig) = auth_client().await else {
280 println!("Fail");
281 return;
282 };
283 let reel = ig.reel("DNaaHuTRxKF").await.unwrap();
284 assert!(!reel.id.is_empty());
285 assert!(!reel.video_url.is_empty());
286 assert!(reel.duration > 0.0);
287 println!("{:?}", reel);
288 }
289
290 #[tokio::test]
291 #[ignore]
292 async fn search_users() {
293 let ig = client();
294 let results = ig.search("instagram").await.unwrap();
295 assert!(!results.users.is_empty());
296 }
297
298 #[tokio::test]
299 #[ignore]
300 async fn fetch_user_posts() {
301 let Some(ig) = auth_client().await else {
302 return;
303 };
304 let user = ig.user("instagram").await.unwrap();
305 let posts = ig.user_posts(&user.id, 12, None).await.unwrap();
306 assert!(!posts.items.is_empty());
307 }
308
309 #[tokio::test]
310 #[ignore]
311 async fn fetch_comments() {
312 let Some(ig) = auth_client().await else {
313 return;
314 };
315 let shortcode = "DSdV9bYkg0G";
316 let comments = ig.comments(shortcode, 20, None).await.unwrap();
317 println!("{:?}", comments);
318 assert!(!comments.items.is_empty());
319 }
320
321 #[tokio::test]
322 #[ignore]
323 async fn fetch_likers() {
324 let Some(ig) = auth_client().await else {
325 return;
326 };
327 let likers = ig.likers("CvIBvWQPxKB", 20, None).await.unwrap();
328 assert!(!likers.items.is_empty());
329 }
330
331 #[tokio::test]
332 #[ignore]
333 async fn fetch_followers() {
334 let Some(ig) = auth_client().await else {
335 return;
336 };
337 let user = ig.user("instagram").await.unwrap();
338 let followers = ig.followers(&user.id, 20, None).await.unwrap();
339 println!("{:?}", followers);
340 assert!(!followers.items.is_empty());
341 }
342
343 #[tokio::test]
344 #[ignore]
345 async fn fetch_following() {
346 let Some(ig) = auth_client().await else {
347 return;
348 };
349 let user = ig.user("instagram").await.unwrap();
350 let following = ig.following(&user.id, 20, None).await.unwrap();
351 assert!(!following.items.is_empty());
352 }
353
354 #[tokio::test]
355 #[ignore]
356 async fn fetch_highlights() {
357 let Some(ig) = auth_client().await else {
358 return;
359 };
360 let user = ig.user("instagram").await.unwrap();
361 let highlights = ig.highlights(&user.id).await.unwrap();
362 assert!(!highlights.is_empty());
363 }
364
365 #[tokio::test]
366 #[ignore]
367 async fn fetch_hashtag_media() {
368 let Some(ig) = auth_client().await else {
369 return;
370 };
371 let media = ig.hashtag_media("rust", 12, None).await.unwrap();
372 println!("{:?}", media);
373 assert!(!media.items.is_empty());
374 }
375
376 #[tokio::test]
377 #[ignore]
378 async fn pagination_flow() {
379 let Some(ig) = auth_client().await else {
380 return;
381 };
382 let user = ig.user("instagram").await.unwrap();
383
384 let first = ig.user_posts(&user.id, 5, None).await.unwrap();
385 assert!(first.has_next);
386
387 let second = ig
388 .user_posts(&user.id, 5, first.cursor.as_deref())
389 .await
390 .unwrap();
391 assert!(!second.items.is_empty());
392 }
393
394 #[tokio::test]
395 #[ignore]
396 async fn login_flow() {
397 let Some(ig) = auth_client().await else {
398 println!("skipping login test / INSTA_ID and INSTA_PASSWORD not set");
399 return;
400 };
401 let user = ig.user("instagram").await.unwrap();
402 println!("{:?}", user);
403 assert!(!user.id.is_empty());
404 }
405}