1
0
mirror of synced 2024-12-11 05:55:58 +01:00
bemaniutils/bemani/client/ddr/ddr2014.py
2024-01-02 02:46:24 +00:00

736 lines
30 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import random
import time
from typing import Any, Dict, List, Optional
from bemani.client.base import BaseClient
from bemani.protocol import Node
class DDR2014Client(BaseClient):
NAME = "TEST"
def verify_game_shop(self, loc: str) -> None:
call = self.call_node()
game = Node.void("game")
call.add_child(game)
game.set_attribute("method", "shop")
game.set_attribute("area", "51")
game.set_attribute("boot", "34")
game.set_attribute("close", "0")
game.set_attribute("close_t", "0")
game.set_attribute("coin", "02.01.--.--.01.G")
game.set_attribute("diff", "3")
game.set_attribute("during", "1")
game.set_attribute("edit_cnt", "0")
game.set_attribute("edit_used", "1")
game.set_attribute("first", "1")
game.set_attribute("ip", "1.5.7.3")
game.set_attribute("is_freefirstplay", "1")
game.set_attribute("is_paseli", "1")
game.set_attribute("loc", loc)
game.set_attribute("mac", "00:11:22:33:44:55")
game.set_attribute("machine", "2")
game.set_attribute("name", "")
game.set_attribute("pay", "0")
game.set_attribute("region", ".")
game.set_attribute("soft", self.config["model"])
game.set_attribute("softid", self.pcbid)
game.set_attribute("stage", "1")
game.set_attribute("time", "60")
game.set_attribute("type", "0")
game.set_attribute("ver", "2014102700")
# Swap with server
resp = self.exchange("", call)
# Verify that response is correct
self.assert_path(resp, "response/game/@stop")
def verify_game_common(self) -> None:
call = self.call_node()
game = Node.void("game")
call.add_child(game)
game.set_attribute("method", "common")
game.set_attribute("ver", "2014102700")
# Swap with server
resp = self.exchange("", call)
# Verify that response is correct
self.assert_path(resp, "response/game/flag/@id")
self.assert_path(resp, "response/game/flag/@s1")
self.assert_path(resp, "response/game/flag/@s2")
self.assert_path(resp, "response/game/flag/@t")
self.assert_path(resp, "response/game/cnt_music_monthly")
self.assert_path(resp, "response/game/cnt_music_weekly")
self.assert_path(resp, "response/game/cnt_music_daily")
def verify_game_hiscore(self) -> None:
call = self.call_node()
game = Node.void("game")
call.add_child(game)
game.set_attribute("method", "hiscore")
game.set_attribute("ver", "2014102700")
# Swap with server
resp = self.exchange("", call)
# Verify that response is correct
self.assert_path(resp, "response/game")
for child in resp.child("game").children:
self.assert_path(child, "music/@reclink_num")
self.assert_path(child, "music/type/@diff")
self.assert_path(child, "music/type/name")
self.assert_path(child, "music/type/score")
self.assert_path(child, "music/type/area")
self.assert_path(child, "music/type/rank")
self.assert_path(child, "music/type/combo_type")
self.assert_path(child, "music/type/code")
def verify_game_area_hiscore(self) -> None:
call = self.call_node()
game = Node.void("game")
call.add_child(game)
game.set_attribute("method", "area_hiscore")
game.set_attribute("shop_area", "51")
game.set_attribute("ver", "2014102700")
# Swap with server
resp = self.exchange("", call)
# Verify that response is correct
self.assert_path(resp, "response/game")
for child in resp.child("game").children:
self.assert_path(child, "music/@reclink_num")
self.assert_path(child, "music/type/@diff")
self.assert_path(child, "music/type/name")
self.assert_path(child, "music/type/score")
self.assert_path(child, "music/type/area")
self.assert_path(child, "music/type/rank")
self.assert_path(child, "music/type/combo_type")
self.assert_path(child, "music/type/code")
def verify_game_message(self) -> None:
call = self.call_node()
game = Node.void("game")
call.add_child(game)
game.set_attribute("method", "message")
game.set_attribute("ver", "2014102700")
# Swap with server
resp = self.exchange("", call)
# Verify that response is correct
self.assert_path(resp, "response/game")
def verify_game_ranking(self) -> None:
call = self.call_node()
game = Node.void("game")
call.add_child(game)
game.set_attribute("method", "ranking")
game.set_attribute("max", "10")
game.set_attribute("ver", "2014102700")
# Swap with server
resp = self.exchange("", call)
# Verify that response is correct
self.assert_path(resp, "response/game")
def verify_game_log(self) -> None:
call = self.call_node()
game = Node.void("game")
call.add_child(game)
game.set_attribute("method", "log")
game.set_attribute("type", "0")
game.set_attribute("soft", self.config["model"])
game.set_attribute("softid", self.pcbid)
game.set_attribute("ver", "2014102700")
game.set_attribute("boot", "34")
game.set_attribute("mac", "00:11:22:33:44:55")
clear = Node.void("clear")
game.add_child(clear)
clear.set_attribute("book", "0")
clear.set_attribute("edit", "0")
clear.set_attribute("rank", "0")
clear.set_attribute("set", "0")
auto = Node.void("auto")
game.add_child(auto)
auto.set_attribute("book", "1")
auto.set_attribute("edit", "1")
auto.set_attribute("rank", "1")
auto.set_attribute("set", "1")
# Swap with server
resp = self.exchange("", call)
# Verify that response is correct
self.assert_path(resp, "response/game")
def verify_game_tax_info(self) -> None:
call = self.call_node()
game = Node.void("game")
call.add_child(game)
game.set_attribute("method", "tax_info")
game.set_attribute("ver", "2014102700")
# Swap with server
resp = self.exchange("", call)
# Verify that response is correct
self.assert_path(resp, "response/game/tax_info/@tax_phase")
def verify_game_recorder(self) -> None:
call = self.call_node()
game = Node.void("game")
call.add_child(game)
game.set_attribute("method", "recorder")
game.set_attribute("assert_cnt", "0")
game.set_attribute("assert_info", "")
game.set_attribute("assert_path", "")
game.set_attribute("assert_time", "0")
game.set_attribute("boot_time", "1706151228")
game.set_attribute("cnt_demo", "1")
game.set_attribute("cnt_music", "1")
game.set_attribute("cnt_play", "0")
game.set_attribute("last_mid", "481")
game.set_attribute("last_seq", "36")
game.set_attribute("last_step", "0")
game.set_attribute("last_time", "1706151235")
game.set_attribute("softcode", self.config["model"])
game.set_attribute("temp_seq", "15")
game.set_attribute("temp_step", "8")
game.set_attribute("temp_time", "1706151234")
game.set_attribute("wd_restart", "0")
# Swap with server
resp = self.exchange("", call)
# Verify that response is correct
self.assert_path(resp, "response/game")
def verify_game_lock(self, ref_id: str, play: int) -> None:
call = self.call_node()
game = Node.void("game")
call.add_child(game)
game.set_attribute("refid", ref_id)
game.set_attribute("method", "lock")
game.set_attribute("ver", "2014102700")
game.set_attribute("play", str(play))
# Swap with server
resp = self.exchange("", call)
# Verify that response is correct
self.assert_path(resp, "response/game/@now_login")
def verify_game_new(self, ref_id: str) -> None:
# Pad the name to 8 characters
name = self.NAME[:8]
while len(name) < 8:
name = name + " "
call = self.call_node()
game = Node.void("game")
call.add_child(game)
game.set_attribute("method", "new")
game.set_attribute("ver", "2014102700")
game.set_attribute("name", name)
game.set_attribute("area", "51")
game.set_attribute("old", "0")
game.set_attribute("refid", ref_id)
# Swap with server
resp = self.exchange("", call)
# Verify that response is correct
self.assert_path(resp, "response/game")
def verify_game_load_daily(self, ref_id: str) -> None:
call = self.call_node()
game = Node.void("game")
call.add_child(game)
game.set_attribute("method", "load_daily")
game.set_attribute("ver", "2014102700")
game.set_attribute("refid", ref_id)
# Swap with server
resp = self.exchange("", call)
self.assert_path(resp, "response/game/daycount/@playcount")
self.assert_path(resp, "response/game/dailycombo/@daily_combo")
self.assert_path(resp, "response/game/dailycombo/@daily_combo_lv")
def verify_game_load(self, ref_id: str, msg_type: str) -> Dict[str, Any]:
call = self.call_node()
game = Node.void("game")
call.add_child(game)
game.set_attribute("method", "load")
game.set_attribute("ver", "2014102700")
game.set_attribute("refid", ref_id)
# Swap with server
resp = self.exchange("", call)
if msg_type == "new":
# Verify that response is correct
self.assert_path(resp, "response/game/@none")
return {}
if msg_type == "existing":
# Verify existing profile and return info
self.assert_path(resp, "response/game/seq")
self.assert_path(resp, "response/game/code")
self.assert_path(resp, "response/game/name")
self.assert_path(resp, "response/game/area")
self.assert_path(resp, "response/game/cnt_s")
self.assert_path(resp, "response/game/cnt_d")
self.assert_path(resp, "response/game/cnt_b")
self.assert_path(resp, "response/game/cnt_m0")
self.assert_path(resp, "response/game/cnt_m1")
self.assert_path(resp, "response/game/cnt_m2")
self.assert_path(resp, "response/game/cnt_m3")
self.assert_path(resp, "response/game/cnt_m4")
self.assert_path(resp, "response/game/cnt_m5")
self.assert_path(resp, "response/game/exp")
self.assert_path(resp, "response/game/exp_o")
self.assert_path(resp, "response/game/star")
self.assert_path(resp, "response/game/star_c")
self.assert_path(resp, "response/game/combo")
self.assert_path(resp, "response/game/timing_diff")
self.assert_path(resp, "response/game/chara")
self.assert_path(resp, "response/game/chara_opt")
self.assert_path(resp, "response/game/daycount/@playcount")
self.assert_path(resp, "response/game/dailycombo/@daily_combo")
self.assert_path(resp, "response/game/dailycombo/@daily_combo_lv")
self.assert_path(resp, "response/game/last/@cate")
self.assert_path(resp, "response/game/last/@cid")
self.assert_path(resp, "response/game/last/@ctype")
self.assert_path(resp, "response/game/last/@fri")
self.assert_path(resp, "response/game/last/@mid")
self.assert_path(resp, "response/game/last/@mode")
self.assert_path(resp, "response/game/last/@mtype")
self.assert_path(resp, "response/game/last/@rival1")
self.assert_path(resp, "response/game/last/@rival2")
self.assert_path(resp, "response/game/last/@rival3")
self.assert_path(resp, "response/game/last/@sid")
self.assert_path(resp, "response/game/last/@sort")
self.assert_path(resp, "response/game/last/@style")
self.assert_path(resp, "response/game/result_star/@slot1")
self.assert_path(resp, "response/game/result_star/@slot2")
self.assert_path(resp, "response/game/result_star/@slot3")
self.assert_path(resp, "response/game/result_star/@slot4")
self.assert_path(resp, "response/game/result_star/@slot5")
self.assert_path(resp, "response/game/result_star/@slot6")
self.assert_path(resp, "response/game/result_star/@slot7")
self.assert_path(resp, "response/game/result_star/@slot8")
self.assert_path(resp, "response/game/result_star/@slot9")
self.assert_path(resp, "response/game/gr_s/@gr1")
self.assert_path(resp, "response/game/gr_s/@gr2")
self.assert_path(resp, "response/game/gr_s/@gr3")
self.assert_path(resp, "response/game/gr_s/@gr4")
self.assert_path(resp, "response/game/gr_s/@gr5")
self.assert_path(resp, "response/game/gr_d/@gr1")
self.assert_path(resp, "response/game/gr_d/@gr2")
self.assert_path(resp, "response/game/gr_d/@gr3")
self.assert_path(resp, "response/game/gr_d/@gr4")
self.assert_path(resp, "response/game/gr_d/@gr5")
self.assert_path(resp, "response/game/opt")
self.assert_path(resp, "response/game/opt_ex")
self.assert_path(resp, "response/game/option_ver/@ver")
self.assert_path(resp, "response/game/flag")
self.assert_path(resp, "response/game/flag_ex")
self.assert_path(resp, "response/game/rank")
self.assert_path(resp, "response/game/target/@flag")
self.assert_path(resp, "response/game/target/@setnum")
for i in range(55):
self.assert_path(resp, f"response/game/play_area/@play_cnt{i}")
gr_s = resp.child("game/gr_s")
gr_d = resp.child("game/gr_d")
return {
"name": resp.child_value("game/name"),
"ext_id": resp.child_value("game/code"),
"single_plays": resp.child_value("game/cnt_s"),
"double_plays": resp.child_value("game/cnt_d"),
"groove_single": [
int(gr_s.attribute("gr1")),
int(gr_s.attribute("gr2")),
int(gr_s.attribute("gr3")),
int(gr_s.attribute("gr4")),
int(gr_s.attribute("gr5")),
],
"groove_double": [
int(gr_d.attribute("gr1")),
int(gr_d.attribute("gr2")),
int(gr_d.attribute("gr3")),
int(gr_d.attribute("gr4")),
int(gr_d.attribute("gr5")),
],
}
raise Exception("Unknown load type!")
def verify_game_load_m(self, ref_id: str) -> Dict[int, Dict[int, Dict[str, Any]]]:
call = self.call_node()
game = Node.void("game")
call.add_child(game)
game.set_attribute("ver", "2014102700")
game.set_attribute("all", "1")
game.set_attribute("refid", ref_id)
game.set_attribute("method", "load_m")
# Swap with server
resp = self.exchange("", call)
# Verify that response is correct
scores: Dict[int, Dict[int, Dict[str, Any]]] = {}
self.assert_path(resp, "response/game")
for child in resp.child("game").children:
self.assert_path(child, "music/@reclink")
reclink = int(child.attribute("reclink"))
for typenode in child.children:
self.assert_path(typenode, "type/@diff")
self.assert_path(typenode, "type/score")
self.assert_path(typenode, "type/count")
self.assert_path(typenode, "type/rank")
self.assert_path(typenode, "type/combo_type")
chart = int(typenode.attribute("diff"))
vals = {
"score": typenode.child_value("score"),
"count": typenode.child_value("count"),
"rank": typenode.child_value("rank"),
"halo": typenode.child_value("combo_type"),
}
if reclink not in scores:
scores[reclink] = {}
scores[reclink][chart] = vals
return scores
def verify_game_load_edit(self, ref_id: str) -> None:
call = self.call_node()
game = Node.void("game")
call.add_child(game)
game.set_attribute("ver", "2014102700")
game.set_attribute("pid", "0")
game.set_attribute("refid", ref_id)
game.set_attribute("method", "load_edit")
# Swap with server
resp = self.exchange("", call)
# Verify that response is correct
self.assert_path(resp, "response/game")
def verify_game_save(self, ref_id: str, style: int, gauge: Optional[List[int]] = None) -> None:
gauge = gauge or [0, 0, 0, 0, 0]
call = self.call_node()
game = Node.void("game")
call.add_child(game)
game.set_attribute("method", "save")
game.set_attribute("refid", ref_id)
game.set_attribute("ver", "2014102700")
game.set_attribute("shop_area", "51")
last = Node.void("last")
game.add_child(last)
last.set_attribute("mode", "1")
last.set_attribute("style", str(style))
gr = Node.void("gr")
game.add_child(gr)
gr.set_attribute("gr1", str(gauge[0]))
gr.set_attribute("gr2", str(gauge[1]))
gr.set_attribute("gr3", str(gauge[2]))
gr.set_attribute("gr4", str(gauge[3]))
gr.set_attribute("gr5", str(gauge[4]))
# Swap with server
resp = self.exchange("", call)
# Verify that response is correct
self.assert_path(resp, "response/game")
def verify_game_save_m(self, ref_id: str, ext_id: str, score: Dict[str, Any]) -> None:
call = self.call_node()
game = Node.void("game")
call.add_child(game)
game.set_attribute("method", "save_m")
game.set_attribute("diff", "12345")
game.set_attribute("mtype", str(score["chart"]))
game.set_attribute("mid", str(score["id"]))
game.set_attribute("refid", ref_id)
game.set_attribute("ver", "2014102700")
data = Node.void("data")
game.add_child(data)
data.set_attribute("score", str(score["score"]))
data.set_attribute("rank", str(score["rank"]))
data.set_attribute("shop_area", "0")
data.set_attribute("playmode", "1")
data.set_attribute("combo", str(score["combo"]))
data.set_attribute("phase", "1")
data.set_attribute("style", "0")
data.set_attribute("full", "1" if score["halo"] >= 1 else "0")
data.set_attribute("great_fc", "1" if score["halo"] == 1 else "0")
data.set_attribute("good_fc", "1" if score["halo"] == 4 else "0")
data.set_attribute("perf_fc", "1" if score["halo"] == 2 else "0")
gauge = Node.void("gauge")
game.add_child(gauge)
gauge.set_attribute("life8", "0")
gauge.set_attribute("assist", "0")
gauge.set_attribute("risky", "0")
gauge.set_attribute("life4", "0")
gauge.set_attribute("hard", "0")
player = Node.void("player")
game.add_child(player)
player.set_attribute("playcnt", "123")
player.set_attribute("code", ext_id)
option = Node.void("option_02")
game.add_child(option)
option.set_attribute("opt02_0", "6")
option.set_attribute("opt02_6", "1")
option.set_attribute("opt02_13", "2")
game.add_child(Node.u8_array("trace", [0] * 512))
game.add_child(Node.u32("size", 512))
# Swap with server
resp = self.exchange("", call)
# Verify that response is correct
self.assert_path(resp, "response/game")
def verify(self, cardid: Optional[str]) -> None:
# Verify boot sequence is okay
self.verify_services_get(
expected_services=[
"pcbtracker",
"pcbevent",
"local",
"message",
"facility",
"cardmng",
"package",
"posevent",
"pkglist",
"dlstatus",
"eacoin",
"lobby",
"ntp",
"keepalive",
]
)
paseli_enabled = self.verify_pcbtracker_alive()
self.verify_message_get()
self.verify_package_list()
location = self.verify_facility_get("EUC_JP")
self.verify_pcbevent_put()
self.verify_game_recorder()
self.verify_game_tax_info()
self.verify_game_shop(location)
self.verify_game_common()
self.verify_game_hiscore()
self.verify_game_area_hiscore()
self.verify_game_message()
self.verify_game_ranking()
self.verify_game_log()
# Verify card registration and profile lookup
if cardid is not None:
card = cardid
else:
card = self.random_card()
print(f"Generated random card ID {card} for use.")
if cardid is None:
self.verify_cardmng_inquire(card, msg_type="unregistered", paseli_enabled=paseli_enabled)
ref_id = self.verify_cardmng_getrefid(card)
if len(ref_id) != 16:
raise Exception(f"Invalid refid '{ref_id}' returned when registering card")
if ref_id != self.verify_cardmng_inquire(card, msg_type="new", paseli_enabled=paseli_enabled):
raise Exception(f"Invalid refid '{ref_id}' returned when querying card")
# Bishi doesn't read a new profile, it just writes out CSV for a blank one
self.verify_game_load(ref_id, msg_type="new")
self.verify_game_new(ref_id)
else:
print("Skipping new card checks for existing card")
ref_id = self.verify_cardmng_inquire(card, msg_type="query", paseli_enabled=paseli_enabled)
# Verify pin handling and return card handling
self.verify_cardmng_authpass(ref_id, correct=True)
self.verify_cardmng_authpass(ref_id, correct=False)
if ref_id != self.verify_cardmng_inquire(card, msg_type="query", paseli_enabled=paseli_enabled):
raise Exception(f"Invalid refid '{ref_id}' returned when querying card")
# Verify locking and unlocking profile ability
self.verify_game_lock(ref_id, 1)
self.verify_game_lock(ref_id, 0)
if cardid is None:
# Verify empty profile
profile = self.verify_game_load(ref_id, msg_type="existing")
ext_id = str(profile["ext_id"])
if profile["name"] != self.NAME:
raise Exception("Profile has invalid name associated with it!")
if profile["single_plays"] != 0:
raise Exception("Profile has plays on single already!")
if profile["double_plays"] != 0:
raise Exception("Profile has plays on double already!")
if any([g != 0 for g in profile["groove_single"]]):
raise Exception("Profile has single groove gauge values already!")
if any([g != 0 for g in profile["groove_double"]]):
raise Exception("Profile has double groove gauge values already!")
# Verify empty scores
scores = self.verify_game_load_m(ref_id)
if len(scores) > 0:
raise Exception("Scores exist on new profile!")
self.verify_game_load_edit(ref_id)
self.verify_game_load_daily(ref_id)
# Verify profile saving
self.verify_game_save(ref_id, 0, [1, 2, 3, 4, 5])
profile = self.verify_game_load(ref_id, msg_type="existing")
if profile["name"] != self.NAME:
raise Exception("Profile has invalid name associated with it!")
if profile["single_plays"] != 1:
raise Exception("Profile has invalid plays on single!")
if profile["double_plays"] != 0:
raise Exception("Profile has invalid plays on double!")
if profile["groove_single"] != [1, 2, 3, 4, 5]:
raise Exception("Profile has invalid single groove gauge values!")
if any([g != 0 for g in profile["groove_double"]]):
raise Exception("Profile has invalid double groove gauge values!")
self.verify_game_save(ref_id, 1, [5, 4, 3, 2, 1])
profile = self.verify_game_load(ref_id, msg_type="existing")
if profile["name"] != self.NAME:
raise Exception("Profile has invalid name associated with it!")
if profile["single_plays"] != 1:
raise Exception("Profile has invalid plays on single!")
if profile["double_plays"] != 1:
raise Exception("Profile has invalid plays on double!")
if profile["groove_single"] != [1, 2, 3, 4, 5]:
raise Exception("Profile has invalid single groove gauge values!")
if profile["groove_double"] != [5, 4, 3, 2, 1]:
raise Exception("Profile has invalid double groove gauge values!")
# Now, write some scores and verify saving
for phase in [1, 2]:
if phase == 1:
dummyscores = [
# An okay score on a chart
{
"id": 593,
"chart": 3,
"score": 800000,
"combo": 123,
"rank": 4,
"halo": 1,
},
# A good score on an easier chart same song
{
"id": 593,
"chart": 2,
"score": 990000,
"combo": 321,
"rank": 2,
"halo": 2,
},
# A perfect score
{
"id": 483,
"chart": 3,
"score": 1000000,
"combo": 400,
"rank": 1,
"halo": 3,
},
# A bad score
{
"id": 483,
"chart": 2,
"score": 100000,
"combo": 5,
"rank": 7,
"halo": 0,
},
{
"id": 483,
"chart": 1,
"score": 60000,
"combo": 5,
"rank": 6,
"halo": 4,
},
]
if phase == 2:
dummyscores = [
# A better score on a chart
{
"id": 593,
"chart": 3,
"score": 850000,
"combo": 234,
"rank": 3,
"halo": 2,
},
# A worse score on another chart
{
"id": 593,
"chart": 2,
"score": 980000,
"combo": 300,
"rank": 3,
"halo": 0,
"expected_score": 990000,
"expected_rank": 2,
"expected_halo": 2,
},
]
for score in dummyscores:
self.verify_game_save_m(ref_id, ext_id, score)
scores = self.verify_game_load_m(ref_id)
for score in dummyscores:
data = scores.get(score["id"], {}).get(score["chart"], None)
if data is None:
raise Exception(f'Expected to get score back for song {score["id"]} chart {score["chart"]}!')
# Verify the attributes of the score
expected_score = score.get("expected_score", score["score"])
expected_rank = score.get("expected_rank", score["rank"])
expected_halo = score.get("expected_halo", score["halo"])
if data["score"] != expected_score:
raise Exception(
f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["score"]}\''
)
if data["rank"] != expected_rank:
raise Exception(
f'Expected a rank of \'{expected_rank}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got rank \'{data["rank"]}\''
)
if data["halo"] != expected_halo:
raise Exception(
f'Expected a halo of \'{expected_halo}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got halo \'{data["halo"]}\''
)
# Sleep so we don't end up putting in score history on the same second
time.sleep(1)
else:
print("Skipping score checks for existing card")
# Verify paseli handling
if paseli_enabled:
print("PASELI enabled for this PCBID, executing PASELI checks")
else:
print("PASELI disabled for this PCBID, skipping PASELI checks")
return
sessid, balance = self.verify_eacoin_checkin(card)
if balance == 0:
print("Skipping PASELI consume check because card has 0 balance")
else:
self.verify_eacoin_consume(sessid, balance, random.randint(0, balance))
self.verify_eacoin_checkout(sessid)