use super::{
open_commit::plain_load_outcome_text,
save_helpers::{NOT_CLOSED_SOLID_MARKER, degenerate_marker_header},
};
use indicatrix::geometry::meet_solver::MeetConstraint;
use indicatrix_cut_core::{
ConstraintTier, Design, FingerprintCheck, FreshDesignSpec, MaterialSelection, PreformSpec,
TierOverlay, load_paired, save_paired,
};
#[test]
fn gear_symmetry_mirror_and_material_all_round_trip_through_save_and_open() {
let spec = FreshDesignSpec {
gear_teeth: 80,
symmetry_order: 5,
mirror: false,
material: MaterialSelection {
name: Some("Quartz".to_string()),
specific_gravity_override: Some(2.65),
refractive_index_override: Some(1.55),
},
preform: PreformSpec::cylinder(80, 1.4, 1.0, 1.3),
};
let mut design = Design::fresh_from_spec(spec);
design.tiers.push(ConstraintTier {
angle_deg: -40.0,
name: "P1".to_string(),
indices: vec![0.0, 16.0, 32.0, 48.0, 64.0],
constraint: MeetConstraint::ScaleReference(0.5),
imported_meet: None,
original_notes: None,
detached: Vec::new(),
});
let saved =
save_paired(&design, "roundtrip.asc", None, None, None).expect("a fresh design must save");
let loaded = load_paired(&saved.asc_text, &saved.native_toml, false).expect("must load back");
assert_eq!(loaded.design.meta.gear_teeth, 80);
assert_eq!(loaded.design.meta.symmetry_order, 5);
assert!(!loaded.design.meta.mirror);
assert_eq!(loaded.design.material.name.as_deref(), Some("Quartz"));
assert_eq!(loaded.design.material.specific_gravity_override, Some(2.65));
assert_eq!(loaded.design.material.refractive_index_override, Some(1.55));
assert!((loaded.design.effective_refractive_index() - 1.55).abs() < 1e-9);
}
#[test]
fn degenerate_marker_header_stamps_the_reason_when_absent() {
let headers: Vec<String> = vec!["GemCad 5.0".to_string()];
let header = degenerate_marker_header(&headers, "Degenerate: only 2 distinct vertices.")
.expect("no existing marker -- must stamp one");
assert!(header.starts_with(NOT_CLOSED_SOLID_MARKER));
assert!(header.contains("Degenerate: only 2 distinct vertices."));
}
#[test]
fn degenerate_marker_header_never_stamps_twice() {
let headers = vec![format!("{NOT_CLOSED_SOLID_MARKER} -- already noted")];
assert!(degenerate_marker_header(&headers, "a different message").is_none());
}
#[test]
fn a_clean_match_and_applied_overlay_reads_as_restored() {
let text = plain_load_outcome_text(&FingerprintCheck::Match, &TierOverlay::Applied);
assert_eq!(text, "Your saved meet constraints were restored.");
}
#[test]
fn a_tier_count_mismatch_names_both_counts_even_on_a_clean_fingerprint() {
let text = plain_load_outcome_text(
&FingerprintCheck::Match,
&TierOverlay::SkippedTierCountMismatch {
native_tiers: 5,
asc_tiers: 6,
},
);
assert!(text.contains('5') && text.contains('6'), "{text}");
assert!(
!text.contains("fingerprint"),
"must read in plain English, not the crate's own diagnostic vocabulary: {text}"
);
}
#[test]
fn a_fingerprint_mismatch_says_the_asc_changed_and_constraints_were_not_restored() {
let text = plain_load_outcome_text(
&FingerprintCheck::Mismatch {
expected_sha256: "aaaa".to_string(),
found_sha256: "bbbb".to_string(),
},
&TierOverlay::SkippedFingerprintMismatch,
);
assert!(
text.contains("changed since this sidecar was saved"),
"{text}"
);
assert!(text.contains("not restored"), "{text}");
assert!(
!text.contains("sha256"),
"must not leak the technical hash text: {text}"
);
}
#[test]
fn applying_despite_a_mismatch_says_it_was_at_the_cutters_own_request() {
let text = plain_load_outcome_text(
&FingerprintCheck::Mismatch {
expected_sha256: "aaaa".to_string(),
found_sha256: "bbbb".to_string(),
},
&TierOverlay::AppliedDespiteMismatch,
);
assert!(text.contains("at your request"), "{text}");
}
#[test]
fn a_draft_overlay_on_a_clean_match_names_the_placeholder_masts() {
let text = plain_load_outcome_text(&FingerprintCheck::Match, &TierOverlay::AppliedFromDraft);
assert!(text.contains("placeholders"), "{text}");
}
use super::{
atomic_write::temp_sibling,
autosave::write_autosave,
confirm::{StatusDecision, decide_write_status},
solve::solve_matches_design,
};
fn round_brilliant_design() -> Design {
Design::new(
PreformSpec::block(2.0, 1.0, 2.0),
indicatrix_cut_core::ScheduleMeta::standard_round_brilliant(),
ConstraintTier::standard_round_brilliant(),
)
}
#[test]
fn solve_matches_design_accepts_a_cache_whose_tier_count_still_matches() {
let design = round_brilliant_design();
let solved = design.solve().expect("standard round brilliant solves");
assert_eq!(solved.len(), design.tiers.len());
assert!(solve_matches_design(Some(&solved), &design).is_some());
}
#[test]
fn solve_matches_design_rejects_a_cache_whose_tier_count_no_longer_matches() {
let design = round_brilliant_design();
let solved = design.solve().expect("standard round brilliant solves");
assert!(solve_matches_design(Some(&solved[..solved.len() - 1]), &design).is_none());
}
#[test]
fn solve_matches_design_rejects_no_cache_at_all() {
let design = round_brilliant_design();
assert!(solve_matches_design(None, &design).is_none());
}
#[test]
fn decide_write_status_is_fine_for_a_closed_solid() {
let design = round_brilliant_design();
let solved = design.solve().expect("standard round brilliant solves");
assert!(matches!(
decide_write_status(&design, Ok(solved.as_slice())),
StatusDecision::Fine
));
}
#[test]
fn decide_write_status_needs_confirm_when_the_design_does_not_solve() {
let design = round_brilliant_design();
match decide_write_status(&design, Err("no scale-reference tier")) {
StatusDecision::NeedsConfirm(message) => {
assert_eq!(message, "no scale-reference tier");
}
StatusDecision::Fine => panic!("a solve error must always need confirmation"),
}
}
#[test]
fn write_autosave_round_trips_its_own_toml_text() {
let path = std::env::temp_dir().join(format!(
"indicatrix_cut_write_autosave_test_{}.toml",
std::process::id()
));
let _ = std::fs::remove_file(&path);
write_autosave(&path, "design = \"round trip\"\n").expect("write must succeed");
let read_back = std::fs::read_to_string(&path).expect("must read back what was written");
assert_eq!(read_back, "design = \"round trip\"\n");
assert!(!temp_sibling(&path).is_file());
let _ = std::fs::remove_file(&path);
}