fifa17-recon: contract test guarding the move-verdict shape (392 checks)
The bug we just closed was invisible to every existing check: PUT /item answered 200
with a well-formed JSON body, and the body told the client the move had FAILED. Seven
attempts, weeks of investigation, and nothing in the suite would have noticed a
regression back to {}.
test_move_verdict_shape asserts the record vector exists, has ONE RECORD PER REQUESTED
ITEM (an empty vector fails the client exactly as hard as a wrong field), and that
id/pile/success carry the number/string/bool types their getters require.
Non-mutating: it asks to move ids that cannot exist, so nothing changes pile. The
verdicts come back success=false, which is honest and is not what is asserted.
Verified it actually catches the regression rather than just passing:
default (ack) 392 checks passed, 0 failed
FUT_MOVE_BODY=empty 382 passed, 1 FAILED -- "returns itemData array"
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
This commit is contained in:
@@ -436,12 +436,58 @@ def test_club_rename_roundtrip():
|
||||
repr((ui.get("clubName"), ui.get("clubAbbr"))))
|
||||
|
||||
|
||||
def test_move_verdict_shape():
|
||||
"""PUT /item must return per-item VERDICT records. This guards the fix for the
|
||||
project's longest-lived bug.
|
||||
|
||||
FutMoveCard's deserializer does not parse an acknowledgement, it builds a vector
|
||||
of verdict records, and the completion handler raises
|
||||
EVENT_CARDS_MOVE_CARD_FAILURE when that vector is EMPTY or when a record's
|
||||
success byte (record+0x0c) is not 1. For seven attempts this endpoint answered
|
||||
{}, an echo of the moved cards, or a dreamSquads stub, and every one of them told
|
||||
the client the move had failed, so the client killed the FUT session. It looked
|
||||
like a client-state bug for weeks because the HTTP status was always 200.
|
||||
|
||||
That failure mode is silent at the transport layer, which is exactly why it needs
|
||||
a contract test: nothing else in this suite would notice a regression to {}.
|
||||
|
||||
NON-MUTATING. It asks to move ids that cannot exist, so no item changes pile. The
|
||||
verdicts therefore come back success=false, which is the honest answer and is not
|
||||
what is being asserted here. What is asserted is the SHAPE and the RECORD COUNT,
|
||||
because an empty vector fails the client just as hard as a wrong flag.
|
||||
"""
|
||||
ghosts = [{"id": 999000001, "pile": "club", "swap": 0, "tradeId": 0},
|
||||
{"id": 999000002, "pile": "club", "swap": 0, "tradeId": 0}]
|
||||
code, body = _req("PUT", G + "/item", {"itemData": ghosts})
|
||||
check("PUT /item -> 200", code == 200, repr(code))
|
||||
check("PUT /item body is an object", is_obj(body), repr(body))
|
||||
recs = body.get("itemData") if is_obj(body) else None
|
||||
check("PUT /item returns itemData array (NOT {} -- {} reads as move-failed)",
|
||||
is_arr(recs), repr(body)[:120])
|
||||
if not is_arr(recs):
|
||||
return
|
||||
check("PUT /item returns one record per requested item",
|
||||
len(recs) == len(ghosts), "%d records for %d items" % (len(recs), len(ghosts)))
|
||||
for i, r in enumerate(recs):
|
||||
check("record[%d] is an object" % i, is_obj(r), repr(r))
|
||||
if not is_obj(r):
|
||||
continue
|
||||
# id(0x15c) INT via 0x1801c79d0 -- a string here desyncs the reader
|
||||
check("record[%d].id is a number" % i, is_num(r.get("id")), repr(r.get("id")))
|
||||
# pile(0x226) STRING via 0x1801c7aa0 -> enum 0x180142650
|
||||
check("record[%d].pile is a string" % i, is_str(r.get("pile")), repr(r.get("pile")))
|
||||
# success(0x2fa) BOOL via 0x1801c7620 -> record+0x0c
|
||||
check("record[%d].success is a bool" % i, isinstance(r.get("success"), bool),
|
||||
repr(r.get("success")))
|
||||
|
||||
|
||||
def main():
|
||||
tests = [test_credits, test_v2_store_gate, test_store_catalog, test_market_bodies,
|
||||
test_auction_record_shape, test_squad_boot, test_squad_list_shape,
|
||||
test_squad_list_endpoint, test_massinfo_shape, test_club_items,
|
||||
test_identity_consistency, test_club_user_shape, test_club_info_shape,
|
||||
test_accountinfo_shape, test_club_rename_roundtrip]
|
||||
test_accountinfo_shape, test_club_rename_roundtrip,
|
||||
test_move_verdict_shape]
|
||||
try:
|
||||
_get(G + "/user/credits")
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user