ipdatepppdate

This commit is contained in:
2026-07-26 16:46:41 +02:00
parent 2620ecc0f1
commit ac04cf6817
30 changed files with 915 additions and 10309 deletions
+16 -3
View File
@@ -118,7 +118,8 @@ def test_harvest_crop(alice):
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("[data-hud-coins]")).not_to_have_text("99995")
assert coins(page) > 99995
expect(page.locator(".game-plot-growing")).to_have_count(0)
@@ -292,7 +293,15 @@ def test_leaderboard_panel_populates(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')")
listed = False
for _ in range(8):
if page.is_visible("a.game-lb-name:has-text('alice_test')"):
listed = True
break
page.wait_for_timeout(3000)
page.reload(wait_until="domcontentloaded")
page.locator(".game-lb-row").first.wait_for(state="visible")
assert listed, "alice_test never appeared on the farm leaderboard within the 15s board cache TTL"
def test_infrastructure_hidden_below_prestige_requirement(alice):
@@ -437,13 +446,17 @@ def test_crop_shows_saturated_label_when_overfarmed(alice):
from devplacepy.utils import generate_uid
reset_farm("alice_test")
from devplacepy.services.game import economy
now = datetime.now(timezone.utc)
crop = economy.crop_for("shell")
worst_days = economy.MARKET_SATURATION_TIERS[-1][0]
get_table("game_market_ticks").insert(
{
"uid": generate_uid(),
"crop_key": "shell",
"hour_bucket": market_store._hour_bucket(now),
"harvests": 600,
"harvests": int(worst_days * 86400 / crop.grow_seconds) + 1,
"updated_at": now.isoformat(),
}
)
+10 -4
View File
@@ -44,10 +44,16 @@ def test_leaderboard_ranks_after_upvote(app_server, browser, seeded_db):
vote_btn.click()
pa.wait_for_timeout(1500)
pa.goto(f"{BASE_URL}/leaderboard", wait_until="domcontentloaded")
body = pa.locator("body").text_content()
assert "Internal Server Error" not in body, f"Got 500 on leaderboard: {body[:300]}"
assert pa.is_visible("a.leaderboard-name:has-text('bob_test')")
ranked = False
for _ in range(15):
pa.goto(f"{BASE_URL}/leaderboard", wait_until="domcontentloaded")
body = pa.locator("body").text_content()
assert "Internal Server Error" not in body, f"Got 500 on leaderboard: {body[:300]}"
if pa.is_visible("a.leaderboard-name:has-text('bob_test')"):
ranked = True
break
pa.wait_for_timeout(5000)
assert ranked, "bob_test never appeared on the leaderboard within the 60s ranking cache TTL"
finally:
ctx_a.close()
ctx_b.close()
+6 -5
View File
@@ -311,18 +311,19 @@ def test_optimistic_send_pending_then_reconciles_no_double_render(alice, bob):
textarea.fill(msg)
page_a.locator(".messages-send-btn").click()
pending = page_a.locator(f".message-bubble.mine.pending:has-text('{msg}')")
pending.wait_for(state="visible", timeout=5000)
client_id = pending.get_attribute("data-client-id")
bubble = page_a.locator(f".message-bubble.mine:has-text('{msg}')").first
bubble.wait_for(state="visible", timeout=10000)
client_id = bubble.get_attribute("data-client-id")
assert client_id
reconciled = page_a.locator(
f".message-bubble.mine[data-client-id='{client_id}'][data-msg-uid]:not(.pending)"
)
reconciled.wait_for(state="visible", timeout=6000)
reconciled.wait_for(state="visible", timeout=10000)
assert page_a.locator(f".message-bubble.mine:has-text('{msg}')").count() == 1
received = page_b.locator(f".message-bubble.theirs:has-text('{msg}')")
received.wait_for(state="visible", timeout=6000)
received.wait_for(state="visible", timeout=10000)
assert received.count() == 1
+9 -3
View File
@@ -397,10 +397,16 @@ def test_feed_vote_voted_state_persists(alice):
page.locator("#create-post-modal button.btn-primary:has-text('Post')").click()
page.wait_for_url(f"{BASE_URL}/posts/*", wait_until="domcontentloaded")
page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded")
page.locator(".post-action-btn.vote-up").first.click()
expect(page.locator(".post-vote-count").first).to_have_text("1")
card = page.locator(
".post-card", has_text="Feed vote persistence test content"
).first
card.locator(".post-action-btn.vote-up").click()
expect(card.locator(".post-vote-count")).to_have_text("1")
page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded")
expect(page.locator(".post-action-btn.vote-up").first).to_have_class(
card = page.locator(
".post-card", has_text="Feed vote persistence test content"
).first
expect(card.locator(".post-action-btn.vote-up")).to_have_class(
re.compile(r"\bvoted\b")
)
+44 -8
View File
@@ -231,20 +231,56 @@ def test_is_golden_requires_both_args():
def test_market_saturation_factor_is_full_when_fresh():
assert economy.market_saturation_factor(0) == 1.0
assert economy.market_saturation_factor(0.0) == 1.0
def test_market_saturation_factor_steps_down():
assert economy.market_saturation_factor(40) < 1.0
assert economy.market_saturation_factor(600) == 0.40
assert economy.market_saturation_factor(40) > economy.market_saturation_factor(600)
assert economy.market_saturation_factor(8.0) < 1.0
assert economy.market_saturation_factor(96.0) == 0.40
assert economy.market_saturation_factor(8.0) > economy.market_saturation_factor(96.0)
def test_market_saturation_factor_is_monotone_non_increasing():
previous = 1.0
step = 0
while step <= 400:
supply = step * 0.5
factor = economy.market_saturation_factor(supply)
assert factor <= previous
previous = factor
step += 1
def test_supply_days_normalizes_by_grow_time():
shell = economy.crop_for("shell")
kernel = economy.crop_for("kernel")
assert economy.supply_days(shell, 0) == 0.0
assert economy.supply_days(shell, 5760) == economy.supply_days(kernel, 24)
assert economy.supply_days(shell, 2880) == 1.0
assert economy.supply_days(kernel, 12) == 1.0
def test_market_buff_factor_only_applies_to_starter_crops():
assert economy.market_buff_factor("kernel", 0.40) == 1.0
assert economy.market_buff_factor("shell", 1.0) == 1.0
assert economy.market_buff_factor("shell", 0.40) > 1.0
assert economy.market_buff_factor("shell", 0.0) <= economy.MARKET_BUFF_CAP
assert economy.market_buff_factor("kernel", 0.60) == 1.0
assert economy.market_buff_factor("shell", 0.0) == 1.0
assert economy.market_buff_factor("shell", 0.15) == 1.0375
assert economy.market_buff_factor("shell", 0.60) == economy.MARKET_BUFF_CAP
assert economy.market_buff_factor("shell", 0.90) == economy.MARKET_BUFF_CAP
def test_market_factor_bounds_across_domain():
floor = economy.MARKET_SATURATION_TIERS[-1][1]
for key in economy.MARKET_TRACKED_CROPS:
crop = economy.crop_for(key)
for harvests in range(0, 300000, 977):
saturation = economy.market_saturation_factor(economy.supply_days(crop, harvests))
for pressure in (0.0, 0.15, 0.30, 0.45, 0.60):
factor = saturation if saturation < 1.0 else economy.market_buff_factor(key, pressure)
assert floor <= factor <= economy.MARKET_BUFF_CAP
if saturation < 1.0:
assert factor == saturation
else:
assert factor >= 1.0
def test_effective_reward_coins_applies_market_factor():
+49 -5
View File
@@ -371,6 +371,7 @@ def _insert_quest(user, kind, goal, progress=0, claimed=0, reward_coins=50, rewa
"user_uid": user["uid"],
"day": store._today(),
"slot_index": 0,
"scope": "daily",
"kind": kind,
"label": f"{kind} {goal}",
"goal": goal,
@@ -723,7 +724,9 @@ def test_harvest_records_market_tick_and_saturates_reward(local_db):
from devplacepy.utils import generate_uid
now = datetime.now(timezone.utc)
worst_tier_count = economy.MARKET_SATURATION_TIERS[-1][0]
crop = economy.crop_for("shell")
worst_days = economy.MARKET_SATURATION_TIERS[-1][0]
worst_tier_count = int(worst_days * 86400 / crop.grow_seconds) + 1
get_table("game_market_ticks").insert(
{
"uid": generate_uid(),
@@ -735,11 +738,52 @@ def test_harvest_records_market_tick_and_saturates_reward(local_db):
)
store.plant(user, 0, "shell")
_warp(user)
crop = economy.crop_for("shell")
result = store.harvest(user, 0)
assert result["coins"] < crop.reward_coins
def _seed_market_ticks(crop_key, harvests):
from devplacepy.services.game.store import market as market_store
from devplacepy.utils import generate_uid
now = datetime.now(timezone.utc)
get_table("game_market_ticks").insert(
{
"uid": generate_uid(),
"crop_key": crop_key,
"hour_bucket": market_store._hour_bucket(now),
"harvests": harvests,
"updated_at": now.isoformat(),
}
)
market_store._saturation_cache.clear()
def _worst_tier_harvests(crop_key):
crop = economy.crop_for(crop_key)
worst_days = economy.MARKET_SATURATION_TIERS[-1][0]
return int(worst_days * 86400 / crop.grow_seconds) + 1
def test_starter_crop_boosted_when_market_saturated(local_db):
_reset("unit_a")
from devplacepy.services.game.store import market as market_store
_seed_market_ticks("kernel", _worst_tier_harvests("kernel"))
assert market_store.market_factor_for("kernel") == economy.MARKET_SATURATION_TIERS[-1][1]
assert market_store.market_factor_for("shell") == economy.MARKET_BUFF_CAP
assert market_store.market_factor_for("rust") == 1.0
def test_own_saturation_beats_boost(local_db):
_reset("unit_a")
from devplacepy.services.game.store import market as market_store
_seed_market_ticks("kernel", _worst_tier_harvests("kernel"))
_seed_market_ticks("shell", _worst_tier_harvests("shell"))
assert market_store.market_factor_for("shell") == economy.MARKET_SATURATION_TIERS[-1][1]
def test_harvest_records_a_market_tick_for_its_own_crop(local_db):
user = _reset("unit_a")
store.plant(user, 0, "shell")
@@ -952,11 +996,11 @@ def test_end_era_awards_stars_to_top_participant(local_db):
user = _reset("unit_era_top")
store.start_era("TestEra", 7)
_set(user, era_coins=50000, era_harvests=10)
before_stars = int(store.get_farm(user["uid"])["stars"])
before_stars = int(store.get_farm(user["uid"])["stars"] or 0)
store.end_era()
farm = store.get_farm(user["uid"])
assert int(farm["stars"]) > before_stars
assert int(farm["era_coins"]) == 0
assert int(farm["stars"] or 0) > before_stars
assert int(farm["era_coins"] or 0) == 0
def test_era_gates_new_crops_and_leaderboard(local_db):