scirs2_core/versioning/
mod.rs

1//! # API Versioning Infrastructure
2//!
3//! Production-grade API versioning system for `SciRS2` Core providing semantic
4//! versioning, backward compatibility guarantees, and version negotiation
5//! capabilities for enterprise deployments and long-term API stability.
6//!
7//! ## Features
8//!
9//! - Semantic versioning (`SemVer`) compliance with custom extensions
10//! - Backward compatibility enforcement and validation
11//! - API version negotiation and client-server compatibility
12//! - Breaking change detection and migration assistance
13//! - Version deprecation management with transition periods
14//! - API evolution tracking and documentation generation
15//! - Enterprise-grade stability guarantees
16//! - Integration with CI/CD pipelines for automated compatibility testing
17//!
18//! ## Modules
19//!
20//! - `semantic`: Semantic versioning implementation with `SciRS2` extensions
21//! - `compatibility`: Backward compatibility checking and enforcement
22//! - `negotiation`: Version negotiation between clients and servers
23//! - `migration`: Migration assistance for API upgrades
24//! - `deprecation`: Deprecation management and transition planning
25//!
26//! ## Example
27//!
28//! ```rust
29//! use scirs2_core::versioning::{Version, VersionManager, CompatibilityLevel, ApiVersionBuilder, ClientCapabilities};
30//!
31//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
32//! // Create version manager
33//! let mut version_manager = VersionManager::new();
34//!
35//! // Register API versions using the builder pattern
36//! let v1_0_0 = ApiVersionBuilder::new(Version::parse("1.0.0")?)
37//!     .new_feature("Initial API release")
38//!     .build()?;
39//! let v1_1_0 = ApiVersionBuilder::new(Version::parse("1.1.0")?)
40//!     .new_feature("Added new computation methods")
41//!     .build()?;
42//! let v2_0_0 = ApiVersionBuilder::new(Version::parse("2.0.0")?)
43//!     .breaking_change("Changed function signatures")
44//!     .build()?;
45//!
46//! version_manager.registerversion(v1_0_0.clone())?;
47//! version_manager.registerversion(v1_1_0.clone())?;
48//! version_manager.registerversion(v2_0_0.clone())?;
49//!
50//! // Check compatibility
51//! let compat = version_manager.check_compatibility(&v1_0_0.version, &v1_1_0.version)?;
52//! assert_eq!(compat, CompatibilityLevel::BackwardCompatible);
53//!
54//! // Negotiate version with client capabilities
55//! let client_caps = ClientCapabilities::new("test_client".to_string(), Version::parse("1.0.5")?);
56//! let negotiated = version_manager.negotiateversion(&client_caps)?;
57//! assert!(negotiated.negotiated_version.major() >= 1);
58//! # Ok(())
59//! # }
60//! ```
61
62pub mod compatibility;
63pub mod deprecation;
64pub mod migration;
65pub mod negotiation;
66pub mod semantic;
67
68use crate::error::CoreError;
69use std::collections::{BTreeSet, HashMap};
70
71#[cfg(feature = "serialization")]
72use serde::{Deserialize, Serialize};
73
74// Re-export main types
75pub use compatibility::{CompatibilityChecker, CompatibilityLevel, CompatibilityReport};
76pub use deprecation::{DeprecationManager, DeprecationPolicy, DeprecationStatus};
77pub use migration::{MigrationManager, MigrationPlan, MigrationStep};
78pub use negotiation::{ClientCapabilities, NegotiationResult, VersionNegotiator};
79pub use semantic::{Version, VersionBuilder, VersionConstraint, VersionRange};
80
81/// API version information with metadata
82#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
83#[derive(Debug, Clone, PartialEq, Eq)]
84pub struct ApiVersion {
85    /// The semantic version
86    pub version: Version,
87    /// Release date
88    pub release_date: chrono::DateTime<chrono::Utc>,
89    /// Stability level
90    pub stability: StabilityLevel,
91    /// Support status
92    pub support_status: SupportStatus,
93    /// End of life date (if applicable)
94    pub end_of_life: Option<chrono::DateTime<chrono::Utc>>,
95    /// Feature flags supported in this version
96    pub features: BTreeSet<String>,
97    /// Breaking changes from previous version
98    pub breakingchanges: Vec<String>,
99    /// New features in this version
100    pub new_features: Vec<String>,
101    /// Bug fixes in this version
102    pub bug_fixes: Vec<String>,
103    /// Deprecated features in this version
104    pub deprecated_features: Vec<String>,
105    /// Minimum client version required
106    pub min_clientversion: Option<Version>,
107    /// Maximum client version supported
108    pub max_clientversion: Option<Version>,
109}
110
111/// API stability levels
112#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
113#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
114pub enum StabilityLevel {
115    /// Experimental - subject to breaking changes
116    Experimental,
117    /// Alpha - feature complete but may have breaking changes
118    Alpha,
119    /// Beta - stable API but may have minor breaking changes
120    Beta,
121    /// Stable - backward compatible changes only
122    Stable,
123    /// Mature - minimal changes, long-term support
124    Mature,
125    /// Legacy - deprecated but still supported
126    Legacy,
127}
128
129impl StabilityLevel {
130    /// Get the string representation
131    #[must_use]
132    pub const fn as_str(&self) -> &'static str {
133        match self {
134            Self::Experimental => "experimental",
135            Self::Alpha => "alpha",
136            Self::Beta => "beta",
137            Self::Stable => "stable",
138            Self::Mature => "mature",
139            Self::Legacy => "legacy",
140        }
141    }
142}
143
144/// Support status for API versions
145#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
146#[derive(Debug, Clone, Copy, PartialEq, Eq)]
147pub enum SupportStatus {
148    /// Active development and support
149    Active,
150    /// Maintenance mode - bug fixes only
151    Maintenance,
152    /// Deprecated - migration encouraged
153    Deprecated,
154    /// End of life - no longer supported
155    EndOfLife,
156    /// Security updates only
157    SecurityOnly,
158}
159
160impl SupportStatus {
161    /// Get the string representation
162    #[must_use]
163    pub const fn as_str(&self) -> &'static str {
164        match self {
165            Self::Active => "active",
166            Self::Maintenance => "maintenance",
167            Self::Deprecated => "deprecated",
168            Self::EndOfLife => "end_of_life",
169            Self::SecurityOnly => "security_only",
170        }
171    }
172}
173
174/// Version manager for coordinating all versioning operations
175pub struct VersionManager {
176    /// Registered API versions
177    versions: HashMap<Version, ApiVersion>,
178    /// Current active version
179    currentversion: Option<Version>,
180    /// Compatibility checker
181    compatibility_checker: CompatibilityChecker,
182    /// Version negotiator
183    negotiator: VersionNegotiator,
184    /// Migration manager
185    migration_manager: MigrationManager,
186    /// Deprecation manager
187    deprecation_manager: DeprecationManager,
188}
189
190impl VersionManager {
191    /// Create a new version manager
192    #[must_use]
193    pub fn new() -> Self {
194        Self {
195            versions: HashMap::new(),
196            currentversion: None,
197            compatibility_checker: CompatibilityChecker::new(),
198            negotiator: VersionNegotiator::new(),
199            migration_manager: MigrationManager::new(),
200            deprecation_manager: DeprecationManager::new(),
201        }
202    }
203
204    /// Register an API version
205    ///
206    /// # Errors
207    ///
208    /// Returns an error if the version is already registered.
209    pub fn registerversion(&mut self, apiversion: ApiVersion) -> Result<(), CoreError> {
210        let version = apiversion.version.clone();
211
212        // Validate version is not already registered
213        if self.versions.contains_key(&version) {
214            return Err(CoreError::ComputationError(
215                crate::error::ErrorContext::new(format!("Version {version} is already registered")),
216            ));
217        }
218
219        // Register with compatibility checker
220        self.compatibility_checker.register_version(&apiversion)?;
221
222        // Register with migration manager
223        self.migration_manager.register_version(&apiversion)?;
224
225        // Register with deprecation manager
226        self.deprecation_manager.register_version(&apiversion)?;
227
228        self.versions.insert(version, apiversion);
229        Ok(())
230    }
231
232    /// Set the current active version
233    ///
234    /// # Errors
235    ///
236    /// Returns an error if the version is not registered.
237    pub fn set_currentversion(&mut self, version: Version) -> Result<(), CoreError> {
238        if !self.versions.contains_key(&version) {
239            return Err(CoreError::ComputationError(
240                crate::error::ErrorContext::new(format!("Version {version} is not registered")),
241            ));
242        }
243
244        self.currentversion = Some(version);
245        Ok(())
246    }
247
248    /// Get the current active version
249    #[must_use]
250    pub fn currentversion(&self) -> Option<&Version> {
251        self.currentversion.as_ref()
252    }
253
254    /// Get all registered versions
255    #[must_use]
256    pub fn getversions(&self) -> Vec<&ApiVersion> {
257        let mut versions: Vec<_> = self.versions.values().collect();
258        versions.sort_by(|a, b| a.version.cmp(&b.version));
259        versions
260    }
261
262    /// Get supported versions (active and maintenance)
263    #[must_use]
264    pub fn get_supportedversions(&self) -> Vec<&ApiVersion> {
265        self.versions
266            .values()
267            .filter(|v| {
268                matches!(
269                    v.support_status,
270                    SupportStatus::Active | SupportStatus::Maintenance
271                )
272            })
273            .collect()
274    }
275
276    /// Get version by version number
277    #[must_use]
278    pub fn getversion(&self, version: &Version) -> Option<&ApiVersion> {
279        self.versions.get(version)
280    }
281
282    /// Check compatibility between two versions
283    ///
284    /// # Errors
285    ///
286    /// Returns an error if compatibility checking fails.
287    pub fn check_compatibility(
288        &self,
289        fromversion: &Version,
290        toversion: &Version,
291    ) -> Result<CompatibilityLevel, CoreError> {
292        self.compatibility_checker
293            .check_compatibility(fromversion, toversion)
294    }
295
296    /// Get detailed compatibility report
297    ///
298    /// # Errors
299    ///
300    /// Returns an error if compatibility report generation fails.
301    pub fn get_compatibility_report(
302        &self,
303        fromversion: &Version,
304        toversion: &Version,
305    ) -> Result<CompatibilityReport, CoreError> {
306        self.compatibility_checker
307            .get_compatibility_report(fromversion, toversion)
308    }
309
310    /// Negotiate version with client
311    ///
312    /// # Errors
313    ///
314    /// Returns an error if version negotiation fails.
315    pub fn negotiateversion(
316        &self,
317        client_capabilities: &ClientCapabilities,
318    ) -> Result<NegotiationResult, CoreError> {
319        let supportedversions: Vec<_> = self
320            .get_supportedversions()
321            .into_iter()
322            .map(|v| &v.version)
323            .collect();
324
325        self.negotiator
326            .negotiate(client_capabilities, &supportedversions)
327    }
328
329    /// Get migration plan between versions
330    ///
331    /// # Errors
332    ///
333    /// Returns an error if migration plan generation fails.
334    pub fn get_migration_plan(
335        &self,
336        fromversion: &Version,
337        toversion: &Version,
338    ) -> Result<MigrationPlan, CoreError> {
339        self.migration_manager
340            .create_migration_plan(fromversion, toversion)
341    }
342
343    /// Check if a version is deprecated
344    #[must_use]
345    pub fn isversion_deprecated(&self, version: &Version) -> bool {
346        if let Some(apiversion) = self.versions.get(version) {
347            matches!(
348                apiversion.support_status,
349                SupportStatus::Deprecated | SupportStatus::EndOfLife
350            )
351        } else {
352            false
353        }
354    }
355
356    /// Get deprecation information for a version
357    #[must_use]
358    pub fn get_deprecation_status(&self, version: &Version) -> Option<DeprecationStatus> {
359        self.deprecation_manager.get_deprecation_status(version)
360    }
361
362    /// Update deprecation status
363    ///
364    /// # Errors
365    ///
366    /// Returns an error if the deprecation status update fails.
367    pub fn update_deprecation_status(
368        &mut self,
369        version: &Version,
370        status: DeprecationStatus,
371    ) -> Result<(), CoreError> {
372        self.deprecation_manager.update_status(version, status)
373    }
374
375    /// Get the latest version in a major version line
376    #[must_use]
377    pub fn get_latest_in_major(&self, major: u64) -> Option<&ApiVersion> {
378        self.versions
379            .values()
380            .filter(|v| v.version.major() == major)
381            .max_by(|a, b| a.version.cmp(&b.version))
382    }
383
384    /// Get the latest stable version
385    #[must_use]
386    pub fn get_latest_stable(&self) -> Option<&ApiVersion> {
387        self.versions
388            .values()
389            .filter(|v| {
390                v.stability == StabilityLevel::Stable || v.stability == StabilityLevel::Mature
391            })
392            .filter(|v| v.support_status == SupportStatus::Active)
393            .max_by(|a, b| a.version.cmp(&b.version))
394    }
395
396    /// Check if an upgrade path exists
397    #[must_use]
398    pub fn has_upgrade_path(&self, fromversion: &Version, toversion: &Version) -> bool {
399        self.migration_manager
400            .has_migration_path(fromversion, toversion)
401    }
402
403    /// Validate version constraints
404    ///
405    /// # Errors
406    ///
407    /// Returns an error if validation fails.
408    pub fn validate_constraint(
409        &self,
410        constraint: &VersionConstraint,
411    ) -> Result<Vec<&Version>, CoreError> {
412        let matchingversions: Vec<_> = self
413            .versions
414            .keys()
415            .filter(|v| constraint.matches(v))
416            .collect();
417
418        Ok(matchingversions)
419    }
420
421    /// Get version statistics
422    #[must_use]
423    pub fn getversion_statistics(&self) -> VersionStatistics {
424        let mut stats = VersionStatistics::default();
425
426        for apiversion in self.versions.values() {
427            stats.totalversions += 1;
428
429            match apiversion.stability {
430                StabilityLevel::Experimental => stats.experimentalversions += 1,
431                StabilityLevel::Alpha => stats.alphaversions += 1,
432                StabilityLevel::Beta => stats.betaversions += 1,
433                StabilityLevel::Stable => stats.stableversions += 1,
434                StabilityLevel::Mature => stats.matureversions += 1,
435                StabilityLevel::Legacy => stats.legacyversions += 1,
436            }
437
438            match apiversion.support_status {
439                SupportStatus::Active => stats.activeversions += 1,
440                SupportStatus::Maintenance => stats.maintenanceversions += 1,
441                SupportStatus::Deprecated => stats.deprecatedversions += 1,
442                SupportStatus::EndOfLife => stats.end_of_lifeversions += 1,
443                SupportStatus::SecurityOnly => stats.security_onlyversions += 1,
444            }
445        }
446
447        stats
448    }
449
450    /// Perform version maintenance tasks
451    ///
452    /// # Errors
453    ///
454    /// Returns an error if maintenance tasks fail.
455    pub fn perform_maintenance(&mut self) -> Result<MaintenanceReport, CoreError> {
456        let mut report = MaintenanceReport::default();
457        let now = chrono::Utc::now();
458
459        // Check for expired versions
460        for (version, apiversion) in &mut self.versions {
461            if let Some(eol_date) = apiversion.end_of_life {
462                if now > eol_date && apiversion.support_status != SupportStatus::EndOfLife {
463                    apiversion.support_status = SupportStatus::EndOfLife;
464                    report.versions_marked_eol.push(version.clone());
465                }
466            }
467        }
468
469        // Update deprecation statuses
470        let deprecation_updates = self.deprecation_manager.perform_maintenance()?;
471        report.deprecation_updates = deprecation_updates.len();
472
473        // Clean up old migration plans
474        let migration_cleanup = self.migration_manager.cleanup_old_plans()?;
475        report.migration_plans_cleaned = migration_cleanup;
476
477        Ok(report)
478    }
479}
480
481impl Default for VersionManager {
482    fn default() -> Self {
483        Self::new()
484    }
485}
486
487/// Version statistics for monitoring and reporting
488#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
489#[derive(Debug, Clone, Default)]
490pub struct VersionStatistics {
491    /// Total number of registered versions
492    pub totalversions: usize,
493    /// Experimental versions
494    pub experimentalversions: usize,
495    /// Alpha versions
496    pub alphaversions: usize,
497    /// Beta versions
498    pub betaversions: usize,
499    /// Stable versions
500    pub stableversions: usize,
501    /// Mature versions
502    pub matureversions: usize,
503    /// Legacy versions
504    pub legacyversions: usize,
505    /// Active versions
506    pub activeversions: usize,
507    /// Maintenance versions
508    pub maintenanceversions: usize,
509    /// Deprecated versions
510    pub deprecatedversions: usize,
511    /// End of life versions
512    pub end_of_lifeversions: usize,
513    /// Security only versions
514    pub security_onlyversions: usize,
515}
516
517/// Maintenance report for version management operations
518#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
519#[derive(Debug, Clone, Default)]
520pub struct MaintenanceReport {
521    /// Versions marked as end of life
522    pub versions_marked_eol: Vec<Version>,
523    /// Number of deprecation status updates
524    pub deprecation_updates: usize,
525    /// Number of migration plans cleaned up
526    pub migration_plans_cleaned: usize,
527}
528
529/// Builder for creating API versions
530pub struct ApiVersionBuilder {
531    version: Option<Version>,
532    release_date: chrono::DateTime<chrono::Utc>,
533    stability: StabilityLevel,
534    support_status: SupportStatus,
535    end_of_life: Option<chrono::DateTime<chrono::Utc>>,
536    features: BTreeSet<String>,
537    breakingchanges: Vec<String>,
538    new_features: Vec<String>,
539    bug_fixes: Vec<String>,
540    deprecated_features: Vec<String>,
541    min_clientversion: Option<Version>,
542    max_clientversion: Option<Version>,
543}
544
545impl ApiVersionBuilder {
546    /// Create a new API version builder
547    #[must_use]
548    pub fn new(version: Version) -> Self {
549        Self {
550            version: Some(version),
551            release_date: chrono::Utc::now(),
552            stability: StabilityLevel::Stable,
553            support_status: SupportStatus::Active,
554            end_of_life: None,
555            features: BTreeSet::new(),
556            breakingchanges: Vec::new(),
557            new_features: Vec::new(),
558            bug_fixes: Vec::new(),
559            deprecated_features: Vec::new(),
560            min_clientversion: None,
561            max_clientversion: None,
562        }
563    }
564
565    /// Set release date
566    #[must_use]
567    pub fn release_date(mut self, date: chrono::DateTime<chrono::Utc>) -> Self {
568        self.release_date = date;
569        self
570    }
571
572    /// Set stability level
573    #[must_use]
574    pub fn stability(mut self, stability: StabilityLevel) -> Self {
575        self.stability = stability;
576        self
577    }
578
579    /// Set support status
580    #[must_use]
581    pub fn support_status(mut self, status: SupportStatus) -> Self {
582        self.support_status = status;
583        self
584    }
585
586    /// Set end of life date
587    #[must_use]
588    pub fn end_of_life(mut self, date: chrono::DateTime<chrono::Utc>) -> Self {
589        self.end_of_life = Some(date);
590        self
591    }
592
593    /// Add a feature
594    #[must_use]
595    pub fn feature(mut self, feature: &str) -> Self {
596        self.features.insert(feature.to_string());
597        self
598    }
599
600    /// Add a breaking change
601    #[must_use]
602    pub fn breaking_change(mut self, change: &str) -> Self {
603        self.breakingchanges.push(change.to_string());
604        self
605    }
606
607    /// Add a new feature
608    #[must_use]
609    pub fn new_feature(mut self, feature: &str) -> Self {
610        self.new_features.push(feature.to_string());
611        self
612    }
613
614    /// Add a bug fix
615    #[must_use]
616    pub fn bug_fix(mut self, fix: &str) -> Self {
617        self.bug_fixes.push(fix.to_string());
618        self
619    }
620
621    /// Add a deprecated feature
622    #[must_use]
623    pub fn deprecated_feature(mut self, feature: &str) -> Self {
624        self.deprecated_features.push(feature.to_string());
625        self
626    }
627
628    /// Set minimum client version
629    #[must_use]
630    pub fn min_clientversion(mut self, version: Version) -> Self {
631        self.min_clientversion = Some(version);
632        self
633    }
634
635    /// Set maximum client version
636    #[must_use]
637    pub fn max_clientversion(mut self, version: Version) -> Self {
638        self.max_clientversion = Some(version);
639        self
640    }
641
642    /// Build the API version
643    ///
644    /// # Errors
645    ///
646    /// Returns an error if the version is not set.
647    pub fn build(self) -> Result<ApiVersion, CoreError> {
648        let version = self.version.ok_or_else(|| {
649            CoreError::ComputationError(crate::error::ErrorContext::new(
650                "Version is required".to_string(),
651            ))
652        })?;
653
654        Ok(ApiVersion {
655            version,
656            release_date: self.release_date,
657            stability: self.stability,
658            support_status: self.support_status,
659            end_of_life: self.end_of_life,
660            features: self.features,
661            breakingchanges: self.breakingchanges,
662            new_features: self.new_features,
663            bug_fixes: self.bug_fixes,
664            deprecated_features: self.deprecated_features,
665            min_clientversion: self.min_clientversion,
666            max_clientversion: self.max_clientversion,
667        })
668    }
669}
670
671#[cfg(test)]
672mod tests {
673    use super::*;
674
675    #[test]
676    fn testversion_manager_creation() {
677        let manager = VersionManager::new();
678        assert!(manager.currentversion().is_none());
679        assert_eq!(manager.getversions().len(), 0);
680    }
681
682    #[test]
683    fn test_apiversion_builder() {
684        let version = Version::parse("1.0.0").unwrap();
685        let apiversion = ApiVersionBuilder::new(version)
686            .stability(StabilityLevel::Stable)
687            .feature("feature1")
688            .new_feature("New awesome feature")
689            .build()
690            .unwrap();
691
692        assert_eq!(apiversion.version.to_string(), "1.0.0");
693        assert_eq!(apiversion.stability, StabilityLevel::Stable);
694        assert!(apiversion.features.contains("feature1"));
695        assert_eq!(apiversion.new_features.len(), 1);
696    }
697
698    #[test]
699    fn testversion_registration() {
700        let mut manager = VersionManager::new();
701        let version = Version::parse("1.0.0").unwrap();
702        let apiversion = ApiVersionBuilder::new(version.clone()).build().unwrap();
703
704        manager.registerversion(apiversion).unwrap();
705        assert_eq!(manager.getversions().len(), 1);
706        assert!(manager.getversion(&version).is_some());
707    }
708
709    #[test]
710    fn test_currentversion_setting() {
711        let mut manager = VersionManager::new();
712        let version = Version::parse("1.0.0").unwrap();
713        let apiversion = ApiVersionBuilder::new(version.clone()).build().unwrap();
714
715        manager.registerversion(apiversion).unwrap();
716        manager.set_currentversion(version.clone()).unwrap();
717        assert_eq!(manager.currentversion(), Some(&version));
718    }
719
720    #[test]
721    fn test_stability_levels() {
722        assert_eq!(StabilityLevel::Experimental.as_str(), "experimental");
723        assert_eq!(StabilityLevel::Stable.as_str(), "stable");
724        assert_eq!(StabilityLevel::Mature.as_str(), "mature");
725
726        assert!(StabilityLevel::Experimental < StabilityLevel::Alpha);
727        assert!(StabilityLevel::Stable > StabilityLevel::Beta);
728    }
729
730    #[test]
731    fn test_support_status() {
732        assert_eq!(SupportStatus::Active.as_str(), "active");
733        assert_eq!(SupportStatus::Deprecated.as_str(), "deprecated");
734        assert_eq!(SupportStatus::EndOfLife.as_str(), "end_of_life");
735    }
736
737    #[test]
738    fn testversion_statistics() {
739        let mut manager = VersionManager::new();
740
741        // Add some versions
742        let v1 = ApiVersionBuilder::new(Version::parse("1.0.0").unwrap())
743            .stability(StabilityLevel::Stable)
744            .build()
745            .unwrap();
746        let v2 = ApiVersionBuilder::new(Version::parse("2.0.0").unwrap())
747            .stability(StabilityLevel::Beta)
748            .support_status(SupportStatus::Maintenance)
749            .build()
750            .unwrap();
751
752        manager.registerversion(v1).unwrap();
753        manager.registerversion(v2).unwrap();
754
755        let stats = manager.getversion_statistics();
756        assert_eq!(stats.totalversions, 2);
757        assert_eq!(stats.stableversions, 1);
758        assert_eq!(stats.betaversions, 1);
759        assert_eq!(stats.activeversions, 1);
760        assert_eq!(stats.maintenanceversions, 1);
761    }
762}