Add property-based tests for redact invariants and push events
Added redact unit tests: idempotence, non-matching passthrough,
identity when no secrets resolved. Added property tests for push
event repo names and updated_refs subtractive invariant.
Assisted-by: GLM-5.1 via pi
diff --git a/src/ci/redact.rs b/src/ci/redact.rs
index c4462b5..9f76862 100644
--- a/src/ci/redact.rs
+++ b/src/ci/redact.rs
@@ -224,4 +224,30 @@ mod tests {
let mut reg = SecretRegistry::new(plain_secrets(&[("key", "hunter2")]));
assert_eq!(reg.resolve("key").unwrap(), "hunter2");
}
+
+ #[test]
+ fn redact_is_idempotent() {
+ let mut reg = SecretRegistry::new(plain_secrets(&[("token", "ghp_long_secret_value")]));
+ reg.resolve("token").unwrap();
+ let input = "hello ghp_long_secret_value world";
+ let first = redact(input, ®);
+ let second = redact(&first, ®);
+ assert_eq!(first, second);
+ }
+
+ #[test]
+ fn redact_preserves_non_matching_text() {
+ let mut reg = SecretRegistry::new(plain_secrets(&[("token", "ghp_long_secret_value")]));
+ reg.resolve("token").unwrap();
+ let input = "nothing to see here";
+ assert_eq!(redact(input, ®), input);
+ }
+
+ #[test]
+ fn redact_with_no_resolves_is_identity() {
+ let reg = SecretRegistry::new(plain_secrets(&[("token", "ghp_long_secret_value")]));
+ // No resolve — no redactions registered.
+ let input = "contains ghp_long_secret_value but not resolved";
+ assert_eq!(redact(input, ®), input);
+ }
}
diff --git a/tests/property.rs b/tests/property.rs
index 1ddf1aa..d489b64 100644
--- a/tests/property.rs
+++ b/tests/property.rs
@@ -86,3 +86,30 @@ fn secret_string_from_file_strips_one_trailing_newline(tc: TestCase) {
let expected = content.strip_suffix('\n').unwrap_or(&content).to_string();
assert_eq!(revealed, expected);
}
+
+#[hegel::test]
+fn push_event_repo_round_trips_json(tc: TestCase) {
+ // Verify that arbitrary repo names survive JSON serialization.
+ let mut event = tc.draw(push_event());
+ event.repo = tc.draw(text());
+ let json = serde_json::to_string(&event).expect("serialize");
+ let parsed: PushEvent = serde_json::from_str(&json).expect("deserialize");
+ assert_eq!(event, parsed);
+}
+
+#[hegel::test]
+fn push_event_updated_refs_is_subtractive(tc: TestCase) {
+ // updated_refs() can only remove refs (zero-sha deletions),
+ // never add or reorder them.
+ let event = tc.draw(push_event());
+ let kept = event.updated_refs();
+ assert!(kept.len() <= event.refs.len());
+ for kept_ref in &kept {
+ assert!(
+ event
+ .refs
+ .iter()
+ .any(|r| r.r#ref == kept_ref.r#ref && r.new_sha == kept_ref.new_sha)
+ );
+ }
+}