#![cfg(test)]
use super::executor::{Publisher, TagCreator, execute_plan};
use super::plan::{LoadedEntry, LoadedPlan, parse_plan};
use super::policy::{
TagKind, TriggerAction, allows_prepublish_tagging, classify_tag, platform_trigger_action,
transaction_trigger_action, verify_crate_tag,
};
use super::registry::FakeRegistry;
use super::tag::{CrateTag, is_crate_tag, is_platform_tag, is_transaction_tag, parse_crate_tag};
use super::validate::validate_plan;
use crate::release::discovered::DiscoveredCrate;
use crate::release::graph::{UnitGraph, UnitNode};
use crate::release::metadata::{CrateMetadata, ReleaseMetadata, Role};
use crate::release::version::Ybf;
use std::collections::{BTreeMap, BTreeSet};
const SHA: &str = "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0";
struct AlwaysSucceedPublisher;
impl Publisher for AlwaysSucceedPublisher {
fn publish_crate(&mut self, _name: &str, _version: &Ybf) -> u16 {
201
}
}
struct AlwaysFailPublisher;
impl Publisher for AlwaysFailPublisher {
fn publish_crate(&mut self, _name: &str, _version: &Ybf) -> u16 {
403
}
}
struct RecordingTagCreator {
tags: Vec<String>,
}
impl RecordingTagCreator {
fn new() -> Self {
Self { tags: Vec::new() }
}
}
impl TagCreator for RecordingTagCreator {
fn create_tag(&mut self, tag: &str) -> Result<(), String> {
self.tags.push(tag.to_string());
Ok(())
}
}
fn make_plan(entries: Vec<LoadedEntry>) -> LoadedPlan {
let map: BTreeMap<String, LoadedEntry> = entries
.into_iter()
.map(|e| (e.crate_name.clone(), e))
.collect();
LoadedPlan {
transaction_id: "2026-08-15.01".to_string(),
commit: SHA.to_string(),
entries: map,
}
}
fn entry(name: &str, version: &str, order: usize) -> LoadedEntry {
LoadedEntry {
crate_name: name.to_string(),
version: Ybf::parse(version).unwrap(),
order,
}
}
fn make_crate(name: &str, unit: &str) -> DiscoveredCrate {
DiscoveredCrate {
name: name.to_string(),
manifest_path: std::path::PathBuf::from(format!("crates/{name}/Cargo.toml")),
proc_macro: false,
publishable: true,
metadata: CrateMetadata::Valid(ReleaseMetadata {
publish: true,
role: Some(Role::Subsystem),
release_unit: Some(unit.to_string()),
}),
}
}
fn single_unit_graph() -> UnitGraph {
let mut nodes = BTreeMap::new();
nodes.insert(
"arcature-auth".to_string(),
UnitNode {
name: "arcature-auth".to_string(),
members: BTreeSet::new(),
dependencies: BTreeMap::new(),
},
);
UnitGraph { nodes }
}
fn core_unit_graph() -> UnitGraph {
let mut nodes = BTreeMap::new();
nodes.insert(
"core".to_string(),
UnitNode {
name: "core".to_string(),
members: BTreeSet::new(),
dependencies: BTreeMap::new(),
},
);
UnitGraph { nodes }
}
#[test]
fn release_selection_from_plan_not_tags() {
let plan = make_plan(vec![
entry("arcature-auth", "2026.1.7", 1),
entry("arcature-cli", "2026.1.9", 2),
]);
let mut registry = FakeRegistry::default();
let mut publisher = AlwaysSucceedPublisher;
let mut tags = RecordingTagCreator::new();
let report = execute_plan(&plan, &mut registry, &mut publisher, &mut tags);
assert!(report.all_published);
assert_eq!(
tags.tags,
vec!["arcature-auth-v2026.1.7", "arcature-cli-v2026.1.9"]
);
}
#[test]
fn one_transaction_different_versions() {
let plan = make_plan(vec![
entry("arcature-auth", "2026.1.7", 1),
entry("arcature-cli", "2026.1.9", 2),
entry("arcature-jobs", "2026.2.2", 3),
]);
let mut registry = FakeRegistry::default();
let mut publisher = AlwaysSucceedPublisher;
let mut tags = RecordingTagCreator::new();
let report = execute_plan(&plan, &mut registry, &mut publisher, &mut tags);
assert!(report.all_published);
assert_eq!(report.published_count(), 3);
assert!(tags.tags.contains(&"arcature-auth-v2026.1.7".to_string()));
assert!(tags.tags.contains(&"arcature-cli-v2026.1.9".to_string()));
assert!(tags.tags.contains(&"arcature-jobs-v2026.2.2".to_string()));
}
#[test]
fn no_shared_global_version_required() {
let plan = make_plan(vec![
entry("arcature-auth", "2026.1.7", 1),
entry("arcature-pages", "2026.1.3", 2),
]);
let mut registry = FakeRegistry::default();
let mut publisher = AlwaysSucceedPublisher;
let mut tags = RecordingTagCreator::new();
let report = execute_plan(&plan, &mut registry, &mut publisher, &mut tags);
assert!(report.all_published);
assert_eq!(report.published_count(), 2);
}
#[test]
fn tags_created_only_after_publish_success() {
let plan = make_plan(vec![
entry("arcature-auth", "2026.1.7", 1),
entry("arcature-cli", "2026.1.9", 2),
]);
let mut registry = FakeRegistry::default();
let mut publisher = AlwaysFailPublisher;
let mut tags = RecordingTagCreator::new();
let report = execute_plan(&plan, &mut registry, &mut publisher, &mut tags);
assert!(!report.all_published);
assert!(tags.tags.is_empty());
}
#[test]
fn tag_created_only_for_successful_crate() {
let plan = make_plan(vec![
entry("arcature-auth", "2026.1.7", 1),
entry("arcature-cli", "2026.1.9", 2),
]);
struct PartialPublisher {
call_count: u32,
}
impl Publisher for PartialPublisher {
fn publish_crate(&mut self, _name: &str, _version: &Ybf) -> u16 {
self.call_count += 1;
if self.call_count == 1 { 201 } else { 403 }
}
}
let mut registry = FakeRegistry::default();
let mut publisher = PartialPublisher { call_count: 0 };
let mut tags = RecordingTagCreator::new();
let report = execute_plan(&plan, &mut registry, &mut publisher, &mut tags);
assert!(!report.all_published);
assert_eq!(report.published_count(), 1);
assert_eq!(tags.tags, vec!["arcature-auth-v2026.1.7"]);
}
#[test]
fn crate_tags_do_not_trigger_publication() {
let crate_tag = "arcature-auth-v2026.1.7";
assert!(is_crate_tag(crate_tag));
let kind = classify_tag(crate_tag).unwrap();
assert_eq!(kind, TagKind::CrateVersion);
assert_eq!(transaction_trigger_action(kind), TriggerAction::Reject);
assert_eq!(platform_trigger_action(kind), TriggerAction::Reject);
}
#[test]
fn platform_tags_do_not_publish() {
let platform_tag = "v2026.3";
assert!(is_platform_tag(platform_tag));
assert!(!is_crate_tag(platform_tag));
let kind = classify_tag(platform_tag).unwrap();
assert_eq!(kind, TagKind::Platform);
assert_eq!(transaction_trigger_action(kind), TriggerAction::Reject);
}
#[test]
fn platform_tags_certify_version_set() {
let platform_tag = "v2026.1";
let kind = classify_tag(platform_tag).unwrap();
assert_eq!(kind, TagKind::Platform);
assert_eq!(platform_trigger_action(kind), TriggerAction::Accept);
}
#[test]
fn partial_publication_resumes() {
let plan = make_plan(vec![
entry("arcature-auth", "2026.1.7", 1),
entry("arcature-cli", "2026.1.9", 2),
]);
struct PartialPublisher {
call_count: u32,
}
impl Publisher for PartialPublisher {
fn publish_crate(&mut self, _name: &str, _version: &Ybf) -> u16 {
self.call_count += 1;
if self.call_count == 1 { 201 } else { 403 }
}
}
let mut registry = FakeRegistry::default();
let mut publisher = PartialPublisher { call_count: 0 };
let mut tags = RecordingTagCreator::new();
let report = execute_plan(&plan, &mut registry, &mut publisher, &mut tags);
assert!(!report.all_published);
assert_eq!(tags.tags, vec!["arcature-auth-v2026.1.7"]);
let mut registry2 = FakeRegistry::default();
registry2
.published
.insert(("arcature-auth".to_string(), "2026.1.7".to_string()), true);
let mut publisher2 = AlwaysSucceedPublisher;
let mut tags2 = RecordingTagCreator::new();
let report2 = execute_plan(&plan, &mut registry2, &mut publisher2, &mut tags2);
assert!(report2.all_published);
assert_eq!(report2.already_published_count(), 1);
assert_eq!(report2.published_count(), 1);
assert_eq!(tags2.tags, vec!["arcature-cli-v2026.1.9"]);
}
#[test]
fn workflow_inputs_cannot_override_versions() {
let plan = make_plan(vec![entry("arcature-auth", "2026.1.7", 1)]);
let mut registry = FakeRegistry::default();
let mut publisher = AlwaysSucceedPublisher;
let mut tags = RecordingTagCreator::new();
let report = execute_plan(&plan, &mut registry, &mut publisher, &mut tags);
assert!(report.all_published);
assert_eq!(tags.tags, vec!["arcature-auth-v2026.1.7"]);
}
#[test]
fn production_transaction_pinned_to_commit() {
let crates = vec![make_crate("arcature-auth", "arcature-auth")];
let plan = make_plan(vec![entry("arcature-auth", "2026.1.7", 1)]);
let findings = validate_plan(&plan, SHA, &crates, &single_unit_graph());
assert!(findings.is_empty());
let wrong_sha = "0000000000000000000000000000000000000000";
let findings = validate_plan(&plan, wrong_sha, &crates, &single_unit_graph());
assert!(
findings
.iter()
.any(|f| f.message.contains("does not match HEAD"))
);
}
#[test]
fn three_tag_kinds_mutually_exclusive() {
let crate_tags = ["arcature-auth-v2026.1.7", "arcature-dx-v2026.2.0"];
let platform_tags = ["v2026.1", "v2027.0"];
let transaction_tags = ["release-2026-08-15.01", "release-2026-12-31.99"];
for tag in &crate_tags {
assert!(is_crate_tag(tag));
assert!(!is_platform_tag(tag));
assert!(!is_transaction_tag(tag));
assert_eq!(classify_tag(tag).unwrap(), TagKind::CrateVersion);
}
for tag in &platform_tags {
assert!(!is_crate_tag(tag));
assert!(is_platform_tag(tag));
assert!(!is_transaction_tag(tag));
assert_eq!(classify_tag(tag).unwrap(), TagKind::Platform);
}
for tag in &transaction_tags {
assert!(!is_crate_tag(tag));
assert!(!is_platform_tag(tag));
assert!(is_transaction_tag(tag));
assert_eq!(classify_tag(tag).unwrap(), TagKind::Transaction);
}
}
#[test]
fn no_prepublish_tagging() {
assert!(!allows_prepublish_tagging(TagKind::CrateVersion));
assert!(!allows_prepublish_tagging(TagKind::Platform));
assert!(!allows_prepublish_tagging(TagKind::Transaction));
}
#[test]
fn post_publish_tag_verification() {
let version = Ybf::parse("2026.1.7").unwrap();
assert!(verify_crate_tag("arcature-auth-v2026.1.7", "arcature-auth", &version).is_ok());
assert!(verify_crate_tag("arcature-cli-v2026.1.7", "arcature-auth", &version).is_err());
assert!(verify_crate_tag("arcature-auth-v2026.1.8", "arcature-auth", &version).is_err());
assert!(verify_crate_tag("not-a-tag", "arcature-auth", &version).is_err());
}
#[test]
fn registry_409_is_idempotent_skip() {
let plan = make_plan(vec![entry("arcature-auth", "2026.1.7", 1)]);
struct ConflictPublisher;
impl Publisher for ConflictPublisher {
fn publish_crate(&mut self, _name: &str, _version: &Ybf) -> u16 {
409
}
}
let mut registry = FakeRegistry::default();
let mut publisher = ConflictPublisher;
let mut tags = RecordingTagCreator::new();
let report = execute_plan(&plan, &mut registry, &mut publisher, &mut tags);
assert!(report.all_published);
assert_eq!(report.already_published_count(), 1);
assert!(tags.tags.is_empty());
}
#[test]
fn auth_error_fails_fast() {
let plan = make_plan(vec![
entry("arcature-auth", "2026.1.7", 1),
entry("arcature-cli", "2026.1.9", 2),
entry("arcature-jobs", "2026.1.5", 3),
]);
let mut registry = FakeRegistry::default();
let mut publisher = AlwaysFailPublisher;
let mut tags = RecordingTagCreator::new();
let report = execute_plan(&plan, &mut registry, &mut publisher, &mut tags);
assert!(!report.all_published);
assert_eq!(report.failed_count(), 1);
assert_eq!(report.skipped_count(), 2);
assert!(tags.tags.is_empty());
}
#[test]
fn build_error_fails_fast() {
let plan = make_plan(vec![
entry("arcature-auth", "2026.1.7", 1),
entry("arcature-cli", "2026.1.9", 2),
]);
struct BuildFailPublisher;
impl Publisher for BuildFailPublisher {
fn publish_crate(&mut self, _name: &str, _version: &Ybf) -> u16 {
400
}
}
let mut registry = FakeRegistry::default();
let mut publisher = BuildFailPublisher;
let mut tags = RecordingTagCreator::new();
let report = execute_plan(&plan, &mut registry, &mut publisher, &mut tags);
assert!(!report.all_published);
assert_eq!(report.failed_count(), 1);
assert_eq!(report.skipped_count(), 1);
}
#[test]
fn empty_plan_succeeds() {
let plan = make_plan(vec![]);
let mut registry = FakeRegistry::default();
let mut publisher = AlwaysSucceedPublisher;
let mut tags = RecordingTagCreator::new();
let report = execute_plan(&plan, &mut registry, &mut publisher, &mut tags);
assert!(report.all_published);
assert_eq!(report.results.len(), 0);
assert!(tags.tags.is_empty());
}
#[test]
fn tag_name_format() {
let v = Ybf::parse("2026.1.7").unwrap();
assert_eq!(
CrateTag::render("arcature-auth", &v),
"arcature-auth-v2026.1.7"
);
assert_eq!(CrateTag::render("arcature-dx", &v), "arcature-dx-v2026.1.7");
let parsed = parse_crate_tag("arcature-auth-v2026.1.7").unwrap();
assert_eq!(parsed.crate_name, "arcature-auth");
assert_eq!(parsed.version, v);
}
#[test]
fn core_atomicity_enforced() {
let crates = vec![
make_crate("arcature", "core"),
make_crate("arcature-dx", "core"),
];
let plan = make_plan(vec![entry("arcature", "2026.1.5", 1)]);
let findings = validate_plan(&plan, SHA, &crates, &core_unit_graph());
assert!(
findings
.iter()
.any(|f| f.message.contains("core atomicity"))
);
let plan = make_plan(vec![
entry("arcature", "2026.1.5", 1),
entry("arcature-dx", "2026.1.6", 2),
]);
let findings = validate_plan(&plan, SHA, &crates, &core_unit_graph());
assert!(
findings
.iter()
.any(|f| f.message.contains("core atomicity"))
);
let plan = make_plan(vec![
entry("arcature", "2026.1.5", 1),
entry("arcature-dx", "2026.1.5", 2),
]);
let findings = validate_plan(&plan, SHA, &crates, &core_unit_graph());
assert!(findings.is_empty());
}
#[test]
fn plan_rejects_invalid_sha() {
let text = "\
[transaction]
id = \"2026-08-15.01\"
commit = \"not-a-sha\"
";
assert!(parse_plan(text).is_err());
}
#[test]
fn plan_rejects_duplicate_crates() {
let text = "\
[transaction]
id = \"2026-08-15.01\"
commit = \"a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0\"
[[publish]]
crate = \"arcature-auth\"
version = \"2026.1.7\"
order = 1
[[publish]]
crate = \"arcature-auth\"
version = \"2026.1.8\"
order = 2
";
assert!(parse_plan(text).is_err());
}
#[test]
fn plan_rejects_invalid_version() {
let text = "\
[transaction]
id = \"2026-08-15.01\"
commit = \"a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0\"
[[publish]]
crate = \"arcature-auth\"
version = \"not-a-version\"
order = 1
";
assert!(parse_plan(text).is_err());
}