use chrono::{DateTime, Utc};
use ironflow_store::models::Artifact;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArtifactResponse {
pub id: Uuid,
pub step_id: Uuid,
pub name: String,
pub content_type: String,
pub size_bytes: u64,
pub sha256: String,
pub created_at: DateTime<Utc>,
}
impl From<Artifact> for ArtifactResponse {
fn from(artifact: Artifact) -> Self {
Self {
id: artifact.id,
step_id: artifact.step_id,
name: artifact.name,
content_type: artifact.content_type,
size_bytes: artifact.size_bytes,
sha256: artifact.sha256,
created_at: artifact.created_at,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample() -> Artifact {
Artifact {
id: Uuid::now_v7(),
run_id: Uuid::now_v7(),
step_id: Uuid::now_v7(),
name: "report.html".to_string(),
storage_key: "artifacts/secret/path/uuid".to_string(),
content_type: "text/html".to_string(),
size_bytes: 142,
sha256: "0".repeat(64),
created_at: Utc::now(),
updated_at: Utc::now(),
}
}
#[test]
fn conversion_keeps_the_caller_facing_fields() {
let artifact = sample();
let response = ArtifactResponse::from(artifact.clone());
assert_eq!(response.id, artifact.id);
assert_eq!(response.step_id, artifact.step_id);
assert_eq!(response.name, artifact.name);
assert_eq!(response.size_bytes, artifact.size_bytes);
assert_eq!(response.sha256, artifact.sha256);
}
#[test]
fn serialization_never_leaks_the_storage_key() {
let json = serde_json::to_string(&ArtifactResponse::from(sample())).expect("serialize");
assert!(!json.contains("storage_key"));
assert!(!json.contains("secret/path"));
}
#[test]
fn serialization_omits_the_run_id_carried_by_the_route() {
let json = serde_json::to_string(&ArtifactResponse::from(sample())).expect("serialize");
assert!(!json.contains("run_id"));
}
}