1use std::collections::HashMap;
6
7use serde::{Deserialize, Serialize};
8
9use super::manifest::ConfigProvenance;
10
11pub const MAGIC_BYTES: &[u8; 13] = b"SQRY_GRAPH_V7";
27
28pub const VERSION: u32 = 7;
32
33pub const MAGIC_BYTES_V7: &[u8; 13] = b"SQRY_GRAPH_V7";
38
39pub const MAGIC_BYTES_V8: &[u8; 13] = b"SQRY_GRAPH_V8";
45
46pub const MAGIC_BYTES_V9: &[u8; 13] = b"SQRY_GRAPH_V9";
53
54pub const MAGIC_BYTES_V10: &[u8; 14] = b"SQRY_GRAPH_V10";
61
62pub const MAGIC_BYTES_V11: &[u8; 14] = b"SQRY_GRAPH_V11";
77
78pub const MAGIC_BYTES_V12: &[u8; 14] = b"SQRY_GRAPH_V12";
100
101pub const MAGIC_BYTES_V13: &[u8; 14] = b"SQRY_GRAPH_V13";
110
111pub const MAGIC_BYTES_V14: &[u8; 14] = b"SQRY_GRAPH_V14";
124
125pub const MAGIC_BYTES_V15: &[u8; 14] = b"SQRY_GRAPH_V15";
138
139pub const MAGIC_BYTES_V16: &[u8; 14] = b"SQRY_GRAPH_V16";
152
153pub const MAGIC_BYTES_V17: &[u8; 14] = b"SQRY_GRAPH_V17";
168
169pub const LEGACY_VERSION_V7: u32 = 7;
172
173#[repr(u32)]
180#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
181pub enum FormatVersion {
182 V7 = 7,
184 V8 = 8,
186 V9 = 9,
190 V10 = 10,
194 V11 = 11,
200 V12 = 12,
204 V13 = 13,
209 V14 = 14,
218 V15 = 15,
227 V16 = 16,
237 V17 = 17,
249}
250
251impl FormatVersion {
252 #[must_use]
254 pub const fn magic(self) -> &'static [u8] {
255 match self {
256 Self::V7 => MAGIC_BYTES_V7.as_slice(),
257 Self::V8 => MAGIC_BYTES_V8.as_slice(),
258 Self::V9 => MAGIC_BYTES_V9.as_slice(),
259 Self::V10 => MAGIC_BYTES_V10.as_slice(),
260 Self::V11 => MAGIC_BYTES_V11.as_slice(),
261 Self::V12 => MAGIC_BYTES_V12.as_slice(),
262 Self::V13 => MAGIC_BYTES_V13.as_slice(),
263 Self::V14 => MAGIC_BYTES_V14.as_slice(),
264 Self::V15 => MAGIC_BYTES_V15.as_slice(),
265 Self::V16 => MAGIC_BYTES_V16.as_slice(),
266 Self::V17 => MAGIC_BYTES_V17.as_slice(),
267 }
268 }
269
270 #[must_use]
272 pub const fn as_u32(self) -> u32 {
273 self as u32
274 }
275
276 #[must_use]
280 pub fn from_magic(bytes: &[u8]) -> Option<Self> {
281 if bytes.len() >= MAGIC_BYTES_V17.len()
285 && bytes[..MAGIC_BYTES_V17.len()] == *MAGIC_BYTES_V17
286 {
287 return Some(Self::V17);
288 }
289 if bytes.len() >= MAGIC_BYTES_V16.len()
290 && bytes[..MAGIC_BYTES_V16.len()] == *MAGIC_BYTES_V16
291 {
292 return Some(Self::V16);
293 }
294 if bytes.len() >= MAGIC_BYTES_V15.len()
295 && bytes[..MAGIC_BYTES_V15.len()] == *MAGIC_BYTES_V15
296 {
297 return Some(Self::V15);
298 }
299 if bytes.len() >= MAGIC_BYTES_V14.len()
300 && bytes[..MAGIC_BYTES_V14.len()] == *MAGIC_BYTES_V14
301 {
302 return Some(Self::V14);
303 }
304 if bytes.len() >= MAGIC_BYTES_V13.len()
305 && bytes[..MAGIC_BYTES_V13.len()] == *MAGIC_BYTES_V13
306 {
307 return Some(Self::V13);
308 }
309 if bytes.len() >= MAGIC_BYTES_V12.len()
310 && bytes[..MAGIC_BYTES_V12.len()] == *MAGIC_BYTES_V12
311 {
312 return Some(Self::V12);
313 }
314 if bytes.len() >= MAGIC_BYTES_V11.len()
315 && bytes[..MAGIC_BYTES_V11.len()] == *MAGIC_BYTES_V11
316 {
317 return Some(Self::V11);
318 }
319 if bytes.len() >= MAGIC_BYTES_V10.len()
320 && bytes[..MAGIC_BYTES_V10.len()] == *MAGIC_BYTES_V10
321 {
322 return Some(Self::V10);
323 }
324 if bytes.len() < MAGIC_BYTES_V7.len() {
325 return None;
326 }
327 let prefix = &bytes[..MAGIC_BYTES_V7.len()];
328 if prefix == MAGIC_BYTES_V7 {
329 Some(Self::V7)
330 } else if prefix == MAGIC_BYTES_V8 {
331 Some(Self::V8)
332 } else if prefix == MAGIC_BYTES_V9 {
333 Some(Self::V9)
334 } else {
335 None
336 }
337 }
338}
339
340pub const CURRENT_VERSION: FormatVersion = FormatVersion::V17;
342
343#[derive(Debug, Clone, Serialize, Deserialize)]
348pub struct GraphHeader {
349 pub version: u32,
351
352 pub node_count: usize,
354
355 pub edge_count: usize,
357
358 pub string_count: usize,
360
361 pub file_count: usize,
363
364 pub timestamp: u64,
366
367 #[serde(default)]
369 pub config_provenance: Option<ConfigProvenance>,
370
371 #[serde(default)]
376 pub plugin_versions: HashMap<String, String>,
377
378 #[serde(default)]
392 pub fact_epoch: u64,
393}
394
395impl GraphHeader {
396 #[must_use]
398 pub fn new(
399 node_count: usize,
400 edge_count: usize,
401 string_count: usize,
402 file_count: usize,
403 ) -> Self {
404 Self {
405 version: VERSION,
406 node_count,
407 edge_count,
408 string_count,
409 file_count,
410 timestamp: std::time::SystemTime::now()
411 .duration_since(std::time::UNIX_EPOCH)
412 .unwrap_or_default()
413 .as_secs(),
414 config_provenance: None,
415 plugin_versions: HashMap::new(),
416 fact_epoch: 0,
417 }
418 }
419
420 #[must_use]
422 pub fn with_provenance(
423 node_count: usize,
424 edge_count: usize,
425 string_count: usize,
426 file_count: usize,
427 provenance: ConfigProvenance,
428 ) -> Self {
429 Self {
430 version: VERSION,
431 node_count,
432 edge_count,
433 string_count,
434 file_count,
435 timestamp: std::time::SystemTime::now()
436 .duration_since(std::time::UNIX_EPOCH)
437 .unwrap_or_default()
438 .as_secs(),
439 config_provenance: Some(provenance),
440 plugin_versions: HashMap::new(),
441 fact_epoch: 0,
442 }
443 }
444
445 #[must_use]
447 pub fn with_provenance_and_plugins(
448 node_count: usize,
449 edge_count: usize,
450 string_count: usize,
451 file_count: usize,
452 provenance: ConfigProvenance,
453 plugin_versions: HashMap<String, String>,
454 ) -> Self {
455 Self {
456 version: VERSION,
457 node_count,
458 edge_count,
459 string_count,
460 file_count,
461 timestamp: std::time::SystemTime::now()
462 .duration_since(std::time::UNIX_EPOCH)
463 .unwrap_or_default()
464 .as_secs(),
465 config_provenance: Some(provenance),
466 plugin_versions,
467 fact_epoch: 0,
468 }
469 }
470
471 #[must_use]
473 pub fn provenance(&self) -> Option<&ConfigProvenance> {
474 self.config_provenance.as_ref()
475 }
476
477 #[must_use]
479 pub fn has_provenance(&self) -> bool {
480 self.config_provenance.is_some()
481 }
482
483 #[must_use]
485 pub fn plugin_versions(&self) -> &HashMap<String, String> {
486 &self.plugin_versions
487 }
488
489 pub fn set_plugin_versions(&mut self, versions: HashMap<String, String>) {
491 self.plugin_versions = versions;
492 }
493
494 #[must_use]
501 pub fn fact_epoch(&self) -> u64 {
502 self.fact_epoch
503 }
504
505 pub fn set_fact_epoch(&mut self, epoch: u64) {
511 self.fact_epoch = epoch;
512 }
513}
514
515#[cfg(test)]
516mod tests {
517 use super::*;
518 use std::collections::HashMap;
519 use std::path::PathBuf;
520
521 fn make_test_provenance() -> ConfigProvenance {
522 ConfigProvenance {
523 config_file: PathBuf::from(".sqry/graph/config/config.json"),
524 config_checksum: "abc123def456".to_string(),
525 schema_version: 1,
526 overrides: HashMap::new(),
527 build_timestamp: std::time::SystemTime::now()
528 .duration_since(std::time::UNIX_EPOCH)
529 .unwrap_or_default()
530 .as_secs(),
531 build_host: Some("test-host".to_string()),
532 }
533 }
534
535 #[test]
536 fn test_magic_bytes() {
537 assert_eq!(MAGIC_BYTES, b"SQRY_GRAPH_V7");
538 assert_eq!(MAGIC_BYTES.len(), 13);
539 }
540
541 #[test]
542 fn test_version() {
543 assert_eq!(VERSION, 7);
544 }
545
546 #[test]
547 fn test_graph_header_new() {
548 let header = GraphHeader::new(100, 50, 200, 10);
549
550 assert_eq!(header.version, VERSION);
551 assert_eq!(header.node_count, 100);
552 assert_eq!(header.edge_count, 50);
553 assert_eq!(header.string_count, 200);
554 assert_eq!(header.file_count, 10);
555 assert!(header.timestamp > 0);
556 assert!(header.config_provenance.is_none());
557 }
558
559 #[test]
560 fn test_graph_header_with_provenance() {
561 let provenance = make_test_provenance();
562 let header = GraphHeader::with_provenance(100, 50, 200, 10, provenance);
563
564 assert_eq!(header.version, VERSION);
565 assert_eq!(header.node_count, 100);
566 assert_eq!(header.edge_count, 50);
567 assert!(header.config_provenance.is_some());
568 assert_eq!(
569 header.config_provenance.as_ref().unwrap().config_checksum,
570 "abc123def456"
571 );
572 }
573
574 #[test]
575 fn test_graph_header_provenance_method() {
576 let header = GraphHeader::new(10, 5, 20, 2);
577 assert!(header.provenance().is_none());
578
579 let provenance = make_test_provenance();
580 let header_with = GraphHeader::with_provenance(10, 5, 20, 2, provenance);
581 assert!(header_with.provenance().is_some());
582 assert_eq!(
583 header_with.provenance().unwrap().config_checksum,
584 "abc123def456"
585 );
586 }
587
588 #[test]
589 fn test_graph_header_has_provenance() {
590 let header = GraphHeader::new(10, 5, 20, 2);
591 assert!(!header.has_provenance());
592
593 let provenance = make_test_provenance();
594 let header_with = GraphHeader::with_provenance(10, 5, 20, 2, provenance);
595 assert!(header_with.has_provenance());
596 }
597
598 #[test]
599 fn test_graph_header_clone() {
600 let header = GraphHeader::new(100, 50, 200, 10);
601 let cloned = header.clone();
602
603 assert_eq!(header.version, cloned.version);
604 assert_eq!(header.node_count, cloned.node_count);
605 assert_eq!(header.edge_count, cloned.edge_count);
606 assert_eq!(header.string_count, cloned.string_count);
607 assert_eq!(header.file_count, cloned.file_count);
608 }
609
610 #[test]
611 fn test_graph_header_debug() {
612 let header = GraphHeader::new(100, 50, 200, 10);
613 let debug_str = format!("{header:?}");
614
615 assert!(debug_str.contains("GraphHeader"));
616 assert!(debug_str.contains("version"));
617 assert!(debug_str.contains("node_count"));
618 }
619
620 #[test]
621 fn test_graph_header_timestamp_is_recent() {
622 let header = GraphHeader::new(10, 5, 20, 2);
623 let now = std::time::SystemTime::now()
624 .duration_since(std::time::UNIX_EPOCH)
625 .unwrap()
626 .as_secs();
627
628 assert!(header.timestamp <= now);
630 assert!(header.timestamp >= now - 1);
631 }
632
633 #[test]
634 fn test_graph_header_zero_counts() {
635 let header = GraphHeader::new(0, 0, 0, 0);
636
637 assert_eq!(header.node_count, 0);
638 assert_eq!(header.edge_count, 0);
639 assert_eq!(header.string_count, 0);
640 assert_eq!(header.file_count, 0);
641 }
642
643 #[test]
644 fn test_graph_header_large_counts() {
645 let header = GraphHeader::new(1_000_000, 5_000_000, 10_000_000, 100_000);
646
647 assert_eq!(header.node_count, 1_000_000);
648 assert_eq!(header.edge_count, 5_000_000);
649 assert_eq!(header.string_count, 10_000_000);
650 assert_eq!(header.file_count, 100_000);
651 }
652
653 #[test]
654 fn test_graph_header_plugin_versions_empty_by_default() {
655 let header = GraphHeader::new(10, 5, 20, 2);
656 assert!(header.plugin_versions().is_empty());
657 }
658
659 #[test]
660 fn test_graph_header_set_plugin_versions() {
661 let mut header = GraphHeader::new(10, 5, 20, 2);
662
663 let mut versions = HashMap::new();
664 versions.insert("rust".to_string(), "3.3.0".to_string());
665 versions.insert("javascript".to_string(), "3.3.0".to_string());
666
667 header.set_plugin_versions(versions.clone());
668
669 assert_eq!(header.plugin_versions().len(), 2);
670 assert_eq!(
671 header.plugin_versions().get("rust"),
672 Some(&"3.3.0".to_string())
673 );
674 assert_eq!(
675 header.plugin_versions().get("javascript"),
676 Some(&"3.3.0".to_string())
677 );
678 }
679
680 #[test]
685 fn phase1_graph_header_new_defaults_fact_epoch_to_zero() {
686 let header = GraphHeader::new(10, 5, 20, 2);
687 assert_eq!(header.fact_epoch, 0);
688 assert_eq!(header.fact_epoch(), 0);
689 }
690
691 #[test]
692 fn phase1_graph_header_with_provenance_defaults_fact_epoch_to_zero() {
693 let header = GraphHeader::with_provenance(10, 5, 20, 2, make_test_provenance());
694 assert_eq!(header.fact_epoch, 0);
695 }
696
697 #[test]
698 fn phase1_graph_header_set_fact_epoch_round_trip() {
699 let mut header = GraphHeader::new(10, 5, 20, 2);
700 header.set_fact_epoch(42);
701 assert_eq!(header.fact_epoch(), 42);
702 }
703
704 #[test]
705 fn phase1_graph_header_postcard_round_trip_with_fact_epoch() {
706 let mut header = GraphHeader::new(100, 50, 200, 10);
707 header.set_fact_epoch(1_234_567);
708
709 let encoded = postcard::to_allocvec(&header).expect("encode");
710 let decoded: GraphHeader = postcard::from_bytes(&encoded).expect("decode");
711
712 assert_eq!(decoded.fact_epoch(), 1_234_567);
713 assert_eq!(decoded.node_count, 100);
714 assert_eq!(decoded.edge_count, 50);
715 }
716
717 #[test]
718 fn phase1_graph_header_fact_epoch_preserved_through_clone() {
719 let mut header = GraphHeader::new(10, 5, 20, 2);
720 header.set_fact_epoch(9_999);
721 let cloned = header.clone();
722 assert_eq!(cloned.fact_epoch(), 9_999);
723 }
724
725 #[test]
730 fn phase1_magic_bytes_v7_matches_legacy() {
731 assert_eq!(MAGIC_BYTES_V7, b"SQRY_GRAPH_V7");
732 assert_eq!(MAGIC_BYTES_V7, MAGIC_BYTES);
733 assert_eq!(MAGIC_BYTES_V7.len(), 13);
734 }
735
736 #[test]
737 fn phase1_magic_bytes_v8_is_distinct_and_13_bytes() {
738 assert_eq!(MAGIC_BYTES_V8, b"SQRY_GRAPH_V8");
739 assert_eq!(MAGIC_BYTES_V8.len(), 13);
740 assert_ne!(MAGIC_BYTES_V8, MAGIC_BYTES_V7);
741 }
742
743 #[test]
744 fn phase1_legacy_version_v7_equals_seven() {
745 assert_eq!(LEGACY_VERSION_V7, 7);
746 }
747
748 #[test]
749 fn phase1_format_version_discriminants() {
750 assert_eq!(FormatVersion::V7 as u32, 7);
751 assert_eq!(FormatVersion::V8 as u32, 8);
752 assert_eq!(FormatVersion::V9 as u32, 9);
753 assert_eq!(FormatVersion::V10 as u32, 10);
754 assert_eq!(FormatVersion::V11 as u32, 11);
755 assert_eq!(FormatVersion::V12 as u32, 12);
756 assert_eq!(FormatVersion::V13 as u32, 13);
757 assert_eq!(FormatVersion::V14 as u32, 14);
758 assert_eq!(FormatVersion::V15 as u32, 15);
759 assert_eq!(FormatVersion::V16 as u32, 16);
760 assert_eq!(FormatVersion::V17 as u32, 17);
761 }
762
763 #[test]
764 fn current_version_is_v17() {
765 assert_eq!(CURRENT_VERSION, FormatVersion::V17);
766 }
767
768 #[test]
769 fn import_classification_magic_bytes_v17_is_distinct_and_14_bytes() {
770 assert_eq!(MAGIC_BYTES_V17, b"SQRY_GRAPH_V17");
771 assert_eq!(MAGIC_BYTES_V17.len(), 14);
772 assert_ne!(MAGIC_BYTES_V17.as_slice(), MAGIC_BYTES_V16.as_slice());
773 assert_ne!(MAGIC_BYTES_V17.as_slice(), MAGIC_BYTES_V15.as_slice());
774 }
775
776 #[test]
779 fn import_classification_format_version_dispatch_v17_before_older() {
780 let mut buf = MAGIC_BYTES_V17.to_vec();
781 buf.extend_from_slice(&[0u8; 8]);
782 assert_eq!(FormatVersion::from_magic(&buf), Some(FormatVersion::V17));
783
784 let mut buf16 = MAGIC_BYTES_V16.to_vec();
785 buf16.extend_from_slice(&[0u8; 8]);
786 assert_eq!(FormatVersion::from_magic(&buf16), Some(FormatVersion::V16));
787 }
788
789 #[test]
790 fn import_classification_format_version_v17_magic_round_trip() {
791 let v = FormatVersion::V17;
792 let bytes = v.magic();
793 assert_eq!(bytes, MAGIC_BYTES_V17.as_slice());
794 assert_eq!(FormatVersion::from_magic(bytes), Some(v));
795 }
796
797 #[test]
798 fn definition_magic_bytes_v16_is_distinct_and_14_bytes() {
799 assert_eq!(MAGIC_BYTES_V16, b"SQRY_GRAPH_V16");
800 assert_eq!(MAGIC_BYTES_V16.len(), 14);
801 assert_ne!(MAGIC_BYTES_V16.as_slice(), MAGIC_BYTES_V15.as_slice());
802 assert_ne!(MAGIC_BYTES_V16.as_slice(), MAGIC_BYTES_V14.as_slice());
803 }
804
805 #[test]
808 fn definition_format_version_dispatch_v16_before_older() {
809 let mut buf = MAGIC_BYTES_V16.to_vec();
810 buf.extend_from_slice(&[0u8; 8]);
811 assert_eq!(FormatVersion::from_magic(&buf), Some(FormatVersion::V16));
812
813 let mut buf15 = MAGIC_BYTES_V15.to_vec();
814 buf15.extend_from_slice(&[0u8; 8]);
815 assert_eq!(FormatVersion::from_magic(&buf15), Some(FormatVersion::V15));
816 }
817
818 #[test]
819 fn definition_format_version_v16_magic_round_trip() {
820 let v = FormatVersion::V16;
821 let bytes = v.magic();
822 assert_eq!(bytes, MAGIC_BYTES_V16.as_slice());
823 assert_eq!(FormatVersion::from_magic(bytes), Some(v));
824 }
825
826 #[test]
827 fn shape_magic_bytes_v15_is_distinct_and_14_bytes() {
828 assert_eq!(MAGIC_BYTES_V15, b"SQRY_GRAPH_V15");
829 assert_eq!(MAGIC_BYTES_V15.len(), 14);
830 assert_ne!(MAGIC_BYTES_V15.as_slice(), MAGIC_BYTES_V14.as_slice());
831 assert_ne!(MAGIC_BYTES_V15.as_slice(), MAGIC_BYTES_V13.as_slice());
832 }
833
834 #[test]
837 fn shape_format_version_dispatch_v15_before_older() {
838 let mut buf = MAGIC_BYTES_V15.to_vec();
839 buf.extend_from_slice(&[0u8; 8]);
840 assert_eq!(FormatVersion::from_magic(&buf), Some(FormatVersion::V15));
841
842 let mut buf14 = MAGIC_BYTES_V14.to_vec();
843 buf14.extend_from_slice(&[0u8; 8]);
844 assert_eq!(FormatVersion::from_magic(&buf14), Some(FormatVersion::V14));
845 }
846
847 #[test]
848 fn shape_format_version_v15_magic_round_trip() {
849 let v = FormatVersion::V15;
850 let bytes = v.magic();
851 assert_eq!(bytes, MAGIC_BYTES_V15.as_slice());
852 assert_eq!(FormatVersion::from_magic(bytes), Some(v));
853 }
854
855 #[test]
856 fn t2_magic_bytes_v14_is_distinct_and_14_bytes() {
857 assert_eq!(MAGIC_BYTES_V14, b"SQRY_GRAPH_V14");
858 assert_eq!(MAGIC_BYTES_V14.len(), 14);
859 assert_ne!(MAGIC_BYTES_V14.as_slice(), MAGIC_BYTES_V13.as_slice());
860 assert_ne!(MAGIC_BYTES_V14.as_slice(), MAGIC_BYTES_V12.as_slice());
861 }
862
863 #[test]
864 fn t2_format_version_from_magic_v14() {
865 assert_eq!(
866 FormatVersion::from_magic(MAGIC_BYTES_V14),
867 Some(FormatVersion::V14),
868 );
869 }
870
871 #[test]
874 fn t2_format_version_dispatch_v14_before_older() {
875 let mut buf = MAGIC_BYTES_V14.to_vec();
876 buf.extend_from_slice(&[0u8; 8]);
877 assert_eq!(FormatVersion::from_magic(&buf), Some(FormatVersion::V14));
878
879 let mut buf13 = MAGIC_BYTES_V13.to_vec();
880 buf13.extend_from_slice(&[0u8; 8]);
881 assert_eq!(FormatVersion::from_magic(&buf13), Some(FormatVersion::V13));
882 }
883
884 #[test]
885 fn t2_format_version_v14_magic_round_trip() {
886 let v = FormatVersion::V14;
887 let bytes = v.magic();
888 assert_eq!(bytes, MAGIC_BYTES_V14.as_slice());
889 assert_eq!(FormatVersion::from_magic(bytes), Some(v));
890 }
891
892 #[test]
893 fn t3_magic_bytes_v13_is_distinct_and_14_bytes() {
894 assert_eq!(MAGIC_BYTES_V13, b"SQRY_GRAPH_V13");
895 assert_eq!(MAGIC_BYTES_V13.len(), 14);
896 assert_ne!(MAGIC_BYTES_V13.as_slice(), MAGIC_BYTES_V12.as_slice());
897 assert_ne!(MAGIC_BYTES_V13.as_slice(), MAGIC_BYTES_V10.as_slice());
898 }
899
900 #[test]
901 fn t3_format_version_from_magic_v13() {
902 assert_eq!(
903 FormatVersion::from_magic(MAGIC_BYTES_V13),
904 Some(FormatVersion::V13),
905 );
906 }
907
908 #[test]
914 fn t3_format_version_dispatch_v13_before_v12_v11_v10() {
915 let mut buf = MAGIC_BYTES_V13.to_vec();
916 buf.extend_from_slice(&[0u8; 8]);
917 assert_eq!(FormatVersion::from_magic(&buf), Some(FormatVersion::V13));
918
919 let mut buf12 = MAGIC_BYTES_V12.to_vec();
920 buf12.extend_from_slice(&[0u8; 8]);
921 assert_eq!(FormatVersion::from_magic(&buf12), Some(FormatVersion::V12));
922 }
923
924 #[test]
925 fn t3_format_version_v13_magic_round_trip() {
926 let v = FormatVersion::V13;
927 let bytes = v.magic();
928 assert_eq!(bytes, MAGIC_BYTES_V13.as_slice());
929 assert_eq!(FormatVersion::from_magic(bytes), Some(v));
930 }
931
932 #[test]
933 fn phase_a_magic_bytes_v11_is_distinct_and_14_bytes() {
934 assert_eq!(MAGIC_BYTES_V11, b"SQRY_GRAPH_V11");
935 assert_eq!(MAGIC_BYTES_V11.len(), 14);
936 assert_ne!(MAGIC_BYTES_V11, MAGIC_BYTES_V10);
937 }
938
939 #[test]
940 fn phase_a_format_version_from_magic_v11() {
941 assert_eq!(
942 FormatVersion::from_magic(MAGIC_BYTES_V11),
943 Some(FormatVersion::V11),
944 );
945 }
946
947 #[test]
953 fn phase_a_format_version_dispatch_v11_before_v10() {
954 let mut buf = MAGIC_BYTES_V11.to_vec();
955 buf.extend_from_slice(&[0u8; 8]);
957 assert_eq!(FormatVersion::from_magic(&buf), Some(FormatVersion::V11));
958
959 let mut buf10 = MAGIC_BYTES_V10.to_vec();
960 buf10.extend_from_slice(&[0u8; 8]);
961 assert_eq!(FormatVersion::from_magic(&buf10), Some(FormatVersion::V10));
962 }
963
964 #[test]
965 fn phase_a_format_version_v11_magic_round_trip() {
966 let v = FormatVersion::V11;
967 let bytes = v.magic();
968 assert_eq!(bytes, MAGIC_BYTES_V11.as_slice());
969 assert_eq!(FormatVersion::from_magic(bytes), Some(v));
970 }
971
972 #[test]
973 fn phase1_format_version_from_magic_v7() {
974 assert_eq!(
975 FormatVersion::from_magic(MAGIC_BYTES_V7),
976 Some(FormatVersion::V7),
977 );
978 }
979
980 #[test]
981 fn phase1_format_version_from_magic_v8() {
982 assert_eq!(
983 FormatVersion::from_magic(MAGIC_BYTES_V8),
984 Some(FormatVersion::V8),
985 );
986 }
987
988 #[test]
989 fn phase2_magic_bytes_v9_is_distinct_and_13_bytes() {
990 assert_eq!(MAGIC_BYTES_V9, b"SQRY_GRAPH_V9");
991 assert_eq!(MAGIC_BYTES_V9.len(), 13);
992 assert_ne!(MAGIC_BYTES_V9, MAGIC_BYTES_V7);
993 assert_ne!(MAGIC_BYTES_V9, MAGIC_BYTES_V8);
994 }
995
996 #[test]
997 fn phase2_format_version_from_magic_v9() {
998 assert_eq!(
999 FormatVersion::from_magic(MAGIC_BYTES_V9),
1000 Some(FormatVersion::V9),
1001 );
1002 }
1003
1004 #[test]
1005 fn phase1_format_version_from_magic_unknown() {
1006 assert_eq!(FormatVersion::from_magic(b"SQRY_GRAPH_V1"), None);
1007 assert_eq!(FormatVersion::from_magic(b"NOT_A_GRAPH_!"), None);
1008 }
1009
1010 #[test]
1011 fn phase1_format_version_magic_round_trip() {
1012 for version in [
1013 FormatVersion::V7,
1014 FormatVersion::V8,
1015 FormatVersion::V9,
1016 FormatVersion::V10,
1017 FormatVersion::V11,
1018 FormatVersion::V12,
1019 FormatVersion::V13,
1020 ] {
1021 let bytes = version.magic();
1022 assert_eq!(FormatVersion::from_magic(bytes), Some(version));
1023 }
1024 }
1025
1026 #[test]
1031 fn phase_beta_magic_bytes_v12_is_distinct_and_14_bytes() {
1032 assert_eq!(MAGIC_BYTES_V12, b"SQRY_GRAPH_V12");
1033 assert_eq!(MAGIC_BYTES_V12.len(), 14);
1034 assert_ne!(MAGIC_BYTES_V12, MAGIC_BYTES_V11);
1035 assert_ne!(MAGIC_BYTES_V12, MAGIC_BYTES_V10);
1036 }
1037
1038 #[test]
1039 fn phase_beta_format_version_from_magic_v12() {
1040 assert_eq!(
1041 FormatVersion::from_magic(MAGIC_BYTES_V12),
1042 Some(FormatVersion::V12),
1043 );
1044 }
1045
1046 #[test]
1051 fn phase_beta_format_version_dispatch_v12_before_v11_v10() {
1052 let mut buf = MAGIC_BYTES_V12.to_vec();
1053 buf.extend_from_slice(&[0u8; 8]);
1054 assert_eq!(FormatVersion::from_magic(&buf), Some(FormatVersion::V12));
1055
1056 let mut buf11 = MAGIC_BYTES_V11.to_vec();
1057 buf11.extend_from_slice(&[0u8; 8]);
1058 assert_eq!(FormatVersion::from_magic(&buf11), Some(FormatVersion::V11));
1059
1060 let mut buf10 = MAGIC_BYTES_V10.to_vec();
1061 buf10.extend_from_slice(&[0u8; 8]);
1062 assert_eq!(FormatVersion::from_magic(&buf10), Some(FormatVersion::V10));
1063 }
1064
1065 #[test]
1066 fn phase_beta_format_version_v12_magic_round_trip() {
1067 let v = FormatVersion::V12;
1068 let bytes = v.magic();
1069 assert_eq!(bytes, MAGIC_BYTES_V12.as_slice());
1070 assert_eq!(FormatVersion::from_magic(bytes), Some(v));
1071 }
1072
1073 #[test]
1074 fn phase1_format_version_copy_eq_debug() {
1075 let v = FormatVersion::V8;
1076 let copied = v;
1077 assert_eq!(v, copied);
1078 assert_eq!(format!("{v:?}"), "V8");
1079 }
1080
1081 #[test]
1082 fn phase2_format_version_v9_copy_eq_debug() {
1083 let v = FormatVersion::V9;
1084 let copied = v;
1085 assert_eq!(v, copied);
1086 assert_eq!(format!("{v:?}"), "V9");
1087 }
1088
1089 #[test]
1090 fn test_graph_header_with_provenance_and_plugins() {
1091 let provenance = make_test_provenance();
1092
1093 let mut plugin_versions = HashMap::new();
1094 plugin_versions.insert("rust".to_string(), "3.3.0".to_string());
1095 plugin_versions.insert("python".to_string(), "3.3.0".to_string());
1096
1097 let header = GraphHeader::with_provenance_and_plugins(
1098 100,
1099 50,
1100 200,
1101 10,
1102 provenance,
1103 plugin_versions.clone(),
1104 );
1105
1106 assert_eq!(header.version, VERSION);
1107 assert_eq!(header.node_count, 100);
1108 assert!(header.config_provenance.is_some());
1109 assert_eq!(header.plugin_versions().len(), 2);
1110 assert_eq!(
1111 header.plugin_versions().get("rust"),
1112 Some(&"3.3.0".to_string())
1113 );
1114 }
1115}