#![allow(clippy::module_name_repetitions)]
use std::collections::BTreeMap;
use std::path::Path;
use caixa_core::{Caixa, MappingExt, lareira_chart_name};
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error("{0}")]
NotAServico(#[from] caixa_core::KindMismatch),
#[error("{0}")]
UnsupportedServicoCount(#[from] caixa_core::ServicoCountMismatch),
#[error("computeunit yaml missing required field: {0}")]
MissingField(&'static str),
#[error("yaml: {0}")]
Yaml(#[from] serde_yaml::Error),
#[error("render: {0}")]
Render(#[from] caixa_core::RenderError),
#[error("io: {0}")]
Io(#[from] std::io::Error),
}
pub type ChartFile = caixa_core::RenderedFile;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ChartDir {
pub name: String,
pub files: Vec<ChartFile>,
}
impl ChartDir {
pub fn write_to(&self, dest: &Path) -> Result<(), Error> {
let root = dest.join(&self.name);
std::fs::create_dir_all(&root)?;
for f in &self.files {
let target = root.join(&f.path);
if let Some(parent) = target.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(&target, &f.contents)?;
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ChartYaml {
#[serde(rename = "apiVersion")]
pub api_version: String,
pub name: String,
pub description: String,
#[serde(rename = "type")]
pub chart_type: String,
pub version: String,
#[serde(rename = "appVersion")]
pub app_version: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub keywords: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub maintainers: Vec<Maintainer>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub home: Option<String>,
pub dependencies: Vec<ChartDependency>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Maintainer {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ChartDependency {
pub name: String,
pub version: String,
pub repository: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub alias: Option<String>,
}
pub const DEFAULT_LIBRARY_REPO: &str = "file://../pleme-computeunit";
pub const DEFAULT_LIBRARY_VERSION: &str = "~0.1.0";
pub use caixa_core::DEFAULT_LIBRARY_NAME;
pub use caixa_core::HELM_CHART_API_VERSION;
pub use caixa_core::HELM_CHART_TYPE_APPLICATION;
pub use caixa_core::HELM_CHART_TYPE_LIBRARY;
pub use caixa_core::HELM_CHART_KEY_TYPE;
pub use caixa_core::HELM_CHART_KEY_APP_VERSION;
pub use caixa_core::HELM_CHART_KEY_API_VERSION;
pub use caixa_core::HELM_CHART_KEY_DEPENDENCIES;
pub use caixa_core::HELM_CHART_DEPENDENCY_KEY_NAME;
pub use caixa_core::HELM_CHART_DEPENDENCY_KEY_VERSION;
pub use caixa_core::HELM_CHART_DEPENDENCY_KEY_REPOSITORY;
pub use caixa_core::HELM_CHART_DEPENDENCY_KEY_ALIAS;
pub use caixa_core::HELM_CHART_YAML_FILENAME;
pub use caixa_core::HELM_VALUES_YAML_FILENAME;
pub use caixa_core::HELM_CHART_README_FILENAME;
pub use caixa_core::KUBE_KEY_SPEC;
pub use caixa_core::HELM_VALUES_KEY_ENABLED;
pub use caixa_core::STANDALONE_LAREIRA_ENABLED_DEFAULT;
pub use caixa_core::COMPUTEUNIT_SPEC_KEY_MODULE;
pub use caixa_core::COMPUTEUNIT_SPEC_KEY_TRIGGER;
pub use caixa_core::COMPUTEUNIT_SPEC_KEY_CAPABILITIES;
pub use caixa_core::servico_spec_and_m2_overlay_entries;
#[derive(Debug, Clone)]
pub struct RenderOpts {
pub library_repo: String,
pub library_version: String,
pub library_name: String,
pub enabled_default: bool,
}
impl Default for RenderOpts {
fn default() -> Self {
Self {
library_repo: DEFAULT_LIBRARY_REPO.into(),
library_version: DEFAULT_LIBRARY_VERSION.into(),
library_name: DEFAULT_LIBRARY_NAME.into(),
enabled_default: STANDALONE_LAREIRA_ENABLED_DEFAULT,
}
}
}
pub fn render_chart_for_servico(
caixa: &Caixa,
computeunit_yaml: &serde_yaml::Value,
) -> Result<ChartDir, Error> {
render_chart_for_servico_with(caixa, computeunit_yaml, &RenderOpts::default())
}
pub fn render_chart_for_servico_with(
caixa: &Caixa,
computeunit_yaml: &serde_yaml::Value,
opts: &RenderOpts,
) -> Result<ChartDir, Error> {
caixa_core::require_v0_servico_shape::<Error>(caixa)?;
let chart_name = lareira_chart_name(caixa.nome());
let chart_yaml = build_chart_yaml(caixa, &chart_name, opts);
let values_yaml = build_values_yaml(caixa, computeunit_yaml, opts)?;
let readme = build_readme(caixa, &chart_name);
Ok(ChartDir {
name: chart_name,
files: vec![
ChartFile::new(
HELM_CHART_YAML_FILENAME,
serde_yaml::to_string(&chart_yaml)?,
),
ChartFile::new(HELM_VALUES_YAML_FILENAME, values_yaml),
ChartFile::new(HELM_CHART_README_FILENAME, readme),
],
})
}
fn build_chart_yaml(caixa: &Caixa, chart_name: &str, opts: &RenderOpts) -> ChartYaml {
let description = caixa
.descricao()
.map(str::to_owned)
.unwrap_or_else(|| format!("Generated chart for caixa Servico {}", caixa.nome()));
let keywords: Vec<String> = caixa
.etiquetas()
.iter()
.cloned()
.chain(
caixa_core::LAREIRA_CHART_KEYWORDS
.iter()
.copied()
.map(String::from),
)
.collect::<Vec<_>>()
.into_iter()
.collect::<std::collections::BTreeSet<_>>()
.into_iter()
.collect();
let maintainers = caixa
.autores()
.iter()
.map(|a| Maintainer {
name: a.clone(),
email: None,
})
.collect();
let versao = caixa.versao().to_string();
ChartYaml {
api_version: HELM_CHART_API_VERSION.into(),
name: chart_name.into(),
description,
chart_type: HELM_CHART_TYPE_APPLICATION.into(),
version: versao.clone(),
app_version: versao,
keywords,
maintainers,
home: caixa.repositorio().map(str::to_owned),
dependencies: vec![ChartDependency {
name: opts.library_name.clone(),
version: opts.library_version.clone(),
repository: opts.library_repo.clone(),
alias: None,
}],
}
}
fn build_values_yaml(
caixa: &Caixa,
computeunit_yaml: &serde_yaml::Value,
opts: &RenderOpts,
) -> Result<String, Error> {
let library_alias = opts.library_name.as_str();
let spec = computeunit_yaml
.get(KUBE_KEY_SPEC)
.ok_or(Error::MissingField(KUBE_KEY_SPEC))?;
let header = format!(
"# Auto-generated by caixa-helm from caixa.lisp + servicos/{nome}.computeunit.yaml.\n\
# Edits to this file are overwritten by `feira chart`.\n\
#\n\
# `{library_alias}:` is the alias under which the library chart\n\
# in pleme-io/helmworks/charts/{library_alias} consumes its values.\n\n",
nome = caixa.nome()
);
let mut block = BTreeMap::new();
block.insert(
HELM_VALUES_KEY_ENABLED.to_string(),
serde_yaml::Value::Bool(opts.enabled_default),
);
for (k, v) in caixa_core::servico_spec_and_m2_overlay_entries(caixa, spec)? {
block.entry(k).or_insert(v);
}
let mut wrapped = serde_yaml::Mapping::new();
wrapped.insert_str_key(library_alias, serde_yaml::to_value(block)?);
let body = serde_yaml::to_string(&serde_yaml::Value::Mapping(wrapped))?;
Ok(format!("{header}{body}"))
}
fn build_readme(caixa: &Caixa, chart_name: &str) -> String {
let descricao = caixa
.descricao()
.map(str::to_owned)
.unwrap_or_else(|| format!("caixa Servico {}", caixa.nome()));
format!(
"# {chart_name}\n\
\n\
{descricao}\n\
\n\
## Origin\n\
\n\
Generated by `caixa-helm` from `{repo}/caixa.lisp` v{versao}.\n\
Edits here are overwritten by `feira chart`.\n\
\n\
## Install\n\
\n\
```bash\n\
helm dependency build\n\
helm template {chart_name} . --values values.yaml\n\
```\n\
\n\
## License\n\
\n\
{license}.\n",
chart_name = chart_name,
descricao = descricao,
repo = caixa.repositorio().unwrap_or(caixa.nome()),
versao = caixa.versao(),
license = caixa.licenca().unwrap_or("MIT"),
)
}
#[cfg(test)]
mod tests {
use super::*;
use caixa_core::{
Caixa, CaixaKind, M2_BEHAVIOR_KEY_ON_CALL, M2_BEHAVIOR_KEY_ON_INIT, M2_KEY_BEHAVIOR,
M2_KEY_LIMITS, M2_KEY_UPGRADE_FROM, M2_LIMITS_KEY_CPU, M2_LIMITS_KEY_FUEL,
M2_LIMITS_KEY_MEMORY, M2_LIMITS_KEY_WALL_CLOCK,
};
use std::path::PathBuf;
fn sample_caixa() -> Caixa {
Caixa {
nome: "hello-rio".into(),
versao: "0.1.0".into(),
kind: CaixaKind::Servico,
edicao: Some("2026".into()),
descricao: Some("Canonical Rust→wasm32-wasip2 caixa Servico.".into()),
repositorio: Some("github:pleme-io/hello-rio".into()),
licenca: Some("MIT".into()),
autores: vec!["pleme-io".into()],
etiquetas: vec!["hello-world".into(), "wasm".into(), "rust".into()],
deps: vec![],
deps_dev: vec![],
exe: vec![],
bibliotecas: vec![],
servicos: vec!["servicos/hello-rio.computeunit.yaml".into()],
limits: None,
behavior: None,
upgrade_from: vec![],
estrategia: None,
max_restarts: None,
restart_window: None,
children: vec![],
membros: vec![],
contratos: vec![],
politicas: None,
placement: None,
entrada: None,
ci: None,
}
}
fn sample_cu_yaml() -> serde_yaml::Value {
serde_yaml::from_str(
r#"
apiVersion: wasm.pleme.io/v1alpha1
kind: ComputeUnit
metadata:
name: hello-rio
spec:
module:
source: oci://ghcr.io/pleme-io/hello-rio:v0.1.0
trigger:
service:
port: 8080
paths: ["/", "/hello", "/healthz"]
breathability:
enabled: true
minReplicas: 0
maxReplicas: 5
cooldownPeriod: 600
capabilities:
- http-in:0.0.0.0:8080
- env
"#,
)
.unwrap()
}
#[test]
fn renders_three_files() {
let dir = render_chart_for_servico(&sample_caixa(), &sample_cu_yaml()).unwrap();
assert_eq!(dir.name, "lareira-hello-rio");
let names: Vec<_> = dir
.files
.iter()
.map(|f| f.path.to_string_lossy().to_string())
.collect();
assert!(names.contains(&HELM_CHART_YAML_FILENAME.to_string()));
assert!(names.contains(&HELM_VALUES_YAML_FILENAME.to_string()));
assert!(names.contains(&HELM_CHART_README_FILENAME.to_string()));
}
#[test]
fn chart_yaml_metadata_propagates() {
let dir = render_chart_for_servico(&sample_caixa(), &sample_cu_yaml()).unwrap();
let chart_file = dir
.files
.iter()
.find(|f| f.path == PathBuf::from(HELM_CHART_YAML_FILENAME))
.unwrap();
let chart: ChartYaml = serde_yaml::from_str(&chart_file.contents).unwrap();
assert_eq!(chart.api_version, "v2");
assert_eq!(chart.name, "lareira-hello-rio");
assert_eq!(chart.version, "0.1.0");
assert_eq!(chart.app_version, "0.1.0");
assert_eq!(chart.dependencies.len(), 1);
assert_eq!(chart.dependencies[0].name, DEFAULT_LIBRARY_NAME);
assert!(chart.keywords.contains(&"caixa-servico".to_string()));
assert!(chart.keywords.contains(&"hello-world".to_string()));
assert_eq!(chart.maintainers[0].name, "pleme-io");
}
#[test]
fn chart_yaml_keywords_union_pins_every_lareira_chart_keywords_entry() {
let dir = render_chart_for_servico(&sample_caixa(), &sample_cu_yaml()).unwrap();
let chart_file = dir
.files
.iter()
.find(|f| f.path == PathBuf::from(HELM_CHART_YAML_FILENAME))
.unwrap();
let chart: ChartYaml = serde_yaml::from_str(&chart_file.contents).unwrap();
for keyword in caixa_core::LAREIRA_CHART_KEYWORDS {
assert!(
chart.keywords.contains(&(*keyword).to_string()),
"rendered Chart.yaml keywords {:?} must contain the \
substrate-fixed LAREIRA_CHART_KEYWORDS entry {keyword:?}",
chart.keywords,
);
}
}
#[test]
fn values_yaml_wraps_under_pleme_computeunit_key() {
let dir = render_chart_for_servico(&sample_caixa(), &sample_cu_yaml()).unwrap();
let values = dir
.files
.iter()
.find(|f| f.path == PathBuf::from(HELM_VALUES_YAML_FILENAME))
.unwrap();
let parsed: serde_yaml::Value = serde_yaml::from_str(&values.contents).unwrap();
let cu_block = parsed
.get(DEFAULT_LIBRARY_NAME)
.expect("must wrap under DEFAULT_LIBRARY_NAME");
assert_eq!(
cu_block.get(HELM_VALUES_KEY_ENABLED),
Some(&serde_yaml::Value::Bool(false))
);
assert!(cu_block.get(COMPUTEUNIT_SPEC_KEY_MODULE).is_some());
assert!(cu_block.get(COMPUTEUNIT_SPEC_KEY_TRIGGER).is_some());
assert!(cu_block.get(COMPUTEUNIT_SPEC_KEY_CAPABILITIES).is_some());
}
#[test]
fn values_yaml_wrap_key_follows_library_name_override() {
let opts = RenderOpts {
library_name: "acme-computeunit".into(),
..RenderOpts::default()
};
let dir = render_chart_for_servico_with(&sample_caixa(), &sample_cu_yaml(), &opts).unwrap();
let values = dir
.files
.iter()
.find(|f| f.path == PathBuf::from(HELM_VALUES_YAML_FILENAME))
.unwrap();
let parsed: serde_yaml::Value = serde_yaml::from_str(&values.contents).unwrap();
assert!(
parsed.get("acme-computeunit").is_some(),
"values wrap key must follow opts.library_name override \
(got top-level keys: {keys:?})",
keys = parsed
.as_mapping()
.map(|m| m
.keys()
.filter_map(|k| k.as_str().map(str::to_string))
.collect::<Vec<_>>())
.unwrap_or_default()
);
assert!(
parsed.get(DEFAULT_LIBRARY_NAME).is_none(),
"values wrap key must not retain the default `{DEFAULT_LIBRARY_NAME}` literal \
when opts.library_name overrides it"
);
let cu_block = parsed.get("acme-computeunit").unwrap();
assert_eq!(
cu_block.get(HELM_VALUES_KEY_ENABLED),
Some(&serde_yaml::Value::Bool(false))
);
assert!(cu_block.get(COMPUTEUNIT_SPEC_KEY_MODULE).is_some());
assert!(cu_block.get(COMPUTEUNIT_SPEC_KEY_TRIGGER).is_some());
assert!(cu_block.get(COMPUTEUNIT_SPEC_KEY_CAPABILITIES).is_some());
}
#[test]
fn values_yaml_wrap_key_matches_chart_dependency_name() {
for library_name in [DEFAULT_LIBRARY_NAME, "acme-computeunit", "fork-pleme-cu"] {
let opts = RenderOpts {
library_name: library_name.into(),
..RenderOpts::default()
};
let dir =
render_chart_for_servico_with(&sample_caixa(), &sample_cu_yaml(), &opts).unwrap();
let chart_file = dir
.files
.iter()
.find(|f| f.path == PathBuf::from(HELM_CHART_YAML_FILENAME))
.unwrap();
let chart: ChartYaml = serde_yaml::from_str(&chart_file.contents).unwrap();
let dep_name = &chart.dependencies[0].name;
let values = dir
.files
.iter()
.find(|f| f.path == PathBuf::from(HELM_VALUES_YAML_FILENAME))
.unwrap();
let parsed: serde_yaml::Value = serde_yaml::from_str(&values.contents).unwrap();
assert!(
parsed.get(dep_name.as_str()).is_some(),
"values.yaml wrap key must match Chart.yaml dependencies[0].name {dep_name:?} \
(library_name = {library_name:?}); Helm's per-dep alias convention scopes \
values under the dep's `name` when no `alias:` is set, so any drift between \
the two axes silently routes the values block nowhere"
);
}
}
#[test]
fn values_yaml_header_comment_follows_library_name_override() {
let opts = RenderOpts {
library_name: "acme-computeunit".into(),
..RenderOpts::default()
};
let dir = render_chart_for_servico_with(&sample_caixa(), &sample_cu_yaml(), &opts).unwrap();
let values = dir
.files
.iter()
.find(|f| f.path == PathBuf::from(HELM_VALUES_YAML_FILENAME))
.unwrap();
assert!(
values.contents.contains("`acme-computeunit:`"),
"header must name the overriding library alias verbatim \
(got: {contents:?})",
contents = values.contents
);
assert!(
values
.contents
.contains("pleme-io/helmworks/charts/acme-computeunit"),
"header's helmworks path must follow the overriding library alias \
(got: {contents:?})",
contents = values.contents
);
assert!(
!values.contents.contains("`pleme-computeunit:`"),
"header must not retain the default library alias literal \
when overridden (got: {contents:?})",
contents = values.contents
);
}
#[test]
fn refuses_non_servico() {
let mut c = sample_caixa();
c.kind = CaixaKind::Biblioteca;
c.servicos = vec![];
let err = render_chart_for_servico(&c, &sample_cu_yaml()).unwrap_err();
assert!(matches!(err, Error::NotAServico(_)));
}
#[test]
fn kind_mismatch_error_names_offending_caixa_nome() {
let mut c = sample_caixa();
c.kind = CaixaKind::Biblioteca;
c.servicos = vec![];
let err = render_chart_for_servico(&c, &sample_cu_yaml()).unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("hello-rio"),
"kind-mismatch diagnostic must name the offending caixa nome \
(got: {msg:?})"
);
assert!(
msg.contains("Servico"),
"diagnostic must name the expected kind (got: {msg:?})"
);
assert!(
msg.contains("Biblioteca"),
"diagnostic must name the actual kind (got: {msg:?})"
);
}
#[test]
fn kind_mismatch_carries_typed_view_via_from_conversion() {
let mut c = sample_caixa();
c.kind = CaixaKind::Aplicacao;
c.servicos = vec![];
let err = render_chart_for_servico(&c, &sample_cu_yaml()).unwrap_err();
match err {
Error::NotAServico(km) => {
assert_eq!(km.nome, "hello-rio");
assert_eq!(km.expected, CaixaKind::Servico);
assert_eq!(km.actual, CaixaKind::Aplicacao);
}
other => panic!("expected Error::NotAServico, got {other:?}"),
}
}
#[test]
fn servico_count_mismatch_carries_typed_view_with_nome() {
let mut c = sample_caixa();
c.servicos = vec![
"servicos/hello-rio.computeunit.yaml".into(),
"servicos/extra.computeunit.yaml".into(),
];
let err = render_chart_for_servico(&c, &sample_cu_yaml()).unwrap_err();
match err {
Error::UnsupportedServicoCount(scm) => {
assert_eq!(scm.nome, "hello-rio");
assert_eq!(scm.count, 2);
}
other => panic!("expected Error::UnsupportedServicoCount, got {other:?}"),
}
}
#[test]
fn servico_count_mismatch_diagnostic_names_offending_caixa_nome() {
let mut c = sample_caixa();
c.servicos = vec![
"servicos/hello-rio.computeunit.yaml".into(),
"servicos/extra.computeunit.yaml".into(),
];
let err = render_chart_for_servico(&c, &sample_cu_yaml()).unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("hello-rio"),
":servicos-count-mismatch diagnostic must name the offending caixa nome \
(got: {msg:?})"
);
assert!(
msg.contains("2"),
"diagnostic must name the actual count (got: {msg:?})"
);
assert!(
msg.contains(":servicos"),
"diagnostic must name the offending field axis (got: {msg:?})"
);
}
#[test]
fn limits_slot_propagates_into_values_block() {
use caixa_core::LimitsSpec;
use std::time::Duration;
let mut c = sample_caixa();
c.limits = Some(LimitsSpec {
memory: Some(64 * 1024 * 1024),
fuel: Some(1_000_000),
wall_clock: Some(Duration::from_secs(30)),
cpu: Some(500),
});
let dir = render_chart_for_servico(&c, &sample_cu_yaml()).unwrap();
let values = dir
.files
.iter()
.find(|f| f.path == PathBuf::from(HELM_VALUES_YAML_FILENAME))
.unwrap();
let parsed: serde_yaml::Value = serde_yaml::from_str(&values.contents).unwrap();
let cu_block = parsed.get(DEFAULT_LIBRARY_NAME).unwrap();
let limits = cu_block.get(M2_KEY_LIMITS).expect("limits must propagate");
assert_eq!(
limits.get(M2_LIMITS_KEY_MEMORY).and_then(|m| m.as_str()),
Some("64MiB")
);
assert_eq!(
limits.get(M2_LIMITS_KEY_FUEL).and_then(|m| m.as_u64()),
Some(1_000_000)
);
assert_eq!(
limits
.get(M2_LIMITS_KEY_WALL_CLOCK)
.and_then(|m| m.as_str()),
Some("30s")
);
assert_eq!(
limits.get(M2_LIMITS_KEY_CPU).and_then(|m| m.as_str()),
Some("500m")
);
}
#[test]
fn behavior_slot_propagates_into_values_block() {
use caixa_core::BehaviorSpec;
let mut c = sample_caixa();
c.behavior = Some(BehaviorSpec {
on_init: Some(PathBuf::from("lib/init.lisp")),
on_call: Some(PathBuf::from("lib/handlers.lisp")),
..Default::default()
});
let dir = render_chart_for_servico(&c, &sample_cu_yaml()).unwrap();
let values = dir
.files
.iter()
.find(|f| f.path == PathBuf::from(HELM_VALUES_YAML_FILENAME))
.unwrap();
let parsed: serde_yaml::Value = serde_yaml::from_str(&values.contents).unwrap();
let cu_block = parsed.get(DEFAULT_LIBRARY_NAME).unwrap();
let behavior = cu_block
.get(M2_KEY_BEHAVIOR)
.expect("behavior must propagate");
assert_eq!(
behavior
.get(M2_BEHAVIOR_KEY_ON_INIT)
.and_then(|v| v.as_str()),
Some("lib/init.lisp")
);
assert_eq!(
behavior
.get(M2_BEHAVIOR_KEY_ON_CALL)
.and_then(|v| v.as_str()),
Some("lib/handlers.lisp")
);
}
#[test]
fn upgrade_from_slot_propagates_into_values_block() {
use caixa_core::{UpgradeFromEntry, UpgradeInstruction};
let mut c = sample_caixa();
c.upgrade_from = vec![UpgradeFromEntry {
from: "0.0.9".into(),
instructions: vec![UpgradeInstruction::LoadModule {
module: "hello-rio".into(),
}],
}];
let dir = render_chart_for_servico(&c, &sample_cu_yaml()).unwrap();
let values = dir
.files
.iter()
.find(|f| f.path == PathBuf::from(HELM_VALUES_YAML_FILENAME))
.unwrap();
let parsed: serde_yaml::Value = serde_yaml::from_str(&values.contents).unwrap();
let cu_block = parsed.get(DEFAULT_LIBRARY_NAME).unwrap();
assert!(cu_block.get(M2_KEY_UPGRADE_FROM).is_some());
}
#[test]
fn empty_m2_slots_do_not_appear() {
let dir = render_chart_for_servico(&sample_caixa(), &sample_cu_yaml()).unwrap();
let values = dir
.files
.iter()
.find(|f| f.path == PathBuf::from(HELM_VALUES_YAML_FILENAME))
.unwrap();
let parsed: serde_yaml::Value = serde_yaml::from_str(&values.contents).unwrap();
let cu_block = parsed.get(DEFAULT_LIBRARY_NAME).unwrap();
assert!(cu_block.get(M2_KEY_LIMITS).is_none());
assert!(cu_block.get(M2_KEY_BEHAVIOR).is_none());
assert!(cu_block.get(M2_KEY_UPGRADE_FROM).is_none());
}
#[test]
fn write_to_creates_files() {
let dir = render_chart_for_servico(&sample_caixa(), &sample_cu_yaml()).unwrap();
let tmp = tempfile::tempdir().unwrap();
dir.write_to(tmp.path()).unwrap();
let chart_root = tmp.path().join("lareira-hello-rio");
assert!(chart_root.join(HELM_CHART_YAML_FILENAME).exists());
assert!(chart_root.join(HELM_VALUES_YAML_FILENAME).exists());
assert!(chart_root.join(HELM_CHART_README_FILENAME).exists());
}
#[test]
fn default_library_name_re_export_points_at_caixa_core_canonical() {
caixa_core::assert_str_reexport_identity(
"DEFAULT_LIBRARY_NAME",
DEFAULT_LIBRARY_NAME,
caixa_core::DEFAULT_LIBRARY_NAME,
);
}
#[test]
fn kube_key_spec_re_export_points_at_caixa_core_canonical() {
caixa_core::assert_str_reexport_identity(
"KUBE_KEY_SPEC",
KUBE_KEY_SPEC,
caixa_core::KUBE_KEY_SPEC,
);
}
#[test]
fn helm_chart_api_version_re_export_points_at_caixa_core_canonical() {
caixa_core::assert_str_reexport_identity(
"HELM_CHART_API_VERSION",
HELM_CHART_API_VERSION,
caixa_core::HELM_CHART_API_VERSION,
);
}
#[test]
fn chart_yaml_uses_lifted_helm_chart_api_version() {
let dir = render_chart_for_servico(&sample_caixa(), &sample_cu_yaml()).unwrap();
let chart_file = dir
.files
.iter()
.find(|f| f.path == PathBuf::from(HELM_CHART_YAML_FILENAME))
.unwrap();
let chart: ChartYaml = serde_yaml::from_str(&chart_file.contents).unwrap();
assert_eq!(
chart.api_version, HELM_CHART_API_VERSION,
"rendered Chart.yaml `apiVersion` must equal the lifted \
HELM_CHART_API_VERSION verbatim — a drifted value silently \
reroutes the rendered chart through the wrong Helm chart-schema \
parser at `helm template` time"
);
}
#[test]
fn helm_chart_type_application_re_export_points_at_caixa_core_canonical() {
caixa_core::assert_str_reexport_identity(
"HELM_CHART_TYPE_APPLICATION",
HELM_CHART_TYPE_APPLICATION,
caixa_core::HELM_CHART_TYPE_APPLICATION,
);
}
#[test]
fn helm_chart_type_library_re_export_points_at_caixa_core_canonical() {
caixa_core::assert_str_reexport_identity(
"HELM_CHART_TYPE_LIBRARY",
HELM_CHART_TYPE_LIBRARY,
caixa_core::HELM_CHART_TYPE_LIBRARY,
);
}
#[test]
fn chart_yaml_uses_lifted_helm_chart_type_application() {
let dir = render_chart_for_servico(&sample_caixa(), &sample_cu_yaml()).unwrap();
let chart_file = dir
.files
.iter()
.find(|f| f.path == PathBuf::from(HELM_CHART_YAML_FILENAME))
.unwrap();
let chart: ChartYaml = serde_yaml::from_str(&chart_file.contents).unwrap();
assert_eq!(
chart.chart_type, HELM_CHART_TYPE_APPLICATION,
"rendered Chart.yaml `type` must equal the lifted \
HELM_CHART_TYPE_APPLICATION verbatim — a drifted value \
silently reroutes the rendered chart through the wrong \
per-release install-shape dispatch at `helm install` time \
(Helm refuses to install a `library` chart directly, or \
silently treats an unrecognized value as the default \
`application` shape masking the schema violation)"
);
}
#[test]
fn helm_chart_key_type_re_export_points_at_caixa_core_canonical() {
caixa_core::assert_str_reexport_identity(
"HELM_CHART_KEY_TYPE",
HELM_CHART_KEY_TYPE,
caixa_core::HELM_CHART_KEY_TYPE,
);
}
#[test]
fn helm_chart_key_app_version_re_export_points_at_caixa_core_canonical() {
caixa_core::assert_str_reexport_identity(
"HELM_CHART_KEY_APP_VERSION",
HELM_CHART_KEY_APP_VERSION,
caixa_core::HELM_CHART_KEY_APP_VERSION,
);
}
#[test]
fn chart_yaml_serializes_type_axis_under_lifted_helm_chart_key_type() {
let dir = render_chart_for_servico(&sample_caixa(), &sample_cu_yaml()).unwrap();
let chart_file = dir
.files
.iter()
.find(|f| f.path == PathBuf::from(HELM_CHART_YAML_FILENAME))
.unwrap();
let doc: serde_yaml::Value = serde_yaml::from_str(&chart_file.contents).unwrap();
let mapping = doc.as_mapping().expect(
"rendered Chart.yaml must be a top-level YAML mapping per \
the Helm 3 chart-schema shape",
);
assert!(
mapping.contains_key(serde_yaml::Value::String(HELM_CHART_KEY_TYPE.to_string())),
"rendered Chart.yaml must carry a top-level {HELM_CHART_KEY_TYPE:?} \
axis-key — a drift on the `#[serde(rename = {HELM_CHART_KEY_TYPE:?})]` \
attribute at ChartYaml's `chart_type` field silently reroutes the \
per-chart-kind discriminator axis through Rust's default snake_case \
serialization (`chart_type:`), which Helm's chart-schema parser \
silently ignores as an unknown top-level key (defaulting the \
per-chart-kind axis to `application` with no process-log signal)"
);
}
#[test]
fn chart_yaml_serializes_app_version_axis_under_lifted_helm_chart_key_app_version() {
let dir = render_chart_for_servico(&sample_caixa(), &sample_cu_yaml()).unwrap();
let chart_file = dir
.files
.iter()
.find(|f| f.path == PathBuf::from(HELM_CHART_YAML_FILENAME))
.unwrap();
let doc: serde_yaml::Value = serde_yaml::from_str(&chart_file.contents).unwrap();
let mapping = doc.as_mapping().expect(
"rendered Chart.yaml must be a top-level YAML mapping per \
the Helm 3 chart-schema shape",
);
assert!(
mapping.contains_key(serde_yaml::Value::String(
HELM_CHART_KEY_APP_VERSION.to_string()
)),
"rendered Chart.yaml must carry a top-level \
{HELM_CHART_KEY_APP_VERSION:?} axis-key — a drift on the \
`#[serde(rename = {HELM_CHART_KEY_APP_VERSION:?})]` attribute at \
ChartYaml's `app_version` field silently reroutes the per-chart \
underlying-application-version axis through Rust's default \
snake_case serialization (`app_version:`), which Helm's \
chart-schema parser silently drops from the parsed chart-metadata \
shape (every downstream Artifact Hub / `helm search` per-chart \
index falls back to \"no application version\" for the rendered \
chart with no process-log signal at the substrate-side emitter site)"
);
}
#[test]
fn chart_yaml_serializes_api_version_axis_under_lifted_helm_chart_key_api_version() {
let dir = render_chart_for_servico(&sample_caixa(), &sample_cu_yaml()).unwrap();
let chart_file = dir
.files
.iter()
.find(|f| f.path == PathBuf::from(HELM_CHART_YAML_FILENAME))
.unwrap();
let doc: serde_yaml::Value = serde_yaml::from_str(&chart_file.contents).unwrap();
let mapping = doc.as_mapping().expect(
"rendered Chart.yaml must be a top-level YAML mapping per \
the Helm 3 chart-schema shape",
);
assert!(
mapping.contains_key(serde_yaml::Value::String(
HELM_CHART_KEY_API_VERSION.to_string()
)),
"rendered Chart.yaml must carry a top-level \
{HELM_CHART_KEY_API_VERSION:?} axis-key — a drift on the \
`#[serde(rename = {HELM_CHART_KEY_API_VERSION:?})]` attribute at \
ChartYaml's `api_version` field silently reroutes the per-chart \
chart-schema-apiVersion axis through Rust's default snake_case \
serialization (`api_version:`), which Helm's chart-schema parser \
rejects at `helm lint` / `helm template` time with an \"apiVersion \
is required\" error far from the drift site"
);
}
#[test]
fn chart_dependency_serializes_tetrad_under_lifted_helm_chart_dependency_keys() {
let dep = ChartDependency {
name: "pleme-computeunit".into(),
version: "~0.1.0".into(),
repository: "file://../pleme-computeunit".into(),
alias: Some("acme-alias".into()),
};
let doc = serde_yaml::to_value(&dep).unwrap();
let mapping = doc.as_mapping().expect(
"ChartDependency must serialize to a top-level YAML mapping per \
the Helm 3 per-dep sub-mapping shape",
);
for key in [
HELM_CHART_DEPENDENCY_KEY_NAME,
HELM_CHART_DEPENDENCY_KEY_VERSION,
HELM_CHART_DEPENDENCY_KEY_REPOSITORY,
HELM_CHART_DEPENDENCY_KEY_ALIAS,
] {
assert!(
mapping.contains_key(serde_yaml::Value::String(key.to_string())),
"serialized ChartDependency must carry a top-level {key:?} \
axis-key — a drift on the `ChartDependency` struct's serde \
field-name (a Rust-side rename, an added \
`#[serde(rename_all)]` attribute, an added `#[serde(rename)]` \
per-field override) silently reroutes the per-dep sub-mapping \
through a Helm-per-dep-resolver drop at `helm dependency \
build` time far from the drift site (Helm silently drops the \
drifted per-dep sub-mapping field and the per-dep resolver \
falls back to the parsed-shape defaults); the emitted mapping \
keys are {keys:?}",
keys = mapping
.keys()
.filter_map(|k| k.as_str().map(str::to_string))
.collect::<Vec<_>>()
);
}
}
#[test]
fn chart_yaml_serializes_dependencies_axis_under_lifted_helm_chart_key_dependencies() {
let dir = render_chart_for_servico(&sample_caixa(), &sample_cu_yaml()).unwrap();
let chart_file = dir
.files
.iter()
.find(|f| f.path == PathBuf::from(HELM_CHART_YAML_FILENAME))
.unwrap();
let doc: serde_yaml::Value = serde_yaml::from_str(&chart_file.contents).unwrap();
let mapping = doc.as_mapping().expect(
"rendered Chart.yaml must be a top-level YAML mapping per \
the Helm 3 chart-schema shape",
);
assert!(
mapping.contains_key(serde_yaml::Value::String(
HELM_CHART_KEY_DEPENDENCIES.to_string()
)),
"rendered Chart.yaml must carry a top-level \
{HELM_CHART_KEY_DEPENDENCIES:?} axis-key — a drift on the \
`ChartYaml.dependencies` field's serde field-name (a Rust-side \
rename to `deps` / `chart_dependencies`, an added \
`#[serde(rename_all)]` attribute on the enclosing struct, an \
added `#[serde(rename)]` per-field override) silently reroutes \
the per-chart dependency-list axis through an unrecognized \
top-level YAML key (`deps:` / `chartDependencies:` / \
`chart_dependencies:`), which Helm's chart-schema parser \
silently drops from the parsed chart-metadata shape (every \
rendered `lareira-<nome>` chart's install fails with \
`template: no template ... associated with template ...` at \
`helm dependency build` / `helm template` time far from the \
drift site with no field naming the top-level-list-key-drift \
root cause); the emitted top-level mapping keys are {keys:?}",
keys = mapping
.keys()
.filter_map(|k| k.as_str().map(str::to_string))
.collect::<Vec<_>>()
);
}
#[test]
fn helm_chart_key_dependencies_re_export_points_at_caixa_core_canonical() {
caixa_core::assert_str_reexport_identity(
"HELM_CHART_KEY_DEPENDENCIES",
HELM_CHART_KEY_DEPENDENCIES,
caixa_core::HELM_CHART_KEY_DEPENDENCIES,
);
}
#[test]
fn render_opts_default_library_name_follows_lifted_constant() {
let opts = RenderOpts::default();
assert_eq!(opts.library_name, caixa_core::DEFAULT_LIBRARY_NAME);
assert_eq!(opts.library_name, "pleme-computeunit");
}
#[test]
fn helm_chart_yaml_filename_re_export_points_at_caixa_core_canonical() {
caixa_core::assert_str_reexport_identity(
"HELM_CHART_YAML_FILENAME",
HELM_CHART_YAML_FILENAME,
caixa_core::HELM_CHART_YAML_FILENAME,
);
}
#[test]
fn helm_values_yaml_filename_re_export_points_at_caixa_core_canonical() {
caixa_core::assert_str_reexport_identity(
"HELM_VALUES_YAML_FILENAME",
HELM_VALUES_YAML_FILENAME,
caixa_core::HELM_VALUES_YAML_FILENAME,
);
}
#[test]
fn helm_chart_readme_filename_re_export_points_at_caixa_core_canonical() {
caixa_core::assert_str_reexport_identity(
"HELM_CHART_README_FILENAME",
HELM_CHART_README_FILENAME,
caixa_core::HELM_CHART_README_FILENAME,
);
}
#[test]
fn helm_values_key_enabled_re_export_points_at_caixa_core_canonical() {
caixa_core::assert_str_reexport_identity(
"HELM_VALUES_KEY_ENABLED",
HELM_VALUES_KEY_ENABLED,
caixa_core::HELM_VALUES_KEY_ENABLED,
);
}
#[test]
fn values_yaml_enable_toggle_key_pins_lifted_helm_values_key_enabled() {
let dir = render_chart_for_servico(&sample_caixa(), &sample_cu_yaml()).unwrap();
let values = dir
.files
.iter()
.find(|f| f.path == PathBuf::from(HELM_VALUES_YAML_FILENAME))
.unwrap();
let parsed: serde_yaml::Value = serde_yaml::from_str(&values.contents).unwrap();
let cu_block = parsed
.get(DEFAULT_LIBRARY_NAME)
.expect("must wrap under DEFAULT_LIBRARY_NAME");
assert_eq!(
cu_block.get(HELM_VALUES_KEY_ENABLED),
Some(&serde_yaml::Value::Bool(false)),
"rendered values.yaml `{DEFAULT_LIBRARY_NAME}.{HELM_VALUES_KEY_ENABLED}` must \
equal the default-off toggle the lifted HELM_VALUES_KEY_ENABLED axis carries — \
a drifted enable-toggle key silently splits the per-values enable-flip across \
two sibling scalar names on the caixa-helm / caixa-flux consumer split"
);
}
#[test]
fn computeunit_spec_key_module_re_export_points_at_caixa_core_canonical() {
caixa_core::assert_str_reexport_identity(
"COMPUTEUNIT_SPEC_KEY_MODULE",
COMPUTEUNIT_SPEC_KEY_MODULE,
caixa_core::COMPUTEUNIT_SPEC_KEY_MODULE,
);
}
#[test]
fn computeunit_spec_key_trigger_re_export_points_at_caixa_core_canonical() {
caixa_core::assert_str_reexport_identity(
"COMPUTEUNIT_SPEC_KEY_TRIGGER",
COMPUTEUNIT_SPEC_KEY_TRIGGER,
caixa_core::COMPUTEUNIT_SPEC_KEY_TRIGGER,
);
}
#[test]
fn computeunit_spec_key_capabilities_re_export_points_at_caixa_core_canonical() {
caixa_core::assert_str_reexport_identity(
"COMPUTEUNIT_SPEC_KEY_CAPABILITIES",
COMPUTEUNIT_SPEC_KEY_CAPABILITIES,
caixa_core::COMPUTEUNIT_SPEC_KEY_CAPABILITIES,
);
}
#[test]
fn chart_file_alias_resolves_to_caixa_core_rendered_file() {
let canonical: caixa_core::RenderedFile = caixa_core::RenderedFile {
path: PathBuf::from(HELM_CHART_YAML_FILENAME),
contents: String::new(),
};
let aliased: ChartFile = canonical.clone();
assert_eq!(aliased, canonical);
let via_alias = ChartFile {
path: PathBuf::from(HELM_VALUES_YAML_FILENAME),
contents: format!("{DEFAULT_LIBRARY_NAME}:\n enabled: false\n"),
};
assert_eq!(via_alias.path.to_string_lossy(), HELM_VALUES_YAML_FILENAME);
}
#[test]
fn chart_file_new_constructor_travels_through_alias_to_canonical() {
let via_alias_new: ChartFile = ChartFile::new(HELM_CHART_YAML_FILENAME, "apiVersion: v2\n");
let via_canonical_new = caixa_core::RenderedFile::new(
HELM_CHART_YAML_FILENAME,
String::from("apiVersion: v2\n"),
);
assert_eq!(via_alias_new, via_canonical_new);
assert_eq!(via_alias_new.path, PathBuf::from(HELM_CHART_YAML_FILENAME));
assert_eq!(via_alias_new.contents, "apiVersion: v2\n");
}
#[test]
fn render_opts_default_library_version_follows_lifted_constant() {
let opts = RenderOpts::default();
assert_eq!(opts.library_version, DEFAULT_LIBRARY_VERSION);
assert_eq!(opts.library_version, "~0.1.0");
}
#[test]
fn render_opts_default_enabled_default_follows_lifted_constant() {
let opts = RenderOpts::default();
assert_eq!(opts.enabled_default, STANDALONE_LAREIRA_ENABLED_DEFAULT);
assert!(!opts.enabled_default);
}
#[test]
fn standalone_lareira_enabled_default_re_export_matches_caixa_core_canonical_value() {
assert_eq!(
STANDALONE_LAREIRA_ENABLED_DEFAULT,
caixa_core::STANDALONE_LAREIRA_ENABLED_DEFAULT,
"STANDALONE_LAREIRA_ENABLED_DEFAULT re-export must remain the \
same `bool` as its caixa-core canonical — a drifted local \
`pub const STANDALONE_LAREIRA_ENABLED_DEFAULT: bool = …` at \
caixa-helm would silently split the substrate's chosen \
standalone per-chart opt-out seed from the peer \
composition-path force-on inversion the caixa-core canonical \
encodes."
);
assert!(
!STANDALONE_LAREIRA_ENABLED_DEFAULT,
"STANDALONE_LAREIRA_ENABLED_DEFAULT must remain `false` — the \
standalone per-chart path is the substrate-side opt-out path \
where cluster operators must opt each caixa in per-cluster, \
inverse of the composition per-cluster-HelmRelease values-\
overlay path's opt-in force-on."
);
}
#[test]
fn render_opts_default_library_repo_follows_lifted_constant() {
let opts = RenderOpts::default();
assert_eq!(opts.library_repo, DEFAULT_LIBRARY_REPO);
assert_eq!(opts.library_repo, "file://../pleme-computeunit");
}
#[test]
fn default_library_version_parses_as_valid_semver_requirement() {
caixa_core::parse_requirement(DEFAULT_LIBRARY_VERSION).unwrap_or_else(|e| {
panic!(
"DEFAULT_LIBRARY_VERSION {DEFAULT_LIBRARY_VERSION:?} must parse as a valid \
semver::VersionReq — every rendered lareira-<nome> chart's Chart.yaml \
dependencies[0].version axis lands this scalar verbatim, and Helm 3's \
chart-schema parser rejects a malformed shape at chart-consumption time \
far from the constant-drift commit's source: {e}",
)
});
}
#[test]
fn default_library_repo_ends_with_lifted_default_library_name() {
assert!(
DEFAULT_LIBRARY_REPO.ends_with(DEFAULT_LIBRARY_NAME),
"DEFAULT_LIBRARY_REPO {DEFAULT_LIBRARY_REPO:?} must terminate with the lifted \
DEFAULT_LIBRARY_NAME {DEFAULT_LIBRARY_NAME:?} — the two-axis (repository, name) \
per-Chart.yaml-dep tuple must resolve to the same library-chart identity on \
disk, so a rebrand on either axis must move both",
);
}
#[test]
fn chart_yaml_version_routes_through_caixa_versao_accessor() {
let caixa = sample_caixa();
let dir = render_chart_for_servico(&caixa, &sample_cu_yaml()).unwrap();
let chart_file = dir
.files
.iter()
.find(|f| f.path == PathBuf::from(HELM_CHART_YAML_FILENAME))
.expect("Chart.yaml present");
let chart: ChartYaml = serde_yaml::from_str(&chart_file.contents).unwrap();
assert_eq!(
chart.version.as_str(),
caixa.versao(),
"Chart.yaml `version:` must derive from the typed \
`caixa_core::Caixa::versao` accessor byte-for-byte — a regression \
that re-inlines `caixa.versao.clone()` at the emit site silently \
splits the per-`Chart.yaml` `version:` axis from every future \
accessor extension (SemVer-2 build-metadata canonicalization, \
OCI-tag normalization, per-edition pre-release-tag overlay) that \
lands on the accessor",
);
}
#[test]
fn chart_yaml_app_version_routes_through_caixa_versao_accessor() {
let caixa = sample_caixa();
let dir = render_chart_for_servico(&caixa, &sample_cu_yaml()).unwrap();
let chart_file = dir
.files
.iter()
.find(|f| f.path == PathBuf::from(HELM_CHART_YAML_FILENAME))
.expect("Chart.yaml present");
let chart: ChartYaml = serde_yaml::from_str(&chart_file.contents).unwrap();
assert_eq!(
chart.app_version.as_str(),
caixa.versao(),
"Chart.yaml `appVersion:` must derive from the typed \
`caixa_core::Caixa::versao` accessor byte-for-byte — a regression \
that re-inlines `caixa.versao.clone()` at the emit site silently \
splits the per-`Chart.yaml` `appVersion:` axis from every future \
accessor extension (SemVer-2 build-metadata canonicalization, \
OCI-tag normalization, per-edition pre-release-tag overlay) that \
lands on the accessor",
);
}
#[test]
fn chart_yaml_name_routes_through_caixa_nome_accessor() {
let caixa = sample_caixa();
let dir = render_chart_for_servico(&caixa, &sample_cu_yaml()).unwrap();
let chart_file = dir
.files
.iter()
.find(|f| f.path == PathBuf::from(HELM_CHART_YAML_FILENAME))
.expect("Chart.yaml present");
let chart: ChartYaml = serde_yaml::from_str(&chart_file.contents).unwrap();
assert_eq!(
chart.name,
caixa_core::lareira_chart_name(caixa.nome()),
"Chart.yaml `name:` must derive from the typed \
`caixa_core::Caixa::nome` accessor through \
`caixa_core::lareira_chart_name` byte-for-byte — a regression \
that re-inlines `lareira_chart_name(&caixa.nome)` at the emit \
site silently splits the per-`Chart.yaml` `name:` axis from \
every future accessor extension (per-cluster alias overlay, \
M4 CR-materializer name rewrite, `:nome-suffix` slot) that \
lands on the accessor",
);
}
#[test]
fn chart_yaml_description_fallback_routes_through_caixa_nome_accessor() {
let caixa = Caixa {
descricao: None,
..sample_caixa()
};
let dir = render_chart_for_servico(&caixa, &sample_cu_yaml()).unwrap();
let chart_file = dir
.files
.iter()
.find(|f| f.path == PathBuf::from(HELM_CHART_YAML_FILENAME))
.expect("Chart.yaml present");
let chart: ChartYaml = serde_yaml::from_str(&chart_file.contents).unwrap();
assert_eq!(
chart.description,
format!("Generated chart for caixa Servico {}", caixa.nome()),
"Chart.yaml `description:` `:descricao`-null fallback must \
derive from the typed `caixa_core::Caixa::nome` accessor \
byte-for-byte — a regression that re-inlines \
`format!(\"Generated chart for caixa Servico {{}}\", caixa.nome)` \
at the emit site silently splits the per-`Chart.yaml` \
`description:` axis from every future accessor extension \
that lands on the accessor",
);
}
#[test]
fn values_yaml_header_nome_routes_through_caixa_nome_accessor() {
let caixa = sample_caixa();
let dir = render_chart_for_servico(&caixa, &sample_cu_yaml()).unwrap();
let values_file = dir
.files
.iter()
.find(|f| f.path == PathBuf::from(HELM_VALUES_YAML_FILENAME))
.expect("values.yaml present");
let expected = format!("servicos/{}.computeunit.yaml", caixa.nome());
assert!(
values_file.contents.contains(&expected),
"values.yaml header comment must carry the typed \
`caixa_core::Caixa::nome` accessor's byte-string \
({expected:?}) verbatim — a regression that re-inlines \
`caixa.nome` in the header format silently splits the \
values.yaml provenance-annotation axis from every future \
accessor extension that lands on the accessor. \
Full contents:\n{contents}",
contents = values_file.contents,
);
}
#[test]
fn readme_descricao_fallback_routes_through_caixa_nome_accessor() {
let caixa = Caixa {
descricao: None,
..sample_caixa()
};
let dir = render_chart_for_servico(&caixa, &sample_cu_yaml()).unwrap();
let readme_file = dir
.files
.iter()
.find(|f| f.path == PathBuf::from(HELM_CHART_README_FILENAME))
.expect("README.md present");
let expected = format!("caixa Servico {}", caixa.nome());
assert!(
readme_file.contents.contains(&expected),
"README.md `:descricao`-null fallback must carry the typed \
`caixa_core::Caixa::nome` accessor's byte-string ({expected:?}) \
verbatim — a regression that re-inlines `format!(\"caixa \
Servico {{}}\", caixa.nome)` at the emit site silently \
splits the README fallback-descricao axis from every future \
accessor extension. Full contents:\n{contents}",
contents = readme_file.contents,
);
}
#[test]
fn readme_body_version_routes_through_caixa_versao_accessor() {
let caixa = sample_caixa();
let dir = render_chart_for_servico(&caixa, &sample_cu_yaml()).unwrap();
let readme_file = dir
.files
.iter()
.find(|f| f.path == PathBuf::from(HELM_CHART_README_FILENAME))
.expect("README.md present");
let expected = format!("caixa.lisp` v{}.", caixa.versao());
assert!(
readme_file.contents.contains(&expected),
"README.md `Origin`-line `v{{versao}}` scalar must derive from \
the typed `caixa_core::Caixa::versao` accessor byte-for-byte \
({expected:?}) — a regression that re-inlines `caixa.versao` \
in the format silently splits the README origin-line version \
axis from every future accessor extension (SemVer-2 \
build-metadata canonicalization, OCI-tag normalization, \
per-edition pre-release-tag overlay) that lands on the \
accessor. Full contents:\n{contents}",
contents = readme_file.contents,
);
}
#[test]
fn readme_repositorio_fallback_routes_through_caixa_nome_accessor() {
let caixa = Caixa {
repositorio: None,
..sample_caixa()
};
let dir = render_chart_for_servico(&caixa, &sample_cu_yaml()).unwrap();
let readme_file = dir
.files
.iter()
.find(|f| f.path == PathBuf::from(HELM_CHART_README_FILENAME))
.expect("README.md present");
let expected = format!(
"Generated by `caixa-helm` from `{}/caixa.lisp`",
caixa.nome()
);
assert!(
readme_file.contents.contains(&expected),
"README.md `:repositorio`-null fallback must derive its \
`repo` interpolation from the typed \
`caixa_core::Caixa::nome` accessor byte-for-byte \
({expected:?}) — a regression that re-inlines \
`caixa.nome.as_str()` at the emit site silently splits \
the README origin-line repo axis from every future \
accessor extension. Full contents:\n{contents}",
contents = readme_file.contents,
);
}
}