Skip to main content

xcom_rs/
test_utils.rs

1/// Test utilities for coordinating test execution and creating test fixtures
2pub mod search_fixtures {
3    use crate::search::models::{SearchTweet, SearchUser};
4
5    /// Create sample tweet fixtures for search tests
6    pub fn create_tweet_fixtures(count: usize) -> Vec<SearchTweet> {
7        (0..count)
8            .map(|i| {
9                let mut tweet = SearchTweet::new(format!("fixture_tweet_{}", i));
10                tweet.text = Some(format!("Fixture tweet text {}", i));
11                tweet.author_id = Some(format!("fixture_user_{}", i));
12                tweet.created_at = Some("2024-01-01T00:00:00Z".to_string());
13                tweet
14            })
15            .collect()
16    }
17
18    /// Create sample user fixtures for search tests
19    pub fn create_user_fixtures(count: usize) -> Vec<SearchUser> {
20        (0..count)
21            .map(|i| {
22                let mut user = SearchUser::new(format!("fixture_user_{}", i));
23                user.name = Some(format!("Fixture User {}", i));
24                user.username = Some(format!("fixture_user_{}", i));
25                user.description = Some(format!("A fixture user {}", i));
26                user
27            })
28            .collect()
29    }
30}
31
32pub mod media_fixtures {
33    use crate::media::models::UploadResult;
34
35    /// Fixed media ID used across unit tests
36    pub const FIXTURE_MEDIA_ID: &str = "fixture_media_id_0000000000000001";
37
38    /// Create an UploadResult with the fixture media_id
39    pub fn create_upload_result() -> UploadResult {
40        UploadResult::new(FIXTURE_MEDIA_ID)
41    }
42
43    /// Create multiple UploadResult fixtures
44    pub fn create_upload_results(count: usize) -> Vec<UploadResult> {
45        (0..count)
46            .map(|i| UploadResult::new(format!("fixture_media_id_{:016}", i)))
47            .collect()
48    }
49}
50
51pub mod env_lock {
52    use std::sync::Mutex;
53
54    /// Global lock for environment variable tests
55    /// This ensures that tests modifying XDG_* env vars don't interfere with each other
56    pub static ENV_LOCK: Mutex<()> = Mutex::new(());
57}
58
59/// Mock fixtures for engagement operations (like/retweet/bookmarks)
60pub mod engagement_fixtures {
61    use crate::tweets::models::Tweet;
62
63    /// Create a fixture tweet for testing
64    pub fn mock_tweet(id: &str) -> Tweet {
65        let mut tweet = Tweet::new(id.to_string());
66        tweet.text = Some(format!("Test tweet content for {}", id));
67        tweet.author_id = Some("user_test123".to_string());
68        tweet.created_at = Some("2024-01-01T00:00:00Z".to_string());
69        tweet
70    }
71
72    /// Create a fixture list of tweets for bookmark list testing
73    pub fn mock_bookmark_tweets(count: usize) -> Vec<Tweet> {
74        (0..count)
75            .map(|i| mock_tweet(&format!("bookmark_tweet_{}", i)))
76            .collect()
77    }
78
79    /// Create mock engagement result data for like operation
80    pub fn mock_like_result(tweet_id: &str) -> serde_json::Value {
81        serde_json::json!({
82            "tweet_id": tweet_id,
83            "success": true
84        })
85    }
86
87    /// Create mock engagement result data for retweet operation
88    pub fn mock_retweet_result(tweet_id: &str) -> serde_json::Value {
89        serde_json::json!({
90            "tweet_id": tweet_id,
91            "success": true
92        })
93    }
94
95    /// Create mock bookmark list result with pagination
96    pub fn mock_bookmark_list_result(limit: usize, offset: usize) -> serde_json::Value {
97        let tweets: Vec<serde_json::Value> = (offset..(offset + limit))
98            .map(|i| {
99                serde_json::json!({
100                    "id": format!("bookmark_tweet_{}", i),
101                    "text": format!("Bookmarked tweet text {}", i),
102                    "author_id": format!("user_{}", i),
103                    "created_at": "2024-01-01T00:00:00Z"
104                })
105            })
106            .collect();
107
108        serde_json::json!({
109            "tweets": tweets,
110            "meta": {
111                "pagination": {
112                    "next_token": format!("bookmark_cursor_{}", offset + limit)
113                }
114            }
115        })
116    }
117}
118
119pub mod helpers {
120    use std::path::{Path, PathBuf};
121    use tempfile::TempDir;
122
123    /// Creates a temporary directory for testing and returns it.
124    /// The directory will be automatically cleaned up when the TempDir is dropped.
125    pub fn create_test_dir(prefix: &str) -> TempDir {
126        TempDir::new().unwrap_or_else(|e| {
127            panic!(
128                "Failed to create test directory with prefix '{}': {}",
129                prefix, e
130            )
131        })
132    }
133
134    /// Creates a test database path in a temporary directory
135    pub fn create_test_db_path(temp_dir: &Path) -> PathBuf {
136        temp_dir.join("test.db")
137    }
138
139    /// Creates a test HOME directory structure in temp and returns the path
140    pub fn create_test_home() -> TempDir {
141        let test_dir = std::env::temp_dir().join(format!("xcom-rs-test-{}", std::process::id()));
142        std::fs::create_dir_all(&test_dir)
143            .unwrap_or_else(|e| panic!("Failed to create test HOME directory: {}", e));
144
145        TempDir::new().unwrap_or_else(|e| panic!("Failed to create test HOME TempDir: {}", e))
146    }
147
148    /// Creates a test IdempotencyLedger with an in-memory database
149    pub fn create_test_ledger() -> crate::tweets::IdempotencyLedger {
150        crate::tweets::IdempotencyLedger::new(None)
151            .expect("Failed to create test IdempotencyLedger")
152    }
153
154    /// Creates a test IdempotencyLedger with a file-based database
155    pub fn create_test_ledger_with_db(db_path: &Path) -> crate::tweets::IdempotencyLedger {
156        crate::tweets::IdempotencyLedger::new(Some(db_path))
157            .expect("Failed to create test IdempotencyLedger with database")
158    }
159
160    /// Helper to parse JSON from command output
161    pub fn parse_json_output(output: &[u8]) -> serde_json::Value {
162        let stdout = String::from_utf8_lossy(output);
163        serde_json::from_str(&stdout)
164            .unwrap_or_else(|e| panic!("Failed to parse JSON output: {}\nOutput: {}", e, stdout))
165    }
166
167    /// Helper to assert command succeeded and return parsed JSON
168    pub fn assert_success_json(output: &std::process::Output) -> serde_json::Value {
169        assert!(
170            output.status.success(),
171            "Command failed with status: {:?}\nStdout: {}\nStderr: {}",
172            output.status.code(),
173            String::from_utf8_lossy(&output.stdout),
174            String::from_utf8_lossy(&output.stderr)
175        );
176        parse_json_output(&output.stdout)
177    }
178
179    /// Helper to assert command failed with expected exit code and return parsed JSON
180    pub fn assert_error_json(
181        output: &std::process::Output,
182        expected_code: i32,
183    ) -> serde_json::Value {
184        assert_eq!(
185            output.status.code(),
186            Some(expected_code),
187            "Expected exit code {} but got {:?}\nStdout: {}\nStderr: {}",
188            expected_code,
189            output.status.code(),
190            String::from_utf8_lossy(&output.stdout),
191            String::from_utf8_lossy(&output.stderr)
192        );
193        parse_json_output(&output.stdout)
194    }
195}