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 LEGACY_VERSION_V7: u32 = 7;
156
157#[repr(u32)]
164#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
165pub enum FormatVersion {
166 V7 = 7,
168 V8 = 8,
170 V9 = 9,
174 V10 = 10,
178 V11 = 11,
184 V12 = 12,
188 V13 = 13,
193 V14 = 14,
202 V15 = 15,
211 V16 = 16,
221}
222
223impl FormatVersion {
224 #[must_use]
226 pub const fn magic(self) -> &'static [u8] {
227 match self {
228 Self::V7 => MAGIC_BYTES_V7.as_slice(),
229 Self::V8 => MAGIC_BYTES_V8.as_slice(),
230 Self::V9 => MAGIC_BYTES_V9.as_slice(),
231 Self::V10 => MAGIC_BYTES_V10.as_slice(),
232 Self::V11 => MAGIC_BYTES_V11.as_slice(),
233 Self::V12 => MAGIC_BYTES_V12.as_slice(),
234 Self::V13 => MAGIC_BYTES_V13.as_slice(),
235 Self::V14 => MAGIC_BYTES_V14.as_slice(),
236 Self::V15 => MAGIC_BYTES_V15.as_slice(),
237 Self::V16 => MAGIC_BYTES_V16.as_slice(),
238 }
239 }
240
241 #[must_use]
243 pub const fn as_u32(self) -> u32 {
244 self as u32
245 }
246
247 #[must_use]
251 pub fn from_magic(bytes: &[u8]) -> Option<Self> {
252 if bytes.len() >= MAGIC_BYTES_V16.len()
256 && bytes[..MAGIC_BYTES_V16.len()] == *MAGIC_BYTES_V16
257 {
258 return Some(Self::V16);
259 }
260 if bytes.len() >= MAGIC_BYTES_V15.len()
261 && bytes[..MAGIC_BYTES_V15.len()] == *MAGIC_BYTES_V15
262 {
263 return Some(Self::V15);
264 }
265 if bytes.len() >= MAGIC_BYTES_V14.len()
266 && bytes[..MAGIC_BYTES_V14.len()] == *MAGIC_BYTES_V14
267 {
268 return Some(Self::V14);
269 }
270 if bytes.len() >= MAGIC_BYTES_V13.len()
271 && bytes[..MAGIC_BYTES_V13.len()] == *MAGIC_BYTES_V13
272 {
273 return Some(Self::V13);
274 }
275 if bytes.len() >= MAGIC_BYTES_V12.len()
276 && bytes[..MAGIC_BYTES_V12.len()] == *MAGIC_BYTES_V12
277 {
278 return Some(Self::V12);
279 }
280 if bytes.len() >= MAGIC_BYTES_V11.len()
281 && bytes[..MAGIC_BYTES_V11.len()] == *MAGIC_BYTES_V11
282 {
283 return Some(Self::V11);
284 }
285 if bytes.len() >= MAGIC_BYTES_V10.len()
286 && bytes[..MAGIC_BYTES_V10.len()] == *MAGIC_BYTES_V10
287 {
288 return Some(Self::V10);
289 }
290 if bytes.len() < MAGIC_BYTES_V7.len() {
291 return None;
292 }
293 let prefix = &bytes[..MAGIC_BYTES_V7.len()];
294 if prefix == MAGIC_BYTES_V7 {
295 Some(Self::V7)
296 } else if prefix == MAGIC_BYTES_V8 {
297 Some(Self::V8)
298 } else if prefix == MAGIC_BYTES_V9 {
299 Some(Self::V9)
300 } else {
301 None
302 }
303 }
304}
305
306pub const CURRENT_VERSION: FormatVersion = FormatVersion::V16;
308
309#[derive(Debug, Clone, Serialize, Deserialize)]
314pub struct GraphHeader {
315 pub version: u32,
317
318 pub node_count: usize,
320
321 pub edge_count: usize,
323
324 pub string_count: usize,
326
327 pub file_count: usize,
329
330 pub timestamp: u64,
332
333 #[serde(default)]
335 pub config_provenance: Option<ConfigProvenance>,
336
337 #[serde(default)]
342 pub plugin_versions: HashMap<String, String>,
343
344 #[serde(default)]
358 pub fact_epoch: u64,
359}
360
361impl GraphHeader {
362 #[must_use]
364 pub fn new(
365 node_count: usize,
366 edge_count: usize,
367 string_count: usize,
368 file_count: usize,
369 ) -> Self {
370 Self {
371 version: VERSION,
372 node_count,
373 edge_count,
374 string_count,
375 file_count,
376 timestamp: std::time::SystemTime::now()
377 .duration_since(std::time::UNIX_EPOCH)
378 .unwrap_or_default()
379 .as_secs(),
380 config_provenance: None,
381 plugin_versions: HashMap::new(),
382 fact_epoch: 0,
383 }
384 }
385
386 #[must_use]
388 pub fn with_provenance(
389 node_count: usize,
390 edge_count: usize,
391 string_count: usize,
392 file_count: usize,
393 provenance: ConfigProvenance,
394 ) -> Self {
395 Self {
396 version: VERSION,
397 node_count,
398 edge_count,
399 string_count,
400 file_count,
401 timestamp: std::time::SystemTime::now()
402 .duration_since(std::time::UNIX_EPOCH)
403 .unwrap_or_default()
404 .as_secs(),
405 config_provenance: Some(provenance),
406 plugin_versions: HashMap::new(),
407 fact_epoch: 0,
408 }
409 }
410
411 #[must_use]
413 pub fn with_provenance_and_plugins(
414 node_count: usize,
415 edge_count: usize,
416 string_count: usize,
417 file_count: usize,
418 provenance: ConfigProvenance,
419 plugin_versions: HashMap<String, String>,
420 ) -> Self {
421 Self {
422 version: VERSION,
423 node_count,
424 edge_count,
425 string_count,
426 file_count,
427 timestamp: std::time::SystemTime::now()
428 .duration_since(std::time::UNIX_EPOCH)
429 .unwrap_or_default()
430 .as_secs(),
431 config_provenance: Some(provenance),
432 plugin_versions,
433 fact_epoch: 0,
434 }
435 }
436
437 #[must_use]
439 pub fn provenance(&self) -> Option<&ConfigProvenance> {
440 self.config_provenance.as_ref()
441 }
442
443 #[must_use]
445 pub fn has_provenance(&self) -> bool {
446 self.config_provenance.is_some()
447 }
448
449 #[must_use]
451 pub fn plugin_versions(&self) -> &HashMap<String, String> {
452 &self.plugin_versions
453 }
454
455 pub fn set_plugin_versions(&mut self, versions: HashMap<String, String>) {
457 self.plugin_versions = versions;
458 }
459
460 #[must_use]
467 pub fn fact_epoch(&self) -> u64 {
468 self.fact_epoch
469 }
470
471 pub fn set_fact_epoch(&mut self, epoch: u64) {
477 self.fact_epoch = epoch;
478 }
479}
480
481#[cfg(test)]
482mod tests {
483 use super::*;
484 use std::collections::HashMap;
485 use std::path::PathBuf;
486
487 fn make_test_provenance() -> ConfigProvenance {
488 ConfigProvenance {
489 config_file: PathBuf::from(".sqry/graph/config/config.json"),
490 config_checksum: "abc123def456".to_string(),
491 schema_version: 1,
492 overrides: HashMap::new(),
493 build_timestamp: std::time::SystemTime::now()
494 .duration_since(std::time::UNIX_EPOCH)
495 .unwrap_or_default()
496 .as_secs(),
497 build_host: Some("test-host".to_string()),
498 }
499 }
500
501 #[test]
502 fn test_magic_bytes() {
503 assert_eq!(MAGIC_BYTES, b"SQRY_GRAPH_V7");
504 assert_eq!(MAGIC_BYTES.len(), 13);
505 }
506
507 #[test]
508 fn test_version() {
509 assert_eq!(VERSION, 7);
510 }
511
512 #[test]
513 fn test_graph_header_new() {
514 let header = GraphHeader::new(100, 50, 200, 10);
515
516 assert_eq!(header.version, VERSION);
517 assert_eq!(header.node_count, 100);
518 assert_eq!(header.edge_count, 50);
519 assert_eq!(header.string_count, 200);
520 assert_eq!(header.file_count, 10);
521 assert!(header.timestamp > 0);
522 assert!(header.config_provenance.is_none());
523 }
524
525 #[test]
526 fn test_graph_header_with_provenance() {
527 let provenance = make_test_provenance();
528 let header = GraphHeader::with_provenance(100, 50, 200, 10, provenance);
529
530 assert_eq!(header.version, VERSION);
531 assert_eq!(header.node_count, 100);
532 assert_eq!(header.edge_count, 50);
533 assert!(header.config_provenance.is_some());
534 assert_eq!(
535 header.config_provenance.as_ref().unwrap().config_checksum,
536 "abc123def456"
537 );
538 }
539
540 #[test]
541 fn test_graph_header_provenance_method() {
542 let header = GraphHeader::new(10, 5, 20, 2);
543 assert!(header.provenance().is_none());
544
545 let provenance = make_test_provenance();
546 let header_with = GraphHeader::with_provenance(10, 5, 20, 2, provenance);
547 assert!(header_with.provenance().is_some());
548 assert_eq!(
549 header_with.provenance().unwrap().config_checksum,
550 "abc123def456"
551 );
552 }
553
554 #[test]
555 fn test_graph_header_has_provenance() {
556 let header = GraphHeader::new(10, 5, 20, 2);
557 assert!(!header.has_provenance());
558
559 let provenance = make_test_provenance();
560 let header_with = GraphHeader::with_provenance(10, 5, 20, 2, provenance);
561 assert!(header_with.has_provenance());
562 }
563
564 #[test]
565 fn test_graph_header_clone() {
566 let header = GraphHeader::new(100, 50, 200, 10);
567 let cloned = header.clone();
568
569 assert_eq!(header.version, cloned.version);
570 assert_eq!(header.node_count, cloned.node_count);
571 assert_eq!(header.edge_count, cloned.edge_count);
572 assert_eq!(header.string_count, cloned.string_count);
573 assert_eq!(header.file_count, cloned.file_count);
574 }
575
576 #[test]
577 fn test_graph_header_debug() {
578 let header = GraphHeader::new(100, 50, 200, 10);
579 let debug_str = format!("{header:?}");
580
581 assert!(debug_str.contains("GraphHeader"));
582 assert!(debug_str.contains("version"));
583 assert!(debug_str.contains("node_count"));
584 }
585
586 #[test]
587 fn test_graph_header_timestamp_is_recent() {
588 let header = GraphHeader::new(10, 5, 20, 2);
589 let now = std::time::SystemTime::now()
590 .duration_since(std::time::UNIX_EPOCH)
591 .unwrap()
592 .as_secs();
593
594 assert!(header.timestamp <= now);
596 assert!(header.timestamp >= now - 1);
597 }
598
599 #[test]
600 fn test_graph_header_zero_counts() {
601 let header = GraphHeader::new(0, 0, 0, 0);
602
603 assert_eq!(header.node_count, 0);
604 assert_eq!(header.edge_count, 0);
605 assert_eq!(header.string_count, 0);
606 assert_eq!(header.file_count, 0);
607 }
608
609 #[test]
610 fn test_graph_header_large_counts() {
611 let header = GraphHeader::new(1_000_000, 5_000_000, 10_000_000, 100_000);
612
613 assert_eq!(header.node_count, 1_000_000);
614 assert_eq!(header.edge_count, 5_000_000);
615 assert_eq!(header.string_count, 10_000_000);
616 assert_eq!(header.file_count, 100_000);
617 }
618
619 #[test]
620 fn test_graph_header_plugin_versions_empty_by_default() {
621 let header = GraphHeader::new(10, 5, 20, 2);
622 assert!(header.plugin_versions().is_empty());
623 }
624
625 #[test]
626 fn test_graph_header_set_plugin_versions() {
627 let mut header = GraphHeader::new(10, 5, 20, 2);
628
629 let mut versions = HashMap::new();
630 versions.insert("rust".to_string(), "3.3.0".to_string());
631 versions.insert("javascript".to_string(), "3.3.0".to_string());
632
633 header.set_plugin_versions(versions.clone());
634
635 assert_eq!(header.plugin_versions().len(), 2);
636 assert_eq!(
637 header.plugin_versions().get("rust"),
638 Some(&"3.3.0".to_string())
639 );
640 assert_eq!(
641 header.plugin_versions().get("javascript"),
642 Some(&"3.3.0".to_string())
643 );
644 }
645
646 #[test]
651 fn phase1_graph_header_new_defaults_fact_epoch_to_zero() {
652 let header = GraphHeader::new(10, 5, 20, 2);
653 assert_eq!(header.fact_epoch, 0);
654 assert_eq!(header.fact_epoch(), 0);
655 }
656
657 #[test]
658 fn phase1_graph_header_with_provenance_defaults_fact_epoch_to_zero() {
659 let header = GraphHeader::with_provenance(10, 5, 20, 2, make_test_provenance());
660 assert_eq!(header.fact_epoch, 0);
661 }
662
663 #[test]
664 fn phase1_graph_header_set_fact_epoch_round_trip() {
665 let mut header = GraphHeader::new(10, 5, 20, 2);
666 header.set_fact_epoch(42);
667 assert_eq!(header.fact_epoch(), 42);
668 }
669
670 #[test]
671 fn phase1_graph_header_postcard_round_trip_with_fact_epoch() {
672 let mut header = GraphHeader::new(100, 50, 200, 10);
673 header.set_fact_epoch(1_234_567);
674
675 let encoded = postcard::to_allocvec(&header).expect("encode");
676 let decoded: GraphHeader = postcard::from_bytes(&encoded).expect("decode");
677
678 assert_eq!(decoded.fact_epoch(), 1_234_567);
679 assert_eq!(decoded.node_count, 100);
680 assert_eq!(decoded.edge_count, 50);
681 }
682
683 #[test]
684 fn phase1_graph_header_fact_epoch_preserved_through_clone() {
685 let mut header = GraphHeader::new(10, 5, 20, 2);
686 header.set_fact_epoch(9_999);
687 let cloned = header.clone();
688 assert_eq!(cloned.fact_epoch(), 9_999);
689 }
690
691 #[test]
696 fn phase1_magic_bytes_v7_matches_legacy() {
697 assert_eq!(MAGIC_BYTES_V7, b"SQRY_GRAPH_V7");
698 assert_eq!(MAGIC_BYTES_V7, MAGIC_BYTES);
699 assert_eq!(MAGIC_BYTES_V7.len(), 13);
700 }
701
702 #[test]
703 fn phase1_magic_bytes_v8_is_distinct_and_13_bytes() {
704 assert_eq!(MAGIC_BYTES_V8, b"SQRY_GRAPH_V8");
705 assert_eq!(MAGIC_BYTES_V8.len(), 13);
706 assert_ne!(MAGIC_BYTES_V8, MAGIC_BYTES_V7);
707 }
708
709 #[test]
710 fn phase1_legacy_version_v7_equals_seven() {
711 assert_eq!(LEGACY_VERSION_V7, 7);
712 }
713
714 #[test]
715 fn phase1_format_version_discriminants() {
716 assert_eq!(FormatVersion::V7 as u32, 7);
717 assert_eq!(FormatVersion::V8 as u32, 8);
718 assert_eq!(FormatVersion::V9 as u32, 9);
719 assert_eq!(FormatVersion::V10 as u32, 10);
720 assert_eq!(FormatVersion::V11 as u32, 11);
721 assert_eq!(FormatVersion::V12 as u32, 12);
722 assert_eq!(FormatVersion::V13 as u32, 13);
723 assert_eq!(FormatVersion::V14 as u32, 14);
724 assert_eq!(FormatVersion::V15 as u32, 15);
725 assert_eq!(FormatVersion::V16 as u32, 16);
726 }
727
728 #[test]
729 fn current_version_is_v16() {
730 assert_eq!(CURRENT_VERSION, FormatVersion::V16);
731 }
732
733 #[test]
734 fn definition_magic_bytes_v16_is_distinct_and_14_bytes() {
735 assert_eq!(MAGIC_BYTES_V16, b"SQRY_GRAPH_V16");
736 assert_eq!(MAGIC_BYTES_V16.len(), 14);
737 assert_ne!(MAGIC_BYTES_V16.as_slice(), MAGIC_BYTES_V15.as_slice());
738 assert_ne!(MAGIC_BYTES_V16.as_slice(), MAGIC_BYTES_V14.as_slice());
739 }
740
741 #[test]
744 fn definition_format_version_dispatch_v16_before_older() {
745 let mut buf = MAGIC_BYTES_V16.to_vec();
746 buf.extend_from_slice(&[0u8; 8]);
747 assert_eq!(FormatVersion::from_magic(&buf), Some(FormatVersion::V16));
748
749 let mut buf15 = MAGIC_BYTES_V15.to_vec();
750 buf15.extend_from_slice(&[0u8; 8]);
751 assert_eq!(FormatVersion::from_magic(&buf15), Some(FormatVersion::V15));
752 }
753
754 #[test]
755 fn definition_format_version_v16_magic_round_trip() {
756 let v = FormatVersion::V16;
757 let bytes = v.magic();
758 assert_eq!(bytes, MAGIC_BYTES_V16.as_slice());
759 assert_eq!(FormatVersion::from_magic(bytes), Some(v));
760 }
761
762 #[test]
763 fn shape_magic_bytes_v15_is_distinct_and_14_bytes() {
764 assert_eq!(MAGIC_BYTES_V15, b"SQRY_GRAPH_V15");
765 assert_eq!(MAGIC_BYTES_V15.len(), 14);
766 assert_ne!(MAGIC_BYTES_V15.as_slice(), MAGIC_BYTES_V14.as_slice());
767 assert_ne!(MAGIC_BYTES_V15.as_slice(), MAGIC_BYTES_V13.as_slice());
768 }
769
770 #[test]
773 fn shape_format_version_dispatch_v15_before_older() {
774 let mut buf = MAGIC_BYTES_V15.to_vec();
775 buf.extend_from_slice(&[0u8; 8]);
776 assert_eq!(FormatVersion::from_magic(&buf), Some(FormatVersion::V15));
777
778 let mut buf14 = MAGIC_BYTES_V14.to_vec();
779 buf14.extend_from_slice(&[0u8; 8]);
780 assert_eq!(FormatVersion::from_magic(&buf14), Some(FormatVersion::V14));
781 }
782
783 #[test]
784 fn shape_format_version_v15_magic_round_trip() {
785 let v = FormatVersion::V15;
786 let bytes = v.magic();
787 assert_eq!(bytes, MAGIC_BYTES_V15.as_slice());
788 assert_eq!(FormatVersion::from_magic(bytes), Some(v));
789 }
790
791 #[test]
792 fn t2_magic_bytes_v14_is_distinct_and_14_bytes() {
793 assert_eq!(MAGIC_BYTES_V14, b"SQRY_GRAPH_V14");
794 assert_eq!(MAGIC_BYTES_V14.len(), 14);
795 assert_ne!(MAGIC_BYTES_V14.as_slice(), MAGIC_BYTES_V13.as_slice());
796 assert_ne!(MAGIC_BYTES_V14.as_slice(), MAGIC_BYTES_V12.as_slice());
797 }
798
799 #[test]
800 fn t2_format_version_from_magic_v14() {
801 assert_eq!(
802 FormatVersion::from_magic(MAGIC_BYTES_V14),
803 Some(FormatVersion::V14),
804 );
805 }
806
807 #[test]
810 fn t2_format_version_dispatch_v14_before_older() {
811 let mut buf = MAGIC_BYTES_V14.to_vec();
812 buf.extend_from_slice(&[0u8; 8]);
813 assert_eq!(FormatVersion::from_magic(&buf), Some(FormatVersion::V14));
814
815 let mut buf13 = MAGIC_BYTES_V13.to_vec();
816 buf13.extend_from_slice(&[0u8; 8]);
817 assert_eq!(FormatVersion::from_magic(&buf13), Some(FormatVersion::V13));
818 }
819
820 #[test]
821 fn t2_format_version_v14_magic_round_trip() {
822 let v = FormatVersion::V14;
823 let bytes = v.magic();
824 assert_eq!(bytes, MAGIC_BYTES_V14.as_slice());
825 assert_eq!(FormatVersion::from_magic(bytes), Some(v));
826 }
827
828 #[test]
829 fn t3_magic_bytes_v13_is_distinct_and_14_bytes() {
830 assert_eq!(MAGIC_BYTES_V13, b"SQRY_GRAPH_V13");
831 assert_eq!(MAGIC_BYTES_V13.len(), 14);
832 assert_ne!(MAGIC_BYTES_V13.as_slice(), MAGIC_BYTES_V12.as_slice());
833 assert_ne!(MAGIC_BYTES_V13.as_slice(), MAGIC_BYTES_V10.as_slice());
834 }
835
836 #[test]
837 fn t3_format_version_from_magic_v13() {
838 assert_eq!(
839 FormatVersion::from_magic(MAGIC_BYTES_V13),
840 Some(FormatVersion::V13),
841 );
842 }
843
844 #[test]
850 fn t3_format_version_dispatch_v13_before_v12_v11_v10() {
851 let mut buf = MAGIC_BYTES_V13.to_vec();
852 buf.extend_from_slice(&[0u8; 8]);
853 assert_eq!(FormatVersion::from_magic(&buf), Some(FormatVersion::V13));
854
855 let mut buf12 = MAGIC_BYTES_V12.to_vec();
856 buf12.extend_from_slice(&[0u8; 8]);
857 assert_eq!(FormatVersion::from_magic(&buf12), Some(FormatVersion::V12));
858 }
859
860 #[test]
861 fn t3_format_version_v13_magic_round_trip() {
862 let v = FormatVersion::V13;
863 let bytes = v.magic();
864 assert_eq!(bytes, MAGIC_BYTES_V13.as_slice());
865 assert_eq!(FormatVersion::from_magic(bytes), Some(v));
866 }
867
868 #[test]
869 fn phase_a_magic_bytes_v11_is_distinct_and_14_bytes() {
870 assert_eq!(MAGIC_BYTES_V11, b"SQRY_GRAPH_V11");
871 assert_eq!(MAGIC_BYTES_V11.len(), 14);
872 assert_ne!(MAGIC_BYTES_V11, MAGIC_BYTES_V10);
873 }
874
875 #[test]
876 fn phase_a_format_version_from_magic_v11() {
877 assert_eq!(
878 FormatVersion::from_magic(MAGIC_BYTES_V11),
879 Some(FormatVersion::V11),
880 );
881 }
882
883 #[test]
889 fn phase_a_format_version_dispatch_v11_before_v10() {
890 let mut buf = MAGIC_BYTES_V11.to_vec();
891 buf.extend_from_slice(&[0u8; 8]);
893 assert_eq!(FormatVersion::from_magic(&buf), Some(FormatVersion::V11));
894
895 let mut buf10 = MAGIC_BYTES_V10.to_vec();
896 buf10.extend_from_slice(&[0u8; 8]);
897 assert_eq!(FormatVersion::from_magic(&buf10), Some(FormatVersion::V10));
898 }
899
900 #[test]
901 fn phase_a_format_version_v11_magic_round_trip() {
902 let v = FormatVersion::V11;
903 let bytes = v.magic();
904 assert_eq!(bytes, MAGIC_BYTES_V11.as_slice());
905 assert_eq!(FormatVersion::from_magic(bytes), Some(v));
906 }
907
908 #[test]
909 fn phase1_format_version_from_magic_v7() {
910 assert_eq!(
911 FormatVersion::from_magic(MAGIC_BYTES_V7),
912 Some(FormatVersion::V7),
913 );
914 }
915
916 #[test]
917 fn phase1_format_version_from_magic_v8() {
918 assert_eq!(
919 FormatVersion::from_magic(MAGIC_BYTES_V8),
920 Some(FormatVersion::V8),
921 );
922 }
923
924 #[test]
925 fn phase2_magic_bytes_v9_is_distinct_and_13_bytes() {
926 assert_eq!(MAGIC_BYTES_V9, b"SQRY_GRAPH_V9");
927 assert_eq!(MAGIC_BYTES_V9.len(), 13);
928 assert_ne!(MAGIC_BYTES_V9, MAGIC_BYTES_V7);
929 assert_ne!(MAGIC_BYTES_V9, MAGIC_BYTES_V8);
930 }
931
932 #[test]
933 fn phase2_format_version_from_magic_v9() {
934 assert_eq!(
935 FormatVersion::from_magic(MAGIC_BYTES_V9),
936 Some(FormatVersion::V9),
937 );
938 }
939
940 #[test]
941 fn phase1_format_version_from_magic_unknown() {
942 assert_eq!(FormatVersion::from_magic(b"SQRY_GRAPH_V1"), None);
943 assert_eq!(FormatVersion::from_magic(b"NOT_A_GRAPH_!"), None);
944 }
945
946 #[test]
947 fn phase1_format_version_magic_round_trip() {
948 for version in [
949 FormatVersion::V7,
950 FormatVersion::V8,
951 FormatVersion::V9,
952 FormatVersion::V10,
953 FormatVersion::V11,
954 FormatVersion::V12,
955 FormatVersion::V13,
956 ] {
957 let bytes = version.magic();
958 assert_eq!(FormatVersion::from_magic(bytes), Some(version));
959 }
960 }
961
962 #[test]
967 fn phase_beta_magic_bytes_v12_is_distinct_and_14_bytes() {
968 assert_eq!(MAGIC_BYTES_V12, b"SQRY_GRAPH_V12");
969 assert_eq!(MAGIC_BYTES_V12.len(), 14);
970 assert_ne!(MAGIC_BYTES_V12, MAGIC_BYTES_V11);
971 assert_ne!(MAGIC_BYTES_V12, MAGIC_BYTES_V10);
972 }
973
974 #[test]
975 fn phase_beta_format_version_from_magic_v12() {
976 assert_eq!(
977 FormatVersion::from_magic(MAGIC_BYTES_V12),
978 Some(FormatVersion::V12),
979 );
980 }
981
982 #[test]
987 fn phase_beta_format_version_dispatch_v12_before_v11_v10() {
988 let mut buf = MAGIC_BYTES_V12.to_vec();
989 buf.extend_from_slice(&[0u8; 8]);
990 assert_eq!(FormatVersion::from_magic(&buf), Some(FormatVersion::V12));
991
992 let mut buf11 = MAGIC_BYTES_V11.to_vec();
993 buf11.extend_from_slice(&[0u8; 8]);
994 assert_eq!(FormatVersion::from_magic(&buf11), Some(FormatVersion::V11));
995
996 let mut buf10 = MAGIC_BYTES_V10.to_vec();
997 buf10.extend_from_slice(&[0u8; 8]);
998 assert_eq!(FormatVersion::from_magic(&buf10), Some(FormatVersion::V10));
999 }
1000
1001 #[test]
1002 fn phase_beta_format_version_v12_magic_round_trip() {
1003 let v = FormatVersion::V12;
1004 let bytes = v.magic();
1005 assert_eq!(bytes, MAGIC_BYTES_V12.as_slice());
1006 assert_eq!(FormatVersion::from_magic(bytes), Some(v));
1007 }
1008
1009 #[test]
1010 fn phase1_format_version_copy_eq_debug() {
1011 let v = FormatVersion::V8;
1012 let copied = v;
1013 assert_eq!(v, copied);
1014 assert_eq!(format!("{v:?}"), "V8");
1015 }
1016
1017 #[test]
1018 fn phase2_format_version_v9_copy_eq_debug() {
1019 let v = FormatVersion::V9;
1020 let copied = v;
1021 assert_eq!(v, copied);
1022 assert_eq!(format!("{v:?}"), "V9");
1023 }
1024
1025 #[test]
1026 fn test_graph_header_with_provenance_and_plugins() {
1027 let provenance = make_test_provenance();
1028
1029 let mut plugin_versions = HashMap::new();
1030 plugin_versions.insert("rust".to_string(), "3.3.0".to_string());
1031 plugin_versions.insert("python".to_string(), "3.3.0".to_string());
1032
1033 let header = GraphHeader::with_provenance_and_plugins(
1034 100,
1035 50,
1036 200,
1037 10,
1038 provenance,
1039 plugin_versions.clone(),
1040 );
1041
1042 assert_eq!(header.version, VERSION);
1043 assert_eq!(header.node_count, 100);
1044 assert!(header.config_provenance.is_some());
1045 assert_eq!(header.plugin_versions().len(), 2);
1046 assert_eq!(
1047 header.plugin_versions().get("rust"),
1048 Some(&"3.3.0".to_string())
1049 );
1050 }
1051}