161 lines
6.7 KiB
Python
161 lines
6.7 KiB
Python
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
from tft.api import app as app_mod
|
|
from tft.constants.loader import load_constants
|
|
from tft.model import artifact as artifact_mod
|
|
from tests.test_sim import make_static
|
|
|
|
|
|
@pytest.fixture
|
|
def client(monkeypatch):
|
|
static = make_static()
|
|
static["meta"]["patch"] = "test"
|
|
art = artifact_mod.build(static)
|
|
cfg = load_constants(17)
|
|
monkeypatch.setattr(app_mod, "_load_game_deps", lambda: (art, cfg))
|
|
return TestClient(app_mod.app)
|
|
|
|
|
|
def test_full_game_via_api(client):
|
|
state = client.post("/api/game", params={"seed": 3}).json()
|
|
game_id = state["game_id"]
|
|
assert state["round"]["label"] == "1-2" # 1-1-Carousel läuft automatisch ab
|
|
assert state["player"]["hp"] == 100
|
|
assert len(state["opponents"]) == 7
|
|
|
|
steps = 0
|
|
while not state["over"]:
|
|
if state["augment_offer"]:
|
|
client.post(f"/api/game/{game_id}/action",
|
|
json={"action": "pick_augment", "choice": 0})
|
|
affordable = [i for i, s in enumerate(state["shop"])
|
|
if s and s["cost"] <= state["player"]["gold"]]
|
|
if affordable:
|
|
client.post(f"/api/game/{game_id}/action",
|
|
json={"action": "buy", "slot": affordable[0]})
|
|
resp = client.post(f"/api/game/{game_id}/action", json={"action": "next"})
|
|
assert resp.status_code == 200
|
|
state = resp.json()
|
|
steps += 1
|
|
assert steps < 100
|
|
assert 1 <= state["placement"] <= 8
|
|
|
|
|
|
def test_invalid_action_is_400(client):
|
|
game_id = client.post("/api/game", params={"seed": 1}).json()["game_id"]
|
|
resp = client.post(f"/api/game/{game_id}/action",
|
|
json={"action": "sell", "where": "bench", "idx": 99})
|
|
assert resp.status_code == 400
|
|
resp = client.post(f"/api/game/{game_id}/action", json={"action": "dance"})
|
|
assert resp.status_code == 400
|
|
assert client.post("/api/game/nope/action", json={"action": "next"}).status_code == 404
|
|
|
|
|
|
def test_state_shape(client):
|
|
state = client.post("/api/game", params={"seed": 5}).json()
|
|
for key in ("round", "player", "board", "bench", "shop", "traits",
|
|
"opponents", "items", "over", "log", "shop_odds", "shop_locked"):
|
|
assert key in state
|
|
assert sum(state["shop_odds"]) == 100
|
|
assert state["player"]["off"] == 0.0
|
|
assert state["player"]["def"] == 0.0
|
|
assert all(o["board"] is not None for o in state["opponents"])
|
|
|
|
|
|
def test_upgrade_hint():
|
|
from tft.api.app import upgrade_hint
|
|
from tft.sim.game import Game
|
|
static = make_static()
|
|
static["meta"]["patch"] = "t"
|
|
game = Game(artifact_mod.build(static), load_constants(17), seed=1)
|
|
assert upgrade_hint(game, "U1_0") == 0
|
|
game.bench = [{"api_name": "U1_0", "stars": 1, "items": []} for _ in range(2)]
|
|
assert upgrade_hint(game, "U1_0") == 2
|
|
game.bench = [{"api_name": "U1_0", "stars": 2, "items": []} for _ in range(2)] + \
|
|
[{"api_name": "U1_0", "stars": 1, "items": []} for _ in range(2)]
|
|
assert upgrade_hint(game, "U1_0") == 3
|
|
game.bench = [{"api_name": "U1_0", "stars": 3, "items": []}]
|
|
assert upgrade_hint(game, "U1_0") == 0
|
|
|
|
|
|
@pytest.fixture
|
|
def group_client(monkeypatch):
|
|
static = make_static()
|
|
static["meta"]["patch"] = "test"
|
|
art = artifact_mod.build(static)
|
|
a, b = list(art["static"]["units"])[:2]
|
|
# Schlüssel sind "api@itemzahl" — die Karte zeigt die Items mit an.
|
|
# Schlüssel sind "api@sterne@itemzahl". Zwei Gruppen teilen sich
|
|
# denselben Carry — nur die bessere darf durchkommen.
|
|
art["groups"] = {
|
|
"2": {f"{a}@2@3|{b}@2@0": {"edge": 1.2, "boards": 400},
|
|
f"{a}@2@3|{b}@1@1": {"edge": 0.9, "boards": 700},
|
|
f"{a}@1@0|{b}@2@3": {"edge": 0.4, "boards": 900}},
|
|
"boards": 5000,
|
|
}
|
|
cfg = load_constants(17)
|
|
monkeypatch.setattr(app_mod, "_load_game_deps", lambda: (art, cfg))
|
|
return TestClient(app_mod.app)
|
|
|
|
|
|
def test_groups_endpoint(group_client):
|
|
"""Der Gruppen-Reiter braucht Name, Kosten und Icon je Unit."""
|
|
r = group_client.get("/api/groups?size=2&limit=3")
|
|
assert r.status_code == 200
|
|
body = r.json()
|
|
assert body["size"] == 2 and body["boards"] == 5000
|
|
assert 0 < len(body["groups"]) <= 3
|
|
first = body["groups"][0]
|
|
assert len(first["units"]) == 2
|
|
assert all({"name", "cost", "icon", "splash", "items", "stars", "traits", "tier"}
|
|
<= set(u) for u in first["units"])
|
|
assert [u["items"] for u in first["units"]] == [3, 0]
|
|
assert [u["stars"] for u in first["units"]] == [2, 2]
|
|
# Je Carry nur eine Gruppe: a@3 kommt einmal vor, dann b@3.
|
|
carries = [g["units"][0]["name"] for g in body["groups"]]
|
|
assert len(carries) == len(set(carries))
|
|
# Nach Vorsprung sortiert, absteigend.
|
|
edges = [g["edge"] for g in body["groups"]]
|
|
assert edges == sorted(edges, reverse=True)
|
|
assert group_client.get("/api/groups?size=2&limit=1").json()["groups"] == [first]
|
|
|
|
|
|
def test_groups_without_data(client):
|
|
# Ohne trainiertes Artefakt gibt es nichts zu zeigen — 404 statt leerer Liste.
|
|
assert client.get("/api/groups?size=2").status_code == 404
|
|
|
|
|
|
def test_groups_unknown_size(group_client):
|
|
assert group_client.get("/api/groups?size=7").status_code == 404
|
|
|
|
|
|
def test_roles_endpoint(monkeypatch):
|
|
"""Beste Unit je Rolle und Kostenstufe, mit den Items dieses Champions."""
|
|
static = make_static()
|
|
static["meta"]["patch"] = "test"
|
|
art = artifact_mod.build(static)
|
|
api = next(a for a, u in art["static"]["units"].items() if "Tank" in (u.get("role") or ""))
|
|
item = next(iter(art["static"]["items"]))
|
|
art["groups"] = {"solo": {f"{api}@2@3": {"edge": 0.5, "boards": 300}},
|
|
"unit_items": {f"{api}|{item}": {"edge": 0.3, "boards": 400},
|
|
f"{api}|schlecht": {"edge": -0.1, "boards": 400}},
|
|
"boards": 9000}
|
|
art["unit_tiers"] = {api: {"tier": "S", "strength": 0.4, "boards": 1100}}
|
|
monkeypatch.setattr(app_mod, "_load_game_deps", lambda: (art, load_constants(17)))
|
|
body = TestClient(app_mod.app).get("/api/roles").json()
|
|
tank = next(r for r in body["roles"] if r["label"] == "Tank")
|
|
entry = next(c for c in tank["costs"] if c["unit"])
|
|
# Buchstabe und Zahl kommen aus derselben Quelle.
|
|
assert entry["unit"]["tier"] == "S"
|
|
assert entry["unit"]["edge"] == 0.4
|
|
# Items haengen am Champion, Negatives ist keine Empfehlung.
|
|
assert [i["edge"] for i in entry["unit"]["items"]] == [0.3]
|
|
assert {"name", "cost", "traits", "splash"} <= set(entry["unit"])
|
|
# Kostenstufen ohne Daten bleiben leer statt zu raten.
|
|
assert any(c["unit"] is None for c in tank["costs"])
|
|
|
|
|
|
def test_roles_without_data(client):
|
|
assert client.get("/api/roles").status_code == 404
|