fabro/lib/foundation/fabro-api/tests/git_context_round_trip.rs
Scott Werner 3d77d371c6 Remove the recorded pre-run push outcome, keep the push
The manifest builder's best-effort pre-run push converted every result
into a PreRunPushOutcome that was serialized into GitContext, expanded
into five OpenAPI union arms, and generated into API clients — but no
production path ever read it; every field read was a test.

Delete the concept while preserving the behavior:

- Drop the PreRunPushOutcome enum and GitContext.push_outcome from
  fabro-types; GitContext keeps origin_url, branch, optional sha, and
  dirty, which remain real execution inputs and provenance.
- Rename the manifest outcome builder to push_manifest_branch_best_effort,
  a side-effect-only helper with the same decision rules: skip without an
  origin, skip on configured-repository mismatch, skip when the branch is
  already synced, otherwise push noninteractively and discard the result
  without failing manifest creation or logging raw Git stderr.
- Prove the push through repository state instead of the deleted enum: a
  branch ahead of a local bare origin is pushed during manifest build, a
  mismatched configured repository is not, and a failing remote helper
  still cannot fail manifest creation.
- Remove push_outcome from GitContext in OpenAPI, delete the five-arm
  union schemas, and drop the fabro-api type replacement and re-export.
- Keep one regression proving historical run.created events with a nested
  push_outcome still deserialize through ordinary unknown-field tolerance
  and reserialize to the reduced shape. No migration or event rewrite.

Old JSON carrying the removed field stays readable. Newly generated
clients omit a field older servers required, so new-client-to-old-server
compatibility is intentionally not promised for this pre-1.0 contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 19:27:33 -04:00

97 lines
2.7 KiB
Rust

use std::any::{TypeId, type_name};
use fabro_api::types::{DirtyStatus as ApiDirtyStatus, GitContext as ApiGitContext};
use fabro_types::{DirtyStatus, GitContext};
use serde_json::json;
#[test]
fn git_context_reuses_canonical_types() {
assert_same_type::<ApiGitContext, GitContext>();
assert_same_type::<ApiDirtyStatus, DirtyStatus>();
}
#[test]
fn dirty_status_serializes_with_snake_case_strings() {
assert_eq!(
serde_json::to_value(DirtyStatus::Clean).unwrap(),
json!("clean")
);
assert_eq!(
serde_json::to_value(DirtyStatus::Dirty).unwrap(),
json!("dirty")
);
assert_eq!(
serde_json::to_value(DirtyStatus::Unknown).unwrap(),
json!("unknown")
);
}
#[test]
fn git_context_with_known_sha_round_trips() {
let ctx = GitContext {
origin_url: "https://github.com/acme/widgets".to_string(),
branch: "main".to_string(),
sha: Some("abc123".to_string()),
dirty: DirtyStatus::Clean,
};
let json = serde_json::to_value(&ctx).unwrap();
assert_eq!(
json,
json!({
"origin_url": "https://github.com/acme/widgets",
"branch": "main",
"sha": "abc123",
"dirty": "clean",
})
);
let round_trip: GitContext = serde_json::from_value(json).unwrap();
assert_eq!(round_trip, ctx);
}
#[test]
fn git_context_omits_absent_sha_on_serialize() {
let ctx = GitContext {
origin_url: "https://github.com/acme/widgets".to_string(),
branch: "feature/foo".to_string(),
sha: None,
dirty: DirtyStatus::Unknown,
};
let json = serde_json::to_value(&ctx).unwrap();
assert!(json.get("sha").is_none());
assert_eq!(json["dirty"], "unknown");
}
#[test]
fn git_context_deserializes_when_sha_is_absent() {
let ctx: GitContext = serde_json::from_value(json!({
"origin_url": "https://github.com/acme/widgets",
"branch": "main",
"dirty": "dirty",
}))
.unwrap();
assert_eq!(ctx.sha, None);
assert_eq!(ctx.dirty, DirtyStatus::Dirty);
}
#[test]
fn git_context_tolerates_legacy_push_outcome_field() {
let ctx: GitContext = serde_json::from_value(json!({
"origin_url": "https://github.com/acme/widgets",
"branch": "main",
"dirty": "clean",
"push_outcome": { "type": "not_attempted" },
}))
.unwrap();
assert_eq!(ctx.origin_url, "https://github.com/acme/widgets");
assert_eq!(ctx.dirty, DirtyStatus::Clean);
}
fn assert_same_type<T: 'static, U: 'static>() {
assert_eq!(
TypeId::of::<T>(),
TypeId::of::<U>(),
"{} should be the same type as {}",
type_name::<T>(),
type_name::<U>()
);
}