Skip to main content

hexz_core/format/
version.rs

1//! Format version management and compatibility checking.
2//!
3//! This module defines the versioning strategy for Hexz snapshot files (`.hxz`),
4//! enabling safe evolution of the on-disk format while maintaining backward and
5//! forward compatibility guarantees. Version negotiation ensures that readers can
6//! detect incompatible snapshots and provide actionable error messages.
7//!
8//! # Versioning Strategy
9//!
10//! Hexz uses a monotonic integer versioning scheme where each version number
11//! represents a distinct on-disk format:
12//!
13//! - **Version 1**: Initial format with two-level index, LZ4/Zstd compression,
14//!   optional encryption, thin provisioning support
15//! - **Future versions**: Reserved for incompatible format changes (e.g., new
16//!   index structures, alternative serialization formats, block metadata extensions)
17//!
18//! ## Semantic Versioning Alignment
19//!
20//! Format versions are independent of Hexz software versions (semver). A software
21//! release may support multiple format versions for backward compatibility. The
22//! mapping is defined as:
23//!
24//! | Format Version | Hexz Software Versions | Key Changes |
25//! |----------------|--------------------------|-------------|
26//! | 1              | 0.1.0+                   | Initial release |
27//!
28//! # Compatibility Model
29//!
30//! ## Backward Compatibility (Reading Old Snapshots)
31//!
32//! Hexz readers maintain compatibility with snapshots created by older software:
33//!
34//! - **MIN_SUPPORTED_VERSION**: Oldest format version we can read (currently 1)
35//! - **Upgrade Path**: Snapshots older than MIN_SUPPORTED_VERSION must be migrated
36//!   using the `hexz-migrate` tool (see Migration section below)
37//!
38//! ## Forward Compatibility (Reading New Snapshots)
39//!
40//! Hexz readers handle snapshots created by newer software:
41//!
42//! - **MAX_SUPPORTED_VERSION**: Newest format version we can read (currently 1)
43//! - **Degraded Mode**: Future versions may enable partial reads with warnings if
44//!   minor features are unrecognized (not yet implemented)
45//! - **Strict Rejection**: Snapshots with `version > MAX_SUPPORTED_VERSION` are
46//!   rejected with an actionable error message
47//!
48//! # Version Negotiation Workflow
49//!
50//! When opening a snapshot file:
51//!
52//! ```text
53//! 1. Read header magic bytes (validate "HEXZ" signature)
54//! 2. Read header.version field
55//! 3. Call check_version(header.version)
56//! 4. If VersionCompatibility::Incompatible:
57//!       - Generate human-readable error via compatibility_message()
58//!       - Suggest upgrade/migration path
59//!       - Abort operation
60//! 5. If VersionCompatibility::Full:
61//!       - Proceed with normal operations
62//! 6. If VersionCompatibility::Degraded (future):
63//!       - Log warning about unsupported features
64//!       - Proceed with limited functionality
65//! ```
66//!
67//! # Adding New Format Versions
68//!
69//! To introduce a breaking format change:
70//!
71//! ## Step 1: Increment Version Constant
72//!
73//! ```rust,ignore
74//! pub const CURRENT_VERSION: u32 = 2;  // Changed from 1
75//! pub const MAX_SUPPORTED_VERSION: u32 = 2;
76//! ```
77//!
78//! ## Step 2: Update Serialization Logic
79//!
80//! Modify [`crate::format::header::Header`] or [`crate::format::index::MasterIndex`]
81//! to include new fields or change existing structures. Use serde attributes to
82//! maintain compatibility:
83//!
84//! ```rust,ignore
85//! #[derive(Serialize, Deserialize)]
86//! pub struct Header {
87//!     // Existing fields...
88//!     #[serde(skip_serializing_if = "Option::is_none")]
89//!     pub new_feature: Option<NewFeatureData>,  // Version 2+ only
90//! }
91//! ```
92//!
93//! ## Step 3: Conditional Deserialization
94//!
95//! Add version-aware deserialization in reader code:
96//!
97//! ```rust,ignore
98//! match header.version {
99//!     1 => {
100//!         // Legacy path for version 1
101//!         read_index_v1(reader)?
102//!     }
103//!     2 => {
104//!         // New path for version 2
105//!         read_index_v2(reader)?
106//!     }
107//!     _ => unreachable!("check_version already validated"),
108//! }
109//! ```
110//!
111//! ## Step 4: Update Tests
112//!
113//! Add test fixtures for the new format version:
114//!
115//! ```rust,ignore
116//! #[test]
117//! fn test_read_v2_snapshot() {
118//!     let snapshot = load_fixture("testdata/v2_snapshot.hxz");
119//!     assert_eq!(snapshot.header.version, 2);
120//!     // Verify new features work correctly
121//! }
122//! ```
123//!
124//! ## Step 5: Document Migration Path
125//!
126//! Update the migration guide with conversion instructions (see Migration section).
127//!
128//! # Migration Between Versions
129//!
130//! The `hexz-migrate` tool converts snapshots between format versions:
131//!
132//! ```bash
133//! # Upgrade old snapshot to current format
134//! hexz-migrate upgrade --input old_v1.hxz --output new_v2.hxz
135//!
136//! # Downgrade for compatibility (if supported)
137//! hexz-migrate downgrade --input new_v2.hxz --output legacy_v1.hxz \
138//!     --target-version 1
139//! ```
140//!
141//! ## Migration Algorithm
142//!
143//! The migration tool performs a streaming rewrite:
144//!
145//! 1. Open source snapshot with reader for version N
146//! 2. Create destination snapshot with writer for version M
147//! 3. Stream all blocks through decompression/re-compression
148//! 4. Rebuild index in target format
149//! 5. Preserve metadata (encryption keys, parent references, etc.)
150//!
151//! ## Lossy Migrations
152//!
153//! Downgrading may lose features unsupported in the target version:
154//!
155//! - Version 2 → 1: Hypothetical new features would be discarded with warnings
156//!
157//! # Performance Considerations
158//!
159//! Version checking is performed once per snapshot open operation:
160//!
161//! - **Overhead**: Negligible (~100ns for integer comparison)
162//! - **Caching**: Version is cached in [`crate::api::file::File`] struct
163//! - **Hot Path**: Not in block read/write paths
164//!
165//! # Examples
166//!
167//! ## Basic Version Check
168//!
169//! ```
170//! use hexz_core::format::version::{check_version, VersionCompatibility};
171//!
172//! let snapshot_version = 1;
173//! let compat = check_version(snapshot_version);
174//!
175//! match compat {
176//!     VersionCompatibility::Full => {
177//!         println!("Snapshot is fully compatible");
178//!     }
179//!     VersionCompatibility::Incompatible => {
180//!         eprintln!("Cannot read snapshot (incompatible version)");
181//!     }
182//!     VersionCompatibility::Degraded => {
183//!         println!("Snapshot readable with warnings");
184//!     }
185//! }
186//! ```
187//!
188//! ## User-Facing Error Messages
189//!
190//! ```
191//! use hexz_core::format::version::compatibility_message;
192//!
193//! let version = 999;  // Future version
194//! let message = compatibility_message(version);
195//! println!("{}", message);
196//! // Output: "Version 999 is too new (max supported: 1). Please upgrade Hexz."
197//! ```
198//!
199//! ## Reader Implementation
200//!
201//! ```rust,ignore
202//! use hexz_core::format::version::check_version;
203//! use hexz_core::error::Error;
204//!
205//! fn open_snapshot(path: &Path) -> Result<Snapshot, Error> {
206//!     let header = read_header(path)?;
207//!
208//!     if !check_version(header.version).is_compatible() {
209//!         return Err(Error::IncompatibleVersion {
210//!             found: header.version,
211//!             min_supported: MIN_SUPPORTED_VERSION,
212//!             max_supported: MAX_SUPPORTED_VERSION,
213//!         });
214//!     }
215//!
216//!     // Proceed with version-aware deserialization
217//!     Ok(Snapshot { header, /* ... */ })
218//! }
219//! ```
220
221use std::fmt;
222
223/// Current format version written by this build of Hexz.
224///
225/// This constant defines the format version number written to the `version` field
226/// of new snapshot headers. It is incremented when the on-disk format changes in
227/// a way that requires readers to adapt their parsing logic.
228///
229/// # Incrementing Policy
230///
231/// Increment `CURRENT_VERSION` when:
232/// - Header fields are added/removed/reordered
233/// - Index structure changes (e.g., new page entry fields)
234/// - Serialization format changes (e.g., switch from bincode to another codec)
235/// - Block metadata semantics change
236///
237/// Do NOT increment for:
238/// - New compression algorithms (use header.compression field)
239/// - New encryption modes (use header.encryption field)
240/// - Performance optimizations that don't affect format
241///
242/// # Current Version
243///
244/// **Version 1** (initial release):
245/// - Two-level index (master index + pages)
246/// - bincode serialization
247/// - LZ4/Zstd compression support
248/// - Optional AES-256-GCM encryption
249/// - Thin provisioning via parent snapshots
250/// - Dual streams (disk + memory)
251pub const CURRENT_VERSION: u32 = 1;
252
253/// Minimum format version readable by this build.
254///
255/// Snapshots with `version < MIN_SUPPORTED_VERSION` are rejected with an
256/// [`VersionCompatibility::Incompatible`] error. Users must migrate such
257/// snapshots using `hexz-migrate upgrade` before reading.
258///
259/// # Rationale
260///
261/// Maintaining backward compatibility indefinitely is untenable as format
262/// complexity grows. This constant defines the "support horizon" for old
263/// snapshots. When incrementing, ensure migration tooling exists.
264///
265/// # Current Policy
266///
267/// - **Hexz 0.1.x - 0.9.x**: Support version 1 only
268/// - **Hexz 1.0+**: May drop support for version 1 (TBD based on adoption)
269pub const MIN_SUPPORTED_VERSION: u32 = 1;
270
271/// Maximum format version readable by this build.
272///
273/// Snapshots with `version > MAX_SUPPORTED_VERSION` are rejected unless
274/// degraded mode is enabled (not yet implemented). This prevents crashes
275/// when reading snapshots created by future Hexz versions.
276///
277/// # Forward Compatibility
278///
279/// Currently, Hexz uses strict versioning: any version mismatch results
280/// in incompatibility. Future releases may implement graceful degradation
281/// for minor version increments (e.g., ignore unknown header fields).
282///
283/// # Upgrade Path
284///
285/// If you encounter a snapshot with `version > MAX_SUPPORTED_VERSION`,
286/// upgrade Hexz to a newer release that supports that version.
287pub const MAX_SUPPORTED_VERSION: u32 = 1;
288
289/// Result of snapshot version compatibility analysis.
290///
291/// Returned by [`check_version`] to indicate whether a snapshot with a given
292/// format version can be read by this build of Hexz. This enum enables
293/// graceful handling of version mismatches with appropriate error messages.
294///
295/// # Variants
296///
297/// - **Full**: Version is within [`MIN_SUPPORTED_VERSION`]..=[`MAX_SUPPORTED_VERSION`]
298///   and all features are supported. Proceed with normal operations.
299///
300/// - **Degraded**: Version is newer than [`MAX_SUPPORTED_VERSION`] but forward
301///   compatibility rules allow partial reads with warnings. Unsupported features
302///   are ignored or substituted with defaults. (Not yet implemented; currently
303///   treated as Incompatible.)
304///
305/// - **Incompatible**: Version is outside the supported range and cannot be read.
306///   The user must upgrade Hexz (for newer snapshots) or migrate the snapshot
307///   (for older snapshots).
308///
309/// # Examples
310///
311/// ```
312/// use hexz_core::format::version::{check_version, VersionCompatibility};
313///
314/// let result = check_version(1);
315/// assert_eq!(result, VersionCompatibility::Full);
316///
317/// let result = check_version(999);
318/// assert_eq!(result, VersionCompatibility::Incompatible);
319/// assert!(!result.is_compatible());
320/// ```
321#[derive(Debug, Clone, Copy, PartialEq, Eq)]
322pub enum VersionCompatibility {
323    /// Fully supported version with all features available.
324    Full,
325    /// Newer version with partial support (warnings emitted, some features unavailable).
326    ///
327    /// This variant is reserved for future forward compatibility modes. Currently,
328    /// all versions outside the supported range are marked as Incompatible.
329    Degraded,
330    /// Incompatible version that cannot be read (too old or too new).
331    Incompatible,
332}
333
334impl VersionCompatibility {
335    /// Tests whether the snapshot can be read with this compatibility status.
336    ///
337    /// Returns `true` for [`Full`](VersionCompatibility::Full) and
338    /// [`Degraded`](VersionCompatibility::Degraded) compatibility, indicating
339    /// that read operations can proceed (possibly with warnings). Returns `false`
340    /// for [`Incompatible`](VersionCompatibility::Incompatible), indicating that
341    /// the snapshot must be rejected.
342    ///
343    /// # Returns
344    ///
345    /// - `true`: Snapshot can be opened (possibly with limited functionality)
346    /// - `false`: Snapshot cannot be opened (hard error)
347    ///
348    /// # Examples
349    ///
350    /// ```
351    /// use hexz_core::format::version::{check_version, VersionCompatibility};
352    ///
353    /// let compat = check_version(1);
354    /// if compat.is_compatible() {
355    ///     println!("Snapshot can be read");
356    /// } else {
357    ///     eprintln!("Snapshot is incompatible");
358    /// }
359    /// ```
360    pub fn is_compatible(&self) -> bool {
361        match self {
362            VersionCompatibility::Full | VersionCompatibility::Degraded => true,
363            VersionCompatibility::Incompatible => false,
364        }
365    }
366}
367
368/// Determines compatibility status of a snapshot format version.
369///
370/// This function implements the version negotiation logic by comparing the
371/// provided version number against the supported range defined by
372/// [`MIN_SUPPORTED_VERSION`] and [`MAX_SUPPORTED_VERSION`].
373///
374/// # Parameters
375///
376/// - `version`: The format version number read from a snapshot header
377///
378/// # Returns
379///
380/// A [`VersionCompatibility`] value indicating whether the snapshot can be read:
381///
382/// - [`Full`](VersionCompatibility::Full): Version is within supported range
383/// - [`Incompatible`](VersionCompatibility::Incompatible): Version is too old or too new
384/// - [`Degraded`](VersionCompatibility::Degraded): Currently unused (reserved for future)
385///
386/// # Version Ranges
387///
388/// | Condition | Result | Reason |
389/// |-----------|--------|--------|
390/// | `version < MIN_SUPPORTED_VERSION` | Incompatible | Too old, needs migration |
391/// | `MIN_SUPPORTED_VERSION <= version <= MAX_SUPPORTED_VERSION` | Full | Within supported range |
392/// | `version > MAX_SUPPORTED_VERSION` | Incompatible | Too new, upgrade Hexz |
393///
394/// # Future Extensions
395///
396/// In future versions, this function may return `Degraded` for snapshots with
397/// minor version mismatches, enabling partial reads with warnings. For example:
398///
399/// ```rust,ignore
400/// // Hypothetical future behavior with major.minor versioning
401/// if version.major == CURRENT_VERSION.major && version.minor > CURRENT_VERSION.minor {
402///     VersionCompatibility::Degraded  // Newer minor version, try best-effort read
403/// }
404/// ```
405///
406/// # Performance
407///
408/// This function performs two integer comparisons and has negligible overhead
409/// (~100ns). It is called once per snapshot open operation and does not affect
410/// hot path performance.
411///
412/// # Examples
413///
414/// ```
415/// use hexz_core::format::version::{
416///     check_version, VersionCompatibility,
417///     MIN_SUPPORTED_VERSION, MAX_SUPPORTED_VERSION
418/// };
419///
420/// // Version within supported range
421/// let compat = check_version(1);
422/// assert_eq!(compat, VersionCompatibility::Full);
423///
424/// // Version too old (hypothetical example if MIN_SUPPORTED_VERSION > 1)
425/// let compat = check_version(0);
426/// assert_eq!(compat, VersionCompatibility::Incompatible);
427///
428/// // Version too new
429/// let compat = check_version(MAX_SUPPORTED_VERSION + 1);
430/// assert_eq!(compat, VersionCompatibility::Incompatible);
431/// ```
432///
433/// ## Error Handling
434///
435/// ```rust,ignore
436/// use hexz_core::format::version::check_version;
437///
438/// fn open_snapshot(header: &Header) -> Result<Snapshot, Error> {
439///     let compat = check_version(header.version);
440///     if !compat.is_compatible() {
441///         return Err(Error::IncompatibleVersion {
442///             found: header.version,
443///             supported_range: (MIN_SUPPORTED_VERSION, MAX_SUPPORTED_VERSION),
444///         });
445///     }
446///     // Proceed with read operations
447///     Ok(Snapshot::new(header))
448/// }
449/// ```
450pub fn check_version(version: u32) -> VersionCompatibility {
451    if version < MIN_SUPPORTED_VERSION {
452        VersionCompatibility::Incompatible
453    } else if version > MAX_SUPPORTED_VERSION {
454        // For now, strict versioning. Future: Check major/minor for degraded mode.
455        VersionCompatibility::Incompatible
456    } else {
457        VersionCompatibility::Full
458    }
459}
460
461/// Generates a user-facing message describing version compatibility status.
462///
463/// This function produces human-readable error messages and recommendations
464/// for handling version mismatches. The messages are designed to be displayed
465/// directly to end users in error logs or CLI output.
466///
467/// # Parameters
468///
469/// - `version`: The format version number from a snapshot header
470///
471/// # Returns
472///
473/// A `String` containing:
474/// - **Full compatibility**: Confirmation message
475/// - **Degraded compatibility**: Warning about potential feature limitations
476/// - **Incompatibility**: Error message with actionable remediation steps
477///
478/// # Message Content
479///
480/// ## Fully Supported Version
481///
482/// ```text
483/// "Version 1 is fully supported."
484/// ```
485///
486/// ## Too Old (version < MIN_SUPPORTED_VERSION)
487///
488/// ```text
489/// "Version 0 is too old (min supported: 1). Please upgrade the snapshot."
490/// ```
491///
492/// Remediation: Use `hexz-migrate upgrade` to convert the snapshot.
493///
494/// ## Too New (version > MAX_SUPPORTED_VERSION)
495///
496/// ```text
497/// "Version 2 is too new (max supported: 1). Please upgrade Hexz."
498/// ```
499///
500/// Remediation: Install a newer version of the Hexz toolchain.
501///
502/// ## Degraded Compatibility (Future)
503///
504/// ```text
505/// "Version 2 is newer than supported (1), features may be missing."
506/// ```
507///
508/// Remediation: Upgrade Hexz for full feature support, or proceed with warnings.
509///
510/// # Usage in Error Handling
511///
512/// This function is typically called when displaying compatibility errors to users:
513///
514/// ```rust,ignore
515/// use hexz_core::format::version::{check_version, compatibility_message};
516///
517/// fn validate_snapshot(header: &Header) -> Result<(), String> {
518///     let compat = check_version(header.version);
519///     if !compat.is_compatible() {
520///         return Err(compatibility_message(header.version));
521///     }
522///     Ok(())
523/// }
524/// ```
525///
526/// # Examples
527///
528/// ```
529/// use hexz_core::format::version::{compatibility_message, MAX_SUPPORTED_VERSION};
530///
531/// // Supported version
532/// let msg = compatibility_message(1);
533/// assert!(msg.contains("fully supported"));
534///
535/// // Version too new
536/// let msg = compatibility_message(MAX_SUPPORTED_VERSION + 1);
537/// assert!(msg.contains("too new"));
538/// assert!(msg.contains("upgrade Hexz"));
539///
540/// // Version too old (hypothetical if MIN_SUPPORTED_VERSION > 1)
541/// let msg = compatibility_message(0);
542/// assert!(msg.contains("too old"));
543/// assert!(msg.contains("upgrade the snapshot"));
544/// ```
545///
546/// ## CLI Integration
547///
548/// ```bash
549/// $ hexz open old_snapshot.hxz
550/// Error: Version 0 is too old (min supported: 1). Please upgrade the snapshot.
551///
552/// Run: hexz-migrate upgrade old_snapshot.hxz new_snapshot.hxz
553/// ```
554pub fn compatibility_message(version: u32) -> String {
555    match check_version(version) {
556        VersionCompatibility::Full => format!("Version {} is fully supported.", version),
557        VersionCompatibility::Degraded => format!(
558            "Version {} is newer than supported ({}), features may be missing.",
559            version, MAX_SUPPORTED_VERSION
560        ),
561        VersionCompatibility::Incompatible => {
562            if version < MIN_SUPPORTED_VERSION {
563                format!(
564                    "Version {} is too old (min supported: {}). Please upgrade the snapshot.",
565                    version, MIN_SUPPORTED_VERSION
566                )
567            } else {
568                format!(
569                    "Version {} is too new (max supported: {}). Please upgrade Hexz.",
570                    version, MAX_SUPPORTED_VERSION
571                )
572            }
573        }
574    }
575}
576
577impl fmt::Display for VersionCompatibility {
578    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
579        match self {
580            VersionCompatibility::Full => write!(f, "full"),
581            VersionCompatibility::Degraded => write!(f, "degraded"),
582            VersionCompatibility::Incompatible => write!(f, "incompatible"),
583        }
584    }
585}
586
587#[cfg(test)]
588mod tests {
589    use super::*;
590
591    #[test]
592    #[allow(clippy::assertions_on_constants)]
593    fn test_version_constants_are_consistent() {
594        // MIN_SUPPORTED_VERSION must be <= MAX_SUPPORTED_VERSION
595        assert!(
596            MIN_SUPPORTED_VERSION <= MAX_SUPPORTED_VERSION,
597            "MIN_SUPPORTED_VERSION ({}) must be <= MAX_SUPPORTED_VERSION ({})",
598            MIN_SUPPORTED_VERSION,
599            MAX_SUPPORTED_VERSION
600        );
601
602        // CURRENT_VERSION must be within supported range
603        assert!(
604            CURRENT_VERSION >= MIN_SUPPORTED_VERSION,
605            "CURRENT_VERSION ({}) must be >= MIN_SUPPORTED_VERSION ({})",
606            CURRENT_VERSION,
607            MIN_SUPPORTED_VERSION
608        );
609        assert!(
610            CURRENT_VERSION <= MAX_SUPPORTED_VERSION,
611            "CURRENT_VERSION ({}) must be <= MAX_SUPPORTED_VERSION ({})",
612            CURRENT_VERSION,
613            MAX_SUPPORTED_VERSION
614        );
615    }
616
617    #[test]
618    fn test_check_version_current_is_fully_supported() {
619        let compat = check_version(CURRENT_VERSION);
620        assert_eq!(compat, VersionCompatibility::Full);
621        assert!(compat.is_compatible());
622    }
623
624    #[test]
625    fn test_check_version_within_range_is_full() {
626        // Test all versions in the supported range
627        for version in MIN_SUPPORTED_VERSION..=MAX_SUPPORTED_VERSION {
628            let compat = check_version(version);
629            assert_eq!(
630                compat,
631                VersionCompatibility::Full,
632                "Version {} should be fully compatible",
633                version
634            );
635            assert!(compat.is_compatible());
636        }
637    }
638
639    #[test]
640    fn test_check_version_too_old_is_incompatible() {
641        if MIN_SUPPORTED_VERSION > 0 {
642            let old_version = MIN_SUPPORTED_VERSION - 1;
643            let compat = check_version(old_version);
644            assert_eq!(compat, VersionCompatibility::Incompatible);
645            assert!(!compat.is_compatible());
646        }
647
648        // Always test version 0 as too old
649        let compat = check_version(0);
650        assert_eq!(compat, VersionCompatibility::Incompatible);
651        assert!(!compat.is_compatible());
652    }
653
654    #[test]
655    fn test_check_version_too_new_is_incompatible() {
656        let new_version = MAX_SUPPORTED_VERSION + 1;
657        let compat = check_version(new_version);
658        assert_eq!(compat, VersionCompatibility::Incompatible);
659        assert!(!compat.is_compatible());
660
661        // Test very high version number
662        let compat = check_version(9999);
663        assert_eq!(compat, VersionCompatibility::Incompatible);
664        assert!(!compat.is_compatible());
665    }
666
667    #[test]
668    fn test_version_compatibility_is_compatible() {
669        // Full is compatible
670        assert!(VersionCompatibility::Full.is_compatible());
671
672        // Degraded is compatible (with warnings)
673        assert!(VersionCompatibility::Degraded.is_compatible());
674
675        // Incompatible is not compatible
676        assert!(!VersionCompatibility::Incompatible.is_compatible());
677    }
678
679    #[test]
680    fn test_version_compatibility_display() {
681        assert_eq!(format!("{}", VersionCompatibility::Full), "full");
682        assert_eq!(format!("{}", VersionCompatibility::Degraded), "degraded");
683        assert_eq!(
684            format!("{}", VersionCompatibility::Incompatible),
685            "incompatible"
686        );
687    }
688
689    #[test]
690    fn test_compatibility_message_for_supported_version() {
691        let msg = compatibility_message(CURRENT_VERSION);
692        assert!(msg.contains("fully supported"), "Message: {}", msg);
693        assert!(
694            msg.contains(&CURRENT_VERSION.to_string()),
695            "Message: {}",
696            msg
697        );
698    }
699
700    #[test]
701    fn test_compatibility_message_for_too_old_version() {
702        if MIN_SUPPORTED_VERSION > 0 {
703            let old_version = MIN_SUPPORTED_VERSION - 1;
704            let msg = compatibility_message(old_version);
705            assert!(msg.contains("too old"), "Message: {}", msg);
706            assert!(
707                msg.contains(&MIN_SUPPORTED_VERSION.to_string()),
708                "Message: {}",
709                msg
710            );
711            assert!(msg.contains("upgrade the snapshot"), "Message: {}", msg);
712        }
713    }
714
715    #[test]
716    fn test_compatibility_message_for_too_new_version() {
717        let new_version = MAX_SUPPORTED_VERSION + 1;
718        let msg = compatibility_message(new_version);
719        assert!(msg.contains("too new"), "Message: {}", msg);
720        assert!(
721            msg.contains(&MAX_SUPPORTED_VERSION.to_string()),
722            "Message: {}",
723            msg
724        );
725        assert!(msg.contains("upgrade Hexz"), "Message: {}", msg);
726    }
727
728    #[test]
729    fn test_compatibility_message_for_degraded() {
730        // Since we don't currently return Degraded, we can't directly test it
731        // But we can test the message format by checking what would happen
732        let msg = match VersionCompatibility::Degraded {
733            VersionCompatibility::Degraded => format!(
734                "Version {} is newer than supported ({}), features may be missing.",
735                99, MAX_SUPPORTED_VERSION
736            ),
737            _ => String::new(),
738        };
739        assert!(msg.contains("newer than supported"));
740        assert!(msg.contains("features may be missing"));
741    }
742
743    #[test]
744    fn test_version_compatibility_enum_properties() {
745        // Test Debug trait
746        let full = VersionCompatibility::Full;
747        assert!(format!("{:?}", full).contains("Full"));
748
749        // Test Clone and Copy
750        let degraded = VersionCompatibility::Degraded;
751        let degraded_copy = degraded;
752        let degraded_clone = degraded;
753        assert_eq!(degraded, degraded_copy);
754        assert_eq!(degraded, degraded_clone);
755
756        // Test PartialEq and Eq
757        assert_eq!(VersionCompatibility::Full, VersionCompatibility::Full);
758        assert_ne!(
759            VersionCompatibility::Full,
760            VersionCompatibility::Incompatible
761        );
762    }
763
764    #[test]
765    fn test_check_version_boundary_conditions() {
766        // Test MIN_SUPPORTED_VERSION boundary
767        let compat = check_version(MIN_SUPPORTED_VERSION);
768        assert_eq!(compat, VersionCompatibility::Full);
769
770        // Test MAX_SUPPORTED_VERSION boundary
771        let compat = check_version(MAX_SUPPORTED_VERSION);
772        assert_eq!(compat, VersionCompatibility::Full);
773
774        // Test just below MIN
775        if MIN_SUPPORTED_VERSION > 0 {
776            let compat = check_version(MIN_SUPPORTED_VERSION - 1);
777            assert_eq!(compat, VersionCompatibility::Incompatible);
778        }
779
780        // Test just above MAX
781        let compat = check_version(MAX_SUPPORTED_VERSION + 1);
782        assert_eq!(compat, VersionCompatibility::Incompatible);
783    }
784
785    #[test]
786    fn test_multiple_version_checks() {
787        // Verify check_version is consistent across multiple calls
788        let version = CURRENT_VERSION;
789        let result1 = check_version(version);
790        let result2 = check_version(version);
791        assert_eq!(result1, result2);
792    }
793
794    #[test]
795    fn test_compatibility_message_contains_version_number() {
796        // Test that messages include the actual version number
797        for version in [0u32, 1, 2, 99, 999] {
798            let msg = compatibility_message(version);
799            assert!(
800                msg.contains(&version.to_string()),
801                "Message for version {} should contain the version number: {}",
802                version,
803                msg
804            );
805        }
806    }
807
808    #[test]
809    #[allow(clippy::assertions_on_constants)]
810    fn test_version_constants_are_reasonable() {
811        // Ensure version numbers are in a reasonable range
812        assert!(CURRENT_VERSION > 0, "CURRENT_VERSION must be positive");
813        assert!(
814            CURRENT_VERSION < 1000,
815            "CURRENT_VERSION seems unreasonably high"
816        );
817        assert!(
818            MIN_SUPPORTED_VERSION > 0,
819            "MIN_SUPPORTED_VERSION must be positive"
820        );
821        assert!(
822            MAX_SUPPORTED_VERSION < 1000,
823            "MAX_SUPPORTED_VERSION seems unreasonably high"
824        );
825    }
826}