use crate::{
config::Config,
versioning::{
ActiveDeployment, ModelMetadata, ModelVersion, ModelVersionManager, RollbackReason,
RollbackRecord, SemanticVersion, TriggerType, VersionStatus, VersioningConfig,
},
};
use anyhow::{Result, anyhow};
use clap::{Args, Subcommand, ValueEnum};
use serde_json;
use std::{collections::HashMap, path::PathBuf};
#[derive(Args)]
pub struct VersioningArgs {
#[command(subcommand)]
pub command: VersioningCommand,
}
#[derive(Subcommand)]
pub enum VersioningCommand {
#[command(about = "Create a new model version")]
Create {
#[arg(help = "Model name")]
model_name: String,
#[arg(help = "Path to model file")]
model_file: PathBuf,
#[arg(long, help = "Version string (e.g., 1.2.3)")]
version: Option<String>,
#[arg(long, help = "Model description")]
description: Option<String>,
#[arg(long, help = "Model type (e.g., llm, vision, embedding)")]
model_type: String,
#[arg(long, help = "Architecture (e.g., transformer, cnn, rnn)")]
architecture: String,
#[arg(long, help = "Framework (e.g., pytorch, tensorflow, onnx)")]
framework: String,
#[arg(long, help = "Framework version")]
framework_version: String,
#[arg(long, help = "Number of parameters")]
parameters: Option<u64>,
#[arg(long, help = "File format (e.g., gguf, onnx, safetensors)")]
format: String,
#[arg(long, help = "Tags (comma-separated)")]
tags: Option<String>,
#[arg(long, help = "Created by")]
created_by: String,
},
#[command(about = "List model versions")]
List {
#[arg(help = "Model name (optional - lists all models if not specified)")]
model_name: Option<String>,
#[arg(long, help = "Show detailed information")]
detailed: bool,
#[arg(long, help = "Filter by status")]
status: Option<VersionStatusArg>,
#[arg(long, help = "Output format", default_value = "table")]
format: OutputFormat,
},
#[command(about = "Show model version details")]
Show {
#[arg(help = "Model name")]
model_name: String,
#[arg(help = "Version ID")]
version_id: String,
#[arg(long, help = "Show metadata")]
metadata: bool,
#[arg(long, help = "Show deployment info")]
deployments: bool,
},
#[command(about = "Promote a version to a new status")]
Promote {
#[arg(help = "Model name")]
model_name: String,
#[arg(help = "Version ID")]
version_id: String,
#[arg(help = "Target status")]
status: VersionStatusArg,
#[arg(long, help = "Promoted by")]
promoted_by: String,
},
#[command(about = "Deploy a version to an environment")]
Deploy {
#[arg(help = "Model name")]
model_name: String,
#[arg(help = "Version ID")]
version_id: String,
#[arg(help = "Environment (e.g., staging, production)")]
environment: String,
#[arg(long, help = "Deployment configuration (JSON)")]
config: Option<String>,
},
#[command(about = "Rollback to a previous version")]
Rollback {
#[arg(help = "Model name")]
model_name: String,
#[arg(help = "Target version ID")]
version_id: String,
#[arg(help = "Environment")]
environment: String,
#[arg(long, help = "Rollback reason", default_value = "manual")]
reason: RollbackReasonArg,
#[arg(long, help = "Triggered by")]
triggered_by: String,
#[arg(long, help = "Force rollback without confirmation")]
force: bool,
},
#[command(about = "Delete a model version")]
Delete {
#[arg(help = "Model name")]
model_name: String,
#[arg(help = "Version ID")]
version_id: String,
#[arg(long, help = "Force deletion without confirmation")]
force: bool,
},
#[command(about = "Compare two versions")]
Compare {
#[arg(help = "Model name")]
model_name: String,
#[arg(help = "First version ID")]
version1: String,
#[arg(help = "Second version ID")]
version2: String,
#[arg(long, help = "Compare metadata")]
metadata: bool,
#[arg(long, help = "Compare performance metrics")]
performance: bool,
},
#[command(about = "Show rollback history")]
History {
#[arg(help = "Model name (optional)")]
model_name: Option<String>,
#[arg(long, help = "Maximum number of records", default_value = "20")]
limit: usize,
#[arg(long, help = "Output format", default_value = "table")]
format: OutputFormat,
},
#[command(about = "Show active deployments")]
Deployments {
#[arg(help = "Model name (optional)")]
model_name: Option<String>,
#[arg(long, help = "Environment filter")]
environment: Option<String>,
#[arg(long, help = "Output format", default_value = "table")]
format: OutputFormat,
},
#[command(about = "Export version data")]
Export {
#[arg(help = "Model name")]
model_name: String,
#[arg(help = "Version ID")]
version_id: String,
#[arg(help = "Output file path")]
output: PathBuf,
#[arg(long, help = "Export format", default_value = "json")]
format: ExportFormat,
#[arg(long, help = "Include model file")]
include_file: bool,
},
#[command(about = "Import version data")]
Import {
#[arg(help = "Import file path")]
input: PathBuf,
#[arg(long, help = "Import format", default_value = "json")]
format: ExportFormat,
#[arg(long, help = "Force overwrite existing versions")]
force: bool,
},
#[command(about = "Validate version integrity")]
Validate {
#[arg(help = "Model name")]
model_name: String,
#[arg(help = "Version ID (optional - validates all if not specified)")]
version_id: Option<String>,
#[arg(long, help = "Fix checksum mismatches")]
fix: bool,
},
#[command(about = "Show registry information")]
Registry {
#[arg(long, help = "Show detailed statistics")]
detailed: bool,
#[arg(long, help = "Output format", default_value = "table")]
format: OutputFormat,
},
#[command(about = "Tag a version")]
Tag {
#[arg(help = "Model name")]
model_name: String,
#[arg(help = "Version ID")]
version_id: String,
#[arg(help = "Tag name")]
tag: String,
#[arg(long, help = "Remove tag instead of adding")]
remove: bool,
},
#[command(about = "Search versions by criteria")]
Search {
#[arg(long, help = "Model name pattern")]
model: Option<String>,
#[arg(long, help = "Tag filter")]
tag: Option<String>,
#[arg(long, help = "Status filter")]
status: Option<VersionStatusArg>,
#[arg(long, help = "Created after (ISO 8601)")]
after: Option<String>,
#[arg(long, help = "Created before (ISO 8601)")]
before: Option<String>,
#[arg(long, help = "Framework filter")]
framework: Option<String>,
},
#[command(about = "Clean up old versions")]
Cleanup {
#[arg(help = "Model name (optional)")]
model_name: Option<String>,
#[arg(long, help = "Keep last N versions", default_value = "10")]
keep: u32,
#[arg(long, help = "Remove versions older than N days")]
older_than_days: Option<u32>,
#[arg(long, help = "Dry run - show what would be deleted")]
dry_run: bool,
#[arg(long, help = "Force cleanup without confirmation")]
force: bool,
},
}
#[derive(Debug, Clone, ValueEnum)]
pub enum VersionStatusArg {
Draft,
Testing,
Staging,
Production,
Deprecated,
Archived,
Failed,
}
impl From<VersionStatusArg> for VersionStatus {
fn from(arg: VersionStatusArg) -> Self {
match arg {
VersionStatusArg::Draft => VersionStatus::Draft,
VersionStatusArg::Testing => VersionStatus::Testing,
VersionStatusArg::Staging => VersionStatus::Staging,
VersionStatusArg::Production => VersionStatus::Production,
VersionStatusArg::Deprecated => VersionStatus::Deprecated,
VersionStatusArg::Archived => VersionStatus::Archived,
VersionStatusArg::Failed => VersionStatus::Failed,
}
}
}
#[derive(Debug, Clone, ValueEnum)]
pub enum RollbackReasonArg {
Manual,
ErrorRate,
ResponseTime,
Throughput,
Accuracy,
HealthCheck,
Emergency,
}
impl From<RollbackReasonArg> for RollbackReason {
fn from(arg: RollbackReasonArg) -> Self {
match arg {
RollbackReasonArg::Manual => RollbackReason::Manual,
RollbackReasonArg::ErrorRate => RollbackReason::AutoTriggered(TriggerType::ErrorRate),
RollbackReasonArg::ResponseTime => {
RollbackReason::AutoTriggered(TriggerType::ResponseTime)
}
RollbackReasonArg::Throughput => RollbackReason::AutoTriggered(TriggerType::Throughput),
RollbackReasonArg::Accuracy => RollbackReason::AutoTriggered(TriggerType::Accuracy),
RollbackReasonArg::HealthCheck => RollbackReason::HealthCheck,
RollbackReasonArg::Emergency => RollbackReason::Emergency,
}
}
}
#[derive(Debug, Clone, ValueEnum)]
pub enum OutputFormat {
Table,
Json,
Yaml,
Csv,
}
#[derive(Debug, Clone, ValueEnum)]
pub enum ExportFormat {
Json,
Yaml,
Archive,
}
pub async fn execute(args: VersioningArgs, _config: &Config) -> Result<()> {
let versioning_config = VersioningConfig::default();
let manager = ModelVersionManager::new(versioning_config).await?;
match args.command {
VersioningCommand::Create {
model_name,
model_file,
version,
description,
model_type,
architecture,
framework,
framework_version,
parameters,
format,
tags,
created_by,
} => {
if model_name.is_empty() {
return Err(anyhow!("Model name cannot be empty"));
}
if !model_file.exists() {
return Err(anyhow!("Model file does not exist: {:?}", model_file));
}
let semantic_version = if let Some(v) = version {
Some(SemanticVersion::from_string(&v)?)
} else {
None
};
let metadata = ModelMetadata {
model_type,
architecture,
framework,
framework_version,
parameters_count: parameters,
file_format: format,
training_info: None,
performance_metrics: HashMap::new(),
custom_metadata: HashMap::new(),
};
let tag_list = tags
.map(|t| t.split(',').map(|s| s.trim().to_string()).collect())
.unwrap_or_default();
println!("Creating new version for model '{}'...", model_name);
let version_config = crate::versioning::CreateVersionConfig {
version: semantic_version,
metadata,
description,
tags: tag_list,
created_by,
};
let version_id = manager
.create_version(&model_name, &model_file, version_config)
.await?;
println!("Model version created successfully!");
println!("Version ID: {}", version_id);
}
VersioningCommand::List {
model_name,
detailed,
status,
format,
} => {
if let Some(model_name) = model_name {
let versions = manager.list_versions(&model_name).await?;
let filtered_versions: Vec<_> = if let Some(status_filter) = status {
let target_status = VersionStatus::from(status_filter);
versions
.into_iter()
.filter(|v| {
std::mem::discriminant(&v.status)
== std::mem::discriminant(&target_status)
})
.collect()
} else {
versions
};
if filtered_versions.is_empty() {
println!("No versions found for model '{}'", model_name);
} else {
display_versions(&filtered_versions, detailed, format);
}
} else {
let models = manager.list_models().await;
if models.is_empty() {
println!("No models found in registry");
} else {
println!("Models in registry:");
for model in models {
println!(" {}", model);
}
}
}
}
VersioningCommand::Show {
model_name,
version_id,
metadata,
deployments,
} => {
if model_name.is_empty() {
return Err(anyhow!("Model name cannot be empty"));
}
if version_id.is_empty() {
return Err(anyhow!("Version ID cannot be empty"));
}
let version = manager.get_version(&model_name, &version_id).await?;
println!("Model Version Details:");
println!("{:-<50}", "");
println!("Model: {}", version.model_name);
println!("Version ID: {}", version.id);
println!("Version: {}", version.version);
println!("Status: {:?}", version.status);
println!("Created: {:?}", version.created_at);
println!("Created by: {}", version.created_by);
println!("File path: {:?}", version.file_path);
println!("Size: {} bytes", version.size_bytes);
println!("Checksum: {}", version.checksum);
if let Some(desc) = &version.description {
println!("Description: {}", desc);
}
if !version.tags.is_empty() {
println!("Tags: {}", version.tags.join(", "));
}
if metadata {
println!("\nMetadata:");
println!(" Type: {}", version.metadata.model_type);
println!(" Architecture: {}", version.metadata.architecture);
println!(
" Framework: {} {}",
version.metadata.framework, version.metadata.framework_version
);
println!(" Format: {}", version.metadata.file_format);
if let Some(params) = version.metadata.parameters_count {
println!(" Parameters: {}", params);
}
if !version.metadata.performance_metrics.is_empty() {
println!(" Performance Metrics:");
for (key, value) in &version.metadata.performance_metrics {
println!(" {}: {}", key, value);
}
}
}
if deployments {
let active_deployments = manager.get_active_deployments().await;
let version_deployments: Vec<_> = active_deployments
.values()
.filter(|d| d.model_name == model_name && d.version_id == version_id)
.collect();
if !version_deployments.is_empty() {
println!("\nActive Deployments:");
for deployment in version_deployments {
println!(" Environment: {}", deployment.environment);
println!(" Deployed: {:?}", deployment.deployed_at);
println!(" Health: {:?}", deployment.health_status);
}
}
}
}
VersioningCommand::Promote {
model_name,
version_id,
status,
promoted_by,
} => {
if model_name.is_empty() {
return Err(anyhow!("Model name cannot be empty"));
}
if version_id.is_empty() {
return Err(anyhow!("Version ID cannot be empty"));
}
let target_status = VersionStatus::from(status);
println!(
"Promoting version {} of {} to {:?}...",
version_id, model_name, target_status
);
manager
.promote_version(&model_name, &version_id, target_status, promoted_by)
.await?;
println!("Version promoted successfully!");
}
VersioningCommand::Deploy {
model_name,
version_id,
environment,
config: deploy_config,
} => {
if model_name.is_empty() {
return Err(anyhow!("Model name cannot be empty"));
}
if version_id.is_empty() {
return Err(anyhow!("Version ID cannot be empty"));
}
if environment.is_empty() {
return Err(anyhow!("Environment cannot be empty"));
}
let deployment_config = if let Some(config_str) = deploy_config {
serde_json::from_str(&config_str)?
} else {
HashMap::new()
};
println!(
"Deploying version {} of {} to {}...",
version_id, model_name, environment
);
manager
.deploy_version(&model_name, &version_id, &environment, deployment_config)
.await?;
println!("Version deployed successfully!");
}
VersioningCommand::Rollback {
model_name,
version_id,
environment,
reason,
triggered_by,
force,
} => {
if model_name.is_empty() {
return Err(anyhow!("Model name cannot be empty"));
}
if version_id.is_empty() {
return Err(anyhow!("Version ID cannot be empty"));
}
if environment.is_empty() {
return Err(anyhow!("Environment cannot be empty"));
}
if !force {
print!(
"Are you sure you want to rollback {} in {} to version {}? [y/N]: ",
model_name, environment, version_id
);
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
if !input.trim().to_lowercase().starts_with('y') {
println!("Rollback cancelled.");
return Ok(());
}
}
let rollback_reason = RollbackReason::from(reason);
println!(
"Rolling back {} in {} to version {}...",
model_name, environment, version_id
);
let rollback_id = manager
.rollback_model(
&model_name,
&version_id,
&environment,
rollback_reason,
triggered_by,
)
.await?;
println!("Rollback completed successfully!");
println!("Rollback ID: {}", rollback_id);
}
VersioningCommand::Delete {
model_name,
version_id,
force,
} => {
if model_name.is_empty() {
return Err(anyhow!("Model name cannot be empty"));
}
if version_id.is_empty() {
return Err(anyhow!("Version ID cannot be empty"));
}
if !force {
print!(
"Are you sure you want to delete version {} of {}? This cannot be undone. [y/N]: ",
version_id, model_name
);
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
if !input.trim().to_lowercase().starts_with('y') {
println!("Deletion cancelled.");
return Ok(());
}
}
println!("Deleting version {} of {}...", version_id, model_name);
manager.delete_version(&model_name, &version_id).await?;
println!("Version deleted successfully!");
}
VersioningCommand::Compare {
model_name,
version1,
version2,
metadata,
performance,
} => {
if model_name.is_empty() {
return Err(anyhow!("Model name cannot be empty"));
}
if version1.is_empty() {
return Err(anyhow!("Version 1 cannot be empty"));
}
if version2.is_empty() {
return Err(anyhow!("Version 2 cannot be empty"));
}
if version1 == version2 {
return Err(anyhow!("Cannot compare version to itself"));
}
let v1 = manager.get_version(&model_name, &version1).await?;
let v2 = manager.get_version(&model_name, &version2).await?;
println!(
"Comparing versions {} and {} of {}:",
version1, version2, model_name
);
println!("{:-<60}", "");
println!("Version 1: {} | Version 2: {}", v1.version, v2.version);
println!("Status: {:?} | {:?}", v1.status, v2.status);
println!("Created: {:?} | {:?}", v1.created_at, v2.created_at);
println!("Size: {} bytes | {} bytes", v1.size_bytes, v2.size_bytes);
if metadata {
println!("\nMetadata Comparison:");
println!(
"Framework: {} {} | {} {}",
v1.metadata.framework,
v1.metadata.framework_version,
v2.metadata.framework,
v2.metadata.framework_version
);
println!(
"Architecture: {} | {}",
v1.metadata.architecture, v2.metadata.architecture
);
println!(
"Format: {} | {}",
v1.metadata.file_format, v2.metadata.file_format
);
if let (Some(p1), Some(p2)) =
(v1.metadata.parameters_count, v2.metadata.parameters_count)
{
println!("Parameters: {} | {}", p1, p2);
}
}
if performance {
println!("\nPerformance Metrics:");
let all_metrics: std::collections::HashSet<_> = v1
.metadata
.performance_metrics
.keys()
.chain(v2.metadata.performance_metrics.keys())
.collect();
for metric in all_metrics {
let val1 = v1.metadata.performance_metrics.get(metric);
let val2 = v2.metadata.performance_metrics.get(metric);
println!("{}: {:?} | {:?}", metric, val1, val2);
}
}
}
VersioningCommand::History {
model_name,
limit,
format,
} => {
let history = manager.get_rollback_history(model_name.as_deref()).await;
let recent_history: Vec<_> = history.into_iter().take(limit).collect();
if recent_history.is_empty() {
println!("No rollback history found");
} else {
display_rollback_history(&recent_history, format);
}
}
VersioningCommand::Deployments {
model_name,
environment,
format,
} => {
let deployments = manager.get_active_deployments().await;
let filtered_deployments: Vec<_> = deployments
.values()
.filter(|d| {
let model_match = model_name
.as_ref()
.map(|m| &d.model_name == m)
.unwrap_or(true);
let env_match = environment
.as_ref()
.map(|e| &d.environment == e)
.unwrap_or(true);
model_match && env_match
})
.collect();
if filtered_deployments.is_empty() {
println!("No active deployments found");
} else {
display_deployments(&filtered_deployments, format);
}
}
VersioningCommand::Registry { detailed, format } => {
let registry_info = manager.get_registry_info().await;
match format {
OutputFormat::Table => {
println!("Model Registry Information:");
println!("{:-<40}", "");
println!("Created: {:?}", registry_info.created_at);
println!("Last Updated: {:?}", registry_info.last_updated);
println!("Registry Version: {}", registry_info.version);
println!("Total Models: {}", registry_info.total_models);
println!("Total Versions: {}", registry_info.total_versions);
println!("Storage Path: {:?}", registry_info.storage_path);
if detailed {
let models = manager.list_models().await;
println!("\nModels:");
for model in models {
let versions = manager.list_versions(&model).await.unwrap_or_default();
println!(" {} ({} versions)", model, versions.len());
}
}
}
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(®istry_info)?);
}
_ => {
println!("Format {:?} not supported for registry info", format);
}
}
}
_ => {
println!("Command not yet implemented");
}
}
Ok(())
}
fn display_versions(versions: &[ModelVersion], detailed: bool, format: OutputFormat) {
match format {
OutputFormat::Table => {
if detailed {
for version in versions {
println!(
"ID: {} | Version: {} | Status: {:?} | Created: {:?}",
version.id, version.version, version.status, version.created_at
);
println!(
" File: {:?} | Size: {} bytes",
version.file_path, version.size_bytes
);
println!(
" Framework: {} {} | Format: {}",
version.metadata.framework,
version.metadata.framework_version,
version.metadata.file_format
);
if !version.tags.is_empty() {
println!(" Tags: {}", version.tags.join(", "));
}
println!();
}
} else {
println!(
"{:<8} {:<12} {:<12} {:<20}",
"Version", "Status", "Size", "Created"
);
println!("{:-<60}", "");
for version in versions {
println!(
"{:<8} {:<12} {:<12} {:<20}",
version.version,
format!("{:?}", version.status),
format!("{}MB", version.size_bytes / 1024 / 1024),
format!("{:?}", version.created_at)
);
}
}
}
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(versions).unwrap());
}
_ => {
println!("Format {:?} not yet implemented", format);
}
}
}
fn display_rollback_history(history: &[RollbackRecord], format: OutputFormat) {
match format {
OutputFormat::Table => {
println!(
"{:<20} {:<12} {:<12} {:<12} {:<20}",
"Model", "From", "To", "Status", "Triggered"
);
println!("{:-<80}", "");
for record in history {
println!(
"{:<20} {:<12} {:<12} {:<12} {:<20}",
record.model_name,
&record.from_version[..8],
&record.to_version[..8],
format!("{:?}", record.status),
format!("{:?}", record.triggered_at)
);
}
}
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(history).unwrap());
}
_ => {
println!("Format {:?} not yet implemented", format);
}
}
}
fn display_deployments(deployments: &[&ActiveDeployment], format: OutputFormat) {
match format {
OutputFormat::Table => {
println!(
"{:<20} {:<12} {:<12} {:<12} {:<20}",
"Model", "Version", "Environment", "Health", "Deployed"
);
println!("{:-<80}", "");
for deployment in deployments {
println!(
"{:<20} {:<12} {:<12} {:<12} {:<20}",
deployment.model_name,
&deployment.version_id[..8],
deployment.environment,
format!("{:?}", deployment.health_status),
format!("{:?}", deployment.deployed_at)
);
}
}
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(deployments).unwrap());
}
_ => {
println!("Format {:?} not yet implemented", format);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version_status_arg_conversion() {
assert!(matches!(
VersionStatus::from(VersionStatusArg::Draft),
VersionStatus::Draft
));
assert!(matches!(
VersionStatus::from(VersionStatusArg::Testing),
VersionStatus::Testing
));
assert!(matches!(
VersionStatus::from(VersionStatusArg::Staging),
VersionStatus::Staging
));
assert!(matches!(
VersionStatus::from(VersionStatusArg::Production),
VersionStatus::Production
));
assert!(matches!(
VersionStatus::from(VersionStatusArg::Deprecated),
VersionStatus::Deprecated
));
assert!(matches!(
VersionStatus::from(VersionStatusArg::Archived),
VersionStatus::Archived
));
assert!(matches!(
VersionStatus::from(VersionStatusArg::Failed),
VersionStatus::Failed
));
}
#[test]
fn test_rollback_reason_arg_conversion() {
assert!(matches!(
RollbackReason::from(RollbackReasonArg::Manual),
RollbackReason::Manual
));
assert!(matches!(
RollbackReason::from(RollbackReasonArg::HealthCheck),
RollbackReason::HealthCheck
));
assert!(matches!(
RollbackReason::from(RollbackReasonArg::Emergency),
RollbackReason::Emergency
));
assert!(matches!(
RollbackReason::from(RollbackReasonArg::ErrorRate),
RollbackReason::AutoTriggered(TriggerType::ErrorRate)
));
assert!(matches!(
RollbackReason::from(RollbackReasonArg::ResponseTime),
RollbackReason::AutoTriggered(TriggerType::ResponseTime)
));
assert!(matches!(
RollbackReason::from(RollbackReasonArg::Throughput),
RollbackReason::AutoTriggered(TriggerType::Throughput)
));
assert!(matches!(
RollbackReason::from(RollbackReasonArg::Accuracy),
RollbackReason::AutoTriggered(TriggerType::Accuracy)
));
}
#[tokio::test]
async fn test_create_validation_empty_model_name() {
let config = Config::default();
let args = VersioningArgs {
command: VersioningCommand::Create {
model_name: String::new(),
model_file: PathBuf::from("/tmp/model.gguf"),
version: None,
description: None,
model_type: "llm".to_string(),
architecture: "transformer".to_string(),
framework: "pytorch".to_string(),
framework_version: "2.0".to_string(),
parameters: None,
format: "gguf".to_string(),
tags: None,
created_by: "test".to_string(),
},
};
let result = execute(args, &config).await;
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("Model name cannot be empty")
);
}
#[tokio::test]
async fn test_create_validation_missing_file() {
let config = Config::default();
let args = VersioningArgs {
command: VersioningCommand::Create {
model_name: "test-model".to_string(),
model_file: PathBuf::from("/nonexistent/path/model.gguf"),
version: None,
description: None,
model_type: "llm".to_string(),
architecture: "transformer".to_string(),
framework: "pytorch".to_string(),
framework_version: "2.0".to_string(),
parameters: None,
format: "gguf".to_string(),
tags: None,
created_by: "test".to_string(),
},
};
let result = execute(args, &config).await;
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("Model file does not exist")
);
}
#[tokio::test]
async fn test_promote_validation_empty_model_name() {
let config = Config::default();
let args = VersioningArgs {
command: VersioningCommand::Promote {
model_name: String::new(),
version_id: "v1.0.0".to_string(),
status: VersionStatusArg::Production,
promoted_by: "test".to_string(),
},
};
let result = execute(args, &config).await;
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("Model name cannot be empty")
);
}
#[tokio::test]
async fn test_promote_validation_empty_version_id() {
let config = Config::default();
let args = VersioningArgs {
command: VersioningCommand::Promote {
model_name: "test-model".to_string(),
version_id: String::new(),
status: VersionStatusArg::Production,
promoted_by: "test".to_string(),
},
};
let result = execute(args, &config).await;
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("Version ID cannot be empty")
);
}
#[tokio::test]
async fn test_rollback_validation_empty_model_name() {
let config = Config::default();
let args = VersioningArgs {
command: VersioningCommand::Rollback {
model_name: String::new(),
version_id: "v1.0.0".to_string(),
environment: "production".to_string(),
reason: RollbackReasonArg::Manual,
triggered_by: "test".to_string(),
force: true,
},
};
let result = execute(args, &config).await;
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("Model name cannot be empty")
);
}
#[tokio::test]
async fn test_rollback_validation_empty_version_id() {
let config = Config::default();
let args = VersioningArgs {
command: VersioningCommand::Rollback {
model_name: "test-model".to_string(),
version_id: String::new(),
environment: "production".to_string(),
reason: RollbackReasonArg::Manual,
triggered_by: "test".to_string(),
force: true,
},
};
let result = execute(args, &config).await;
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("Version ID cannot be empty")
);
}
#[tokio::test]
async fn test_compare_validation_empty_model_name() {
let config = Config::default();
let args = VersioningArgs {
command: VersioningCommand::Compare {
model_name: String::new(),
version1: "v1.0.0".to_string(),
version2: "v2.0.0".to_string(),
metadata: false,
performance: false,
},
};
let result = execute(args, &config).await;
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("Model name cannot be empty")
);
}
#[tokio::test]
async fn test_compare_validation_same_versions() {
let config = Config::default();
let args = VersioningArgs {
command: VersioningCommand::Compare {
model_name: "test-model".to_string(),
version1: "v1.0.0".to_string(),
version2: "v1.0.0".to_string(),
metadata: false,
performance: false,
},
};
let result = execute(args, &config).await;
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("Cannot compare version to itself")
);
}
#[tokio::test]
async fn test_delete_validation_empty_model_name() {
let config = Config::default();
let args = VersioningArgs {
command: VersioningCommand::Delete {
model_name: String::new(),
version_id: "v1.0.0".to_string(),
force: true,
},
};
let result = execute(args, &config).await;
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("Model name cannot be empty")
);
}
#[tokio::test]
async fn test_delete_validation_empty_version_id() {
let config = Config::default();
let args = VersioningArgs {
command: VersioningCommand::Delete {
model_name: "test-model".to_string(),
version_id: String::new(),
force: true,
},
};
let result = execute(args, &config).await;
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("Version ID cannot be empty")
);
}
#[tokio::test]
async fn test_deploy_validation_empty_environment() {
let config = Config::default();
let args = VersioningArgs {
command: VersioningCommand::Deploy {
model_name: "test-model".to_string(),
version_id: "v1.0.0".to_string(),
environment: String::new(),
config: None,
},
};
let result = execute(args, &config).await;
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("Environment cannot be empty")
);
}
#[tokio::test]
async fn test_show_validation_empty_model_name() {
let config = Config::default();
let args = VersioningArgs {
command: VersioningCommand::Show {
model_name: String::new(),
version_id: "v1.0.0".to_string(),
metadata: false,
deployments: false,
},
};
let result = execute(args, &config).await;
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("Model name cannot be empty")
);
}
#[tokio::test]
async fn test_show_validation_empty_version_id() {
let config = Config::default();
let args = VersioningArgs {
command: VersioningCommand::Show {
model_name: "test-model".to_string(),
version_id: String::new(),
metadata: false,
deployments: false,
},
};
let result = execute(args, &config).await;
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("Version ID cannot be empty")
);
}
}