use quilt_rs::io::remote::HostConfig;
use quilt_rs::uri::ManifestUri;
use quilt_rs::uri::Namespace;
use crate::cli::model::Commands;
use crate::cli::output::Std;
use crate::cli::Error;
#[derive(Debug)]
pub struct Input {
pub namespace: Namespace,
pub host_config: Option<HostConfig>,
}
#[derive(Debug)]
pub struct Output {
pub hash: String,
}
impl std::fmt::Display for Output {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, r##"New revision "{}" pushed"##, self.hash)
}
}
pub async fn command(m: impl Commands, args: Input) -> Std {
Std::from_result(m.push(args).await)
}
async fn push_package(
local_domain: &quilt_rs::LocalDomain,
namespace: Namespace,
host_config: Option<HostConfig>,
) -> Result<ManifestUri, Error> {
match local_domain.get_installed_package(&namespace).await? {
Some(installed_package) => Ok(installed_package.push(host_config).await?),
None => Err(Error::NamespaceNotFound(namespace)),
}
}
pub async fn model(
local_domain: &quilt_rs::LocalDomain,
Input {
namespace,
host_config,
}: Input,
) -> Result<Output, Error> {
let manifest_uri = push_package(local_domain, namespace, host_config).await?;
Ok(Output {
hash: manifest_uri.hash,
})
}
#[cfg(test)]
mod tests {
use super::*;
use test_log::test;
use quilt_rs::io::storage::LocalStorage;
use quilt_rs::io::storage::Storage;
use std::path::PathBuf;
use crate::cli::commit;
use crate::cli::fixtures::packages::default as pkg;
use crate::cli::model::create_model_in_temp_dir;
use crate::cli::model::install_package_into_temp_dir;
#[test(tokio::test)]
async fn test_namespace_not_found() -> Result<(), Error> {
let (m, _temp_dir) = create_model_in_temp_dir().await?;
if let Std::Err(error_str) = command(
m,
Input {
namespace: ("in", "valid").into(),
host_config: None,
},
)
.await
{
assert_eq!(error_str.to_string(), "Package in/valid not found");
} else {
return Err(Error::Test("Expected package not found error".to_string()));
}
Ok(())
}
#[test(tokio::test)]
async fn test_no_commit() -> Result<(), Error> {
let uri = pkg::URI;
let (m, _, _temp_dir) = install_package_into_temp_dir(uri).await?;
if let Std::Err(error_str) = command(
m,
Input {
namespace: pkg::NAMESPACE.into(),
host_config: None,
},
)
.await
{
assert_eq!(
error_str.to_string(),
"quilt_rs error: Push error: No commits to push"
);
} else {
return Err(Error::Test("Expected no changes error".to_string()));
}
Ok(())
}
#[test(tokio::test)]
async fn test_push_sha256_checksum() -> Result<(), Error> {
let namespace: Namespace = ("quilt_rs", "test").into();
let uri = "quilt+s3://data-yaml-spec-tests#package=quilt_rs/test";
let host_config = Some(HostConfig::default_sha256_chunked());
let (m, installed_package, _temp_dir) = install_package_into_temp_dir(uri).await?;
let initial_lineage = installed_package.lineage().await?;
let initial_hash = initial_lineage.current_hash();
assert_eq!(
initial_hash, "4076eb7774f5159aab212302288a2a2a9e59fab69cf4e41e827072fee80fabb4",
"Initial top hash should match expected value"
);
let working_dir = installed_package.package_home().await?;
let storage = LocalStorage::new();
let e0_file_path = working_dir.join(PathBuf::from("e0-0.txt"));
storage
.write_file(&e0_file_path, b"Emperor-Drainage8-Presoak\n")
.await?;
m.commit(commit::Input {
message: "Unbounded Defy 2 Landmine".to_string(),
namespace: namespace.clone(),
user_meta: Some(serde_json::json!({"Naturist": "Conjure"})),
workflow: None,
host_config: None,
})
.await?;
let first_push_output = m
.push(Input {
namespace: namespace.clone(),
host_config: host_config.clone(),
})
.await?;
assert_eq!(
first_push_output.hash,
"c8027e8697016feb74b8ea523ca55934243653b890b94d64166ef2664a71ebab",
"First push top hash should match expected value"
);
storage
.write_file(&e0_file_path, b"Thu Feb 29 19:07:56 PST 2024\n")
.await?;
m.commit(commit::Input {
message: "Equate 1 Fragment Grimace".to_string(),
namespace: namespace.clone(),
user_meta: Some(serde_json::json!({"Antitoxic": "Mankind"})),
workflow: None,
host_config: None,
})
.await?;
let final_push_output = m
.push(Input {
namespace,
host_config,
})
.await?;
assert_eq!(
final_push_output.hash,
"4076eb7774f5159aab212302288a2a2a9e59fab69cf4e41e827072fee80fabb4",
"Final push top hash should match original expected value"
);
Ok(())
}
#[test(tokio::test)]
async fn test_push_crc64_checksum() -> Result<(), Error> {
let namespace: Namespace = ("crc64", "s3").into();
let uri = "quilt+s3://fiskus-us-east-1#package=crc64/s3";
let host_config = Some(HostConfig::default_crc64());
let (m, installed_package, _temp_dir) = install_package_into_temp_dir(uri).await?;
let initial_lineage = installed_package.lineage().await?;
let initial_hash = initial_lineage.current_hash();
assert_eq!(
initial_hash, "b427c3867bce2445a988f69f43ad3998237d2fedf6f5e678822acd1a1e8f580a",
"Initial top hash should match expected value"
);
let working_dir = installed_package.package_home().await?;
let storage = LocalStorage::new();
let file_path = working_dir.join(PathBuf::from("1.txt"));
storage
.write_file(&file_path, b"Emperor-Drainage8-Presoak\n")
.await?;
m.commit(commit::Input {
message: "Unbounded Defy 2 Landmine".to_string(),
namespace: namespace.clone(),
user_meta: Some(serde_json::json!({"Naturist": "Conjure"})),
workflow: None,
host_config: None,
})
.await?;
let first_push_output = m
.push(Input {
namespace: namespace.clone(),
host_config: host_config.clone(),
})
.await?;
assert_eq!(
first_push_output.hash,
"8c9beced00f51cb100da861e62688e71f77a692a1c71bce422e329706ede6e63",
"First push top hash should match expected value"
);
storage
.write_file(&file_path, b"jue 27 nov 2025 16:36:45 CET\n")
.await?;
m.commit(commit::Input {
message: "Initial commit".to_string(),
namespace: namespace.clone(),
user_meta: None,
workflow: None,
host_config: None,
})
.await?;
let final_push_output = m
.push(Input {
namespace,
host_config,
})
.await?;
assert_eq!(
final_push_output.hash,
"b427c3867bce2445a988f69f43ad3998237d2fedf6f5e678822acd1a1e8f580a",
"Final push top hash should match original expected value"
);
Ok(())
}
}