1use std::collections::HashMap;
30
31#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum PeerMigrationState {
34 Idle,
36 Preparing,
38 Transferring,
40 Verifying,
42 Completed,
44 Failed(String),
46}
47
48impl std::fmt::Display for PeerMigrationState {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 match self {
51 Self::Idle => write!(f, "Idle"),
52 Self::Preparing => write!(f, "Preparing"),
53 Self::Transferring => write!(f, "Transferring"),
54 Self::Verifying => write!(f, "Verifying"),
55 Self::Completed => write!(f, "Completed"),
56 Self::Failed(reason) => write!(f, "Failed({})", reason),
57 }
58 }
59}
60
61#[derive(Debug, Clone)]
63pub struct PeerMigrationRecord {
64 pub migration_id: u64,
66 pub source_peer: String,
68 pub target_peer: String,
70 pub state: PeerMigrationState,
72 pub items_total: u64,
74 pub items_transferred: u64,
76 pub bytes_transferred: u64,
78 pub started_at: u64,
80 pub completed_at: Option<u64>,
82 pub checksum: u64,
84}
85
86#[derive(Debug, Clone)]
88pub struct PeerMigrationConfig {
89 pub max_concurrent: usize,
91 pub chunk_size: usize,
93 pub timeout_ms: u64,
95 pub verify_after_transfer: bool,
97 pub retry_count: u32,
99}
100
101impl Default for PeerMigrationConfig {
102 fn default() -> Self {
103 Self {
104 max_concurrent: 4,
105 chunk_size: 256,
106 timeout_ms: 30_000,
107 verify_after_transfer: true,
108 retry_count: 3,
109 }
110 }
111}
112
113#[derive(Debug, Clone)]
115pub struct MigrationItem {
116 pub key: String,
118 pub data: Vec<u8>,
120 pub sequence: u64,
122}
123
124#[derive(Debug, Clone, Default)]
126pub struct PeerMigrationStats {
127 pub total_migrations: u64,
129 pub successful: u64,
131 pub failed: u64,
133 pub bytes_migrated: u64,
135 pub avg_duration_ms: f64,
137}
138
139pub struct PeerMigrationManager {
141 config: PeerMigrationConfig,
142 active_migrations: HashMap<u64, PeerMigrationRecord>,
143 completed_migrations: Vec<PeerMigrationRecord>,
144 next_id: u64,
145 stats: PeerMigrationStats,
146 items: HashMap<u64, Vec<MigrationItem>>,
148}
149
150impl PeerMigrationManager {
151 pub fn new(config: PeerMigrationConfig) -> Self {
153 Self {
154 config,
155 active_migrations: HashMap::new(),
156 completed_migrations: Vec::new(),
157 next_id: 1,
158 stats: PeerMigrationStats::default(),
159 items: HashMap::new(),
160 }
161 }
162
163 pub fn start_migration(
168 &mut self,
169 source: &str,
170 target: &str,
171 total_items: u64,
172 ) -> Result<u64, String> {
173 if source.is_empty() {
174 return Err("source peer cannot be empty".to_string());
175 }
176 if target.is_empty() {
177 return Err("target peer cannot be empty".to_string());
178 }
179 if source == target {
180 return Err("source and target peers must be different".to_string());
181 }
182 if total_items == 0 {
183 return Err("total_items must be greater than zero".to_string());
184 }
185
186 let active = self.active_count();
187 if active >= self.config.max_concurrent {
188 return Err(format!(
189 "concurrent migration limit reached ({}/{})",
190 active, self.config.max_concurrent
191 ));
192 }
193
194 let id = self.next_id;
195 self.next_id = self.next_id.saturating_add(1);
196
197 let now = current_timestamp_ms();
198
199 let record = PeerMigrationRecord {
200 migration_id: id,
201 source_peer: source.to_string(),
202 target_peer: target.to_string(),
203 state: PeerMigrationState::Preparing,
204 items_total: total_items,
205 items_transferred: 0,
206 bytes_transferred: 0,
207 started_at: now,
208 completed_at: None,
209 checksum: 0,
210 };
211
212 self.active_migrations.insert(id, record);
213 self.items.insert(id, Vec::new());
214 self.stats.total_migrations = self.stats.total_migrations.saturating_add(1);
215
216 Ok(id)
217 }
218
219 pub fn add_items(
224 &mut self,
225 migration_id: u64,
226 new_items: Vec<MigrationItem>,
227 ) -> Result<(), String> {
228 let record = self
229 .active_migrations
230 .get_mut(&migration_id)
231 .ok_or_else(|| format!("migration {} not found", migration_id))?;
232
233 if record.state != PeerMigrationState::Transferring {
234 return Err(format!(
235 "migration {} is in state {}, expected Transferring",
236 migration_id, record.state
237 ));
238 }
239
240 let item_count = new_items.len() as u64;
241 let byte_count: u64 = new_items.iter().map(|item| item.data.len() as u64).sum();
242
243 let new_transferred = record.items_transferred.saturating_add(item_count);
245 if new_transferred > record.items_total {
246 return Err(format!(
247 "adding {} items would exceed total ({} + {} > {})",
248 item_count, record.items_transferred, item_count, record.items_total
249 ));
250 }
251
252 record.items_transferred = new_transferred;
253 record.bytes_transferred = record.bytes_transferred.saturating_add(byte_count);
254
255 let checksum_delta: u64 = compute_items_checksum(&new_items);
257 record.checksum = record.checksum.wrapping_add(checksum_delta);
258
259 let stored = self.items.entry(migration_id).or_default();
260 stored.extend(new_items);
261
262 Ok(())
263 }
264
265 pub fn advance_state(&mut self, migration_id: u64) -> Result<PeerMigrationState, String> {
273 let verify = self.config.verify_after_transfer;
274
275 let record = self
276 .active_migrations
277 .get_mut(&migration_id)
278 .ok_or_else(|| format!("migration {} not found", migration_id))?;
279
280 let next = match &record.state {
281 PeerMigrationState::Idle => PeerMigrationState::Preparing,
282 PeerMigrationState::Preparing => PeerMigrationState::Transferring,
283 PeerMigrationState::Transferring => PeerMigrationState::Verifying,
284 PeerMigrationState::Verifying => {
285 if verify && record.items_transferred < record.items_total {
287 return Err(format!(
288 "verification failed: only {}/{} items transferred",
289 record.items_transferred, record.items_total
290 ));
291 }
292 PeerMigrationState::Completed
293 }
294 PeerMigrationState::Completed => {
295 return Err(format!("migration {} is already completed", migration_id));
296 }
297 PeerMigrationState::Failed(reason) => {
298 return Err(format!(
299 "migration {} has failed ({}), cannot advance",
300 migration_id, reason
301 ));
302 }
303 };
304
305 record.state = next.clone();
306
307 if next == PeerMigrationState::Completed {
309 self.finalize_migration(migration_id);
310 }
311
312 Ok(next)
313 }
314
315 pub fn complete_migration(&mut self, migration_id: u64, checksum: u64) -> Result<(), String> {
320 let verify = self.config.verify_after_transfer;
321
322 let record = self
323 .active_migrations
324 .get_mut(&migration_id)
325 .ok_or_else(|| format!("migration {} not found", migration_id))?;
326
327 if record.state != PeerMigrationState::Verifying {
328 return Err(format!(
329 "migration {} is in state {}, expected Verifying",
330 migration_id, record.state
331 ));
332 }
333
334 if verify && checksum != record.checksum {
335 return Err(format!(
336 "checksum mismatch: expected {}, got {}",
337 record.checksum, checksum
338 ));
339 }
340
341 record.state = PeerMigrationState::Completed;
342 record.checksum = checksum;
343
344 self.finalize_migration(migration_id);
345
346 Ok(())
347 }
348
349 pub fn fail_migration(&mut self, migration_id: u64, reason: &str) -> Result<(), String> {
351 let record = self
352 .active_migrations
353 .get_mut(&migration_id)
354 .ok_or_else(|| format!("migration {} not found", migration_id))?;
355
356 match &record.state {
357 PeerMigrationState::Completed => {
358 return Err(format!(
359 "migration {} is already completed, cannot fail",
360 migration_id
361 ));
362 }
363 PeerMigrationState::Failed(_) => {
364 return Err(format!("migration {} has already failed", migration_id));
365 }
366 _ => {}
367 }
368
369 record.state = PeerMigrationState::Failed(reason.to_string());
370 record.completed_at = Some(current_timestamp_ms());
371
372 self.stats.failed = self.stats.failed.saturating_add(1);
373
374 if let Some(rec) = self.active_migrations.remove(&migration_id) {
376 self.completed_migrations.push(rec);
377 }
378 self.items.remove(&migration_id);
379
380 Ok(())
381 }
382
383 pub fn get_migration(&self, migration_id: u64) -> Option<&PeerMigrationRecord> {
387 self.active_migrations.get(&migration_id).or_else(|| {
388 self.completed_migrations
389 .iter()
390 .find(|r| r.migration_id == migration_id)
391 })
392 }
393
394 pub fn progress(&self, migration_id: u64) -> Option<f64> {
398 self.get_migration(migration_id).map(|record| {
399 if record.items_total == 0 {
400 return 0.0;
401 }
402 let ratio = record.items_transferred as f64 / record.items_total as f64;
403 ratio.clamp(0.0, 1.0)
404 })
405 }
406
407 pub fn active_count(&self) -> usize {
409 self.active_migrations.len()
410 }
411
412 pub fn cleanup_completed(&mut self, before: u64) -> usize {
416 let initial_len = self.completed_migrations.len();
417 self.completed_migrations
418 .retain(|rec| rec.completed_at.is_none_or(|ts| ts >= before));
419 initial_len - self.completed_migrations.len()
420 }
421
422 pub fn stats(&self) -> &PeerMigrationStats {
424 &self.stats
425 }
426
427 pub fn migrations_for_peer(&self, peer: &str) -> Vec<&PeerMigrationRecord> {
430 let mut results: Vec<&PeerMigrationRecord> = Vec::new();
431
432 for record in self.active_migrations.values() {
433 if record.source_peer == peer || record.target_peer == peer {
434 results.push(record);
435 }
436 }
437
438 for record in &self.completed_migrations {
439 if record.source_peer == peer || record.target_peer == peer {
440 results.push(record);
441 }
442 }
443
444 results
445 }
446
447 fn finalize_migration(&mut self, migration_id: u64) {
449 let now = current_timestamp_ms();
450
451 if let Some(record) = self.active_migrations.get_mut(&migration_id) {
452 record.completed_at = Some(now);
453 }
454
455 if let Some(rec) = self.active_migrations.remove(&migration_id) {
456 let duration = now.saturating_sub(rec.started_at);
457 self.stats.successful = self.stats.successful.saturating_add(1);
458 self.stats.bytes_migrated = self
459 .stats
460 .bytes_migrated
461 .saturating_add(rec.bytes_transferred);
462
463 let completed_count = self.stats.successful as f64;
465 if completed_count > 0.0 {
466 self.stats.avg_duration_ms = self.stats.avg_duration_ms
467 + (duration as f64 - self.stats.avg_duration_ms) / completed_count;
468 }
469
470 self.completed_migrations.push(rec);
471 }
472
473 self.items.remove(&migration_id);
474 }
475
476 pub fn config(&self) -> &PeerMigrationConfig {
478 &self.config
479 }
480
481 pub fn get_items(&self, migration_id: u64) -> Option<&[MigrationItem]> {
483 self.items.get(&migration_id).map(|v| v.as_slice())
484 }
485}
486
487fn compute_items_checksum(items: &[MigrationItem]) -> u64 {
491 let mut checksum: u64 = 0;
492 for item in items {
493 let mut item_hash: u64 = item.sequence;
494 for (i, b) in item.key.bytes().enumerate() {
495 item_hash = item_hash.wrapping_add((b as u64).wrapping_mul((i as u64).wrapping_add(1)));
496 }
497 for (i, b) in item.data.iter().enumerate() {
498 item_hash =
499 item_hash.wrapping_add((*b as u64).wrapping_mul((i as u64).wrapping_add(1)));
500 }
501 checksum = checksum.wrapping_add(item_hash);
502 }
503 checksum
504}
505
506fn current_timestamp_ms() -> u64 {
510 std::time::SystemTime::now()
511 .duration_since(std::time::UNIX_EPOCH)
512 .map(|d| d.as_millis() as u64)
513 .unwrap_or(0)
514}
515
516#[cfg(test)]
517mod tests {
518 use super::*;
519
520 fn default_config() -> PeerMigrationConfig {
521 PeerMigrationConfig::default()
522 }
523
524 fn make_items(count: usize) -> Vec<MigrationItem> {
525 (0..count)
526 .map(|i| MigrationItem {
527 key: format!("key-{}", i),
528 data: vec![i as u8; 32],
529 sequence: i as u64,
530 })
531 .collect()
532 }
533
534 #[test]
537 fn test_start_migration() {
538 let mut mgr = PeerMigrationManager::new(default_config());
539 let id = mgr
540 .start_migration("peer-a", "peer-b", 10)
541 .expect("should start");
542 assert_eq!(id, 1);
543 assert_eq!(mgr.active_count(), 1);
544 }
545
546 #[test]
547 fn test_full_lifecycle() {
548 let mut mgr = PeerMigrationManager::new(default_config());
549 let id = mgr.start_migration("peer-a", "peer-b", 3).expect("start");
550
551 let state = mgr.advance_state(id).expect("advance to transferring");
553 assert_eq!(state, PeerMigrationState::Transferring);
554
555 mgr.add_items(id, make_items(3)).expect("add items");
557
558 let state = mgr.advance_state(id).expect("advance to verifying");
560 assert_eq!(state, PeerMigrationState::Verifying);
561
562 let state = mgr.advance_state(id).expect("advance to completed");
564 assert_eq!(state, PeerMigrationState::Completed);
565
566 assert_eq!(mgr.active_count(), 0);
567 assert_eq!(mgr.stats().successful, 1);
568 }
569
570 #[test]
571 fn test_complete_with_checksum() {
572 let mut mgr = PeerMigrationManager::new(default_config());
573 let id = mgr.start_migration("peer-a", "peer-b", 2).expect("start");
574
575 mgr.advance_state(id).expect("to transferring");
576 let items = make_items(2);
577 let expected_checksum = compute_items_checksum(&items);
578 mgr.add_items(id, items).expect("add");
579 mgr.advance_state(id).expect("to verifying");
580
581 mgr.complete_migration(id, expected_checksum)
582 .expect("complete");
583
584 let rec = mgr.get_migration(id).expect("should find");
585 assert_eq!(rec.state, PeerMigrationState::Completed);
586 }
587
588 #[test]
589 fn test_checksum_mismatch() {
590 let mut mgr = PeerMigrationManager::new(default_config());
591 let id = mgr.start_migration("peer-a", "peer-b", 1).expect("start");
592 mgr.advance_state(id).expect("to transferring");
593 mgr.add_items(id, make_items(1)).expect("add");
594 mgr.advance_state(id).expect("to verifying");
595
596 let result = mgr.complete_migration(id, 999_999);
597 assert!(result.is_err());
598 assert!(result
599 .expect_err("should fail")
600 .contains("checksum mismatch"));
601 }
602
603 #[test]
606 fn test_advance_from_idle() {
607 let mut mgr = PeerMigrationManager::new(default_config());
609 let id = mgr.start_migration("a", "b", 1).expect("start");
610
611 mgr.active_migrations.get_mut(&id).expect("record").state = PeerMigrationState::Idle;
613 let state = mgr.advance_state(id).expect("advance");
614 assert_eq!(state, PeerMigrationState::Preparing);
615 }
616
617 #[test]
618 fn test_advance_completed_fails() {
619 let mut mgr = PeerMigrationManager::new(default_config());
620 let id = mgr.start_migration("a", "b", 1).expect("start");
621 mgr.advance_state(id).expect("to transferring");
622 mgr.add_items(id, make_items(1)).expect("add");
623 mgr.advance_state(id).expect("to verifying");
624 mgr.advance_state(id).expect("to completed");
625
626 let result = mgr.advance_state(id);
628 assert!(result.is_err());
629 }
630
631 #[test]
632 fn test_advance_failed_fails() {
633 let mut mgr = PeerMigrationManager::new(default_config());
634 let id = mgr.start_migration("a", "b", 1).expect("start");
635 mgr.advance_state(id).expect("to transferring");
636
637 mgr.active_migrations.get_mut(&id).expect("r").state =
639 PeerMigrationState::Failed("test".to_string());
640
641 let result = mgr.advance_state(id);
642 assert!(result.is_err());
643 }
644
645 #[test]
646 fn test_verification_fails_if_incomplete() {
647 let mut mgr = PeerMigrationManager::new(default_config());
648 let id = mgr.start_migration("a", "b", 5).expect("start");
649 mgr.advance_state(id).expect("to transferring");
650 mgr.add_items(id, make_items(2)).expect("add partial");
651 mgr.advance_state(id).expect("to verifying");
652
653 let result = mgr.advance_state(id);
655 assert!(result.is_err());
656 assert!(result.expect_err("err").contains("verification failed"));
657 }
658
659 #[test]
660 fn test_verification_skipped_when_disabled() {
661 let config = PeerMigrationConfig {
662 verify_after_transfer: false,
663 ..default_config()
664 };
665 let mut mgr = PeerMigrationManager::new(config);
666 let id = mgr.start_migration("a", "b", 10).expect("start");
667 mgr.advance_state(id).expect("to transferring");
668 mgr.add_items(id, make_items(2)).expect("add partial");
669 mgr.advance_state(id).expect("to verifying");
670
671 let state = mgr.advance_state(id).expect("to completed");
673 assert_eq!(state, PeerMigrationState::Completed);
674 }
675
676 #[test]
679 fn test_concurrent_limit() {
680 let config = PeerMigrationConfig {
681 max_concurrent: 2,
682 ..default_config()
683 };
684 let mut mgr = PeerMigrationManager::new(config);
685
686 mgr.start_migration("a", "b", 1).expect("first");
687 mgr.start_migration("c", "d", 1).expect("second");
688
689 let result = mgr.start_migration("e", "f", 1);
690 assert!(result.is_err());
691 assert!(result.expect_err("err").contains("limit reached"));
692 }
693
694 #[test]
695 fn test_concurrent_limit_freed_after_completion() {
696 let config = PeerMigrationConfig {
697 max_concurrent: 1,
698 verify_after_transfer: false,
699 ..default_config()
700 };
701 let mut mgr = PeerMigrationManager::new(config);
702
703 let id1 = mgr.start_migration("a", "b", 1).expect("first");
704
705 assert!(mgr.start_migration("c", "d", 1).is_err());
707
708 mgr.advance_state(id1).expect("to transferring");
710 mgr.advance_state(id1).expect("to verifying");
711 mgr.advance_state(id1).expect("to completed");
712
713 mgr.start_migration("c", "d", 1)
715 .expect("second should work");
716 }
717
718 #[test]
721 fn test_progress_zero() {
722 let mut mgr = PeerMigrationManager::new(default_config());
723 let id = mgr.start_migration("a", "b", 10).expect("start");
724 assert_eq!(mgr.progress(id), Some(0.0));
725 }
726
727 #[test]
728 fn test_progress_partial() {
729 let mut mgr = PeerMigrationManager::new(default_config());
730 let id = mgr.start_migration("a", "b", 4).expect("start");
731 mgr.advance_state(id).expect("to transferring");
732 mgr.add_items(id, make_items(2)).expect("add");
733
734 let p = mgr.progress(id).expect("progress");
735 assert!((p - 0.5).abs() < 1e-9);
736 }
737
738 #[test]
739 fn test_progress_complete() {
740 let mut mgr = PeerMigrationManager::new(default_config());
741 let id = mgr.start_migration("a", "b", 3).expect("start");
742 mgr.advance_state(id).expect("to transferring");
743 mgr.add_items(id, make_items(3)).expect("add all");
744
745 let p = mgr.progress(id).expect("progress");
746 assert!((p - 1.0).abs() < 1e-9);
747 }
748
749 #[test]
750 fn test_progress_nonexistent() {
751 let mgr = PeerMigrationManager::new(default_config());
752 assert_eq!(mgr.progress(999), None);
753 }
754
755 #[test]
758 fn test_fail_migration() {
759 let mut mgr = PeerMigrationManager::new(default_config());
760 let id = mgr.start_migration("a", "b", 5).expect("start");
761 mgr.advance_state(id).expect("to transferring");
762
763 mgr.fail_migration(id, "network timeout").expect("fail");
764
765 let rec = mgr.get_migration(id).expect("find");
766 assert_eq!(
767 rec.state,
768 PeerMigrationState::Failed("network timeout".to_string())
769 );
770 assert_eq!(mgr.active_count(), 0);
771 assert_eq!(mgr.stats().failed, 1);
772 }
773
774 #[test]
775 fn test_fail_already_completed() {
776 let config = PeerMigrationConfig {
777 verify_after_transfer: false,
778 ..default_config()
779 };
780 let mut mgr = PeerMigrationManager::new(config);
781 let id = mgr.start_migration("a", "b", 1).expect("start");
782 mgr.advance_state(id).expect("to transferring");
783 mgr.advance_state(id).expect("to verifying");
784 mgr.advance_state(id).expect("to completed");
785
786 let result = mgr.fail_migration(id, "too late");
788 assert!(result.is_err());
789 }
790
791 #[test]
792 fn test_fail_already_failed() {
793 let mut mgr = PeerMigrationManager::new(default_config());
794 let id = mgr.start_migration("a", "b", 1).expect("start");
795 mgr.fail_migration(id, "first").expect("fail once");
796
797 let result = mgr.fail_migration(id, "second");
798 assert!(result.is_err());
799 }
800
801 #[test]
804 fn test_cleanup_completed() {
805 let config = PeerMigrationConfig {
806 verify_after_transfer: false,
807 ..default_config()
808 };
809 let mut mgr = PeerMigrationManager::new(config);
810
811 let id1 = mgr.start_migration("a", "b", 1).expect("m1");
813 mgr.advance_state(id1).expect("t");
814 mgr.advance_state(id1).expect("v");
815 mgr.advance_state(id1).expect("c");
816
817 let id2 = mgr.start_migration("c", "d", 1).expect("m2");
818 mgr.advance_state(id2).expect("t");
819 mgr.advance_state(id2).expect("v");
820 mgr.advance_state(id2).expect("c");
821
822 let removed = mgr.cleanup_completed(u64::MAX);
824 assert_eq!(removed, 2);
825 assert!(mgr.completed_migrations.is_empty());
826 }
827
828 #[test]
829 fn test_cleanup_none_removed() {
830 let mut mgr = PeerMigrationManager::new(default_config());
831 let removed = mgr.cleanup_completed(0);
832 assert_eq!(removed, 0);
833 }
834
835 #[test]
838 fn test_stats_initial() {
839 let mgr = PeerMigrationManager::new(default_config());
840 let s = mgr.stats();
841 assert_eq!(s.total_migrations, 0);
842 assert_eq!(s.successful, 0);
843 assert_eq!(s.failed, 0);
844 assert_eq!(s.bytes_migrated, 0);
845 assert!((s.avg_duration_ms - 0.0).abs() < 1e-9);
846 }
847
848 #[test]
849 fn test_stats_after_success() {
850 let config = PeerMigrationConfig {
851 verify_after_transfer: false,
852 ..default_config()
853 };
854 let mut mgr = PeerMigrationManager::new(config);
855 let id = mgr.start_migration("a", "b", 2).expect("start");
856 mgr.advance_state(id).expect("t");
857 mgr.add_items(id, make_items(2)).expect("add");
858 mgr.advance_state(id).expect("v");
859 mgr.advance_state(id).expect("c");
860
861 let s = mgr.stats();
862 assert_eq!(s.total_migrations, 1);
863 assert_eq!(s.successful, 1);
864 assert!(s.bytes_migrated > 0);
865 }
866
867 #[test]
868 fn test_stats_after_failure() {
869 let mut mgr = PeerMigrationManager::new(default_config());
870 let id = mgr.start_migration("a", "b", 1).expect("start");
871 mgr.fail_migration(id, "oops").expect("fail");
872
873 let s = mgr.stats();
874 assert_eq!(s.total_migrations, 1);
875 assert_eq!(s.failed, 1);
876 assert_eq!(s.successful, 0);
877 }
878
879 #[test]
882 fn test_add_items_wrong_state() {
883 let mut mgr = PeerMigrationManager::new(default_config());
884 let id = mgr.start_migration("a", "b", 1).expect("start");
885
886 let result = mgr.add_items(id, make_items(1));
888 assert!(result.is_err());
889 }
890
891 #[test]
892 fn test_add_items_exceeds_total() {
893 let mut mgr = PeerMigrationManager::new(default_config());
894 let id = mgr.start_migration("a", "b", 2).expect("start");
895 mgr.advance_state(id).expect("to transferring");
896
897 let result = mgr.add_items(id, make_items(3));
898 assert!(result.is_err());
899 assert!(result.expect_err("err").contains("exceed total"));
900 }
901
902 #[test]
903 fn test_add_items_bytes_tracked() {
904 let mut mgr = PeerMigrationManager::new(default_config());
905 let id = mgr.start_migration("a", "b", 5).expect("start");
906 mgr.advance_state(id).expect("to transferring");
907
908 let items = vec![
909 MigrationItem {
910 key: "k1".to_string(),
911 data: vec![0u8; 100],
912 sequence: 0,
913 },
914 MigrationItem {
915 key: "k2".to_string(),
916 data: vec![0u8; 200],
917 sequence: 1,
918 },
919 ];
920 mgr.add_items(id, items).expect("add");
921
922 let rec = mgr.get_migration(id).expect("find");
923 assert_eq!(rec.items_transferred, 2);
924 assert_eq!(rec.bytes_transferred, 300);
925 }
926
927 #[test]
928 fn test_add_items_nonexistent_migration() {
929 let mut mgr = PeerMigrationManager::new(default_config());
930 let result = mgr.add_items(999, make_items(1));
931 assert!(result.is_err());
932 }
933
934 #[test]
937 fn test_migrations_for_peer_source() {
938 let mut mgr = PeerMigrationManager::new(default_config());
939 mgr.start_migration("peer-x", "peer-y", 1).expect("m1");
940 mgr.start_migration("peer-z", "peer-w", 1).expect("m2");
941
942 let results = mgr.migrations_for_peer("peer-x");
943 assert_eq!(results.len(), 1);
944 assert_eq!(results[0].source_peer, "peer-x");
945 }
946
947 #[test]
948 fn test_migrations_for_peer_target() {
949 let mut mgr = PeerMigrationManager::new(default_config());
950 mgr.start_migration("peer-a", "peer-b", 1).expect("m1");
951
952 let results = mgr.migrations_for_peer("peer-b");
953 assert_eq!(results.len(), 1);
954 assert_eq!(results[0].target_peer, "peer-b");
955 }
956
957 #[test]
958 fn test_migrations_for_peer_empty() {
959 let mgr = PeerMigrationManager::new(default_config());
960 let results = mgr.migrations_for_peer("nobody");
961 assert!(results.is_empty());
962 }
963
964 #[test]
967 fn test_start_empty_source() {
968 let mut mgr = PeerMigrationManager::new(default_config());
969 let result = mgr.start_migration("", "b", 1);
970 assert!(result.is_err());
971 }
972
973 #[test]
974 fn test_start_empty_target() {
975 let mut mgr = PeerMigrationManager::new(default_config());
976 let result = mgr.start_migration("a", "", 1);
977 assert!(result.is_err());
978 }
979
980 #[test]
981 fn test_start_same_source_target() {
982 let mut mgr = PeerMigrationManager::new(default_config());
983 let result = mgr.start_migration("a", "a", 1);
984 assert!(result.is_err());
985 }
986
987 #[test]
988 fn test_start_zero_items() {
989 let mut mgr = PeerMigrationManager::new(default_config());
990 let result = mgr.start_migration("a", "b", 0);
991 assert!(result.is_err());
992 }
993
994 #[test]
995 fn test_complete_wrong_state() {
996 let mut mgr = PeerMigrationManager::new(default_config());
997 let id = mgr.start_migration("a", "b", 1).expect("start");
998
999 let result = mgr.complete_migration(id, 0);
1001 assert!(result.is_err());
1002 }
1003
1004 #[test]
1005 fn test_complete_nonexistent() {
1006 let mut mgr = PeerMigrationManager::new(default_config());
1007 let result = mgr.complete_migration(999, 0);
1008 assert!(result.is_err());
1009 }
1010
1011 #[test]
1014 fn test_config_access() {
1015 let config = PeerMigrationConfig {
1016 max_concurrent: 8,
1017 chunk_size: 512,
1018 timeout_ms: 60_000,
1019 verify_after_transfer: false,
1020 retry_count: 5,
1021 };
1022 let mgr = PeerMigrationManager::new(config);
1023 assert_eq!(mgr.config().max_concurrent, 8);
1024 assert_eq!(mgr.config().chunk_size, 512);
1025 }
1026
1027 #[test]
1028 fn test_default_config() {
1029 let config = PeerMigrationConfig::default();
1030 assert_eq!(config.max_concurrent, 4);
1031 assert!(config.verify_after_transfer);
1032 }
1033
1034 #[test]
1035 fn test_state_display() {
1036 assert_eq!(format!("{}", PeerMigrationState::Idle), "Idle");
1037 assert_eq!(format!("{}", PeerMigrationState::Preparing), "Preparing");
1038 assert_eq!(
1039 format!("{}", PeerMigrationState::Transferring),
1040 "Transferring"
1041 );
1042 assert_eq!(format!("{}", PeerMigrationState::Verifying), "Verifying");
1043 assert_eq!(format!("{}", PeerMigrationState::Completed), "Completed");
1044 assert_eq!(
1045 format!("{}", PeerMigrationState::Failed("err".to_string())),
1046 "Failed(err)"
1047 );
1048 }
1049
1050 #[test]
1051 fn test_migration_id_increments() {
1052 let mut mgr = PeerMigrationManager::new(default_config());
1053 let id1 = mgr.start_migration("a", "b", 1).expect("m1");
1054 let id2 = mgr.start_migration("c", "d", 1).expect("m2");
1055 assert_eq!(id2, id1 + 1);
1056 }
1057
1058 #[test]
1059 fn test_get_items() {
1060 let mut mgr = PeerMigrationManager::new(default_config());
1061 let id = mgr.start_migration("a", "b", 3).expect("start");
1062 mgr.advance_state(id).expect("to transferring");
1063
1064 let items = make_items(2);
1065 mgr.add_items(id, items).expect("add");
1066
1067 let stored = mgr.get_items(id).expect("get");
1068 assert_eq!(stored.len(), 2);
1069 assert_eq!(stored[0].key, "key-0");
1070 assert_eq!(stored[1].key, "key-1");
1071 }
1072
1073 #[test]
1074 fn test_checksum_computation_deterministic() {
1075 let items = make_items(5);
1076 let c1 = compute_items_checksum(&items);
1077 let c2 = compute_items_checksum(&items);
1078 assert_eq!(c1, c2);
1079 }
1080
1081 #[test]
1082 fn test_checksum_different_for_different_items() {
1083 let items_a = make_items(3);
1084 let items_b = vec![MigrationItem {
1085 key: "different".to_string(),
1086 data: vec![255; 64],
1087 sequence: 99,
1088 }];
1089 let c1 = compute_items_checksum(&items_a);
1090 let c2 = compute_items_checksum(&items_b);
1091 assert_ne!(c1, c2);
1092 }
1093}