# retoor 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_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(".perk-card:not(.legacy-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(".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)) 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("50") assert page.locator("form[data-game-action='prestige']").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") 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()