feat(utas-host): serve GET /squad/active from Core
Migrate the active-squad READ off the Python oracle to the existing Core-backed projector, completing the squad authority (read + write + /squad/list + userMassInfo overlay) on one projector. - classify: GET /ut/game/<t>/squad/active -> Route::SquadActive. Numeric GET /squad/<n> stays on Python (no Core multi-squad model yet). - handle_squad_active returns the projector object via user_mass_info_squad(v, persona) — byte-identical to userMassInfo.squad; degrades to an empty overlay on stale/missing/Core-error, never falls back to Python. - persona: new REQUIRED OPENFUT_PERSONA_ID (non-zero) on HostConfig, injected not baked, must match LSX/Blaze/POW/UTAS identity. - tests: squad_active parity test; classify updated; README config table + A/B command. fmt + clippy -D warnings + tests (24 host + adapter) green.
This commit is contained in:
@@ -13,9 +13,10 @@ use openfut_adapter_fifa17::fut::club_response::{CoreOwnedItem, ItemIdentityReso
|
||||
use openfut_adapter_fifa17::fut::entities::Fifa17Entities;
|
||||
use openfut_identity::JsonIdentityStore;
|
||||
use openfut_utas_host::{
|
||||
classify, handle_put_squad, handle_squad_list, handle_user_mass_info, read_request, CoreAccess,
|
||||
CoreError, CoreExtState, CorePage, CoreReplaceRequest, CoreReplaceResult, CoreSquadRead,
|
||||
CoreSquadSlot, Fifa17IdentityResolver, HttpCoreClient, PassClient, Route, Server, SquadDeps,
|
||||
classify, handle_put_squad, handle_squad_active, handle_squad_list, handle_user_mass_info,
|
||||
read_request, CoreAccess, CoreError, CoreExtState, CorePage, CoreReplaceRequest,
|
||||
CoreReplaceResult, CoreSquadRead, CoreSquadSlot, Fifa17IdentityResolver, HttpCoreClient,
|
||||
PassClient, Route, Server, SquadDeps,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use serde_json::Value;
|
||||
@@ -274,6 +275,7 @@ fn build_server(
|
||||
Arc::new(entities()),
|
||||
resolver,
|
||||
Arc::new(PassClient::new(upstream)),
|
||||
33_068_179,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -632,6 +634,7 @@ fn club_end_to_end_through_real_resolver_and_sends_game_header() {
|
||||
Arc::new(entities()),
|
||||
resolver,
|
||||
Arc::new(PassClient::new("http://127.0.0.1:1")),
|
||||
33_068_179,
|
||||
);
|
||||
|
||||
let resp = server.handle("GET", "/ut/game/fifa17/club?level=gold", &[], b"");
|
||||
@@ -791,14 +794,14 @@ fn classify_squad_and_usermassinfo_routes() {
|
||||
classify("GET", "/ut/game/fifa17/userMassInfo"),
|
||||
Route::UserMassInfo
|
||||
);
|
||||
// /squad/active stays with Python (not numeric); GET squad/<n> is NOT a Rust
|
||||
// read route; a squad PUT is never a GET.
|
||||
assert_eq!(
|
||||
classify("PUT", "/ut/game/fifa17/squad/active"),
|
||||
Route::Passthrough
|
||||
);
|
||||
// GET /squad/active is now Core-backed (SquadActive). A squad PUT is never a
|
||||
// GET; a numeric GET /squad/<n> for a non-active squad stays on Python.
|
||||
assert_eq!(
|
||||
classify("GET", "/ut/game/fifa17/squad/active"),
|
||||
Route::SquadActive
|
||||
);
|
||||
assert_eq!(
|
||||
classify("PUT", "/ut/game/fifa17/squad/active"),
|
||||
Route::Passthrough
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -1079,6 +1082,55 @@ fn coupled_read_after_write_list_and_usermassinfo_agree() {
|
||||
);
|
||||
}
|
||||
|
||||
/// `GET /squad/active` serves the Core-backed squad object at top level, stamped
|
||||
/// with the host-configured persona (NOT proxied from Python), byte-consistent
|
||||
/// with the committed squad.
|
||||
#[test]
|
||||
fn squad_active_serves_core_backed_object_with_configured_persona() {
|
||||
let items = vec![gk(), st()];
|
||||
let (resolver, w) = resolver_with_wires(&items, ASSETS);
|
||||
let core = FakeCore::new(items.clone(), 2);
|
||||
let ent = entities();
|
||||
let deps = SquadDeps {
|
||||
core: &core,
|
||||
resolver: &resolver,
|
||||
entities: &ent,
|
||||
};
|
||||
// Commit a squad so Core has a fresh canonical squad + extension.
|
||||
let body = put_body(
|
||||
"f442",
|
||||
w["oc-a"],
|
||||
&[(0, w["oc-a"], 1), (1, w["oc-b"], 9)],
|
||||
"[1,2,3]",
|
||||
);
|
||||
let (put, _) = handle_put_squad(&body, &deps);
|
||||
assert_eq!(put.status, 200);
|
||||
|
||||
const PERSONA: i64 = 33_068_179;
|
||||
let (resp, log) = handle_squad_active(&deps, PERSONA);
|
||||
assert_eq!(log.outcome, "ok");
|
||||
assert_eq!(resp.status, 200);
|
||||
let sq: Value = serde_json::from_slice(&resp.body).unwrap();
|
||||
|
||||
// Top-level squad object (not wrapped), persona from host config not Python.
|
||||
assert_eq!(sq["id"], 0);
|
||||
assert_eq!(sq["personaId"], PERSONA, "persona from host config");
|
||||
assert_eq!(sq["formation"], "f442");
|
||||
assert_eq!(sq["captain"], w["oc-a"], "captain is the wire id");
|
||||
assert_eq!(sq["changed"], 0);
|
||||
assert!(sq["actives"].as_array().unwrap().is_empty());
|
||||
let occ: Vec<&Value> = sq["players"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|p| p["itemData"]["id"].as_i64().unwrap() != 0)
|
||||
.collect();
|
||||
assert_eq!(occ.len(), 2, "the two committed players");
|
||||
let p0 = occ.iter().find(|p| p["index"] == 0).unwrap();
|
||||
assert_eq!(p0["itemData"]["id"], w["oc-a"]);
|
||||
assert_eq!(p0["itemData"]["resourceId"], 20801);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_path_is_bounded_no_per_slot_lookup() {
|
||||
let items = vec![gk(), st()];
|
||||
|
||||
Reference in New Issue
Block a user