1use rusmes_storage::ModSeq;
13use std::fmt;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum QResyncState {
18 Disabled,
20 Enabled,
22}
23
24impl QResyncState {
25 pub fn is_enabled(&self) -> bool {
27 matches!(self, Self::Enabled)
28 }
29}
30
31#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct QResyncParams {
38 pub uidvalidity: u32,
40 pub modseq: ModSeq,
42 pub known_uids: Option<UidSet>,
44 pub seq_match_data: Option<SeqMatchData>,
46}
47
48impl QResyncParams {
49 pub fn new(uidvalidity: u32, modseq: ModSeq) -> Self {
51 Self {
52 uidvalidity,
53 modseq,
54 known_uids: None,
55 seq_match_data: None,
56 }
57 }
58
59 pub fn with_known_uids(uidvalidity: u32, modseq: ModSeq, known_uids: UidSet) -> Self {
61 Self {
62 uidvalidity,
63 modseq,
64 known_uids: Some(known_uids),
65 seq_match_data: None,
66 }
67 }
68
69 pub fn with_seq_match_data(
71 uidvalidity: u32,
72 modseq: ModSeq,
73 known_uids: UidSet,
74 seq_match_data: SeqMatchData,
75 ) -> Self {
76 Self {
77 uidvalidity,
78 modseq,
79 known_uids: Some(known_uids),
80 seq_match_data: Some(seq_match_data),
81 }
82 }
83
84 pub fn parse(args: &str) -> Result<Self, QResyncError> {
88 let args = args.trim().trim_matches(|c| c == '(' || c == ')');
89 let parts: Vec<&str> = args.split_whitespace().collect();
90
91 if parts.len() < 2 {
92 return Err(QResyncError::InvalidSyntax(args.to_string()));
93 }
94
95 let uidvalidity = parts[0]
97 .parse::<u32>()
98 .map_err(|_| QResyncError::InvalidUidValidity(parts[0].to_string()))?;
99
100 let modseq_value = parts[1]
102 .parse::<u64>()
103 .map_err(|_| QResyncError::InvalidModSeq(parts[1].to_string()))?;
104
105 if modseq_value == 0 {
106 return Err(QResyncError::ZeroModSeq);
107 }
108
109 let modseq = ModSeq::new(modseq_value);
110
111 let known_uids = if parts.len() > 2 {
113 Some(UidSet::parse(parts[2])?)
114 } else {
115 None
116 };
117
118 let seq_match_data = if parts.len() > 3 {
120 Some(SeqMatchData::parse(parts[3])?)
121 } else {
122 None
123 };
124
125 Ok(Self {
126 uidvalidity,
127 modseq,
128 known_uids,
129 seq_match_data,
130 })
131 }
132
133 pub fn to_imap_string(&self) -> String {
135 let mut result = format!("({} {}", self.uidvalidity, self.modseq);
136
137 if let Some(ref known_uids) = self.known_uids {
138 result.push(' ');
139 result.push_str(&known_uids.to_string());
140
141 if let Some(ref seq_match_data) = self.seq_match_data {
142 result.push(' ');
143 result.push_str(&seq_match_data.to_string());
144 }
145 }
146
147 result.push(')');
148 result
149 }
150}
151
152#[derive(Debug, Clone, PartialEq, Eq)]
156pub struct UidSet {
157 ranges: Vec<UidRange>,
159}
160
161impl UidSet {
162 pub fn new(ranges: Vec<UidRange>) -> Self {
164 Self { ranges }
165 }
166
167 pub fn parse(s: &str) -> Result<Self, QResyncError> {
171 let parts: Vec<&str> = s.split(',').collect();
172 let mut ranges = Vec::new();
173
174 for part in parts {
175 ranges.push(UidRange::parse(part)?);
176 }
177
178 if ranges.is_empty() {
179 return Err(QResyncError::InvalidUidSet(s.to_string()));
180 }
181
182 Ok(Self { ranges })
183 }
184
185 pub fn contains(&self, uid: u32) -> bool {
187 self.ranges.iter().any(|range| range.contains(uid))
188 }
189
190 pub fn ranges(&self) -> &[UidRange] {
192 &self.ranges
193 }
194}
195
196impl fmt::Display for UidSet {
197 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198 let ranges_str: Vec<String> = self.ranges.iter().map(|r| r.to_string()).collect();
199 write!(f, "{}", ranges_str.join(","))
200 }
201}
202
203#[derive(Debug, Clone, PartialEq, Eq)]
205pub enum UidRange {
206 Single(u32),
208 Range { start: u32, end: u32 },
210 RangeToMax { start: u32 },
212}
213
214impl UidRange {
215 pub fn parse(s: &str) -> Result<Self, QResyncError> {
217 if s.contains(':') {
218 let parts: Vec<&str> = s.split(':').collect();
219 if parts.len() != 2 {
220 return Err(QResyncError::InvalidUidRange(s.to_string()));
221 }
222
223 let start = parts[0]
224 .parse::<u32>()
225 .map_err(|_| QResyncError::InvalidUidRange(s.to_string()))?;
226
227 if parts[1] == "*" {
228 Ok(Self::RangeToMax { start })
229 } else {
230 let end = parts[1]
231 .parse::<u32>()
232 .map_err(|_| QResyncError::InvalidUidRange(s.to_string()))?;
233
234 if start > end {
235 return Err(QResyncError::InvalidUidRange(s.to_string()));
236 }
237
238 Ok(Self::Range { start, end })
239 }
240 } else {
241 let uid = s
242 .parse::<u32>()
243 .map_err(|_| QResyncError::InvalidUidRange(s.to_string()))?;
244 Ok(Self::Single(uid))
245 }
246 }
247
248 pub fn contains(&self, uid: u32) -> bool {
250 match self {
251 Self::Single(u) => *u == uid,
252 Self::Range { start, end } => uid >= *start && uid <= *end,
253 Self::RangeToMax { start } => uid >= *start,
254 }
255 }
256}
257
258impl fmt::Display for UidRange {
259 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260 match self {
261 Self::Single(uid) => write!(f, "{}", uid),
262 Self::Range { start, end } => write!(f, "{}:{}", start, end),
263 Self::RangeToMax { start } => write!(f, "{}:*", start),
264 }
265 }
266}
267
268#[derive(Debug, Clone, PartialEq, Eq)]
272pub struct SeqMatchData {
273 sequences: Vec<u32>,
275}
276
277impl SeqMatchData {
278 pub fn new(sequences: Vec<u32>) -> Self {
280 Self { sequences }
281 }
282
283 pub fn parse(s: &str) -> Result<Self, QResyncError> {
287 let s = s.trim().trim_matches(|c| c == '(' || c == ')');
288 let parts: Vec<&str> = s.split_whitespace().collect();
289
290 let mut sequences = Vec::new();
291 for part in parts {
292 let seq = part
293 .parse::<u32>()
294 .map_err(|_| QResyncError::InvalidSeqMatchData(s.to_string()))?;
295 sequences.push(seq);
296 }
297
298 Ok(Self { sequences })
299 }
300
301 pub fn sequences(&self) -> &[u32] {
303 &self.sequences
304 }
305}
306
307impl fmt::Display for SeqMatchData {
308 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309 let seqs: Vec<String> = self.sequences.iter().map(|s| s.to_string()).collect();
310 write!(f, "({})", seqs.join(" "))
311 }
312}
313
314#[derive(Debug, Clone, PartialEq, Eq)]
319pub struct VanishedResponse {
320 pub uids: UidSet,
322 pub earlier: bool,
324}
325
326impl VanishedResponse {
327 pub fn new(uids: UidSet) -> Self {
329 Self {
330 uids,
331 earlier: false,
332 }
333 }
334
335 pub fn earlier(uids: UidSet) -> Self {
337 Self {
338 uids,
339 earlier: true,
340 }
341 }
342
343 pub fn to_imap_response(&self) -> String {
349 if self.earlier {
350 format!("* VANISHED (EARLIER) {}", self.uids)
351 } else {
352 format!("* VANISHED {}", self.uids)
353 }
354 }
355
356 pub fn parse(line: &str) -> Result<Self, QResyncError> {
358 let line = line.trim();
359
360 if !line.starts_with("* VANISHED") && !line.starts_with("VANISHED") {
361 return Err(QResyncError::InvalidVanishedResponse(line.to_string()));
362 }
363
364 let line = line
365 .trim_start_matches("* ")
366 .trim_start_matches("VANISHED")
367 .trim();
368
369 let (earlier, uid_str) = if line.starts_with("(EARLIER)") {
370 (true, line.trim_start_matches("(EARLIER)").trim())
371 } else {
372 (false, line)
373 };
374
375 let uids = UidSet::parse(uid_str)?;
376
377 Ok(Self { uids, earlier })
378 }
379}
380
381#[derive(Debug, Clone, PartialEq, Eq)]
383pub enum QResyncError {
384 InvalidSyntax(String),
386 InvalidUidValidity(String),
388 InvalidModSeq(String),
390 ZeroModSeq,
392 InvalidUidSet(String),
394 InvalidUidRange(String),
396 InvalidSeqMatchData(String),
398 InvalidVanishedResponse(String),
400 NotEnabled,
402 CondStoreRequired,
404 UidValidityMismatch {
406 expected: u32,
408 actual: u32,
410 },
411}
412
413impl fmt::Display for QResyncError {
414 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
415 match self {
416 QResyncError::InvalidSyntax(s) => write!(f, "Invalid QRESYNC syntax: {}", s),
417 QResyncError::InvalidUidValidity(s) => write!(f, "Invalid UIDVALIDITY: {}", s),
418 QResyncError::InvalidModSeq(s) => write!(f, "Invalid MODSEQ: {}", s),
419 QResyncError::ZeroModSeq => write!(f, "MODSEQ cannot be zero"),
420 QResyncError::InvalidUidSet(s) => write!(f, "Invalid UID set: {}", s),
421 QResyncError::InvalidUidRange(s) => write!(f, "Invalid UID range: {}", s),
422 QResyncError::InvalidSeqMatchData(s) => write!(f, "Invalid sequence match data: {}", s),
423 QResyncError::InvalidVanishedResponse(s) => {
424 write!(f, "Invalid VANISHED response: {}", s)
425 }
426 QResyncError::NotEnabled => write!(f, "QRESYNC not enabled"),
427 QResyncError::CondStoreRequired => write!(f, "CONDSTORE required for QRESYNC"),
428 QResyncError::UidValidityMismatch { expected, actual } => {
429 write!(
430 f,
431 "UIDVALIDITY mismatch: expected {}, got {}",
432 expected, actual
433 )
434 }
435 }
436 }
437}
438
439impl std::error::Error for QResyncError {}
440
441#[derive(Debug)]
446pub struct QResyncLogic {
447 pub uidvalidity: u32,
449 pub highest_modseq: ModSeq,
451}
452
453impl QResyncLogic {
454 pub fn new(uidvalidity: u32, highest_modseq: ModSeq) -> Self {
456 Self {
457 uidvalidity,
458 highest_modseq,
459 }
460 }
461
462 pub fn validate_params(&self, params: &QResyncParams) -> Result<(), QResyncError> {
466 if params.uidvalidity != self.uidvalidity {
467 return Err(QResyncError::UidValidityMismatch {
468 expected: params.uidvalidity,
469 actual: self.uidvalidity,
470 });
471 }
472 Ok(())
473 }
474
475 pub fn find_vanished_uids(&self, known_uids: &UidSet, current_uids: &[u32]) -> Vec<u32> {
480 let mut vanished = Vec::new();
481
482 for range in known_uids.ranges() {
484 match range {
485 UidRange::Single(uid) => {
486 if !current_uids.contains(uid) {
487 vanished.push(*uid);
488 }
489 }
490 UidRange::Range { start, end } => {
491 for uid in *start..=*end {
492 if !current_uids.contains(&uid) {
493 vanished.push(uid);
494 }
495 }
496 }
497 UidRange::RangeToMax { start } => {
498 let max_uid = current_uids.iter().max().copied().unwrap_or(*start);
500 for uid in *start..=max_uid {
501 if !current_uids.contains(&uid) {
502 vanished.push(uid);
503 }
504 }
505 }
506 }
507 }
508
509 vanished.sort_unstable();
510 vanished
511 }
512
513 pub fn create_vanished_response(
517 &self,
518 vanished_uids: Vec<u32>,
519 earlier: bool,
520 ) -> Option<VanishedResponse> {
521 if vanished_uids.is_empty() {
522 return None;
523 }
524
525 let ranges = Self::compress_to_ranges(vanished_uids);
526 let uid_set = UidSet::new(ranges);
527
528 Some(if earlier {
529 VanishedResponse::earlier(uid_set)
530 } else {
531 VanishedResponse::new(uid_set)
532 })
533 }
534
535 fn compress_to_ranges(mut uids: Vec<u32>) -> Vec<UidRange> {
539 if uids.is_empty() {
540 return Vec::new();
541 }
542
543 uids.sort_unstable();
544 uids.dedup();
545
546 let mut ranges = Vec::new();
547 let mut range_start = uids[0];
548 let mut range_end = uids[0];
549
550 for &uid in &uids[1..] {
551 if uid == range_end + 1 {
552 range_end = uid;
554 } else {
555 if range_start == range_end {
557 ranges.push(UidRange::Single(range_start));
558 } else {
559 ranges.push(UidRange::Range {
560 start: range_start,
561 end: range_end,
562 });
563 }
564 range_start = uid;
565 range_end = uid;
566 }
567 }
568
569 if range_start == range_end {
571 ranges.push(UidRange::Single(range_start));
572 } else {
573 ranges.push(UidRange::Range {
574 start: range_start,
575 end: range_end,
576 });
577 }
578
579 ranges
580 }
581
582 pub fn needs_resync(&self, client_modseq: ModSeq) -> bool {
586 client_modseq < self.highest_modseq
587 }
588}
589
590#[cfg(test)]
591mod tests {
592 use super::*;
593
594 #[test]
595 fn test_qresync_state() {
596 let state = QResyncState::Disabled;
597 assert!(!state.is_enabled());
598
599 let state = QResyncState::Enabled;
600 assert!(state.is_enabled());
601 }
602
603 #[test]
604 fn test_qresync_params_basic() {
605 let params = QResyncParams::new(12345, ModSeq::new(67890));
606 assert_eq!(params.uidvalidity, 12345);
607 assert_eq!(params.modseq.value(), 67890);
608 assert!(params.known_uids.is_none());
609 assert!(params.seq_match_data.is_none());
610 }
611
612 #[test]
613 fn test_qresync_params_parse_basic() {
614 let params = QResyncParams::parse("(12345 67890)").expect("basic QRESYNC params parse");
615 assert_eq!(params.uidvalidity, 12345);
616 assert_eq!(params.modseq.value(), 67890);
617 assert!(params.known_uids.is_none());
618 }
619
620 #[test]
621 fn test_qresync_params_parse_with_known_uids() {
622 let params = QResyncParams::parse("(12345 67890 1:100)")
623 .expect("QRESYNC params with known UIDs parse");
624 assert_eq!(params.uidvalidity, 12345);
625 assert_eq!(params.modseq.value(), 67890);
626 assert!(params.known_uids.is_some());
627 }
628
629 #[test]
630 fn test_qresync_params_parse_full() {
631 let params = QResyncParams::parse("(12345 67890 1:100 (1 2 3 4 5))")
632 .expect("full QRESYNC params parse");
633 assert_eq!(params.uidvalidity, 12345);
634 assert_eq!(params.modseq.value(), 67890);
635 assert!(params.known_uids.is_some());
636 assert!(params.seq_match_data.is_some());
637 }
638
639 #[test]
640 fn test_qresync_params_parse_invalid() {
641 assert!(QResyncParams::parse("(12345)").is_err());
642 assert!(QResyncParams::parse("(abc 67890)").is_err());
643 assert!(QResyncParams::parse("(12345 0)").is_err());
644 }
645
646 #[test]
647 fn test_qresync_params_to_imap_string() {
648 let params = QResyncParams::new(12345, ModSeq::new(67890));
649 assert_eq!(params.to_imap_string(), "(12345 67890)");
650
651 let params = QResyncParams::with_known_uids(
652 12345,
653 ModSeq::new(67890),
654 UidSet::parse("1:100").expect("valid UID set parse"),
655 );
656 assert_eq!(params.to_imap_string(), "(12345 67890 1:100)");
657 }
658
659 #[test]
660 fn test_uid_range_parse_single() {
661 let range = UidRange::parse("42").expect("single UID range parse");
662 assert_eq!(range, UidRange::Single(42));
663 assert!(range.contains(42));
664 assert!(!range.contains(41));
665 assert_eq!(range.to_string(), "42");
666 }
667
668 #[test]
669 fn test_uid_range_parse_range() {
670 let range = UidRange::parse("10:20").expect("UID range 10:20 parse");
671 assert_eq!(range, UidRange::Range { start: 10, end: 20 });
672 assert!(range.contains(10));
673 assert!(range.contains(15));
674 assert!(range.contains(20));
675 assert!(!range.contains(9));
676 assert!(!range.contains(21));
677 assert_eq!(range.to_string(), "10:20");
678 }
679
680 #[test]
681 fn test_uid_range_parse_to_max() {
682 let range = UidRange::parse("100:*").expect("UID range 100:* parse");
683 assert_eq!(range, UidRange::RangeToMax { start: 100 });
684 assert!(range.contains(100));
685 assert!(range.contains(1000));
686 assert!(range.contains(u32::MAX));
687 assert!(!range.contains(99));
688 assert_eq!(range.to_string(), "100:*");
689 }
690
691 #[test]
692 fn test_uid_range_parse_invalid() {
693 assert!(UidRange::parse("abc").is_err());
694 assert!(UidRange::parse("10:5").is_err());
695 assert!(UidRange::parse("10:20:30").is_err());
696 }
697
698 #[test]
699 fn test_uid_set_parse_single() {
700 let set = UidSet::parse("42").expect("single UID set parse");
701 assert_eq!(set.ranges.len(), 1);
702 assert!(set.contains(42));
703 assert!(!set.contains(41));
704 assert_eq!(set.to_string(), "42");
705 }
706
707 #[test]
708 fn test_uid_set_parse_multiple() {
709 let set = UidSet::parse("1,3,5").expect("multiple single UIDs set parse");
710 assert_eq!(set.ranges.len(), 3);
711 assert!(set.contains(1));
712 assert!(!set.contains(2));
713 assert!(set.contains(3));
714 assert!(!set.contains(4));
715 assert!(set.contains(5));
716 assert_eq!(set.to_string(), "1,3,5");
717 }
718
719 #[test]
720 fn test_uid_set_parse_ranges() {
721 let set = UidSet::parse("1:5,10:20,100:*").expect("UID set with ranges parse");
722 assert_eq!(set.ranges.len(), 3);
723 assert!(set.contains(1));
724 assert!(set.contains(5));
725 assert!(!set.contains(6));
726 assert!(set.contains(10));
727 assert!(set.contains(20));
728 assert!(!set.contains(21));
729 assert!(set.contains(100));
730 assert!(set.contains(1000));
731 assert_eq!(set.to_string(), "1:5,10:20,100:*");
732 }
733
734 #[test]
735 fn test_uid_set_parse_mixed() {
736 let set = UidSet::parse("1,5:10,15,20:*").expect("mixed UID set parse");
737 assert_eq!(set.ranges.len(), 4);
738 assert!(set.contains(1));
739 assert!(set.contains(7));
740 assert!(set.contains(15));
741 assert!(set.contains(100));
742 assert_eq!(set.to_string(), "1,5:10,15,20:*");
743 }
744
745 #[test]
746 fn test_seq_match_data_parse() {
747 let data = SeqMatchData::parse("(1 2 3 4 5)").expect("SeqMatchData with parens parse");
748 assert_eq!(data.sequences.len(), 5);
749 assert_eq!(data.sequences[0], 1);
750 assert_eq!(data.sequences[4], 5);
751 assert_eq!(data.to_string(), "(1 2 3 4 5)");
752 }
753
754 #[test]
755 fn test_seq_match_data_parse_without_parens() {
756 let data = SeqMatchData::parse("1 2 3").expect("SeqMatchData without parens parse");
757 assert_eq!(data.sequences.len(), 3);
758 assert_eq!(data.sequences(), &[1, 2, 3]);
759 }
760
761 #[test]
762 fn test_vanished_response_new() {
763 let uids = UidSet::parse("1:5,7,9:12").expect("UID set parse for VANISHED test");
764 let response = VanishedResponse::new(uids);
765 assert!(!response.earlier);
766 assert_eq!(response.to_imap_response(), "* VANISHED 1:5,7,9:12");
767 }
768
769 #[test]
770 fn test_vanished_response_earlier() {
771 let uids = UidSet::parse("1:5,7,9:12").expect("UID set parse for VANISHED EARLIER test");
772 let response = VanishedResponse::earlier(uids);
773 assert!(response.earlier);
774 assert_eq!(
775 response.to_imap_response(),
776 "* VANISHED (EARLIER) 1:5,7,9:12"
777 );
778 }
779
780 #[test]
781 fn test_vanished_response_parse() {
782 let response =
783 VanishedResponse::parse("* VANISHED 1:5,7").expect("VANISHED response parse");
784 assert!(!response.earlier);
785 assert!(response.uids.contains(1));
786 assert!(response.uids.contains(5));
787 assert!(response.uids.contains(7));
788
789 let response = VanishedResponse::parse("* VANISHED (EARLIER) 1:5,7")
790 .expect("VANISHED (EARLIER) response parse");
791 assert!(response.earlier);
792 assert!(response.uids.contains(1));
793 }
794
795 #[test]
796 fn test_vanished_response_parse_without_star() {
797 let response =
798 VanishedResponse::parse("VANISHED 1:5").expect("VANISHED without * prefix parse");
799 assert!(!response.earlier);
800 assert!(response.uids.contains(1));
801 }
802
803 #[test]
804 fn test_qresync_error_display() {
805 let err = QResyncError::NotEnabled;
806 assert_eq!(err.to_string(), "QRESYNC not enabled");
807
808 let err = QResyncError::ZeroModSeq;
809 assert_eq!(err.to_string(), "MODSEQ cannot be zero");
810
811 let err = QResyncError::UidValidityMismatch {
812 expected: 100,
813 actual: 200,
814 };
815 assert_eq!(
816 err.to_string(),
817 "UIDVALIDITY mismatch: expected 100, got 200"
818 );
819 }
820
821 #[test]
822 fn test_uid_set_empty() {
823 assert!(UidSet::parse("").is_err());
824 }
825
826 #[test]
827 fn test_qresync_params_with_seq_match_data() {
828 let known_uids = UidSet::parse("1:100").expect("UID set 1:100 parse");
829 let seq_match = SeqMatchData::new(vec![1, 2, 3]);
830 let params =
831 QResyncParams::with_seq_match_data(12345, ModSeq::new(67890), known_uids, seq_match);
832 assert_eq!(params.uidvalidity, 12345);
833 assert!(params.seq_match_data.is_some());
834 assert_eq!(params.to_imap_string(), "(12345 67890 1:100 (1 2 3))");
835 }
836
837 #[test]
838 fn test_qresync_logic_new() {
839 let logic = QResyncLogic::new(12345, ModSeq::new(67890));
840 assert_eq!(logic.uidvalidity, 12345);
841 assert_eq!(logic.highest_modseq.value(), 67890);
842 }
843
844 #[test]
845 fn test_qresync_logic_validate_params_success() {
846 let logic = QResyncLogic::new(12345, ModSeq::new(100));
847 let params = QResyncParams::new(12345, ModSeq::new(50));
848 assert!(logic.validate_params(¶ms).is_ok());
849 }
850
851 #[test]
852 fn test_qresync_logic_validate_params_mismatch() {
853 let logic = QResyncLogic::new(12345, ModSeq::new(100));
854 let params = QResyncParams::new(99999, ModSeq::new(50));
855 let result = logic.validate_params(¶ms);
856 assert!(result.is_err());
857 match result.unwrap_err() {
858 QResyncError::UidValidityMismatch { expected, actual } => {
859 assert_eq!(expected, 99999);
860 assert_eq!(actual, 12345);
861 }
862 _ => panic!("Expected UidValidityMismatch error"),
863 }
864 }
865
866 #[test]
867 fn test_qresync_logic_find_vanished_uids_single() {
868 let logic = QResyncLogic::new(12345, ModSeq::new(100));
869 let known_uids = UidSet::parse("1,2,3,4,5").expect("UID set 1,2,3,4,5 parse");
870 let current_uids = vec![1, 3, 5];
871 let vanished = logic.find_vanished_uids(&known_uids, ¤t_uids);
872 assert_eq!(vanished, vec![2, 4]);
873 }
874
875 #[test]
876 fn test_qresync_logic_find_vanished_uids_range() {
877 let logic = QResyncLogic::new(12345, ModSeq::new(100));
878 let known_uids = UidSet::parse("1:10").expect("UID set 1:10 parse");
879 let current_uids = vec![1, 2, 5, 6, 7, 10];
880 let vanished = logic.find_vanished_uids(&known_uids, ¤t_uids);
881 assert_eq!(vanished, vec![3, 4, 8, 9]);
882 }
883
884 #[test]
885 fn test_qresync_logic_find_vanished_uids_none() {
886 let logic = QResyncLogic::new(12345, ModSeq::new(100));
887 let known_uids = UidSet::parse("1,2,3").expect("UID set 1,2,3 parse");
888 let current_uids = vec![1, 2, 3];
889 let vanished = logic.find_vanished_uids(&known_uids, ¤t_uids);
890 assert!(vanished.is_empty());
891 }
892
893 #[test]
894 fn test_qresync_logic_find_vanished_uids_mixed() {
895 let logic = QResyncLogic::new(12345, ModSeq::new(100));
896 let known_uids = UidSet::parse("1:5,10,15:20").expect("UID set 1:5,10,15:20 parse");
897 let current_uids = vec![1, 3, 5, 15, 17, 20];
898 let vanished = logic.find_vanished_uids(&known_uids, ¤t_uids);
899 assert_eq!(vanished, vec![2, 4, 10, 16, 18, 19]);
900 }
901
902 #[test]
903 fn test_qresync_logic_compress_to_ranges_single() {
904 let ranges = QResyncLogic::compress_to_ranges(vec![5]);
905 assert_eq!(ranges.len(), 1);
906 assert_eq!(ranges[0], UidRange::Single(5));
907 }
908
909 #[test]
910 fn test_qresync_logic_compress_to_ranges_consecutive() {
911 let ranges = QResyncLogic::compress_to_ranges(vec![1, 2, 3, 4, 5]);
912 assert_eq!(ranges.len(), 1);
913 assert_eq!(ranges[0], UidRange::Range { start: 1, end: 5 });
914 }
915
916 #[test]
917 fn test_qresync_logic_compress_to_ranges_gaps() {
918 let ranges = QResyncLogic::compress_to_ranges(vec![1, 3, 5, 7]);
919 assert_eq!(ranges.len(), 4);
920 assert_eq!(ranges[0], UidRange::Single(1));
921 assert_eq!(ranges[1], UidRange::Single(3));
922 assert_eq!(ranges[2], UidRange::Single(5));
923 assert_eq!(ranges[3], UidRange::Single(7));
924 }
925
926 #[test]
927 fn test_qresync_logic_compress_to_ranges_mixed() {
928 let ranges = QResyncLogic::compress_to_ranges(vec![1, 2, 3, 5, 7, 8, 9, 15]);
929 assert_eq!(ranges.len(), 4);
930 assert_eq!(ranges[0], UidRange::Range { start: 1, end: 3 });
931 assert_eq!(ranges[1], UidRange::Single(5));
932 assert_eq!(ranges[2], UidRange::Range { start: 7, end: 9 });
933 assert_eq!(ranges[3], UidRange::Single(15));
934 }
935
936 #[test]
937 fn test_qresync_logic_compress_to_ranges_unordered() {
938 let ranges = QResyncLogic::compress_to_ranges(vec![5, 1, 3, 2, 4]);
939 assert_eq!(ranges.len(), 1);
940 assert_eq!(ranges[0], UidRange::Range { start: 1, end: 5 });
941 }
942
943 #[test]
944 fn test_qresync_logic_compress_to_ranges_duplicates() {
945 let ranges = QResyncLogic::compress_to_ranges(vec![1, 2, 2, 3, 3, 4]);
946 assert_eq!(ranges.len(), 1);
947 assert_eq!(ranges[0], UidRange::Range { start: 1, end: 4 });
948 }
949
950 #[test]
951 fn test_qresync_logic_create_vanished_response_empty() {
952 let logic = QResyncLogic::new(12345, ModSeq::new(100));
953 let response = logic.create_vanished_response(vec![], false);
954 assert!(response.is_none());
955 }
956
957 #[test]
958 fn test_qresync_logic_create_vanished_response_single() {
959 let logic = QResyncLogic::new(12345, ModSeq::new(100));
960 let response = logic
961 .create_vanished_response(vec![5], false)
962 .expect("VANISHED response for single UID");
963 assert!(!response.earlier);
964 assert_eq!(response.to_imap_response(), "* VANISHED 5");
965 }
966
967 #[test]
968 fn test_qresync_logic_create_vanished_response_range() {
969 let logic = QResyncLogic::new(12345, ModSeq::new(100));
970 let response = logic
971 .create_vanished_response(vec![1, 2, 3, 4, 5], false)
972 .expect("VANISHED response for sequential UIDs");
973 assert_eq!(response.to_imap_response(), "* VANISHED 1:5");
974 }
975
976 #[test]
977 fn test_qresync_logic_create_vanished_response_earlier() {
978 let logic = QResyncLogic::new(12345, ModSeq::new(100));
979 let response = logic
980 .create_vanished_response(vec![1, 2, 3], true)
981 .expect("VANISHED (EARLIER) response");
982 assert!(response.earlier);
983 assert_eq!(response.to_imap_response(), "* VANISHED (EARLIER) 1:3");
984 }
985
986 #[test]
987 fn test_qresync_logic_create_vanished_response_mixed() {
988 let logic = QResyncLogic::new(12345, ModSeq::new(100));
989 let response = logic
990 .create_vanished_response(vec![1, 2, 3, 5, 7, 8, 9], false)
991 .expect("VANISHED response for mixed UIDs");
992 assert_eq!(response.to_imap_response(), "* VANISHED 1:3,5,7:9");
993 }
994
995 #[test]
996 fn test_qresync_logic_needs_resync() {
997 let logic = QResyncLogic::new(12345, ModSeq::new(100));
998 assert!(logic.needs_resync(ModSeq::new(50)));
999 assert!(logic.needs_resync(ModSeq::new(99)));
1000 assert!(!logic.needs_resync(ModSeq::new(100)));
1001 assert!(!logic.needs_resync(ModSeq::new(101)));
1002 }
1003
1004 #[test]
1005 fn test_qresync_integration_full_resync() {
1006 let logic = QResyncLogic::new(12345, ModSeq::new(200));
1008
1009 let params = QResyncParams::with_known_uids(
1011 12345,
1012 ModSeq::new(100),
1013 UidSet::parse("1:50").expect("UID set 1:50 parse"),
1014 );
1015
1016 assert!(logic.validate_params(¶ms).is_ok());
1018 assert!(logic.needs_resync(params.modseq));
1019
1020 let current_uids: Vec<u32> = (1..=50).filter(|&n| n % 3 != 0).collect();
1022
1023 let known = params
1025 .known_uids
1026 .as_ref()
1027 .expect("known_uids should be set");
1028 let vanished = logic.find_vanished_uids(known, ¤t_uids);
1029 assert!(!vanished.is_empty());
1030
1031 let response = logic.create_vanished_response(vanished, true);
1033 assert!(response.is_some());
1034 assert!(response.expect("VANISHED response should be Some").earlier);
1035 }
1036
1037 #[test]
1038 fn test_uid_range_boundary_values() {
1039 let range = UidRange::Range {
1041 start: 1,
1042 end: u32::MAX,
1043 };
1044 assert!(range.contains(1));
1045 assert!(range.contains(u32::MAX));
1046 assert!(range.contains(u32::MAX / 2));
1047 }
1048
1049 #[test]
1050 fn test_uid_set_large_range() {
1051 let set = UidSet::parse("1:4294967295").expect("UID set spanning full u32 range parse");
1052 assert!(set.contains(1));
1053 assert!(set.contains(1000000));
1054 assert!(set.contains(u32::MAX));
1055 }
1056
1057 #[test]
1058 fn test_vanished_response_roundtrip() {
1059 let original =
1060 VanishedResponse::earlier(UidSet::parse("1:5,10,20:25").expect("UID set parse"));
1061 let imap_str = original.to_imap_response();
1062 let parsed = VanishedResponse::parse(&imap_str).expect("roundtrip VANISHED response parse");
1063 assert_eq!(parsed.earlier, original.earlier);
1064 assert_eq!(parsed.to_imap_response(), original.to_imap_response());
1065 }
1066
1067 #[test]
1068 fn test_qresync_params_roundtrip() {
1069 let original = QResyncParams::with_seq_match_data(
1070 12345,
1071 ModSeq::new(67890),
1072 UidSet::parse("1:100,200:300").expect("UID set 1:100,200:300 parse"),
1073 SeqMatchData::new(vec![1, 2, 3, 4, 5]),
1074 );
1075 let imap_str = original.to_imap_string();
1076 let parsed = QResyncParams::parse(&imap_str).expect("roundtrip QRESYNC params parse");
1077 assert_eq!(parsed.uidvalidity, original.uidvalidity);
1078 assert_eq!(parsed.modseq.value(), original.modseq.value());
1079 }
1080}