|
# retoor <retoor@molodetz.nl>
|
|
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
from playwright.sync_api import expect
|
|
|
|
from tests.conftest import BASE_URL
|
|
|
|
|
|
def reset_farm(username, coins=100000):
|
|
from devplacepy.database import get_table
|
|
from devplacepy.services.game import store
|
|
|
|
user = get_table("users").find_one(username=username)
|
|
uid = user["uid"]
|
|
farm = store.get_farm(uid)
|
|
if farm:
|
|
get_table("game_plots").delete(farm_uid=farm["uid"])
|
|
get_table("game_quests").delete(farm_uid=farm["uid"])
|
|
get_table("game_farms").delete(uid=farm["uid"])
|
|
get_table("game_cosmetics").delete(user_uid=uid)
|
|
get_table("game_steals").delete(thief_uid=uid)
|
|
get_table("game_steals").delete(owner_uid=uid)
|
|
get_table("game_market_ticks").delete()
|
|
clear_game_caches()
|
|
farm = store.ensure_farm(uid)
|
|
get_table("game_farms").update({"uid": farm["uid"], "coins": coins}, ["uid"])
|
|
return {"uid": uid, "username": username}
|
|
|
|
|
|
def warp_ready(username):
|
|
from devplacepy.database import get_table
|
|
from devplacepy.services.game import store
|
|
|
|
user = get_table("users").find_one(username=username)
|
|
farm = store.get_farm(user["uid"])
|
|
past = (datetime.now(timezone.utc) - timedelta(seconds=5)).isoformat()
|
|
for plot in store.get_plots(farm["uid"]):
|
|
if plot.get("crop_key"):
|
|
get_table("game_plots").update(
|
|
{"uid": plot["uid"], "ready_at": past}, ["uid"]
|
|
)
|
|
|
|
|
|
def set_farm_xp(username, xp):
|
|
from devplacepy.database import get_table
|
|
from devplacepy.services.game import store
|
|
|
|
user = get_table("users").find_one(username=username)
|
|
farm = store.get_farm(user["uid"])
|
|
get_table("game_farms").update({"uid": farm["uid"], "xp": xp}, ["uid"])
|
|
|
|
|
|
def set_farm(username, **fields):
|
|
from devplacepy.database import get_table
|
|
from devplacepy.services.game import store
|
|
|
|
user = get_table("users").find_one(username=username)
|
|
farm = store.get_farm(user["uid"])
|
|
get_table("game_farms").update({"uid": farm["uid"], **fields}, ["uid"])
|
|
|
|
|
|
def clear_game_caches():
|
|
from devplacepy.services.game.store import farm as farm_store
|
|
from devplacepy.services.game.store import market as market_store
|
|
|
|
farm_store._leaderboard_cache.clear()
|
|
farm_store._board_cache.clear()
|
|
market_store._saturation_cache.clear()
|
|
|
|
|
|
def open_game(page):
|
|
page.goto(f"{BASE_URL}/game", wait_until="domcontentloaded")
|
|
page.locator("[data-game-grid]").first.wait_for(state="visible")
|
|
page.wait_for_timeout(800)
|
|
|
|
|
|
def coins(page):
|
|
return int(page.locator("[data-hud-coins]").inner_text())
|
|
|
|
|
|
def test_game_page_loads(alice):
|
|
page, _ = alice
|
|
reset_farm("alice_test")
|
|
open_game(page)
|
|
assert page.locator(".game-header h1").is_visible()
|
|
expect(page.locator("[data-hud-coins]")).to_have_text("100000")
|
|
expect(page.locator("[data-game-grid] [data-slot]")).to_have_count(4)
|
|
expect(page.locator("[data-perk-host] .perk-card")).to_have_count(4)
|
|
expect(page.locator(".quest-item")).to_have_count(3)
|
|
|
|
|
|
def test_guest_redirected_to_login(page):
|
|
page.goto(f"{BASE_URL}/game", wait_until="domcontentloaded")
|
|
page.wait_for_timeout(500)
|
|
assert "/auth/login" in page.url
|
|
|
|
|
|
def test_plant_crop(alice):
|
|
page, _ = alice
|
|
reset_farm("alice_test")
|
|
open_game(page)
|
|
form = page.locator("form[data-game-action='plant']").first
|
|
form.locator("select[name='crop']").select_option("shell")
|
|
form.locator("button").click()
|
|
expect(page.locator(".game-plot-growing")).to_have_count(1)
|
|
expect(page.locator("[data-hud-coins]")).to_have_text("99995")
|
|
|
|
|
|
def test_harvest_crop(alice):
|
|
page, _ = alice
|
|
reset_farm("alice_test")
|
|
open_game(page)
|
|
form = page.locator("form[data-game-action='plant']").first
|
|
form.locator("select[name='crop']").select_option("shell")
|
|
form.locator("button").click()
|
|
expect(page.locator(".game-plot-growing")).to_have_count(1)
|
|
warp_ready("alice_test")
|
|
open_game(page)
|
|
page.locator("form[data-game-action='harvest'] button").first.click()
|
|
expect(page.locator("[data-hud-coins]")).to_have_text("100006")
|
|
expect(page.locator(".game-plot-growing")).to_have_count(0)
|
|
|
|
|
|
def test_fertilize_spends_coins(alice):
|
|
page, _ = alice
|
|
reset_farm("alice_test")
|
|
open_game(page)
|
|
form = page.locator("form[data-game-action='plant']").first
|
|
form.locator("select[name='crop']").select_option("python")
|
|
form.locator("button").click()
|
|
expect(page.locator("[data-hud-coins]")).to_have_text("99985")
|
|
fert = page.locator("form[data-game-action='fertilize'] button").first
|
|
fert.wait_for(state="visible")
|
|
fert.click()
|
|
expect(page.locator("[data-hud-coins]")).not_to_have_text("99985")
|
|
assert coins(page) < 99985
|
|
|
|
|
|
def test_buy_plot(alice):
|
|
page, _ = alice
|
|
reset_farm("alice_test")
|
|
open_game(page)
|
|
page.locator("form[data-game-action='buy-plot'] button").click()
|
|
expect(page.locator("[data-game-grid] [data-slot]")).to_have_count(5)
|
|
expect(page.locator("[data-hud-coins]")).to_have_text("99900")
|
|
|
|
|
|
def test_upgrade_ci(alice):
|
|
page, _ = alice
|
|
reset_farm("alice_test")
|
|
open_game(page)
|
|
page.locator("form[data-game-action='upgrade'] button").click()
|
|
expect(page.locator("[data-hud-ci]")).to_have_text("2")
|
|
expect(page.locator("[data-hud-coins]")).to_have_text("99850")
|
|
|
|
|
|
def test_claim_daily(alice):
|
|
page, _ = alice
|
|
reset_farm("alice_test")
|
|
open_game(page)
|
|
page.locator("form[data-game-action='daily'] button").click()
|
|
expect(page.locator("[data-hud-coins]")).to_have_text("100020")
|
|
expect(page.locator("[data-daily-host] .daily-claimed")).to_be_visible()
|
|
|
|
|
|
def test_upgrade_perk(alice):
|
|
page, _ = alice
|
|
reset_farm("alice_test")
|
|
open_game(page)
|
|
growth = page.locator(
|
|
"form[data-game-action='perk']:has(input[value='growth']) button"
|
|
)
|
|
growth.click()
|
|
expect(page.locator("[data-hud-coins]")).to_have_text("99850")
|
|
expect(
|
|
page.locator(".perk-card:has-text('Build Cache') .perk-level")
|
|
).to_have_text("Lv 1/10")
|
|
|
|
|
|
def _set_farm_stars(username, stars):
|
|
from devplacepy.database import get_table
|
|
from devplacepy.services.game import store
|
|
|
|
user = get_table("users").find_one(username=username)
|
|
farm = store.get_farm(user["uid"])
|
|
get_table("game_farms").update({"uid": farm["uid"], "stars": stars}, ["uid"])
|
|
|
|
|
|
def test_upgrade_legacy_spends_stars(alice):
|
|
page, _ = alice
|
|
reset_farm("alice_test")
|
|
_set_farm_stars("alice_test", 1000)
|
|
open_game(page)
|
|
buy = page.locator(
|
|
"form[data-game-action='legacy']:has(input[value='multiplier']) button"
|
|
)
|
|
buy.wait_for(state="visible")
|
|
buy.click()
|
|
expect(
|
|
page.locator(".legacy-card:has-text('Tech Debt Payoff') .perk-level")
|
|
).to_have_text("Lv 1/10")
|
|
|
|
|
|
def test_claim_quest(alice):
|
|
page, _ = alice
|
|
from devplacepy.database import get_table
|
|
from devplacepy.services.game import store
|
|
|
|
farm_info = reset_farm("alice_test")
|
|
open_game(page)
|
|
farm = store.get_farm(farm_info["uid"])
|
|
quest = list(get_table("game_quests").find(farm_uid=farm["uid"]))[0]
|
|
get_table("game_quests").update(
|
|
{"uid": quest["uid"], "progress": quest["goal"]}, ["uid"]
|
|
)
|
|
open_game(page)
|
|
page.locator("form[data-game-action='claim-quest'] button").first.click()
|
|
expect(page.locator("[data-hud-coins]")).not_to_have_text("100000")
|
|
assert coins(page) > 100000
|
|
|
|
|
|
def test_prestige_hidden_below_level(alice):
|
|
page, _ = alice
|
|
reset_farm("alice_test")
|
|
open_game(page)
|
|
assert page.locator("form[data-game-action='prestige']").count() == 0
|
|
assert page.is_visible("text=Reach level 10 to refactor")
|
|
|
|
|
|
def test_prestige_resets_farm(alice):
|
|
page, _ = alice
|
|
from devplacepy.services.game import economy
|
|
|
|
reset_farm("alice_test")
|
|
set_farm_xp("alice_test", economy.xp_threshold(economy.PRESTIGE_MIN_LEVEL))
|
|
fee = economy.refactor_cost(0, 100000)
|
|
carried = economy.refactor_carryover(100000, fee, 0)
|
|
open_game(page)
|
|
prestige = page.locator("form[data-game-action='prestige'] button")
|
|
prestige.wait_for(state="visible")
|
|
prestige.click()
|
|
confirm_btn = page.locator(".dialog-confirm")
|
|
confirm_btn.wait_for(state="visible")
|
|
confirm_btn.click()
|
|
expect(page.locator("[data-hud-prestige]")).to_have_text("1")
|
|
expect(page.locator("[data-hud-coins]")).to_have_text(
|
|
str(economy.STARTING_COINS + carried)
|
|
)
|
|
assert page.locator("form[data-game-action='prestige']").count() == 0
|
|
|
|
|
|
def test_prestige_unaffordable_shows_cost_without_button(alice):
|
|
page, _ = alice
|
|
from devplacepy.services.game import economy
|
|
|
|
reset_farm("alice_test", coins=100)
|
|
set_farm_xp("alice_test", economy.xp_threshold(economy.PRESTIGE_MIN_LEVEL))
|
|
open_game(page)
|
|
assert page.locator("form[data-game-action='prestige']").count() == 0
|
|
assert page.is_visible("text=keep farming to afford it")
|
|
|
|
|
|
def test_grant_section_claim(alice):
|
|
page, _ = alice
|
|
from devplacepy.database import get_table
|
|
from devplacepy.services.game import economy, store
|
|
from devplacepy.services.game.store import _iso_week, _now
|
|
|
|
reset_farm("alice_test", coins=100)
|
|
store.ensure_treasury()
|
|
get_table("game_treasury").update(
|
|
{"uid": "treasury-main", "balance": 100000}, ["uid"]
|
|
)
|
|
set_farm(
|
|
"alice_test",
|
|
harvests_week=economy.GRANT_MIN_WEEK_HARVESTS,
|
|
harvests_week_start=_iso_week(_now()),
|
|
)
|
|
open_game(page)
|
|
claim = page.locator("form[data-game-action='grant'] button")
|
|
claim.wait_for(state="visible")
|
|
claim.click()
|
|
expect(page.locator("[data-hud-coins]")).to_have_text(
|
|
str(100 + economy.GRANT_CAP)
|
|
)
|
|
assert page.locator("form[data-game-action='grant']").count() == 0
|
|
|
|
|
|
def test_leaderboard_panel_populates(alice):
|
|
page, _ = alice
|
|
reset_farm("alice_test")
|
|
open_game(page)
|
|
page.locator(".game-lb-row").first.wait_for(state="visible")
|
|
assert page.is_visible("a.game-lb-name:has-text('alice_test')")
|
|
|
|
|
|
def test_infrastructure_hidden_below_prestige_requirement(alice):
|
|
page, _ = alice
|
|
reset_farm("alice_test", coins=100_000_000)
|
|
open_game(page)
|
|
assert page.is_visible("text=Requires prestige 3")
|
|
assert page.locator("form[data-game-action='infrastructure']").count() == 0
|
|
|
|
|
|
def test_infrastructure_buy_succeeds_when_eligible(alice):
|
|
page, _ = alice
|
|
reset_farm("alice_test", coins=100_000_000)
|
|
set_farm("alice_test", prestige=5)
|
|
open_game(page)
|
|
buy = page.locator("form[data-game-action='infrastructure'] button").first
|
|
buy.wait_for(state="visible")
|
|
buy.click()
|
|
confirm_btn = page.locator(".dialog-confirm")
|
|
confirm_btn.wait_for(state="visible")
|
|
confirm_btn.click()
|
|
expect(page.locator("form[data-game-action='infrastructure']")).to_have_count(0)
|
|
assert coins(page) == 100_000_000 - 25_000_000
|
|
|
|
|
|
def test_defense_upgrade_spends_coins(alice):
|
|
page, _ = alice
|
|
reset_farm("alice_test")
|
|
open_game(page)
|
|
upgrade = page.locator("form[data-game-action='defense'] button").first
|
|
upgrade.wait_for(state="visible")
|
|
upgrade.click()
|
|
expect(page.locator("[data-hud-coins]")).to_have_text("95000")
|
|
|
|
|
|
def test_cosmetics_buy_and_equip_flow(alice):
|
|
page, _ = alice
|
|
reset_farm("alice_test", coins=1_000_000)
|
|
open_game(page)
|
|
buy = page.locator("form[data-game-action='cosmetic-buy']").first
|
|
buy.locator("button").wait_for(state="visible")
|
|
buy.locator("button").click()
|
|
confirm_btn = page.locator(".dialog-confirm")
|
|
confirm_btn.wait_for(state="visible")
|
|
confirm_btn.click()
|
|
equip = page.locator("form[data-game-action='cosmetic-equip'] button").first
|
|
equip.wait_for(state="visible")
|
|
equip.click()
|
|
expect(page.locator(".game-cosmetics")).to_contain_text("Equipped")
|
|
|
|
|
|
def test_mastery_panel_hidden_without_mastery_points(alice):
|
|
page, _ = alice
|
|
reset_farm("alice_test")
|
|
open_game(page)
|
|
assert page.locator(".game-mastery").count() == 0
|
|
|
|
|
|
def test_mastery_panel_visible_and_functional_once_unlocked(alice):
|
|
page, _ = alice
|
|
reset_farm("alice_test")
|
|
set_farm("alice_test", mastery_points=1000, mastery_points_earned_total=1)
|
|
open_game(page)
|
|
upgrade = page.locator(
|
|
"form[data-game-action='mastery']:has(input[value='autoreplant']) button"
|
|
)
|
|
upgrade.wait_for(state="visible")
|
|
upgrade.click()
|
|
expect(
|
|
page.locator(".perk-card:has-text('Continuous Delivery') .perk-level")
|
|
).to_have_text("Lv 1/1")
|
|
|
|
|
|
def test_leaderboard_board_selector_switches_boards(alice):
|
|
page, _ = alice
|
|
reset_farm("alice_test")
|
|
set_farm("alice_test", prestige=7)
|
|
open_game(page)
|
|
page.locator(".game-lb-row").first.wait_for(state="visible")
|
|
page.select_option("[data-lb-board]", "prestige")
|
|
page.wait_for_timeout(400)
|
|
page.locator(".game-lb-row").first.wait_for(state="visible")
|
|
assert page.is_visible("a.game-lb-name:has-text('alice_test')")
|
|
|
|
|
|
def test_underdog_banner_shows_after_raiding_a_much_richer_farm(bob):
|
|
page, _ = bob
|
|
from devplacepy.database import get_table
|
|
from devplacepy.services.game import store
|
|
|
|
reset_farm("alice_test", coins=1_000_000)
|
|
reset_farm("bob_test", coins=0)
|
|
alice_user = get_table("users").find_one(username="alice_test")
|
|
bob_user = get_table("users").find_one(username="bob_test")
|
|
store.plant({"uid": alice_user["uid"], "username": "alice_test"}, 0, "shell")
|
|
past = (datetime.now(timezone.utc) - timedelta(days=1)).isoformat()
|
|
farm = store.get_farm(alice_user["uid"])
|
|
plot = store._plot_at(farm["uid"], 0)
|
|
get_table("game_plots").update(
|
|
{"uid": plot["uid"], "planted_at": past, "ready_at": past}, ["uid"]
|
|
)
|
|
store.steal({"uid": bob_user["uid"], "username": "bob_test"}, alice_user, 0)
|
|
page.goto(f"{BASE_URL}/game", wait_until="domcontentloaded")
|
|
page.locator("[data-underdog-banner]").wait_for(state="visible")
|
|
|
|
|
|
def test_weekly_contract_appears_and_can_be_claimed(alice):
|
|
page, _ = alice
|
|
from devplacepy.database import get_table
|
|
from devplacepy.services.game import store
|
|
from devplacepy.services.game.store.common import _iso_week
|
|
from devplacepy.services.game.store.quests import ensure_weekly_contract
|
|
|
|
reset_farm("alice_test")
|
|
set_farm("alice_test", mastery_points=1000)
|
|
user = get_table("users").find_one(username="alice_test")
|
|
store.upgrade_mastery({"uid": user["uid"], "username": "alice_test"}, "contracts")
|
|
farm = store.get_farm(user["uid"])
|
|
iso_week = _iso_week(datetime.now(timezone.utc))
|
|
ensure_weekly_contract(farm, iso_week)
|
|
row = get_table("game_quests").find_one(
|
|
farm_uid=farm["uid"], day=iso_week, scope="weekly"
|
|
)
|
|
get_table("game_quests").update(
|
|
{"uid": row["uid"], "progress": row["goal"]}, ["uid"]
|
|
)
|
|
open_game(page)
|
|
claim = page.locator(
|
|
"form[data-game-action='claim-quest']:has(input[value='weekly']) button"
|
|
)
|
|
claim.wait_for(state="visible")
|
|
claim.click()
|
|
page.wait_for_timeout(400)
|
|
farm = store.get_farm(user["uid"])
|
|
assert int(farm["stars"]) > 0
|
|
|
|
|
|
def test_crop_shows_saturated_label_when_overfarmed(alice):
|
|
page, _ = alice
|
|
from devplacepy.database import get_table
|
|
from devplacepy.services.game.store import market as market_store
|
|
from devplacepy.utils import generate_uid
|
|
|
|
reset_farm("alice_test")
|
|
now = datetime.now(timezone.utc)
|
|
get_table("game_market_ticks").insert(
|
|
{
|
|
"uid": generate_uid(),
|
|
"crop_key": "shell",
|
|
"hour_bucket": market_store._hour_bucket(now),
|
|
"harvests": 600,
|
|
"updated_at": now.isoformat(),
|
|
}
|
|
)
|
|
open_game(page)
|
|
option = page.locator("form[data-game-action='plant'] select option[value='shell']").first
|
|
option.wait_for(state="attached")
|
|
deadline = datetime.now(timezone.utc) + timedelta(seconds=40)
|
|
while "saturated" not in option.inner_text() and datetime.now(timezone.utc) < deadline:
|
|
page.wait_for_timeout(2000)
|
|
page.reload(wait_until="domcontentloaded")
|
|
page.locator("[data-game-grid]").first.wait_for(state="visible")
|
|
option = page.locator(
|
|
"form[data-game-action='plant'] select option[value='shell']"
|
|
).first
|
|
option.wait_for(state="attached")
|
|
assert "saturated" in option.inner_text()
|
|
|
|
|
|
def test_mobile_no_horizontal_overflow(mobile_page):
|
|
page, _ = mobile_page
|
|
reset_farm("alice_test")
|
|
open_game(page)
|
|
scroll_w = page.evaluate("document.documentElement.scrollWidth")
|
|
client_w = page.evaluate("document.documentElement.clientWidth")
|
|
assert scroll_w - client_w <= 1, f"horizontal overflow: {scroll_w} vs {client_w}"
|
|
assert page.locator("[data-game-grid]").first.is_visible()
|