#!/usr/bin/env python3 """Offline unit test for the transfer-market BUY flow (tools/utas_server.trade_route). Runs entirely against a TEMP profile (FUT_PROFILE) so it never mutates the real save. Verifies: buy-now deducts coins + grants the won card to the club + echoes a CLOSED auction with the validated record shape; insufficient funds -> 461. No server / no live FIFA needed. Run: python3 tools/test_market_buy.py """ import os, json, sys, tempfile os.environ["FUT_PROFILE"] = os.path.join(tempfile.gettempdir(), "fut_buy_test_profile.json") if os.path.exists(os.environ["FUT_PROFILE"]): os.remove(os.environ["FUT_PROFILE"]) sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import utas_server as u # noqa: E402 class FakeH: def __init__(self, cmd, path, body=b""): self.command, self.path, self._body = cmd, path, body def main(): fails = [] def ok(name, cond, detail=""): if not cond: fails.append(f"{name}: {detail}") # a listing to buy (cheapest affordable) _, mkt = u.auctionhouse_route(FakeH("GET", "/ut/game/fifa17/auctionhouse?type=player")) recs = sorted(mkt["auctionInfo"], key=lambda r: r["buyNowPrice"]) rec0 = recs[0] tid, price = rec0["tradeId"], rec0["buyNowPrice"] coins0, items0 = u.STORE.coins(), len(u.STORE.items()) ok("test setup: affordable listing exists", price <= coins0, f"price {price} coins {coins0}") # BUY NOW code, resp = u.trade_route(FakeH("POST", f"/ut/game/fifa17/trade/{tid}/bid", json.dumps({"bid": price}).encode())) r = resp.get("auctionInfo", [{}])[0] ok("buy returns 200", code == 200, str(code)) ok("buy auction closed", r.get("tradeState") == "closed", r.get("tradeState")) ok("buy bidState highest", r.get("bidState") == "highest", r.get("bidState")) ok("coins deducted by buyNow", u.STORE.coins() == coins0 - price, f"{coins0}->{u.STORE.coins()} price {price}") ok("won card added to club", len(u.STORE.items()) == items0 + 1, f"{items0}->{len(u.STORE.items())}") ok("won itemData is object", isinstance(r.get("itemData"), dict)) ok("won itemData.attributeList is array", isinstance(r.get("itemData", {}).get("attributeList"), list)) ok("credits is int", isinstance(resp.get("credits"), int)) # GET view of an auction _, v = u.trade_route(FakeH("GET", f"/ut/game/fifa17/trade/{tid}")) ok("view auctionInfo is array", isinstance(v.get("auctionInfo"), list)) # insufficient funds -> 461, coins unchanged u.STORE._p["coins"] = 10 u.STORE._save() c2, _ = u.trade_route(FakeH("POST", f"/ut/game/fifa17/trade/{tid}/bid", b'{"bid":999999}')) ok("insufficient funds -> 461", c2 == 461, str(c2)) ok("coins unchanged on failed buy", u.STORE.coins() == 10, str(u.STORE.coins())) # ---- SELL / list flow ---- owned_id = u.STORE.items()[0]["id"] _, s = u.auctionhouse_route(FakeH("POST", "/ut/game/fifa17/auctionhouse", json.dumps({"itemData": {"id": owned_id}, "startingBid": 1000, "buyNowPrice": 5000}).encode())) ltid = s.get("id") ok("list returns a tradeId", isinstance(ltid, int), repr(s)) _, tp = u.tradepile_route(FakeH("GET", "/ut/game/fifa17/tradePile")) ok("tradePile shows the listing", tp.get("total") == 1, repr(tp.get("total"))) if tp.get("auctionInfo"): rec = tp["auctionInfo"][0] ok("listing tradeId matches", rec.get("tradeId") == ltid) ok("listing buyNowPrice preserved", rec.get("buyNowPrice") == 5000) ok("listing itemData is object", isinstance(rec.get("itemData"), dict)) ok("listing itemData.attributeList is array", isinstance(rec.get("itemData", {}).get("attributeList"), list)) u.delete_trade_route(FakeH("DELETE", f"/ut/delete/game/fifa17/trade/{ltid}")) _, tp2 = u.tradepile_route(FakeH("GET", "/ut/game/fifa17/tradePile")) ok("delist empties tradePile", tp2.get("total") == 0, repr(tp2.get("total"))) os.remove(os.environ["FUT_PROFILE"]) print(f"{'PASS' if not fails else 'FAIL'} - market buy+sell flow " f"({0 if fails else 'all'} checks; {len(fails)} failed)") for f in fails: print(" FAIL:", f) return 1 if fails else 0 if __name__ == "__main__": sys.exit(main())