1use crate::models::{DayData, WorkRecord};
2use crate::timer::TimerState;
3use anyhow::{Context, Result};
4use std::fs;
5use std::path::PathBuf;
6use std::time::SystemTime;
7use time::Date;
8
9#[derive(Clone)]
10pub struct Storage {
11 data_dir: PathBuf,
12}
13
14pub struct StorageManager {
17 storage: Storage,
18 file_modified_times: std::collections::HashMap<Date, Option<SystemTime>>,
19}
20
21impl StorageManager {
22 pub fn new() -> Result<Self> {
24 Ok(StorageManager {
25 storage: Storage::new()?,
26 file_modified_times: std::collections::HashMap::new(),
27 })
28 }
29
30 #[doc(hidden)]
32 #[allow(dead_code)]
33 pub fn new_with_dir(data_dir: PathBuf) -> Result<Self> {
34 Ok(StorageManager {
35 storage: Storage::new_with_dir(data_dir)?,
36 file_modified_times: std::collections::HashMap::new(),
37 })
38 }
39
40 pub fn load_with_tracking(&mut self, date: Date) -> Result<DayData> {
43 let data = self.storage.load(&date)?;
44 let modified_time = self.storage.get_file_modified_time(&date);
45 self.file_modified_times.insert(date, modified_time);
46 Ok(data)
47 }
48
49 pub fn check_and_reload(&mut self, date: Date) -> Result<Option<DayData>> {
52 let current_modified = self.storage.get_file_modified_time(&date);
53
54 let is_tracked = self.file_modified_times.contains_key(&date);
56
57 if !is_tracked {
58 let data = self.storage.load(&date)?;
60 self.file_modified_times.insert(date, current_modified);
61 Ok(Some(data))
62 } else {
63 let last_known = self.file_modified_times.get(&date).copied().flatten();
64
65 if current_modified != last_known {
67 let data = self.storage.load(&date)?;
68 self.file_modified_times.insert(date, current_modified);
69 Ok(Some(data))
70 } else {
71 Ok(None)
72 }
73 }
74 }
75
76 #[allow(dead_code)]
78 pub fn add_record(&mut self, date: Date, record: WorkRecord) -> Result<()> {
79 let mut day_data = self.storage.load(&date)?;
80 day_data.add_record(record);
81 self.storage.save(&day_data)?;
82
83 let modified_time = self.storage.get_file_modified_time(&date);
85 self.file_modified_times.insert(date, modified_time);
86
87 Ok(())
88 }
89
90 #[allow(dead_code)]
92 pub fn update_record(&mut self, date: Date, record: WorkRecord) -> Result<()> {
93 let mut day_data = self.storage.load(&date)?;
94
95 day_data.add_record(record);
97
98 self.storage.save(&day_data)?;
99
100 let modified_time = self.storage.get_file_modified_time(&date);
102 self.file_modified_times.insert(date, modified_time);
103
104 Ok(())
105 }
106
107 #[allow(dead_code)]
110 pub fn remove_record(&mut self, date: Date, id: u32) -> Result<WorkRecord> {
111 let mut day_data = self.storage.load(&date)?;
112
113 let record = day_data
114 .work_records
115 .remove(&id)
116 .context(format!("Record with ID {} not found", id))?;
117
118 self.storage.save(&day_data)?;
119
120 let modified_time = self.storage.get_file_modified_time(&date);
122 self.file_modified_times.insert(date, modified_time);
123
124 Ok(record)
125 }
126
127 pub fn save(&mut self, day_data: &DayData) -> Result<()> {
129 self.storage.save(day_data)?;
130
131 let modified_time = self.storage.get_file_modified_time(&day_data.date);
133 self.file_modified_times
134 .insert(day_data.date, modified_time);
135
136 Ok(())
137 }
138
139 pub fn get_last_modified(&self, date: &Date) -> Option<SystemTime> {
141 self.file_modified_times.get(date).copied().flatten()
142 }
143
144 #[allow(dead_code)]
146 pub fn save_active_timer(&self, timer: &TimerState) -> Result<()> {
147 self.storage.save_active_timer(timer)
148 }
149
150 pub fn load_active_timer(&self) -> Result<Option<TimerState>> {
151 self.storage.load_active_timer()
152 }
153
154 #[allow(dead_code)]
155 pub fn clear_active_timer(&self) -> Result<()> {
156 self.storage.clear_active_timer()
157 }
158
159 fn create_timer_manager(&self) -> crate::timer::TimerManager {
162 crate::timer::TimerManager::new(self.storage.clone())
165 }
166
167 pub fn start_timer(
169 &self,
170 task_name: String,
171 description: Option<String>,
172 source_record_id: Option<u32>,
173 source_record_date: Option<time::Date>,
174 ) -> Result<TimerState> {
175 let timer_manager = self.create_timer_manager();
176 timer_manager.start(task_name, description, source_record_id, source_record_date)
177 }
178
179 pub fn stop_timer(&self) -> Result<crate::models::WorkRecord> {
181 let timer_manager = self.create_timer_manager();
182 timer_manager.stop()
183 }
184
185 pub fn pause_timer(&self) -> Result<TimerState> {
187 let timer_manager = self.create_timer_manager();
188 timer_manager.pause()
189 }
190
191 pub fn resume_timer(&self) -> Result<TimerState> {
193 let timer_manager = self.create_timer_manager();
194 timer_manager.resume()
195 }
196
197 #[allow(dead_code)]
199 pub fn get_timer_elapsed(&self, timer: &TimerState) -> std::time::Duration {
200 let timer_manager = self.create_timer_manager();
201 timer_manager.get_elapsed_duration(timer)
202 }
203}
204
205impl Storage {
206 pub fn new() -> Result<Self> {
207 let data_dir = Self::get_data_directory()?;
208 fs::create_dir_all(&data_dir).context("Failed to create data directory")?;
209
210 Ok(Storage { data_dir })
211 }
212
213 #[doc(hidden)]
215 #[allow(dead_code)]
216 pub fn new_with_dir(data_dir: PathBuf) -> Result<Self> {
217 fs::create_dir_all(&data_dir).context("Failed to create data directory")?;
218 Ok(Storage { data_dir })
219 }
220
221 fn get_data_directory() -> Result<PathBuf> {
222 if let Some(data_dir) = dirs::data_local_dir() {
224 let app_dir = data_dir.join("work-tuimer");
225 if fs::create_dir_all(&app_dir).is_ok() {
226 return Ok(app_dir);
227 }
228 }
229
230 let local_data = PathBuf::from("./data");
232 if fs::create_dir_all(&local_data).is_ok() {
233 return Ok(local_data);
234 }
235
236 anyhow::bail!("Failed to create data directory in system location or ./data")
237 }
238
239 fn get_file_path(&self, date: &Date) -> PathBuf {
240 self.data_dir.join(format!(
241 "{}-{:02}-{:02}.json",
242 date.year(),
243 date.month() as u8,
244 date.day()
245 ))
246 }
247
248 pub fn load(&self, date: &Date) -> Result<DayData> {
249 let path = self.get_file_path(date);
250
251 if !path.exists() {
252 return Ok(DayData::new(*date));
253 }
254
255 let contents =
256 fs::read_to_string(&path).context(format!("Failed to read file: {:?}", path))?;
257
258 let day_data: DayData = serde_json::from_str(&contents).context("Failed to parse JSON")?;
259
260 Ok(day_data)
261 }
262
263 pub fn save(&self, day_data: &DayData) -> Result<()> {
264 let path = self.get_file_path(&day_data.date);
265
266 let json = serde_json::to_string_pretty(day_data).context("Failed to serialize data")?;
267
268 fs::write(&path, json).context(format!("Failed to write file: {:?}", path))?;
269
270 Ok(())
271 }
272
273 pub fn get_file_modified_time(&self, date: &Date) -> Option<std::time::SystemTime> {
276 let path = self.get_file_path(date);
277 if path.exists() {
278 fs::metadata(&path).ok().and_then(|m| m.modified().ok())
279 } else {
280 None
281 }
282 }
283
284 fn get_timer_file_path(&self) -> PathBuf {
286 self.data_dir.join("running_timer.json")
287 }
288
289 pub fn save_active_timer(&self, timer: &TimerState) -> Result<()> {
291 let path = self.get_timer_file_path();
292 let json = serde_json::to_string_pretty(timer).context("Failed to serialize timer")?;
293 fs::write(&path, json).context(format!("Failed to write timer file: {:?}", path))?;
294 Ok(())
295 }
296
297 pub fn load_active_timer(&self) -> Result<Option<TimerState>> {
301 let path = self.get_timer_file_path();
302
303 if !path.exists() {
304 return Ok(None);
305 }
306
307 let contents =
308 fs::read_to_string(&path).context(format!("Failed to read timer file: {:?}", path))?;
309 let timer: TimerState =
310 serde_json::from_str(&contents).context("Failed to parse timer JSON")?;
311
312 Ok(Some(timer))
313 }
314
315 pub fn clear_active_timer(&self) -> Result<()> {
317 let path = self.get_timer_file_path();
318
319 if path.exists() {
320 fs::remove_file(&path).context(format!("Failed to delete timer file: {:?}", path))?;
321 }
322
323 Ok(())
324 }
325}
326
327#[cfg(test)]
328mod tests {
329 use super::*;
330 use crate::models::{TimePoint, WorkRecord};
331 use tempfile::TempDir;
332 use time::Date;
333
334 fn create_test_date() -> Date {
335 Date::from_calendar_date(2025, time::Month::November, 6).unwrap()
336 }
337
338 fn create_test_record(id: u32, name: &str) -> WorkRecord {
339 let start = TimePoint::new(9, 0).unwrap();
340 let end = TimePoint::new(17, 0).unwrap();
341 WorkRecord::new(id, name.to_string(), start, end)
342 }
343
344 #[test]
345 fn test_new_storage_with_temp_dir() {
346 let temp_dir = TempDir::new().unwrap();
347 let storage = Storage::new_with_dir(temp_dir.path().to_path_buf());
348
349 assert!(storage.is_ok());
350 assert!(temp_dir.path().exists());
351 }
352
353 #[test]
354 fn test_get_file_path_format() {
355 let temp_dir = TempDir::new().unwrap();
356 let storage = Storage::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
357 let date = create_test_date();
358
359 let file_path = storage.get_file_path(&date);
360
361 assert_eq!(file_path.file_name().unwrap(), "2025-11-06.json");
362 }
363
364 #[test]
365 fn test_load_nonexistent_file_returns_empty_day() {
366 let temp_dir = TempDir::new().unwrap();
367 let storage = Storage::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
368 let date = create_test_date();
369
370 let result = storage.load(&date);
371
372 assert!(result.is_ok());
373 let day_data = result.unwrap();
374 assert_eq!(day_data.date, date);
375 assert_eq!(day_data.work_records.len(), 0);
376 assert_eq!(day_data.last_id, 0);
377 }
378
379 #[test]
380 fn test_save_and_load_empty_day() {
381 let temp_dir = TempDir::new().unwrap();
382 let storage = Storage::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
383 let date = create_test_date();
384 let day_data = DayData::new(date);
385
386 let save_result = storage.save(&day_data);
388 assert!(save_result.is_ok());
389
390 let load_result = storage.load(&date);
392 assert!(load_result.is_ok());
393 let loaded_data = load_result.unwrap();
394
395 assert_eq!(loaded_data.date, date);
396 assert_eq!(loaded_data.work_records.len(), 0);
397 }
398
399 #[test]
400 fn test_save_and_load_with_records() {
401 let temp_dir = TempDir::new().unwrap();
402 let storage = Storage::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
403 let date = create_test_date();
404
405 let mut day_data = DayData::new(date);
406 day_data.add_record(create_test_record(1, "Coding"));
407 day_data.add_record(create_test_record(2, "Meeting"));
408
409 storage.save(&day_data).unwrap();
411
412 let loaded_data = storage.load(&date).unwrap();
414
415 assert_eq!(loaded_data.date, date);
416 assert_eq!(loaded_data.work_records.len(), 2);
417 assert_eq!(loaded_data.last_id, 2);
418 assert!(loaded_data.work_records.contains_key(&1));
419 assert!(loaded_data.work_records.contains_key(&2));
420 }
421
422 #[test]
423 fn test_save_overwrites_existing_file() {
424 let temp_dir = TempDir::new().unwrap();
425 let storage = Storage::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
426 let date = create_test_date();
427
428 let mut day_data1 = DayData::new(date);
430 day_data1.add_record(create_test_record(1, "Task1"));
431 storage.save(&day_data1).unwrap();
432
433 let mut day_data2 = DayData::new(date);
435 day_data2.add_record(create_test_record(2, "Task2"));
436 storage.save(&day_data2).unwrap();
437
438 let loaded_data = storage.load(&date).unwrap();
440 assert_eq!(loaded_data.work_records.len(), 1);
441 assert!(loaded_data.work_records.contains_key(&2));
442 assert!(!loaded_data.work_records.contains_key(&1));
443 }
444
445 #[test]
446 fn test_save_creates_file() {
447 let temp_dir = TempDir::new().unwrap();
448 let storage = Storage::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
449 let date = create_test_date();
450 let day_data = DayData::new(date);
451
452 let file_path = storage.get_file_path(&date);
453 assert!(!file_path.exists());
454
455 storage.save(&day_data).unwrap();
456
457 assert!(file_path.exists());
458 }
459
460 #[test]
461 fn test_load_preserves_record_details() {
462 let temp_dir = TempDir::new().unwrap();
463 let storage = Storage::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
464 let date = create_test_date();
465
466 let mut day_data = DayData::new(date);
467 let mut record = create_test_record(1, "Important Task");
468 record.description = "This is a description".to_string();
469 day_data.add_record(record);
470
471 storage.save(&day_data).unwrap();
472 let loaded_data = storage.load(&date).unwrap();
473
474 let loaded_record = loaded_data.work_records.get(&1).unwrap();
475 assert_eq!(loaded_record.name, "Important Task");
476 assert_eq!(loaded_record.description, "This is a description");
477 assert_eq!(loaded_record.total_minutes, 480);
478 }
479
480 #[test]
481 fn test_multiple_dates() {
482 let temp_dir = TempDir::new().unwrap();
483 let storage = Storage::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
484
485 let date1 = Date::from_calendar_date(2025, time::Month::November, 5).unwrap();
486 let date2 = Date::from_calendar_date(2025, time::Month::November, 6).unwrap();
487
488 let mut day1 = DayData::new(date1);
489 day1.add_record(create_test_record(1, "Day1Task"));
490
491 let mut day2 = DayData::new(date2);
492 day2.add_record(create_test_record(1, "Day2Task"));
493
494 storage.save(&day1).unwrap();
495 storage.save(&day2).unwrap();
496
497 let loaded_day1 = storage.load(&date1).unwrap();
498 let loaded_day2 = storage.load(&date2).unwrap();
499
500 assert_eq!(loaded_day1.work_records.get(&1).unwrap().name, "Day1Task");
501 assert_eq!(loaded_day2.work_records.get(&1).unwrap().name, "Day2Task");
502 }
503
504 #[test]
505 fn test_json_format_is_pretty() {
506 let temp_dir = TempDir::new().unwrap();
507 let storage = Storage::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
508 let date = create_test_date();
509
510 let mut day_data = DayData::new(date);
511 day_data.add_record(create_test_record(1, "Task"));
512
513 storage.save(&day_data).unwrap();
514
515 let file_path = storage.get_file_path(&date);
516 let contents = fs::read_to_string(file_path).unwrap();
517
518 assert!(contents.contains('\n'));
520 assert!(contents.contains(" ")); }
522
523 #[test]
524 fn test_load_active_timer_returns_none_when_not_exists() {
525 let temp_dir = TempDir::new().unwrap();
526 let storage = Storage::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
527
528 let result = storage.load_active_timer().unwrap();
529 assert!(result.is_none());
530 }
531
532 #[test]
533 fn test_save_and_load_active_timer() {
534 use crate::timer::{TimerState, TimerStatus};
535 use time::OffsetDateTime;
536
537 let temp_dir = TempDir::new().unwrap();
538 let storage = Storage::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
539
540 let now = OffsetDateTime::now_utc();
541 let timer = TimerState {
542 id: None,
543 task_name: "Work".to_string(),
544 description: Some("Important".to_string()),
545 start_time: now,
546 end_time: None,
547 date: now.date(),
548 status: TimerStatus::Running,
549 paused_duration_secs: 0,
550 paused_at: None,
551 created_at: now,
552 updated_at: now,
553 source_record_id: None,
554 source_record_date: None,
555 };
556
557 storage.save_active_timer(&timer).unwrap();
559
560 let loaded = storage.load_active_timer().unwrap();
562 assert!(loaded.is_some());
563
564 let loaded_timer = loaded.unwrap();
565 assert_eq!(loaded_timer.task_name, "Work");
566 assert_eq!(loaded_timer.description, Some("Important".to_string()));
567 assert_eq!(loaded_timer.status, TimerStatus::Running);
568 }
569
570 #[test]
571 fn test_clear_active_timer() {
572 use crate::timer::{TimerState, TimerStatus};
573 use time::OffsetDateTime;
574
575 let temp_dir = TempDir::new().unwrap();
576 let storage = Storage::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
577
578 let now = OffsetDateTime::now_utc();
579 let timer = TimerState {
580 id: None,
581 task_name: "Work".to_string(),
582 description: None,
583 start_time: now,
584 end_time: None,
585 date: now.date(),
586 status: TimerStatus::Running,
587 paused_duration_secs: 0,
588 paused_at: None,
589 created_at: now,
590 updated_at: now,
591 source_record_id: None,
592 source_record_date: None,
593 };
594
595 storage.save_active_timer(&timer).unwrap();
597 assert!(storage.load_active_timer().unwrap().is_some());
598
599 storage.clear_active_timer().unwrap();
601
602 assert!(storage.load_active_timer().unwrap().is_none());
604 }
605
606 #[test]
607 fn test_clear_active_timer_when_none_exists() {
608 let temp_dir = TempDir::new().unwrap();
609 let storage = Storage::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
610
611 let result = storage.clear_active_timer();
613 assert!(result.is_ok());
614 }
615
616 #[test]
618 fn test_storage_manager_new() {
619 let temp_dir = TempDir::new().unwrap();
620 let manager = StorageManager::new_with_dir(temp_dir.path().to_path_buf());
621 assert!(manager.is_ok());
622 }
623
624 #[test]
625 fn test_storage_manager_load_with_tracking() {
626 let temp_dir = TempDir::new().unwrap();
627 let mut manager = StorageManager::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
628 let date = create_test_date();
629
630 let result = manager.load_with_tracking(date);
631 assert!(result.is_ok());
632
633 assert!(manager.file_modified_times.contains_key(&date));
635 }
636
637 #[test]
638 fn test_storage_manager_add_record_transactional() {
639 let temp_dir = TempDir::new().unwrap();
640 let mut manager = StorageManager::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
641 let date = create_test_date();
642
643 let record = create_test_record(1, "Test Task");
644
645 let result = manager.add_record(date, record);
647 assert!(result.is_ok());
648
649 let day_data = manager.load_with_tracking(date).unwrap();
651 assert_eq!(day_data.work_records.len(), 1);
652 assert!(day_data.work_records.contains_key(&1));
653
654 assert!(manager.get_last_modified(&date).is_some());
656 }
657
658 #[test]
659 fn test_storage_manager_update_record_transactional() {
660 let temp_dir = TempDir::new().unwrap();
661 let mut manager = StorageManager::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
662 let date = create_test_date();
663
664 let record1 = create_test_record(1, "Original Task");
666 manager.add_record(date, record1).unwrap();
667
668 let record2 = create_test_record(1, "Updated Task");
670 let result = manager.update_record(date, record2);
671 assert!(result.is_ok());
672
673 let day_data = manager.load_with_tracking(date).unwrap();
675 assert_eq!(day_data.work_records.len(), 1);
676 assert_eq!(day_data.work_records.get(&1).unwrap().name, "Updated Task");
677 }
678
679 #[test]
680 fn test_storage_manager_remove_record_transactional() {
681 let temp_dir = TempDir::new().unwrap();
682 let mut manager = StorageManager::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
683 let date = create_test_date();
684
685 let record = create_test_record(1, "To Be Removed");
687 manager.add_record(date, record).unwrap();
688
689 let result = manager.remove_record(date, 1);
691 assert!(result.is_ok());
692 assert_eq!(result.unwrap().name, "To Be Removed");
693
694 let day_data = manager.load_with_tracking(date).unwrap();
696 assert_eq!(day_data.work_records.len(), 0);
697 }
698
699 #[test]
700 fn test_storage_manager_remove_nonexistent_record_fails() {
701 let temp_dir = TempDir::new().unwrap();
702 let mut manager = StorageManager::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
703 let date = create_test_date();
704
705 let result = manager.remove_record(date, 999);
707 assert!(result.is_err());
708 }
709
710 #[test]
711 fn test_storage_manager_check_and_reload_no_change() {
712 let temp_dir = TempDir::new().unwrap();
713 let mut manager = StorageManager::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
714 let date = create_test_date();
715
716 manager.load_with_tracking(date).unwrap();
718
719 let result = manager.check_and_reload(date);
721 assert!(result.is_ok());
722 assert!(result.unwrap().is_none()); }
724
725 #[test]
726 fn test_storage_manager_check_and_reload_with_external_change() {
727 let temp_dir = TempDir::new().unwrap();
728 let mut manager = StorageManager::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
729 let date = create_test_date();
730
731 manager.load_with_tracking(date).unwrap();
733
734 let mut external_manager =
736 StorageManager::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
737 let record = create_test_record(1, "External Change");
738 external_manager.add_record(date, record).unwrap();
739
740 let result = manager.check_and_reload(date);
742 assert!(result.is_ok());
743 let reloaded_data = result.unwrap();
744 assert!(reloaded_data.is_some()); assert_eq!(reloaded_data.unwrap().work_records.len(), 1);
746 }
747
748 #[test]
749 fn test_storage_manager_save_updates_tracking() {
750 let temp_dir = TempDir::new().unwrap();
751 let mut manager = StorageManager::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
752 let date = create_test_date();
753
754 let mut day_data = DayData::new(date);
755 day_data.add_record(create_test_record(1, "Task"));
756
757 manager.save(&day_data).unwrap();
759
760 assert!(manager.get_last_modified(&date).is_some());
761 }
762
763 #[test]
765 fn test_get_file_modified_time_returns_none_for_nonexistent_file() {
766 let temp_dir = TempDir::new().unwrap();
767 let storage = Storage::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
768 let date = create_test_date();
769
770 let modified_time = storage.get_file_modified_time(&date);
771 assert!(modified_time.is_none());
772 }
773
774 #[test]
775 fn test_get_file_modified_time_returns_some_after_save() {
776 let temp_dir = TempDir::new().unwrap();
777 let storage = Storage::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
778 let date = create_test_date();
779 let day_data = DayData::new(date);
780
781 assert!(storage.get_file_modified_time(&date).is_none());
783
784 storage.save(&day_data).unwrap();
786 assert!(storage.get_file_modified_time(&date).is_some());
787 }
788
789 #[test]
790 fn test_get_timer_file_path_format() {
791 let temp_dir = TempDir::new().unwrap();
792 let storage = Storage::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
793
794 let timer_path = storage.get_timer_file_path();
795 assert_eq!(timer_path.file_name().unwrap(), "running_timer.json");
796 }
797
798 #[test]
799 fn test_file_path_format_with_single_digit_month_and_day() {
800 let temp_dir = TempDir::new().unwrap();
801 let storage = Storage::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
802 let date = Date::from_calendar_date(2025, time::Month::January, 5).unwrap();
803
804 let file_path = storage.get_file_path(&date);
805 assert_eq!(file_path.file_name().unwrap(), "2025-01-05.json");
806 }
807
808 #[test]
809 fn test_file_path_format_with_december() {
810 let temp_dir = TempDir::new().unwrap();
811 let storage = Storage::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
812 let date = Date::from_calendar_date(2025, time::Month::December, 31).unwrap();
813
814 let file_path = storage.get_file_path(&date);
815 assert_eq!(file_path.file_name().unwrap(), "2025-12-31.json");
816 }
817
818 #[test]
819 fn test_save_and_load_timer_with_paused_status() {
820 use crate::timer::{TimerState, TimerStatus};
821 use time::OffsetDateTime;
822
823 let temp_dir = TempDir::new().unwrap();
824 let storage = Storage::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
825
826 let now = OffsetDateTime::now_utc();
827 let timer = TimerState {
828 id: None,
829 task_name: "Paused Work".to_string(),
830 description: None,
831 start_time: now,
832 end_time: None,
833 date: now.date(),
834 status: TimerStatus::Paused,
835 paused_duration_secs: 120,
836 paused_at: Some(now),
837 created_at: now,
838 updated_at: now,
839 source_record_id: None,
840 source_record_date: None,
841 };
842
843 storage.save_active_timer(&timer).unwrap();
844 let loaded = storage.load_active_timer().unwrap().unwrap();
845
846 assert_eq!(loaded.status, TimerStatus::Paused);
847 assert_eq!(loaded.paused_duration_secs, 120);
848 assert!(loaded.paused_at.is_some());
849 }
850
851 #[test]
852 fn test_save_and_load_timer_with_source_record() {
853 use crate::timer::{TimerState, TimerStatus};
854 use time::OffsetDateTime;
855
856 let temp_dir = TempDir::new().unwrap();
857 let storage = Storage::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
858
859 let now = OffsetDateTime::now_utc();
860 let source_date = Date::from_calendar_date(2025, time::Month::November, 5).unwrap();
861 let timer = TimerState {
862 id: None,
863 task_name: "Continued Work".to_string(),
864 description: Some("From record 5".to_string()),
865 start_time: now,
866 end_time: None,
867 date: now.date(),
868 status: TimerStatus::Running,
869 paused_duration_secs: 0,
870 paused_at: None,
871 created_at: now,
872 updated_at: now,
873 source_record_id: Some(5),
874 source_record_date: Some(source_date),
875 };
876
877 storage.save_active_timer(&timer).unwrap();
878 let loaded = storage.load_active_timer().unwrap().unwrap();
879
880 assert_eq!(loaded.source_record_id, Some(5));
881 assert_eq!(loaded.source_record_date, Some(source_date));
882 }
883
884 #[test]
885 fn test_save_multiple_records_preserves_order() {
886 let temp_dir = TempDir::new().unwrap();
887 let storage = Storage::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
888 let date = create_test_date();
889
890 let mut day_data = DayData::new(date);
891 for i in 1..=5 {
892 day_data.add_record(create_test_record(i, &format!("Task{}", i)));
893 }
894
895 storage.save(&day_data).unwrap();
896 let loaded = storage.load(&date).unwrap();
897
898 assert_eq!(loaded.work_records.len(), 5);
899 assert_eq!(loaded.last_id, 5);
900 for i in 1..=5 {
901 assert!(loaded.work_records.contains_key(&i));
902 }
903 }
904
905 #[test]
907 fn test_storage_manager_timer_passthrough_save_and_load() {
908 use crate::timer::{TimerState, TimerStatus};
909 use time::OffsetDateTime;
910
911 let temp_dir = TempDir::new().unwrap();
912 let manager = StorageManager::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
913
914 let now = OffsetDateTime::now_utc();
915 let timer = TimerState {
916 id: None,
917 task_name: "Test".to_string(),
918 description: None,
919 start_time: now,
920 end_time: None,
921 date: now.date(),
922 status: TimerStatus::Running,
923 paused_duration_secs: 0,
924 paused_at: None,
925 created_at: now,
926 updated_at: now,
927 source_record_id: None,
928 source_record_date: None,
929 };
930
931 manager.save_active_timer(&timer).unwrap();
933 let loaded = manager.load_active_timer().unwrap();
934 assert!(loaded.is_some());
935
936 manager.clear_active_timer().unwrap();
937 let cleared = manager.load_active_timer().unwrap();
938 assert!(cleared.is_none());
939 }
940
941 #[test]
942 fn test_storage_manager_tracks_multiple_dates() {
943 let temp_dir = TempDir::new().unwrap();
944 let mut manager = StorageManager::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
945
946 let date1 = Date::from_calendar_date(2025, time::Month::November, 1).unwrap();
947 let date2 = Date::from_calendar_date(2025, time::Month::November, 2).unwrap();
948 let date3 = Date::from_calendar_date(2025, time::Month::November, 3).unwrap();
949
950 manager.load_with_tracking(date1).unwrap();
952 manager.load_with_tracking(date2).unwrap();
953 manager.load_with_tracking(date3).unwrap();
954
955 assert!(manager.file_modified_times.contains_key(&date1));
957 assert!(manager.file_modified_times.contains_key(&date2));
958 assert!(manager.file_modified_times.contains_key(&date3));
959 }
960
961 #[test]
962 fn test_storage_manager_get_last_modified_returns_none_for_untracked_date() {
963 let temp_dir = TempDir::new().unwrap();
964 let manager = StorageManager::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
965 let date = create_test_date();
966
967 assert!(manager.get_last_modified(&date).is_none());
969 }
970
971 #[test]
972 fn test_storage_manager_check_and_reload_before_tracking() {
973 let temp_dir = TempDir::new().unwrap();
974 let mut manager = StorageManager::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
975 let date = create_test_date();
976
977 let result = manager.check_and_reload(date).unwrap();
979 assert!(result.is_some());
980 }
981
982 #[test]
983 fn test_storage_manager_add_multiple_records_incrementally() {
984 let temp_dir = TempDir::new().unwrap();
985 let mut manager = StorageManager::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
986 let date = create_test_date();
987
988 manager
990 .add_record(date, create_test_record(1, "Task1"))
991 .unwrap();
992 manager
993 .add_record(date, create_test_record(2, "Task2"))
994 .unwrap();
995 manager
996 .add_record(date, create_test_record(3, "Task3"))
997 .unwrap();
998
999 let day_data = manager.load_with_tracking(date).unwrap();
1001 assert_eq!(day_data.work_records.len(), 3);
1002 }
1003
1004 #[test]
1005 fn test_storage_manager_update_multiple_times() {
1006 let temp_dir = TempDir::new().unwrap();
1007 let mut manager = StorageManager::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
1008 let date = create_test_date();
1009
1010 manager
1012 .add_record(date, create_test_record(1, "Version1"))
1013 .unwrap();
1014
1015 manager
1017 .update_record(date, create_test_record(1, "Version2"))
1018 .unwrap();
1019 manager
1020 .update_record(date, create_test_record(1, "Version3"))
1021 .unwrap();
1022
1023 let day_data = manager.load_with_tracking(date).unwrap();
1025 assert_eq!(day_data.work_records.get(&1).unwrap().name, "Version3");
1026 }
1027
1028 #[test]
1029 fn test_storage_manager_remove_from_multiple_records() {
1030 let temp_dir = TempDir::new().unwrap();
1031 let mut manager = StorageManager::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
1032 let date = create_test_date();
1033
1034 manager
1036 .add_record(date, create_test_record(1, "Task1"))
1037 .unwrap();
1038 manager
1039 .add_record(date, create_test_record(2, "Task2"))
1040 .unwrap();
1041 manager
1042 .add_record(date, create_test_record(3, "Task3"))
1043 .unwrap();
1044
1045 let removed = manager.remove_record(date, 2).unwrap();
1047 assert_eq!(removed.name, "Task2");
1048
1049 let day_data = manager.load_with_tracking(date).unwrap();
1051 assert_eq!(day_data.work_records.len(), 2);
1052 assert!(day_data.work_records.contains_key(&1));
1053 assert!(!day_data.work_records.contains_key(&2));
1054 assert!(day_data.work_records.contains_key(&3));
1055 }
1056
1057 #[test]
1058 fn test_storage_manager_save_empty_day_data() {
1059 let temp_dir = TempDir::new().unwrap();
1060 let mut manager = StorageManager::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
1061 let date = create_test_date();
1062
1063 let day_data = DayData::new(date);
1064 manager.save(&day_data).unwrap();
1065
1066 let loaded = manager.load_with_tracking(date).unwrap();
1068 assert_eq!(loaded.work_records.len(), 0);
1069 assert_eq!(loaded.last_id, 0);
1070 }
1071
1072 #[test]
1073 fn test_storage_manager_save_overwrites_with_tracking_update() {
1074 let temp_dir = TempDir::new().unwrap();
1075 let mut manager = StorageManager::new_with_dir(temp_dir.path().to_path_buf()).unwrap();
1076 let date = create_test_date();
1077
1078 let mut day_data1 = DayData::new(date);
1080 day_data1.add_record(create_test_record(1, "Task1"));
1081 manager.save(&day_data1).unwrap();
1082 let first_modified = manager.get_last_modified(&date);
1083
1084 std::thread::sleep(std::time::Duration::from_millis(10));
1086
1087 let mut day_data2 = DayData::new(date);
1089 day_data2.add_record(create_test_record(2, "Task2"));
1090 manager.save(&day_data2).unwrap();
1091 let second_modified = manager.get_last_modified(&date);
1092
1093 assert_ne!(first_modified, second_modified);
1095
1096 let loaded = manager.load_with_tracking(date).unwrap();
1098 assert_eq!(loaded.work_records.len(), 1);
1099 assert!(loaded.work_records.contains_key(&2));
1100 assert!(!loaded.work_records.contains_key(&1));
1101 }
1102}