use lightshuttle_export::{Emitter, ExportArtifacts, HelmEmitter, lower};
use lightshuttle_manifest::Manifest;
mod common;
const STACK: &str = r"
project:
name: shop
version: 1.4.0
export:
helm:
chart_name: shop-chart
resources:
db:
postgres:
version: '16'
password: devsecret
volume: dbdata
api:
container:
image: alpine:3.20
ports:
- 8080:80
env:
LOG_LEVEL: info
API_TOKEN: t0ken
depends_on: [db]
";
fn artifacts(yaml: &str) -> ExportArtifacts {
let manifest = Manifest::parse(yaml).expect("manifest parses");
let model = lower(&manifest).expect("lowering succeeds");
HelmEmitter.emit(&model).expect("emit succeeds")
}
fn file<'a>(artifacts: &'a ExportArtifacts, name: &str) -> &'a str {
let found = artifacts
.files
.iter()
.find(|f| f.path.to_str() == Some(name))
.unwrap_or_else(|| panic!("missing file {name}"));
found.contents.as_str()
}
#[test]
fn matches_golden_files() {
let a = artifacts(STACK);
assert_eq!(
file(&a, "Chart.yaml"),
include_str!("golden/helm/Chart.yaml"),
"Chart.yaml drifted"
);
assert_eq!(
file(&a, "values.yaml"),
include_str!("golden/helm/values.yaml"),
"values.yaml drifted"
);
assert_eq!(
file(&a, "templates/db.yaml"),
include_str!("golden/helm/db.yaml"),
"templates/db.yaml drifted"
);
}
#[test]
fn values_carry_resource_knobs() {
let values = {
let a = artifacts(STACK);
file(&a, "values.yaml").to_owned()
};
assert!(values.contains("replicas: 1"));
assert!(values.contains("repository: postgres"));
assert!(values.contains("LOG_LEVEL: info"), "env in values");
assert!(
values.contains("API_TOKEN: '***'"),
"secret placeholder in values"
);
}
#[test]
fn templates_reference_values() {
let a = artifacts(STACK);
let db = file(&a, "templates/db.yaml");
assert!(db.contains(r#"index .Values.services "db""#), "got:\n{db}");
assert!(db.contains("replicas: {{ $svc.replicas }}"), "got:\n{db}");
assert!(db.contains("range $k, $v := $svc.env"), "got:\n{db}");
}
#[test]
#[ignore = "requires helm on the host"]
fn output_passes_helm_lint() {
use std::io::Write;
if !common::tool_available("helm") {
eprintln!("skipping: helm not found on PATH");
return;
}
let dir = tempfile::tempdir().expect("temp dir");
let chart = dir.path().join("chart");
for f in &artifacts(STACK).files {
let path = chart.join(&f.path);
std::fs::create_dir_all(path.parent().expect("parent")).expect("mkdir");
std::fs::File::create(&path)
.and_then(|mut file| file.write_all(f.contents.as_bytes()))
.expect("write chart file");
}
let output = std::process::Command::new("helm")
.arg("lint")
.arg(&chart)
.output()
.expect("helm runs");
assert!(
output.status.success(),
"helm lint rejected the chart:\n{}\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}