style: rustfmt squad host + adapter files
Formatting-only. Runs the project formatter over the files authored/edited this session (host lib+tests, adapter item/squad_ext/squad_projection/mod + projection test). The intentionally-preserved dirty catalog.rs and pre-existing /club-era host drift beyond these files are out of scope.
This commit is contained in:
@@ -87,7 +87,11 @@ fn discard_value(rating: u8) -> i64 {
|
||||
/// identical by construction. `id` is the owned instance's resolved FIFA
|
||||
/// identity — pass the resolver's answer for *this* owned copy so two copies of
|
||||
/// one definition stay distinct on the wire.
|
||||
pub fn shape_item(item: &CoreOwnedItem, id: Fifa17Identity, ent: &impl ReverseEntityResolver) -> Value {
|
||||
pub fn shape_item(
|
||||
item: &CoreOwnedItem,
|
||||
id: Fifa17Identity,
|
||||
ent: &impl ReverseEntityResolver,
|
||||
) -> Value {
|
||||
let asset = id.asset_id;
|
||||
let league_id = ent.league_id(&item.league).unwrap_or(0);
|
||||
let team_id = ent.team_id(&item.club).unwrap_or(0);
|
||||
@@ -155,13 +159,19 @@ mod tests {
|
||||
let ent = entities();
|
||||
let it = shape_item(
|
||||
&item("oc1", "card_ch_1", 86, "CDM"),
|
||||
Fifa17Identity { item_id: 100000001, asset_id: 20801 },
|
||||
Fifa17Identity {
|
||||
item_id: 100000001,
|
||||
asset_id: 20801,
|
||||
},
|
||||
&ent,
|
||||
);
|
||||
assert_eq!(it["id"], 100000001, "wire instance id");
|
||||
assert_eq!(it["resourceId"], 20801);
|
||||
assert_eq!(it["assetId"], 20801);
|
||||
assert_eq!(it["definitionId"], 20801, "version byte 0 => resourceId==assetId==definitionId");
|
||||
assert_eq!(
|
||||
it["definitionId"], 20801,
|
||||
"version byte 0 => resourceId==assetId==definitionId"
|
||||
);
|
||||
assert_eq!(it["rating"], 86);
|
||||
assert_eq!(it["preferredPosition"], "CDM");
|
||||
assert_eq!(it["leagueId"], 13);
|
||||
@@ -180,16 +190,28 @@ mod tests {
|
||||
let ent = entities();
|
||||
let a = shape_item(
|
||||
&item("oc-a", "fifa17_101490", 84, "ST"),
|
||||
Fifa17Identity { item_id: 100000030, asset_id: 101490 },
|
||||
Fifa17Identity {
|
||||
item_id: 100000030,
|
||||
asset_id: 101490,
|
||||
},
|
||||
&ent,
|
||||
);
|
||||
let b = shape_item(
|
||||
&item("oc-b", "fifa17_101490", 84, "ST"),
|
||||
Fifa17Identity { item_id: 100000031, asset_id: 101490 },
|
||||
Fifa17Identity {
|
||||
item_id: 100000031,
|
||||
asset_id: 101490,
|
||||
},
|
||||
&ent,
|
||||
);
|
||||
assert_eq!(a["resourceId"], b["resourceId"], "same definition => same asset");
|
||||
assert_ne!(a["id"], b["id"], "distinct owned copies keep distinct wire ids");
|
||||
assert_eq!(
|
||||
a["resourceId"], b["resourceId"],
|
||||
"same definition => same asset"
|
||||
);
|
||||
assert_ne!(
|
||||
a["id"], b["id"],
|
||||
"distinct owned copies keep distinct wire ids"
|
||||
);
|
||||
assert_eq!(a["id"], 100000030);
|
||||
assert_eq!(b["id"], 100000031);
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
//! socket — a Rust UTAS host wires it to Core later.
|
||||
pub mod catalog;
|
||||
pub mod club_response;
|
||||
pub mod item;
|
||||
pub mod entities;
|
||||
pub mod item;
|
||||
pub mod owned_query;
|
||||
pub mod squad;
|
||||
pub mod squad_ext;
|
||||
|
||||
@@ -51,7 +51,10 @@ pub struct WireItemRef {
|
||||
|
||||
impl From<&SquadEntityRef> for WireItemRef {
|
||||
fn from(r: &SquadEntityRef) -> Self {
|
||||
WireItemRef { id: r.id, dream: r.dream }
|
||||
WireItemRef {
|
||||
id: r.id,
|
||||
dream: r.dream,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +138,10 @@ impl Fifa17SquadExtensionV1 {
|
||||
.iter()
|
||||
.map(|k| KicktakerRef {
|
||||
index: k.index,
|
||||
item: WireItemRef { id: k.id, dream: k.dream },
|
||||
item: WireItemRef {
|
||||
id: k.id,
|
||||
dream: k.dream,
|
||||
},
|
||||
})
|
||||
.collect(),
|
||||
client_reported: ClientReportedSquadEval {
|
||||
@@ -213,16 +219,23 @@ pub fn build_squad_write(
|
||||
) -> Result<SquadWriteBuild, SquadBuildError> {
|
||||
let canonical = crate::fut::squad::to_proposed(put, resolver);
|
||||
if !canonical.unresolved_wire_ids.is_empty() {
|
||||
return Err(SquadBuildError::UnresolvedWireIds(canonical.unresolved_wire_ids.clone()));
|
||||
return Err(SquadBuildError::UnresolvedWireIds(
|
||||
canonical.unresolved_wire_ids.clone(),
|
||||
));
|
||||
}
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
for slot in &canonical.slots {
|
||||
if !seen.insert(slot.owned_card_id.as_str()) {
|
||||
return Err(SquadBuildError::DuplicateOwnedItem(slot.owned_card_id.clone()));
|
||||
return Err(SquadBuildError::DuplicateOwnedItem(
|
||||
slot.owned_card_id.clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
let extension = Fifa17SquadExtensionV1::from_put(put, &canonical);
|
||||
Ok(SquadWriteBuild { canonical, extension })
|
||||
Ok(SquadWriteBuild {
|
||||
canonical,
|
||||
extension,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -266,7 +279,8 @@ mod tests {
|
||||
let ext = built().extension;
|
||||
assert_eq!(ext.custom, put.custom, "opaque custom carried verbatim");
|
||||
// survives a serialize/parse cycle unchanged.
|
||||
let back = Fifa17SquadExtensionV1::from_payload(EXT_SCHEMA_VERSION, &ext.to_payload()).unwrap();
|
||||
let back =
|
||||
Fifa17SquadExtensionV1::from_payload(EXT_SCHEMA_VERSION, &ext.to_payload()).unwrap();
|
||||
assert_eq!(back.custom, put.custom);
|
||||
}
|
||||
|
||||
@@ -285,14 +299,24 @@ mod tests {
|
||||
let ext = built().extension;
|
||||
assert_eq!(
|
||||
ext.client_reported,
|
||||
ClientReportedSquadEval { chemistry: Some(52), rating: Some(90), star_rating: Some(90) }
|
||||
ClientReportedSquadEval {
|
||||
chemistry: Some(52),
|
||||
rating: Some(90),
|
||||
star_rating: Some(90)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manager_and_kicktakers_preserved_opaquely() {
|
||||
let ext = built().extension;
|
||||
assert_eq!(ext.manager, vec![WireItemRef { id: 100000427, dream: false }]);
|
||||
assert_eq!(
|
||||
ext.manager,
|
||||
vec![WireItemRef {
|
||||
id: 100000427,
|
||||
dream: false
|
||||
}]
|
||||
);
|
||||
assert_eq!(ext.kicktakers.len(), 5);
|
||||
// All five reference the same wire id in this capture; carried verbatim,
|
||||
// NEVER normalized to the captain even though they coincide here.
|
||||
@@ -341,7 +365,10 @@ mod tests {
|
||||
m.insert(100000003, "oc-dup".to_string());
|
||||
m.insert(100000004, "oc-dup".to_string()); // collide onto same owned id
|
||||
let err = build_squad_write(&put, &MapResolver(m)).unwrap_err();
|
||||
assert_eq!(err, SquadBuildError::DuplicateOwnedItem("oc-dup".to_string()));
|
||||
assert_eq!(
|
||||
err,
|
||||
SquadBuildError::DuplicateOwnedItem("oc-dup".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -350,6 +377,10 @@ mod tests {
|
||||
assert_eq!(build.canonical.slots.len(), 11, "whole squad, not a diff");
|
||||
assert_eq!(build.canonical.formation.as_deref(), Some("f442"));
|
||||
// No FIFA wire integer survives into the canonical slots.
|
||||
assert!(build.canonical.slots.iter().all(|s| s.owned_card_id.starts_with("oc-")));
|
||||
assert!(build
|
||||
.canonical
|
||||
.slots
|
||||
.iter()
|
||||
.all(|s| s.owned_card_id.starts_with("oc-")));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +109,10 @@ impl std::fmt::Display for SquadProjectError {
|
||||
write!(f, "projection input missing owned item {id}")
|
||||
}
|
||||
SquadProjectError::NoFifaIdentity(id) => {
|
||||
write!(f, "owned item {id} has no real FIFA asset identity (cannot render)")
|
||||
write!(
|
||||
f,
|
||||
"owned item {id} has no real FIFA asset identity (cannot render)"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,10 +142,9 @@ pub fn project_squad<I: ItemIdentityResolver + ?Sized>(
|
||||
for index in 0..FIFA17_SQUAD_SLOTS {
|
||||
match by_index.get(&index) {
|
||||
Some(slot) => {
|
||||
let item = input
|
||||
.owned
|
||||
.get(&slot.owned_card_id)
|
||||
.ok_or_else(|| SquadProjectError::MissingOwnedItem(slot.owned_card_id.clone()))?;
|
||||
let item = input.owned.get(&slot.owned_card_id).ok_or_else(|| {
|
||||
SquadProjectError::MissingOwnedItem(slot.owned_card_id.clone())
|
||||
})?;
|
||||
let id = ident
|
||||
.resolve(item)
|
||||
.ok_or_else(|| SquadProjectError::NoFifaIdentity(slot.owned_card_id.clone()))?;
|
||||
@@ -150,7 +152,11 @@ pub fn project_squad<I: ItemIdentityResolver + ?Sized>(
|
||||
captain_wire = id.item_id as i64;
|
||||
}
|
||||
// kit follows the player: look it up by owned id, never by index.
|
||||
let kit = ext.kit_numbers.get(&slot.owned_card_id).copied().unwrap_or(0);
|
||||
let kit = ext
|
||||
.kit_numbers
|
||||
.get(&slot.owned_card_id)
|
||||
.copied()
|
||||
.unwrap_or(0);
|
||||
players.push(json!({
|
||||
"index": index,
|
||||
"itemData": shape_item(item, id, ent),
|
||||
@@ -278,14 +284,24 @@ mod tests {
|
||||
owned.insert("oc1".to_string(), owned_item("oc1", "card_x"));
|
||||
let ident = TableIdentity(HashMap::from([(
|
||||
"oc1".to_string(),
|
||||
Fifa17Identity { item_id: 100000042, asset_id: 20801 },
|
||||
Fifa17Identity {
|
||||
item_id: 100000042,
|
||||
asset_id: 20801,
|
||||
},
|
||||
)]));
|
||||
let input = one_slot_input(&owned, SquadExtInput::Fresh(fresh_ext()));
|
||||
let SquadProjection::Projected(v) = project_squad(&input, &ident, &ent()).unwrap() else {
|
||||
panic!("expected Projected");
|
||||
};
|
||||
assert_eq!(v["players"].as_array().unwrap().len(), 23, "fixed 23-slot array");
|
||||
assert_eq!(v["players"][0]["itemData"]["id"], 100000042, "wire id, not resourceId");
|
||||
assert_eq!(
|
||||
v["players"].as_array().unwrap().len(),
|
||||
23,
|
||||
"fixed 23-slot array"
|
||||
);
|
||||
assert_eq!(
|
||||
v["players"][0]["itemData"]["id"], 100000042,
|
||||
"wire id, not resourceId"
|
||||
);
|
||||
assert_eq!(v["players"][0]["itemData"]["resourceId"], 20801);
|
||||
assert_eq!(v["players"][0]["kitNumber"], 9, "kit from ext by owned id");
|
||||
assert_eq!(v["players"][1]["itemData"]["id"], 0, "empty slot");
|
||||
@@ -301,7 +317,10 @@ mod tests {
|
||||
// A stale extension IS carried (host may log it) but must not be applied.
|
||||
let input = one_slot_input(&owned, SquadExtInput::Stale(fresh_ext()));
|
||||
let ident = TableIdentity(HashMap::new());
|
||||
assert_eq!(project_squad(&input, &ident, &ent()).unwrap(), SquadProjection::Stale);
|
||||
assert_eq!(
|
||||
project_squad(&input, &ident, &ent()).unwrap(),
|
||||
SquadProjection::Stale
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -309,7 +328,10 @@ mod tests {
|
||||
let owned = HashMap::new();
|
||||
let input = one_slot_input(&owned, SquadExtInput::Missing);
|
||||
let ident = TableIdentity(HashMap::new());
|
||||
assert_eq!(project_squad(&input, &ident, &ent()).unwrap(), SquadProjection::Missing);
|
||||
assert_eq!(
|
||||
project_squad(&input, &ident, &ent()).unwrap(),
|
||||
SquadProjection::Missing
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -332,21 +354,46 @@ mod tests {
|
||||
owned.insert("oc-a".to_string(), owned_item("oc-a", "fifa17_101490"));
|
||||
owned.insert("oc-b".to_string(), owned_item("oc-b", "fifa17_101490"));
|
||||
let ident = TableIdentity(HashMap::from([
|
||||
("oc-a".to_string(), Fifa17Identity { item_id: 100000030, asset_id: 101490 }),
|
||||
("oc-b".to_string(), Fifa17Identity { item_id: 100000031, asset_id: 101490 }),
|
||||
(
|
||||
"oc-a".to_string(),
|
||||
Fifa17Identity {
|
||||
item_id: 100000030,
|
||||
asset_id: 101490,
|
||||
},
|
||||
),
|
||||
(
|
||||
"oc-b".to_string(),
|
||||
Fifa17Identity {
|
||||
item_id: 100000031,
|
||||
asset_id: 101490,
|
||||
},
|
||||
),
|
||||
]));
|
||||
let mut kit = std::collections::BTreeMap::new();
|
||||
kit.insert("oc-a".to_string(), 7);
|
||||
kit.insert("oc-b".to_string(), 19);
|
||||
let ext = Fifa17SquadExtensionV1 { kit_numbers: kit, ..fresh_ext() };
|
||||
let ext = Fifa17SquadExtensionV1 {
|
||||
kit_numbers: kit,
|
||||
..fresh_ext()
|
||||
};
|
||||
let owned_ref = &owned;
|
||||
let input = SquadProjectionInput {
|
||||
fifa_squad_id: 0,
|
||||
name: "OpenFUT".into(),
|
||||
formation: "f442".into(),
|
||||
slots: vec![
|
||||
ProjectionSlot { owned_card_id: "oc-a".into(), index: 0, is_captain: false, is_on_bench: false },
|
||||
ProjectionSlot { owned_card_id: "oc-b".into(), index: 1, is_captain: false, is_on_bench: false },
|
||||
ProjectionSlot {
|
||||
owned_card_id: "oc-a".into(),
|
||||
index: 0,
|
||||
is_captain: false,
|
||||
is_on_bench: false,
|
||||
},
|
||||
ProjectionSlot {
|
||||
owned_card_id: "oc-b".into(),
|
||||
index: 1,
|
||||
is_captain: false,
|
||||
is_on_bench: false,
|
||||
},
|
||||
],
|
||||
ext: SquadExtInput::Fresh(ext),
|
||||
owned: owned_ref,
|
||||
@@ -356,9 +403,18 @@ mod tests {
|
||||
};
|
||||
let a = &v["players"][0]["itemData"];
|
||||
let b = &v["players"][1]["itemData"];
|
||||
assert_eq!(a["resourceId"], b["resourceId"], "same definition => same asset");
|
||||
assert_ne!(a["id"], b["id"], "distinct owned copies keep distinct wire ids");
|
||||
assert_eq!(
|
||||
a["resourceId"], b["resourceId"],
|
||||
"same definition => same asset"
|
||||
);
|
||||
assert_ne!(
|
||||
a["id"], b["id"],
|
||||
"distinct owned copies keep distinct wire ids"
|
||||
);
|
||||
assert_eq!(v["players"][0]["kitNumber"], 7);
|
||||
assert_eq!(v["players"][1]["kitNumber"], 19, "kit stays with the instance");
|
||||
assert_eq!(
|
||||
v["players"][1]["kitNumber"], 19,
|
||||
"kit stays with the instance"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,9 +60,15 @@ impl ItemIdentityResolver for TableIdentity {
|
||||
/// projector round-trip asserts item *identity* (wire id + asset), not entity ids.
|
||||
struct NoEntities;
|
||||
impl openfut_adapter_fifa17::fut::entities::ReverseEntityResolver for NoEntities {
|
||||
fn league_id(&self, _: &str) -> Option<u32> { None }
|
||||
fn team_id(&self, _: &str) -> Option<u32> { None }
|
||||
fn nation_id(&self, _: &str) -> Option<u32> { None }
|
||||
fn league_id(&self, _: &str) -> Option<u32> {
|
||||
None
|
||||
}
|
||||
fn team_id(&self, _: &str) -> Option<u32> {
|
||||
None
|
||||
}
|
||||
fn nation_id(&self, _: &str) -> Option<u32> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the owned-item map + identity table from the persisted read oracle, so
|
||||
@@ -99,7 +105,13 @@ fn oracle_tables() -> (HashMap<String, CoreOwnedItem>, TableIdentity) {
|
||||
attributes: [attrs[0], attrs[1], attrs[2], attrs[3], attrs[4], attrs[5]],
|
||||
},
|
||||
);
|
||||
ident.insert(oc, Fifa17Identity { item_id: wire as u32, asset_id: asset });
|
||||
ident.insert(
|
||||
oc,
|
||||
Fifa17Identity {
|
||||
item_id: wire as u32,
|
||||
asset_id: asset,
|
||||
},
|
||||
);
|
||||
}
|
||||
(owned, TableIdentity(ident))
|
||||
}
|
||||
@@ -111,8 +123,10 @@ fn project_put(
|
||||
owned: &HashMap<String, CoreOwnedItem>,
|
||||
ident: &TableIdentity,
|
||||
) -> Value {
|
||||
let SquadWriteBuild { canonical, extension } =
|
||||
build_squad_write(put, &OcResolver).expect("build must succeed for a full valid squad");
|
||||
let SquadWriteBuild {
|
||||
canonical,
|
||||
extension,
|
||||
} = build_squad_write(put, &OcResolver).expect("build must succeed for a full valid squad");
|
||||
let slots: Vec<ProjectionSlot> = canonical
|
||||
.slots
|
||||
.iter()
|
||||
@@ -148,7 +162,10 @@ fn occupied(v: &Value) -> HashMap<i64, (i64, i64)> {
|
||||
.map(|p| {
|
||||
(
|
||||
p["index"].as_i64().unwrap(),
|
||||
(p["itemData"]["id"].as_i64().unwrap(), p["kitNumber"].as_i64().unwrap()),
|
||||
(
|
||||
p["itemData"]["id"].as_i64().unwrap(),
|
||||
p["kitNumber"].as_i64().unwrap(),
|
||||
),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
@@ -163,11 +180,18 @@ fn baseline_projects_the_known_squad_round_trip() {
|
||||
// Fixed 23-slot array; 11 occupied at 0..=10.
|
||||
assert_eq!(projected["players"].as_array().unwrap().len(), 23);
|
||||
let put_v: Value = serde_json::from_str(PUT_BASELINE).unwrap();
|
||||
assert_eq!(occupied(&projected), occupied(&put_v), "wire id + kit per index round-trip");
|
||||
assert_eq!(
|
||||
occupied(&projected),
|
||||
occupied(&put_v),
|
||||
"wire id + kit per index round-trip"
|
||||
);
|
||||
|
||||
// CANONICAL: formation verbatim, captain follows the semantic player.
|
||||
assert_eq!(projected["formation"], "f442");
|
||||
assert_eq!(projected["captain"], 100000001, "captain is the player's WIRE id");
|
||||
assert_eq!(
|
||||
projected["captain"], 100000001,
|
||||
"captain is the player's WIRE id"
|
||||
);
|
||||
// EXTENSION: custom byte-identical, manager + squadType preserved.
|
||||
assert_eq!(projected["custom"], put_v["custom"]);
|
||||
assert_eq!(projected["manager"], put_v["manager"]);
|
||||
@@ -184,12 +208,20 @@ fn swap_moves_two_players_with_their_kits_and_round_trips() {
|
||||
// build(swap) must reproduce the swap wire exactly, and the affected players'
|
||||
// kit numbers must have travelled with them (kit follows the player).
|
||||
let (owned, ident) = oracle_tables();
|
||||
let projected = project_put(&parse_squad_put(PUT_SWAP.as_bytes()).unwrap(), &owned, &ident);
|
||||
let projected = project_put(
|
||||
&parse_squad_put(PUT_SWAP.as_bytes()).unwrap(),
|
||||
&owned,
|
||||
&ident,
|
||||
);
|
||||
let swap_v: Value = serde_json::from_str(PUT_SWAP).unwrap();
|
||||
let base_v: Value = serde_json::from_str(PUT_BASELINE).unwrap();
|
||||
|
||||
// CANONICAL: the projected occupancy per index matches the swap PUT exactly.
|
||||
assert_eq!(occupied(&projected), occupied(&swap_v), "player+kit per index round-trip");
|
||||
assert_eq!(
|
||||
occupied(&projected),
|
||||
occupied(&swap_v),
|
||||
"player+kit per index round-trip"
|
||||
);
|
||||
|
||||
// The swap is real: at least two indices carry a different player than baseline.
|
||||
let (proj_occ, base_occ) = (occupied(&projected), occupied(&base_v));
|
||||
@@ -198,7 +230,10 @@ fn swap_moves_two_players_with_their_kits_and_round_trips() {
|
||||
.filter(|(idx, pair)| base_occ.get(idx).map(|b| b.0) != Some(pair.0))
|
||||
.map(|(idx, _)| *idx)
|
||||
.collect();
|
||||
assert!(moved.len() >= 2, "a swap changes at least two slots, got {moved:?}");
|
||||
assert!(
|
||||
moved.len() >= 2,
|
||||
"a swap changes at least two slots, got {moved:?}"
|
||||
);
|
||||
|
||||
// kit follows the PLAYER: for every player, its kit in baseline == its kit
|
||||
// in the swap projection, regardless of which slot it now occupies.
|
||||
@@ -211,8 +246,14 @@ fn swap_moves_two_players_with_their_kits_and_round_trips() {
|
||||
"each player kept its kit number through the swap"
|
||||
);
|
||||
|
||||
assert_eq!(projected["captain"], 100000001, "captain follows the semantic player");
|
||||
assert_eq!(projected["custom"], swap_v["custom"], "opaque custom unchanged by the swap");
|
||||
assert_eq!(
|
||||
projected["captain"], 100000001,
|
||||
"captain follows the semantic player"
|
||||
);
|
||||
assert_eq!(
|
||||
projected["custom"], swap_v["custom"],
|
||||
"opaque custom unchanged by the swap"
|
||||
);
|
||||
// SHADOW: the client-reported chemistry from THIS PUT (58) is round-tripped
|
||||
// as-is — never reconciled to a server recompute.
|
||||
assert_eq!(projected["chemistry"], 58);
|
||||
@@ -225,7 +266,9 @@ fn persisted_read_round_trips_via_reconstructed_canonical_and_extension() {
|
||||
// evidence, then project and require the read back — the strongest fidelity
|
||||
// check across all four ownership classes.
|
||||
use openfut_adapter_fifa17::fut::squad::ClientReportedSquadEval;
|
||||
use openfut_adapter_fifa17::fut::squad_ext::{Fifa17SquadExtensionV1, KicktakerRef, WireItemRef};
|
||||
use openfut_adapter_fifa17::fut::squad_ext::{
|
||||
Fifa17SquadExtensionV1, KicktakerRef, WireItemRef,
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
let oracle: Value = serde_json::from_str(READ_ORACLE).unwrap();
|
||||
@@ -250,7 +293,8 @@ fn persisted_read_round_trips_via_reconstructed_canonical_and_extension() {
|
||||
});
|
||||
}
|
||||
let manager: Vec<WireItemRef> = serde_json::from_value(oracle["manager"].clone()).unwrap();
|
||||
let kicktakers: Vec<KicktakerRef> = serde_json::from_value(oracle["kicktakers"].clone()).unwrap();
|
||||
let kicktakers: Vec<KicktakerRef> =
|
||||
serde_json::from_value(oracle["kicktakers"].clone()).unwrap();
|
||||
let ext = Fifa17SquadExtensionV1 {
|
||||
custom: oracle["custom"].as_str().map(str::to_string),
|
||||
squad_type: oracle["squadType"].as_str().map(str::to_string),
|
||||
@@ -277,14 +321,32 @@ fn persisted_read_round_trips_via_reconstructed_canonical_and_extension() {
|
||||
};
|
||||
|
||||
// CANONICAL + DERIVED: identity and placement per slot match the read.
|
||||
assert_eq!(occupied(&projected), occupied(&oracle), "player+kit per index");
|
||||
assert_eq!(
|
||||
occupied(&projected),
|
||||
occupied(&oracle),
|
||||
"player+kit per index"
|
||||
);
|
||||
assert_eq!(projected["captain"], oracle["captain"]);
|
||||
assert_eq!(projected["formation"], oracle["formation"]);
|
||||
for (pp, op) in projected["players"].as_array().unwrap().iter().zip(oracle["players"].as_array().unwrap()) {
|
||||
assert_eq!(pp["itemData"]["id"], op["itemData"]["id"], "wire id per slot");
|
||||
assert_eq!(pp["itemData"]["resourceId"], op["itemData"]["resourceId"], "asset id per slot");
|
||||
for (pp, op) in projected["players"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.zip(oracle["players"].as_array().unwrap())
|
||||
{
|
||||
assert_eq!(
|
||||
pp["itemData"]["id"], op["itemData"]["id"],
|
||||
"wire id per slot"
|
||||
);
|
||||
assert_eq!(
|
||||
pp["itemData"]["resourceId"], op["itemData"]["resourceId"],
|
||||
"asset id per slot"
|
||||
);
|
||||
assert_eq!(pp["itemData"]["rating"], op["itemData"]["rating"]);
|
||||
assert_eq!(pp["itemData"]["preferredPosition"], op["itemData"]["preferredPosition"]);
|
||||
assert_eq!(
|
||||
pp["itemData"]["preferredPosition"],
|
||||
op["itemData"]["preferredPosition"]
|
||||
);
|
||||
}
|
||||
// EXTENSION + SHADOW: sourced from the read, so they round-trip identically.
|
||||
assert_eq!(projected["custom"], oracle["custom"]);
|
||||
@@ -299,8 +361,16 @@ fn persisted_read_round_trips_via_reconstructed_canonical_and_extension() {
|
||||
#[test]
|
||||
fn formation_change_reindexes_without_deriving_slots_and_kit_follows_player() {
|
||||
let (owned, ident) = oracle_tables();
|
||||
let swap = project_put(&parse_squad_put(PUT_SWAP.as_bytes()).unwrap(), &owned, &ident);
|
||||
let f433 = project_put(&parse_squad_put(PUT_F433.as_bytes()).unwrap(), &owned, &ident);
|
||||
let swap = project_put(
|
||||
&parse_squad_put(PUT_SWAP.as_bytes()).unwrap(),
|
||||
&owned,
|
||||
&ident,
|
||||
);
|
||||
let f433 = project_put(
|
||||
&parse_squad_put(PUT_F433.as_bytes()).unwrap(),
|
||||
&owned,
|
||||
&ident,
|
||||
);
|
||||
|
||||
assert_eq!(f433["formation"], "f433");
|
||||
assert_eq!(swap["formation"], "f442");
|
||||
@@ -311,29 +381,50 @@ fn formation_change_reindexes_without_deriving_slots_and_kit_follows_player() {
|
||||
ids.sort();
|
||||
ids
|
||||
};
|
||||
assert_eq!(set(&swap), set(&f433), "same 11 players survive the formation change");
|
||||
assert_eq!(
|
||||
set(&swap),
|
||||
set(&f433),
|
||||
"same 11 players survive the formation change"
|
||||
);
|
||||
|
||||
// The captain (wire 100000001) moved from index 8 (f442) to index 10 (f433) —
|
||||
// proof indices are round-tripped, not derived from the formation.
|
||||
let idx_of = |v: &Value, wire: i64| -> i64 {
|
||||
occupied(v).into_iter().find(|(_, (id, _))| *id == wire).unwrap().0
|
||||
occupied(v)
|
||||
.into_iter()
|
||||
.find(|(_, (id, _))| *id == wire)
|
||||
.unwrap()
|
||||
.0
|
||||
};
|
||||
assert_eq!(idx_of(&swap, 100000001), 8);
|
||||
assert_eq!(idx_of(&f433, 100000001), 10);
|
||||
|
||||
// kit follows the PLAYER, not the slot: captain keeps kit 8 across the reindex.
|
||||
let kit_of = |v: &Value, wire: i64| -> i64 {
|
||||
occupied(v).into_iter().find(|(_, (id, _))| *id == wire).unwrap().1 .1
|
||||
occupied(v)
|
||||
.into_iter()
|
||||
.find(|(_, (id, _))| *id == wire)
|
||||
.unwrap()
|
||||
.1
|
||||
.1
|
||||
};
|
||||
assert_eq!(kit_of(&swap, 100000001), 8);
|
||||
assert_eq!(kit_of(&f433, 100000001), 8, "kit stayed with the player despite the reindex");
|
||||
assert_eq!(
|
||||
kit_of(&f433, 100000001),
|
||||
8,
|
||||
"kit stayed with the player despite the reindex"
|
||||
);
|
||||
assert_eq!(f433["captain"], 100000001, "captain still the same player");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_projector_serves_every_endpoint_no_divergence() {
|
||||
let (owned, ident) = oracle_tables();
|
||||
let projected = project_put(&parse_squad_put(PUT_SWAP.as_bytes()).unwrap(), &owned, &ident);
|
||||
let projected = project_put(
|
||||
&parse_squad_put(PUT_SWAP.as_bytes()).unwrap(),
|
||||
&owned,
|
||||
&ident,
|
||||
);
|
||||
|
||||
// userMassInfo.squad = the projected object + session envelope.
|
||||
let ummi = user_mass_info_squad(projected.clone(), 33068179);
|
||||
@@ -346,8 +437,18 @@ fn one_projector_serves_every_endpoint_no_divergence() {
|
||||
// squad/list = a summary SUBSET of the SAME object, not a second projection.
|
||||
let list = squad_list(&projected);
|
||||
let entry = &list["squad"][0];
|
||||
for k in ["id", "squadName", "formation", "squadType", "rating", "chemistry"] {
|
||||
assert_eq!(entry[k], projected[k], "summary field {k} derived from the one projection");
|
||||
for k in [
|
||||
"id",
|
||||
"squadName",
|
||||
"formation",
|
||||
"squadType",
|
||||
"rating",
|
||||
"chemistry",
|
||||
] {
|
||||
assert_eq!(
|
||||
entry[k], projected[k],
|
||||
"summary field {k} derived from the one projection"
|
||||
);
|
||||
}
|
||||
// The summary carries only those six keys — no divergent squad shape.
|
||||
assert_eq!(entry.as_object().unwrap().len(), 6);
|
||||
|
||||
Reference in New Issue
Block a user