use release_tool::command::SystemCommandRunner;
use release_tool::config::{Config, TargetConfig};
use release_tool::domain::{ReleaseCandidate, ReleaseIntent};
use release_tool::tag::CalendarTag;
use release_tool::target::{DockerArchiveTarget, TargetAdapter};
use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::sync::Arc;
#[test]
fn docker_target_prepares_a_verified_archive_manifest_inside_staging() {
let root = tempfile::tempdir().unwrap();
let staging = root.path().join("staging");
let bin = root.path().join("bin");
fs::create_dir(&bin).unwrap();
let log = root.path().join("commands.log");
executable(
&bin.join("build-image"),
"#!/usr/bin/env bash\nset -eu\nprintf 'build %s\\n' \"$*\" >> \"$COMMAND_LOG\"\n",
);
executable(
&bin.join("check-image"),
"#!/usr/bin/env bash\nset -eu\nprintf 'check %s\\n' \"$*\" >> \"$COMMAND_LOG\"\n",
);
executable(
&bin.join("docker"),
r#"#!/usr/bin/env bash
set -eu
case "$1 $2" in
"image inspect") printf 'linux/amd64\n' ;;
"save -o") printf 'deterministic docker archive\n' > "$3" ;;
*) printf 'unexpected docker command: %s\n' "$*" >&2; exit 2 ;;
esac
"#,
);
executable(
&bin.join("xz"),
r#"#!/usr/bin/env bash
set -eu
input="$2"
cp "$input" "$input.xz"
rm "$input"
"#,
);
let path = format!("{}:{}", bin.display(), std::env::var("PATH").unwrap());
unsafe {
std::env::set_var("PATH", path);
std::env::set_var("COMMAND_LOG", &log);
}
let config = Config::parse(
r#"required_version = "0.1.0"
[repository]
github = "computed-parameter/example"
branch = "main"
[publishers.release]
kind = "github_release"
title = "Example {version}"
prerelease = true
[[targets]]
name = "app"
kind = "docker_archive"
publisher = "release"
default = true
platform = "linux/amd64"
image = "example:{version}"
asset = "example-{version}-linux-amd64.tar.xz"
build = ["build-image", "{image}"]
local_check = ["check-image", "{image}"]
"#,
)
.unwrap();
let target = match &config.targets[0] {
value @ TargetConfig::DockerArchive { .. } => value,
_ => unreachable!(),
};
let release = ReleaseCandidate {
repository: "computed-parameter/example".to_owned(),
commit: "commit01".to_owned(),
tag: CalendarTag::parse("2026.08.23.1").unwrap(),
intent: ReleaseIntent::New,
tag_already_sealed: false,
};
let adapter =
DockerArchiveTarget::new(root.path(), target, Arc::new(SystemCommandRunner)).unwrap();
let plan = adapter.resolve(&release).unwrap();
let manifest = adapter.prepare(&plan, &staging).unwrap();
assert_eq!(manifest.target, "app");
assert_eq!(manifest.artifacts.len(), 2);
assert_eq!(
manifest.artifacts[0].path.file_name().unwrap(),
"example-2026.08.23.1-linux-amd64.tar.xz"
);
assert!(manifest.artifacts[0].path.starts_with(&staging));
assert_eq!(manifest.artifacts[0].sha256.len(), 64);
assert_eq!(
fs::read_to_string(&log).unwrap(),
"build example:2026.08.23.1\ncheck example:2026.08.23.1\n"
);
let checksum = fs::read_to_string(&manifest.artifacts[1].path).unwrap();
assert_eq!(
checksum,
format!(
"{} example-2026.08.23.1-linux-amd64.tar.xz\n",
manifest.artifacts[0].sha256
)
);
}
fn executable(path: &std::path::Path, source: &str) {
fs::write(path, source).unwrap();
let mut permissions = fs::metadata(path).unwrap().permissions();
permissions.set_mode(0o755);
fs::set_permissions(path, permissions).unwrap();
}