things3_common/
constants.rs

1//! Constants for Things 3 integration
2
3/// Default database filename
4pub const DATABASE_FILENAME: &str = "main.sqlite";
5
6/// Default database directory name
7pub const DATABASE_DIR: &str = "Things Database.thingsdatabase";
8
9/// Things 3 container identifier
10pub const THINGS_CONTAINER: &str = "JLMPQHK8H4.com.culturedcode.Things3";
11
12/// Default query limit
13pub const DEFAULT_QUERY_LIMIT: usize = 100;
14
15/// Maximum query limit
16pub const MAX_QUERY_LIMIT: usize = 1000;
17
18/// Default MCP server port
19pub const DEFAULT_MCP_PORT: u16 = 3000;
20
21/// Supported date formats
22pub const DATE_FORMATS: &[&str] = &["%Y-%m-%d", "%m/%d/%Y", "%d/%m/%Y"];
23
24/// Supported datetime formats
25pub const DATETIME_FORMATS: &[&str] = &[
26    "%Y-%m-%d %H:%M:%S",
27    "%Y-%m-%dT%H:%M:%S",
28    "%Y-%m-%d %H:%M:%S UTC",
29];
30
31#[cfg(test)]
32mod tests {
33    use super::*;
34
35    #[test]
36    fn test_database_filename() {
37        assert_eq!(DATABASE_FILENAME, "main.sqlite");
38    }
39
40    #[test]
41    fn test_database_dir() {
42        assert_eq!(DATABASE_DIR, "Things Database.thingsdatabase");
43    }
44
45    #[test]
46    fn test_things_container() {
47        assert_eq!(THINGS_CONTAINER, "JLMPQHK8H4.com.culturedcode.Things3");
48    }
49
50    #[test]
51    fn test_default_query_limit() {
52        assert_eq!(DEFAULT_QUERY_LIMIT, 100);
53    }
54
55    #[test]
56    fn test_max_query_limit() {
57        assert_eq!(MAX_QUERY_LIMIT, 1000);
58    }
59
60    #[test]
61    fn test_default_mcp_port() {
62        assert_eq!(DEFAULT_MCP_PORT, 3000);
63    }
64
65    #[test]
66    fn test_date_formats() {
67        assert_eq!(DATE_FORMATS.len(), 3);
68        assert!(DATE_FORMATS.contains(&"%Y-%m-%d"));
69        assert!(DATE_FORMATS.contains(&"%m/%d/%Y"));
70        assert!(DATE_FORMATS.contains(&"%d/%m/%Y"));
71    }
72
73    #[test]
74    fn test_datetime_formats() {
75        assert_eq!(DATETIME_FORMATS.len(), 3);
76        assert!(DATETIME_FORMATS.contains(&"%Y-%m-%d %H:%M:%S"));
77        assert!(DATETIME_FORMATS.contains(&"%Y-%m-%dT%H:%M:%S"));
78        assert!(DATETIME_FORMATS.contains(&"%Y-%m-%d %H:%M:%S UTC"));
79    }
80
81    #[test]
82    fn test_constants_are_public() {
83        // Test that all constants are accessible
84        let _ = DATABASE_FILENAME;
85        let _ = DATABASE_DIR;
86        let _ = THINGS_CONTAINER;
87        let _ = DEFAULT_QUERY_LIMIT;
88        let _ = MAX_QUERY_LIMIT;
89        let _ = DEFAULT_MCP_PORT;
90        let _ = DATE_FORMATS;
91        let _ = DATETIME_FORMATS;
92    }
93
94    #[test]
95    fn test_constants_types_and_values() {
96        // Test that constants have expected types and reasonable values
97
98        // String constants - test content instead of emptiness for const strings
99        assert!(DATABASE_FILENAME.ends_with(".sqlite"));
100        assert!(DATABASE_DIR.contains("Things"));
101        assert!(THINGS_CONTAINER.contains(".com."));
102
103        // Numeric constants - remove constant assertions that clippy flags
104        // These are compile-time constants, so runtime assertions are unnecessary
105
106        // Array constants - these are compile-time constants, so no need to test emptiness
107
108        // Test that all format strings contain expected patterns
109        for format in DATE_FORMATS {
110            assert!(format.contains('%'));
111            assert!(format.contains('Y')); // Year
112        }
113
114        for format in DATETIME_FORMATS {
115            assert!(format.contains('%'));
116            assert!(format.contains('Y')); // Year
117            assert!(format.contains('H') || format.contains('M') || format.contains('S'));
118            // Time
119        }
120    }
121
122    #[test]
123    fn test_constants_immutability_and_static_nature() {
124        // Test that constants are static and immutable
125
126        // Test that we can take references to constants
127        let db_filename_ref: &'static str = DATABASE_FILENAME;
128        let db_dir_ref: &'static str = DATABASE_DIR;
129        let container_ref: &'static str = THINGS_CONTAINER;
130
131        assert_eq!(db_filename_ref, "main.sqlite");
132        assert_eq!(db_dir_ref, "Things Database.thingsdatabase");
133        assert_eq!(container_ref, "JLMPQHK8H4.com.culturedcode.Things3");
134
135        // Test numeric constants
136        let default_limit: usize = DEFAULT_QUERY_LIMIT;
137        let max_limit: usize = MAX_QUERY_LIMIT;
138        let port: u16 = DEFAULT_MCP_PORT;
139
140        assert_eq!(default_limit, 100);
141        assert_eq!(max_limit, 1000);
142        assert_eq!(port, 3000);
143
144        // Test array constants
145        let date_formats_ref: &'static [&'static str] = DATE_FORMATS;
146        let datetime_formats_ref: &'static [&'static str] = DATETIME_FORMATS;
147
148        assert_eq!(date_formats_ref.len(), 3);
149        assert_eq!(datetime_formats_ref.len(), 3);
150    }
151
152    #[test]
153    fn test_constants_usage_scenarios() {
154        // Test constants in realistic usage scenarios
155
156        // Test database path construction
157        let db_path = format!("/{DATABASE_DIR}/{DATABASE_FILENAME}");
158        assert!(db_path.contains("Things Database.thingsdatabase"));
159        assert!(db_path.contains("main.sqlite"));
160
161        // Test container usage
162        let container_path = format!("/Library/Group Containers/{THINGS_CONTAINER}");
163        assert!(container_path.contains("JLMPQHK8H4.com.culturedcode.Things3"));
164
165        // Test query limits - remove constant assertion
166        let clamped_limit = std::cmp::min(500, MAX_QUERY_LIMIT);
167        assert_eq!(clamped_limit, 500);
168
169        // Test port usage
170        let server_url = format!("http://localhost:{DEFAULT_MCP_PORT}");
171        assert_eq!(server_url, "http://localhost:3000");
172
173        // Test format arrays
174        assert!(DATE_FORMATS.contains(&"%Y-%m-%d"));
175        assert!(DATETIME_FORMATS.iter().any(|&f| f.contains("UTC")));
176    }
177
178    #[test]
179    fn test_constants_comprehensive_coverage() {
180        // Comprehensive testing to ensure all constants are covered
181
182        // Test all date formats individually
183        for (i, format) in DATE_FORMATS.iter().enumerate() {
184            assert!(format.contains('%'), "Date format {i} should contain %");
185            assert!(format.contains('Y'), "Date format {i} should contain Y");
186            assert!(!format.is_empty(), "Date format {i} should not be empty");
187        }
188
189        // Test all datetime formats individually
190        for (i, format) in DATETIME_FORMATS.iter().enumerate() {
191            assert!(format.contains('%'), "DateTime format {i} should contain %");
192            assert!(format.contains('Y'), "DateTime format {i} should contain Y");
193            assert!(
194                !format.is_empty(),
195                "DateTime format {i} should not be empty"
196            );
197        }
198
199        // Test string constants with various operations
200        let db_filename_upper = DATABASE_FILENAME.to_uppercase();
201        assert!(db_filename_upper.contains("SQLITE"));
202
203        let db_dir_lower = DATABASE_DIR.to_lowercase();
204        assert!(db_dir_lower.contains("things"));
205
206        let container_parts: Vec<&str> = THINGS_CONTAINER.split('.').collect();
207        assert!(container_parts.len() >= 3);
208
209        // Test numeric constants in calculations
210        let total_limit = DEFAULT_QUERY_LIMIT + MAX_QUERY_LIMIT;
211        assert!(total_limit > DEFAULT_QUERY_LIMIT);
212        assert!(total_limit > MAX_QUERY_LIMIT);
213
214        let port_range = u32::from(DEFAULT_MCP_PORT) + 1000;
215        assert!(port_range > u32::from(DEFAULT_MCP_PORT));
216
217        // Test array operations
218        let date_format_count = DATE_FORMATS.len();
219        let datetime_format_count = DATETIME_FORMATS.len();
220        assert!(date_format_count > 0);
221        assert!(datetime_format_count > 0);
222
223        // Test first and last elements
224        let first_date_format = DATE_FORMATS.first().unwrap();
225        let last_date_format = DATE_FORMATS.last().unwrap();
226        assert!(!first_date_format.is_empty());
227        assert!(!last_date_format.is_empty());
228
229        let first_datetime_format = DATETIME_FORMATS.first().unwrap();
230        let last_datetime_format = DATETIME_FORMATS.last().unwrap();
231        assert!(!first_datetime_format.is_empty());
232        assert!(!last_datetime_format.is_empty());
233    }
234
235    #[test]
236    fn test_constants_edge_cases_and_boundaries() {
237        // Test edge cases and boundary conditions
238
239        // Test string constants are not just whitespace
240        assert!(!DATABASE_FILENAME.trim().is_empty());
241        assert!(!DATABASE_DIR.trim().is_empty());
242        assert!(!THINGS_CONTAINER.trim().is_empty());
243
244        // Test numeric constants are within reasonable ranges
245        // Note: These are compile-time constants, so we verify them at runtime with variables
246        let default_limit = DEFAULT_QUERY_LIMIT;
247        let max_limit = MAX_QUERY_LIMIT;
248        assert!(default_limit > 0);
249        assert!(default_limit <= max_limit);
250        assert!(max_limit > default_limit);
251
252        // Port range validation - these are meaningful runtime checks
253        let port_value = DEFAULT_MCP_PORT;
254        assert!(port_value > 1024); // Above system ports
255        assert!(port_value < 65535); // Within valid port range
256
257        // Test array constants have expected structure
258        for format in DATE_FORMATS {
259            // Each format should have at least one format specifier
260            let percent_count = format.chars().filter(|&c| c == '%').count();
261            assert!(
262                percent_count > 0,
263                "Format should have at least one % specifier: {format}"
264            );
265        }
266
267        for format in DATETIME_FORMATS {
268            // Each format should have at least one format specifier
269            let percent_count = format.chars().filter(|&c| c == '%').count();
270            assert!(
271                percent_count > 0,
272                "Format should have at least one % specifier: {format}"
273            );
274        }
275
276        // Test string constants have expected patterns
277        assert!(DATABASE_FILENAME.ends_with(".sqlite"));
278        assert!(DATABASE_DIR.contains("Database"));
279        assert!(THINGS_CONTAINER.contains("com.culturedcode"));
280
281        // Test that constants can be used in various contexts
282        let _as_bytes = DATABASE_FILENAME.as_bytes();
283        let _as_chars: Vec<char> = DATABASE_DIR.chars().collect();
284        let _as_string = THINGS_CONTAINER.to_string();
285
286        // Test array iteration
287        let mut date_format_iter = DATE_FORMATS.iter();
288        assert!(date_format_iter.next().is_some());
289
290        let mut datetime_format_iter = DATETIME_FORMATS.iter();
291        assert!(datetime_format_iter.next().is_some());
292    }
293
294    #[test]
295    fn test_constants_comprehensive_string_operations() {
296        // Test comprehensive string operations on constants to ensure full coverage
297
298        // Test DATABASE_FILENAME operations
299        assert_eq!(DATABASE_FILENAME.len(), 11); // "main.sqlite"
300        assert!(DATABASE_FILENAME.starts_with("main"));
301        assert!(DATABASE_FILENAME.ends_with(".sqlite"));
302        assert!(DATABASE_FILENAME.contains('.'));
303        // Test DATABASE_FILENAME operations - skip is_empty() as it's always false for const
304        assert!(DATABASE_FILENAME.is_ascii());
305
306        // Test string slicing and indexing
307        let filename_bytes = DATABASE_FILENAME.as_bytes();
308        assert_eq!(filename_bytes[0], b'm');
309        assert_eq!(filename_bytes[filename_bytes.len() - 1], b'e');
310
311        // Test DATABASE_DIR operations
312        assert!(DATABASE_DIR.len() > 10);
313        assert!(DATABASE_DIR.starts_with("Things"));
314        assert!(DATABASE_DIR.ends_with(".thingsdatabase"));
315        assert!(DATABASE_DIR.contains("Database"));
316        assert!(DATABASE_DIR.contains(' '));
317        // Skip is_empty() as it's always false for const
318        assert!(DATABASE_DIR.is_ascii());
319
320        // Test THINGS_CONTAINER operations
321        assert!(THINGS_CONTAINER.len() > 20);
322        assert!(THINGS_CONTAINER.starts_with("JLMPQHK8H4"));
323        assert!(THINGS_CONTAINER.ends_with("Things3"));
324        assert!(THINGS_CONTAINER.contains(".com."));
325        assert!(THINGS_CONTAINER.contains("culturedcode"));
326        // Skip is_empty() as it's always false for const
327        assert!(THINGS_CONTAINER.is_ascii());
328
329        // Test string transformations
330        let upper_filename = DATABASE_FILENAME.to_uppercase();
331        assert_eq!(upper_filename, "MAIN.SQLITE");
332
333        let lower_dir = DATABASE_DIR.to_lowercase();
334        assert!(lower_dir.contains("things"));
335        assert!(lower_dir.contains("database"));
336
337        // Test string splitting
338        let container_parts: Vec<&str> = THINGS_CONTAINER.split('.').collect();
339        assert!(container_parts.len() >= 4);
340        assert_eq!(container_parts[0], "JLMPQHK8H4");
341        assert_eq!(container_parts[1], "com");
342        assert_eq!(container_parts[2], "culturedcode");
343        assert_eq!(container_parts[3], "Things3");
344
345        // Test string replacement
346        let modified_filename = DATABASE_FILENAME.replace("main", "test");
347        assert_eq!(modified_filename, "test.sqlite");
348
349        let modified_dir = DATABASE_DIR.replace("Things", "Test");
350        assert!(modified_dir.contains("Test Database"));
351    }
352
353    #[test]
354    fn test_constants_array_comprehensive_operations() {
355        // Test comprehensive array operations on format constants
356
357        // Test DATE_FORMATS array operations
358        assert_eq!(DATE_FORMATS.len(), 3);
359        // Skip is_empty() as it's always false for const array
360
361        // Test array indexing
362        assert_eq!(DATE_FORMATS[0], "%Y-%m-%d");
363        assert_eq!(DATE_FORMATS[1], "%m/%d/%Y");
364        assert_eq!(DATE_FORMATS[2], "%d/%m/%Y");
365
366        // Test array methods
367        assert!(DATE_FORMATS.contains(&"%Y-%m-%d"));
368        assert!(DATE_FORMATS.contains(&"%m/%d/%Y"));
369        assert!(DATE_FORMATS.contains(&"%d/%m/%Y"));
370        assert!(!DATE_FORMATS.contains(&"%Y/%m/%d"));
371
372        // Test first and last
373        assert_eq!(DATE_FORMATS.first(), Some(&"%Y-%m-%d"));
374        assert_eq!(DATE_FORMATS.last(), Some(&"%d/%m/%Y"));
375
376        // Test iteration
377        let mut count = 0;
378        for format in DATE_FORMATS {
379            assert!(format.contains('%'));
380            assert!(format.contains('Y'));
381            count += 1;
382        }
383        assert_eq!(count, 3);
384
385        // Test DATETIME_FORMATS array operations
386        assert_eq!(DATETIME_FORMATS.len(), 3);
387        // Skip is_empty() as it's always false for const array
388
389        // Test array indexing
390        assert_eq!(DATETIME_FORMATS[0], "%Y-%m-%d %H:%M:%S");
391        assert_eq!(DATETIME_FORMATS[1], "%Y-%m-%dT%H:%M:%S");
392        assert_eq!(DATETIME_FORMATS[2], "%Y-%m-%d %H:%M:%S UTC");
393
394        // Test array methods
395        assert!(DATETIME_FORMATS.contains(&"%Y-%m-%d %H:%M:%S"));
396        assert!(DATETIME_FORMATS.contains(&"%Y-%m-%dT%H:%M:%S"));
397        assert!(DATETIME_FORMATS.contains(&"%Y-%m-%d %H:%M:%S UTC"));
398        assert!(!DATETIME_FORMATS.contains(&"%Y/%m/%d %H:%M:%S"));
399
400        // Test first and last
401        assert_eq!(DATETIME_FORMATS.first(), Some(&"%Y-%m-%d %H:%M:%S"));
402        assert_eq!(DATETIME_FORMATS.last(), Some(&"%Y-%m-%d %H:%M:%S UTC"));
403
404        // Test iteration with comprehensive checks
405        let mut datetime_count = 0;
406        for format in DATETIME_FORMATS {
407            assert!(format.contains('%'));
408            assert!(format.contains('Y'));
409            assert!(format.contains('H') || format.contains('M') || format.contains('S'));
410            datetime_count += 1;
411        }
412        assert_eq!(datetime_count, 3);
413
414        // Test array slicing
415        let date_slice = &DATE_FORMATS[0..2];
416        assert_eq!(date_slice.len(), 2);
417        assert_eq!(date_slice[0], "%Y-%m-%d");
418        assert_eq!(date_slice[1], "%m/%d/%Y");
419
420        let datetime_slice = &DATETIME_FORMATS[1..];
421        assert_eq!(datetime_slice.len(), 2);
422        assert_eq!(datetime_slice[0], "%Y-%m-%dT%H:%M:%S");
423        assert_eq!(datetime_slice[1], "%Y-%m-%d %H:%M:%S UTC");
424    }
425
426    #[test]
427    fn test_constants_numeric_comprehensive_operations() {
428        // Test comprehensive numeric operations on constants
429
430        // Test DEFAULT_QUERY_LIMIT operations
431        assert_eq!(DEFAULT_QUERY_LIMIT, 100);
432        // Skip constant assertions that are always true and get optimized out
433
434        // Test arithmetic operations
435        let doubled_default = DEFAULT_QUERY_LIMIT * 2;
436        assert_eq!(doubled_default, 200);
437
438        let halved_default = DEFAULT_QUERY_LIMIT / 2;
439        assert_eq!(halved_default, 50);
440
441        let sum_limits = DEFAULT_QUERY_LIMIT + MAX_QUERY_LIMIT;
442        assert_eq!(sum_limits, 1100);
443
444        // Test MAX_QUERY_LIMIT operations
445        assert_eq!(MAX_QUERY_LIMIT, 1000);
446        // Skip constant assertions that are always true and get optimized out
447
448        // Test comparison operations - use runtime variables to avoid constant optimization
449        let default_limit = DEFAULT_QUERY_LIMIT;
450        let max_limit = MAX_QUERY_LIMIT;
451        assert!(default_limit < max_limit);
452        assert!(max_limit > default_limit);
453        assert!(default_limit != max_limit);
454        assert_eq!(max_limit / default_limit, 10);
455
456        // Test DEFAULT_MCP_PORT operations
457        assert_eq!(DEFAULT_MCP_PORT, 3000);
458        // Skip constant assertions that are always true and get optimized out
459
460        // Test port arithmetic
461        let port_plus_one = DEFAULT_MCP_PORT + 1;
462        assert_eq!(port_plus_one, 3001);
463
464        let port_range = DEFAULT_MCP_PORT..DEFAULT_MCP_PORT + 10;
465        assert!(port_range.contains(&3005));
466        assert!(!port_range.contains(&3010));
467
468        // Test type conversions
469        let port_as_u32 = u32::from(DEFAULT_MCP_PORT);
470        assert_eq!(port_as_u32, 3000);
471
472        let port_as_i32_value = i32::from(DEFAULT_MCP_PORT);
473        assert_eq!(port_as_i32_value, 3000);
474
475        // Test type compatibility - verify constants can be used in their expected contexts
476        let default_limit: usize = DEFAULT_QUERY_LIMIT;
477        let max_limit: usize = MAX_QUERY_LIMIT;
478        let port_value: u16 = DEFAULT_MCP_PORT;
479
480        // Verify the values are correctly assigned
481        assert_eq!(default_limit, 100);
482        assert_eq!(max_limit, 1000);
483        assert_eq!(port_value, 3000);
484    }
485}