import atexit import os import pathlib import tempfile import time import unittest # Point the engine at a fresh empty save to keep these tests hermetic. import engine.collection as collection _fd, _tmp_path = tempfile.mkstemp(suffix=".json") os.close(_fd) _TMP = pathlib.Path(_tmp_path) _ORIG_DATA_FILE = collection._DATA_FILE collection._DATA_FILE = _TMP @atexit.register def _restore_collection(): collection._DATA_FILE = _ORIG_DATA_FILE if _TMP.exists(): _TMP.unlink() class SoilTest(unittest.TestCase): def setUp(self): data = collection.load() data["owned"] = ["Rose", "Lavender", "Zinnia"] data["soil"] = {"seed_cd_until": 0.0, "seeds": [], "plots": []} data["water"] = 10 data["fertilizer"] = 10 collection.save(data) def test_retrieve_seed(self): self.assertEqual(collection.retrieve_seed("Rose"), "") state = collection.soil_state() self.assertEqual(len(state["seeds"]), 1) self.assertEqual(state["seeds"][0]["plant"], "Rose") self.assertGreater(state["cd_left"], 0) # cooldown set def test_retrieve_respects_cooldown(self): collection.retrieve_seed("Rose") self.assertNotEqual(collection.retrieve_seed("Lavender"), "") state = collection.soil_state() self.assertEqual(len(state["seeds"]), 1) def test_retrieve_requires_owned(self): self.assertNotEqual(collection.retrieve_seed("NotAPlant"), "") data = collection.load() data["discovered"] = ["Marigold"] collection.save(data) self.assertNotEqual(collection.retrieve_seed("Marigold"), "") def test_plant_creates_stage_zero_plot(self): collection.retrieve_seed("Rose") self.assertEqual(collection.plant_seed(0), "") state = collection.soil_state() self.assertEqual(len(state["plots"]), 1) self.assertEqual(state["seeds"], []) plot = state["plots"][0] self.assertEqual(plot["stage"], 0) self.assertFalse(plot["grown"]) self.assertFalse(plot["stalled"]) # stage not yet full def test_stage_fills_over_time(self): data = collection.load() data["soil"]["plots"] = [{"plant": "Rose", "stage_started_at": 0, "stage": 0, "water": 0}] collection.save(data) state = collection.soil_state() plot = state["plots"][0] self.assertEqual(plot["stage_progress"], 1.0) self.assertTrue(plot["stalled"]) # stage full -> needs water self.assertFalse(plot["grown"]) # but not the last stage def test_water_requires_stalled(self): collection.retrieve_seed("Rose") collection.plant_seed(0) self.assertNotEqual(collection.water_plot(0), "") # not stalled yet def test_water_advances_stage(self): data = collection.load() data["soil"]["plots"] = [{"plant": "Rose", "stage_started_at": 0, "stage": 0, "water": 0}] collection.save(data) self.assertEqual(collection.water_plot(0), "") self.assertEqual(collection.available_fertilizer(), 9) state = collection.soil_state() self.assertEqual(state["plots"][0]["stage"], 1) self.assertEqual(state["plots"][0]["water"], 1) self.assertFalse(state["plots"][0]["stalled"]) # new stage just started def test_water_requires_fertilizer_pool(self): data = collection.load() data["fertilizer"] = 0 data["soil"]["plots"] = [{"plant": "Rose", "stage_started_at": 0, "stage": 0, "water": 0}] collection.save(data) self.assertNotEqual(collection.water_plot(0), "") def test_harvest_requires_grown(self): collection.retrieve_seed("Rose") collection.plant_seed(0) self.assertNotEqual(collection.harvest_plot(0), "") # not grown def test_grow_all_stages_then_harvest(self): # Rose is super-rare -> 4 stages. Plant with an old stage_started_at so each # stage is instantly full, then water through stages 0..2. data = collection.load() data["soil"]["plots"] = [{"plant": "Rose", "stage_started_at": 0, "stage": 0, "water": 0}] collection.save(data) total = collection.GROWTH_STAGES["super_rare"] for expected in range(total - 1): self.assertNotEqual(collection.harvest_plot(0), "") # not grown mid-way self.assertEqual(collection.water_plot(0), "", f"water to advance to {expected + 1}") # each watered stage is instantly full again (stage_started_at=now) data = collection.load() data["soil"]["plots"][0]["stage_started_at"] = 0 collection.save(data) state = collection.soil_state() self.assertTrue(state["plots"][0]["grown"]) dup_before = collection.get_progress("Rose")["duplicates"] self.assertEqual(collection.harvest_plot(0), "") state = collection.soil_state() self.assertEqual(state["plots"], []) self.assertEqual(collection.get_progress("Rose")["duplicates"], dup_before + 1) def test_higher_rarity_more_stages(self): self.assertGreater(collection.GROWTH_STAGES["super_super_rare"], collection.GROWTH_STAGES["super_rare"]) self.assertGreater(collection.GROWTH_STAGES["super_rare"], collection.GROWTH_STAGES["rare"]) self.assertGreater(collection.GROWTH_STAGES["rare"], collection.GROWTH_STAGES["common"]) def test_default_one_plot_unlocked(self): state = collection.soil_state() self.assertEqual(state["plots_unlocked"], 1) self.assertEqual(state["plots_max"], collection.SOIL_PLOTS_MAX) def test_soil_gated_by_owned_plants(self): data = collection.load() data["owned"] = ["Rose", "Lavender"] collection.save(data) self.assertFalse(collection.soil_unlocked()) data = collection.load() data["owned"] = [f"Plant{i}" for i in range(collection.SOIL_GATE_PLANTS)] for n in data["owned"]: data["progress"].setdefault(n, {"level": 1, "stage": 1, "xp": 0, "duplicates": 0, "watered_at": 0}) collection.save(data) self.assertTrue(collection.soil_unlocked()) def test_plot_unlock_costs_escalate(self): self.assertEqual(collection.plot_unlock_cost(2), (10, 5)) self.assertEqual(collection.plot_unlock_cost(3), (50, 20)) self.assertEqual(collection.plot_unlock_cost(4), (100, 50)) self.assertEqual(collection.plot_unlock_cost(5), (0, 0)) def test_plant_limited_to_unlocked_plots(self): data = collection.load() data["soil"]["plots"] = [{"plant": "Rose", "stage_started_at": 0, "stage": 0, "water": 0}] collection.save(data) # 1 unlocked, 1 planted -> planting another seed fails collection.retrieve_seed("Lavender") self.assertEqual(len(collection.soil_state()["seeds"]), 1) self.assertNotEqual(collection.plant_seed(0), "") def test_unlock_plot_spends_coins_and_fertilizer(self): data = collection.load() data["coins"] = 10 data["fertilizer"] = 5 collection.save(data) self.assertEqual(collection.unlock_plot(), "") self.assertEqual(collection.soil_state()["plots_unlocked"], 2) self.assertEqual(collection.available_coins(), 0) self.assertEqual(collection.available_fertilizer(), 0) # too little fertilizer now self.assertNotEqual(collection.unlock_plot(), "") def test_unlock_plot_requires_coins(self): data = collection.load() data["coins"] = 0 data["fertilizer"] = 50 collection.save(data) self.assertNotEqual(collection.unlock_plot(), "") def test_unlock_plot_requires_fertilizer(self): data = collection.load() data["coins"] = 50 data["fertilizer"] = 4 collection.save(data) self.assertNotEqual(collection.unlock_plot(), "") def test_unlock_plot_max_four(self): data = collection.load() data["coins"] = 500 data["fertilizer"] = 500 collection.save(data) for _ in range(3): self.assertEqual(collection.unlock_plot(), "") self.assertEqual(collection.soil_state()["plots_unlocked"], collection.SOIL_PLOTS_MAX) self.assertNotEqual(collection.unlock_plot(), "") # capped def test_after_unlock_extra_plot_usable(self): data = collection.load() data["coins"] = 10 data["fertilizer"] = 5 data["soil"]["plots"] = [{"plant": "Rose", "stage_started_at": 0, "stage": 0, "water": 0}] collection.save(data) collection.unlock_plot() # now 2 unlocked, 1 used collection.retrieve_seed("Lavender") self.assertEqual(collection.plant_seed(0), "") self.assertEqual(len(collection.soil_state()["plots"]), 2) class AutoFarmerTest(unittest.TestCase): def setUp(self): data = collection.load() data["owned"] = ["Rose", "Lavender", "Zinnia"] data["soil"] = {"seed_cd_until": 0.0, "seeds": [], "plots": []} data["fertilizer"] = 10 data["coins"] = 100 data["auto_farmer"] = False collection.save(data) def _stalled_plot(self): data = collection.load() data["soil"]["plots"] = [{"plant": "Rose", "stage_started_at": 0, "stage": 0, "water": 0}] collection.save(data) def test_accrue_fertilizer_grants_one_per_hour(self): data = collection.load() data["fertilizer_accrued_at"] = time.time() - 7200 # 2 hours ago data["fertilizer"] = 0 collection.save(data) self.assertEqual(collection.accrue_fertilizer(), 2) self.assertEqual(collection.available_fertilizer(), 2) def test_accrue_fertilizer_caps(self): data = collection.load() data["fertilizer_accrued_at"] = time.time() - 7200 data["fertilizer"] = 99 collection.save(data) self.assertEqual(collection.accrue_fertilizer(), 1) self.assertEqual(collection.available_fertilizer(), 100) def test_add_fertilizer_caps(self): data = collection.load() data["fertilizer"] = 99 collection.save(data) collection.add_fertilizer(10) self.assertEqual(collection.available_fertilizer(), 100) def test_buy_auto_farmer(self): self.assertEqual(collection.buy_auto_farmer(), "") self.assertTrue(collection.auto_farmer_owned()) self.assertEqual(collection.available_coins(), 0) self.assertNotEqual(collection.buy_auto_farmer(), "") # already owned def test_buy_auto_farmer_requires_coins(self): data = collection.load() data["coins"] = 50 collection.save(data) self.assertNotEqual(collection.buy_auto_farmer(), "") self.assertFalse(collection.auto_farmer_owned()) def test_buy_activates_farmer(self): self.assertEqual(collection.buy_auto_farmer(), "") self.assertTrue(collection.auto_farmer_active()) def test_toggle_auto_farmer(self): collection.buy_auto_farmer() self.assertTrue(collection.auto_farmer_active()) self.assertFalse(collection.toggle_auto_farmer()) # turned off self.assertFalse(collection.auto_farmer_active()) self.assertTrue(collection.toggle_auto_farmer()) # back on self.assertTrue(collection.auto_farmer_owned()) # still owns it def test_auto_farm_respects_toggle(self): self._stalled_plot() collection.buy_auto_farmer() collection.toggle_auto_farmer() # turn it off self.assertEqual(collection.auto_farm(), "Auto farmer off") self.assertEqual(collection.available_fertilizer(), 10) # nothing spent self.assertEqual(collection.soil_state()["plots"][0]["stage"], 0) def test_auto_farm_fertilizes_stalled_plot(self): self._stalled_plot() self.assertEqual(collection.buy_auto_farmer(), "") self.assertEqual(collection.auto_farm(), "") self.assertEqual(collection.available_fertilizer(), 9) self.assertEqual(collection.soil_state()["plots"][0]["stage"], 1) def test_auto_farm_requires_farmer(self): self._stalled_plot() self.assertEqual(collection.auto_farm(), "No auto farmer") def test_auto_farm_does_not_plant_or_harvest(self): data = collection.load() data["soil"]["plots"] = [ {"plant": "Rose", "stage_started_at": 0, "stage": 0, "water": 0}, # stalled {"plant": "Rose", "stage_started_at": 0, "stage": 3, "water": 0}, # grown (epic) ] data["soil"]["seeds"] = [{"plant": "Lavender"}] collection.save(data) self.assertEqual(collection.buy_auto_farmer(), "") self.assertEqual(collection.auto_farm(), "") state = collection.soil_state() self.assertEqual(len(state["plots"]), 2) # grown plot not harvested self.assertEqual(len(state["seeds"]), 1) # seed not planted self.assertEqual(state["plots"][0]["stage"], 1) # stalled plot advanced if __name__ == "__main__": unittest.main()