StoreItem and choose_upgrade, get_store_items, buy_upgrade, get_cps, run_bot with a Clock port, main and python -m cookie_clicker_bot. 74 offline tests. Reviewed in RC-012. Task: MIL-003#1 Task: MIL-003#2 Task: MIL-003#3 Task: MIL-003#4 Task: MIL-003#5 Task: MIL-003#6 Closes #13 Closes #14 Closes #15 Closes #16 Closes #17 Closes #18
108 lines
3.3 KiB
Python
108 lines
3.3 KiB
Python
"""@file
|
|
@brief Tests of the run loop against a fake game and a fake clock.
|
|
"""
|
|
|
|
from decimal import Decimal
|
|
|
|
import pytest
|
|
|
|
from cookie_clicker_bot.application.use_cases.run_bot import run_bot
|
|
from cookie_clicker_bot.constants import (
|
|
BIG_COOKIE_ID,
|
|
CLASS_ATTRIBUTE,
|
|
CLICKS_PER_BURST,
|
|
COOKIE_COUNTER_ID,
|
|
CPS_ID,
|
|
PRODUCT_ID_PREFIX,
|
|
PRODUCT_PRICE_ID_PREFIX,
|
|
RUN_DURATION_SECONDS,
|
|
STORE_CHECK_INTERVAL_SECONDS,
|
|
)
|
|
from cookie_clicker_bot.domain.errors import UnreadableNumberError
|
|
from tests.unit.fakes import FakeClock, FakeGame
|
|
|
|
PRODUCT = f"{PRODUCT_ID_PREFIX}0"
|
|
|
|
|
|
def make_game(cookies: int = 100, price: int = 15) -> FakeGame:
|
|
"""A store with one affordable product that is spent once, then unaffordable."""
|
|
game = FakeGame(
|
|
texts={
|
|
COOKIE_COUNTER_ID: f"{cookies} cookies",
|
|
f"{PRODUCT_PRICE_ID_PREFIX}0": str(price),
|
|
CPS_ID: "per second: 4.5",
|
|
},
|
|
attributes={(PRODUCT, CLASS_ATTRIBUTE): "product unlocked enabled"},
|
|
)
|
|
|
|
def spend(element_id: str) -> None:
|
|
if element_id == PRODUCT:
|
|
game.texts[COOKIE_COUNTER_ID] = f"{cookies - price} cookies"
|
|
game.attributes[(PRODUCT, CLASS_ATTRIBUTE)] = "product unlocked disabled"
|
|
|
|
game.on_click = spend
|
|
return game
|
|
|
|
|
|
def test_run_bot_returns_the_cps() -> None:
|
|
assert run_bot(make_game(), FakeClock(), 3) == Decimal("4.5")
|
|
|
|
|
|
def test_run_bot_clicks_in_bursts_until_the_duration_is_over() -> None:
|
|
game = FakeGame(texts={CPS_ID: "per second: 1"})
|
|
run_bot(game, FakeClock(step=1.0), 3)
|
|
assert game.clicked == [BIG_COOKIE_ID] * CLICKS_PER_BURST * 3
|
|
|
|
|
|
def test_run_bot_stops_after_the_duration() -> None:
|
|
clock = FakeClock(step=10.0)
|
|
game = FakeGame(texts={CPS_ID: "per second: 1"})
|
|
run_bot(game, clock, 25)
|
|
assert clock.now == 40.0
|
|
|
|
|
|
def test_run_bot_does_not_visit_the_store_before_the_interval() -> None:
|
|
game = make_game()
|
|
run_bot(game, FakeClock(step=1.0), STORE_CHECK_INTERVAL_SECONDS - 1)
|
|
assert PRODUCT not in game.clicked
|
|
|
|
|
|
def test_run_bot_buys_at_the_interval() -> None:
|
|
game = make_game()
|
|
run_bot(game, FakeClock(step=1.0), STORE_CHECK_INTERVAL_SECONDS + 2)
|
|
assert game.clicked.count(PRODUCT) == 1
|
|
|
|
|
|
def test_run_bot_stops_buying_when_nothing_is_affordable() -> None:
|
|
game = make_game()
|
|
run_bot(game, FakeClock(step=1.0), STORE_CHECK_INTERVAL_SECONDS * 3)
|
|
assert game.clicked.count(PRODUCT) == 1
|
|
|
|
|
|
def test_run_bot_stops_buying_when_a_purchase_fails() -> None:
|
|
game = make_game()
|
|
game.on_click = None
|
|
run_bot(game, FakeClock(step=1.0), STORE_CHECK_INTERVAL_SECONDS + 1)
|
|
assert game.clicked.count(PRODUCT) == 1
|
|
|
|
|
|
@pytest.mark.parametrize("duration", [0, -1])
|
|
def test_run_bot_rejects_a_non_positive_duration(duration: float) -> None:
|
|
game = FakeGame()
|
|
with pytest.raises(ValueError, match="positive"):
|
|
run_bot(game, FakeClock(), duration)
|
|
assert game.clicked == []
|
|
|
|
|
|
def test_run_bot_propagates_an_unreadable_cps() -> None:
|
|
game = FakeGame(texts={CPS_ID: "nothing"})
|
|
with pytest.raises(UnreadableNumberError):
|
|
run_bot(game, FakeClock(), 1)
|
|
|
|
|
|
def test_run_duration_default_is_the_constant() -> None:
|
|
clock = FakeClock(step=100.0)
|
|
game = FakeGame(texts={CPS_ID: "per second: 1"})
|
|
run_bot(game, clock)
|
|
assert clock.now == 100.0 * (RUN_DURATION_SECONDS // 100 + 1)
|