#!/usr/bin/env python3 """Tests for the house buying/selling sub-parser.""" import sys import os import unittest sys.path.insert(0, os.path.dirname(__file__)) from house_parser import HouseParser def feed_lines(hp, lines): """Feed lines, return the first non-None result.""" for sender, msg in lines: result = hp.feed(sender, msg) if result is not None: return result return None class TestBuyBasic(unittest.TestCase): """Basic house buying flow.""" def test_buy_3_properties(self): """Buy 1+1+2 houses on Red monopoly.""" hp = HouseParser() result = feed_lines(hp, [ ("monop", "Kentucky ave. (R) (0) Indiana ave. (R) (0) Illinois ave. (R) (0) "), ("monop", "Houses will cost $150"), ("monop", "How many houses do you wish to buy for"), ("monop", "Kentucky ave. (R) (0): "), ("merp", ".1"), ("monop", "Indiana ave. (R) (0): "), ("merp", ".1"), ("monop", "Illinois ave. (R) (0): "), ("merp", ".2"), ("monop", "You asked for 4 houses for $600"), ("monop", "Is that ok? "), ("merp", ".y"), ]) self.assertIsNotNone(result) self.assertEqual(result["action"], "buy") self.assertEqual(result["changes"], {21: 1, 23: 1, 24: 2}) self.assertEqual(result["cost"], 600) def test_buy_2_property_monopoly(self): """Buy houses on a 2-property monopoly (Purple).""" hp = HouseParser() result = feed_lines(hp, [ ("monop", "Mediterranean ave. (P) (0) Baltic ave. (P) (0) "), ("monop", "Houses will cost $50"), ("monop", "How many houses do you wish to buy for"), ("monop", "Mediterranean ave. (P) (0): "), ("fbs", ".2"), ("monop", "Baltic ave. (P) (0): "), ("fbs", ".2"), ("monop", "You asked for 4 houses for $200"), ("monop", "Is that ok? "), ("fbs", ".y"), ]) self.assertEqual(result["changes"], {1: 2, 3: 2}) self.assertEqual(result["cost"], 200) def test_buy_with_existing_houses(self): """Buy more houses on properties that already have some.""" hp = HouseParser() result = feed_lines(hp, [ ("monop", "Houses will cost $100"), ("monop", "How many houses do you wish to buy for"), ("monop", "St. James pl. (O) (2): "), ("merp", ".1"), ("monop", "Tennessee ave. (O) (1): "), ("merp", ".2"), ("monop", "New York ave. (O) (1): "), ("merp", ".2"), ("monop", "You asked for 5 houses for $500"), ("monop", "Is that ok? "), ("merp", ".y"), ]) self.assertEqual(result["changes"], {16: 3, 18: 3, 19: 3}) def test_buy_rejected(self): """Player says no to 'Is that ok?'.""" hp = HouseParser() result = feed_lines(hp, [ ("monop", "Houses will cost $150"), ("monop", "How many houses do you wish to buy for"), ("monop", "Kentucky ave. (R) (0): "), ("merp", ".1"), ("monop", "Indiana ave. (R) (0): "), ("merp", ".1"), ("monop", "Illinois ave. (R) (0): "), ("merp", ".1"), ("monop", "You asked for 3 houses for $450"), ("monop", "Is that ok? "), ("merp", ".n"), ]) self.assertEqual(result, {"action": "cancel"}) self.assertFalse(hp.active) def test_buy_to_hotel(self): """Buy up to hotel (5 houses).""" hp = HouseParser() result = feed_lines(hp, [ ("monop", "Houses will cost $200"), ("monop", "How many houses do you wish to buy for"), ("monop", "Park place (D) (4): "), ("fbs", ".1"), ("monop", "Boardwalk (D) (4): "), ("fbs", ".1"), ("monop", "You asked for 2 houses for $400"), ("monop", "Is that ok? "), ("fbs", ".y"), ]) self.assertEqual(result["changes"], {37: 5, 39: 5}) def test_buy_skip_hotel(self): """Property already has hotel — prompt shows (H), auto-skipped.""" hp = HouseParser() result = feed_lines(hp, [ ("monop", "Houses will cost $50"), ("monop", "How many houses do you wish to buy for"), ("monop", "Oriental ave. (L) (3): "), ("fbs", ".2"), ("monop", "Vermont ave. (L) (H):"), # auto-skipped ("monop", "Connecticut ave. (L) (4): "), ("fbs", ".1"), ("monop", "You asked for 3 houses for $150"), ("monop", "Is that ok? "), ("fbs", ".y"), ]) self.assertEqual(result["changes"], {6: 5, 9: 5}) # Vermont (8) not in changes — already at hotel, no change self.assertNotIn(8, result["changes"]) class TestSellBasic(unittest.TestCase): """Basic house selling flow.""" def test_sell_houses(self): """Sell houses from Red monopoly.""" hp = HouseParser() result = feed_lines(hp, [ ("monop", "Houses will get you $75 apiece"), ("monop", "Kentucky ave. (R) (1) Indiana ave. (R) (1) Illinois ave. (R) (2) "), ("monop", "How many houses do you wish to sell from"), ("monop", "Kentucky ave. (R) (1): "), ("merp", ".1"), ("monop", "Indiana ave. (R) (1): "), ("merp", ".1"), ("monop", "Illinois ave. (R) (2): "), ("merp", ".2"), ("monop", "You asked to sell 4 houses for $300"), ("monop", "Is that ok? "), ("merp", ".y"), ]) self.assertEqual(result["action"], "sell") self.assertEqual(result["changes"], {21: 0, 23: 0, 24: 0}) self.assertEqual(result["cost"], 300) def test_sell_from_hotel(self): """Sell 1 house from a hotel.""" hp = HouseParser() result = feed_lines(hp, [ ("monop", "Houses will get you $25 apiece"), ("monop", "How many houses do you wish to sell from"), ("monop", "Oriental ave. (L) (H): "), ("fbs", ".1"), ("monop", "Vermont ave. (L) (H): "), ("fbs", ".0"), ("monop", "Connecticut ave. (L) (H): "), ("fbs", ".0"), ("monop", "You asked to sell 1 houses for $25"), ("monop", "Is that ok? "), ("fbs", ".y"), ]) self.assertEqual(result["changes"], {6: 4}) # hotel(5) - 1 = 4 def test_sell_skip_zero(self): """Property with 0 houses — auto-skipped during sell.""" hp = HouseParser() result = feed_lines(hp, [ ("monop", "Houses will get you $100 apiece"), ("monop", "How many houses do you wish to sell from"), ("monop", "St. James pl. (O) (0):"), # auto-skipped ("monop", "Tennessee ave. (O) (2): "), ("merp", ".1"), ("monop", "New York ave. (O) (3): "), ("merp", ".2"), ("monop", "You asked to sell 3 houses for $300"), ("monop", "Is that ok? "), ("merp", ".y"), ]) self.assertEqual(result["changes"], {18: 1, 19: 1}) self.assertNotIn(16, result["changes"]) # St. James had 0 class TestErrorHandling(unittest.TestCase): """Error cases and retries.""" def test_spread_too_wide_retry(self): """'Spread too wide' resets all prompts.""" hp = HouseParser() result = feed_lines(hp, [ ("monop", "Houses will cost $150"), ("monop", "How many houses do you wish to buy for"), ("monop", "Kentucky ave. (R) (0): "), ("merp", ".3"), ("monop", "Indiana ave. (R) (0): "), ("merp", ".0"), ("monop", "Illinois ave. (R) (0): "), ("merp", ".0"), ("monop", "That makes the spread too wide. Try again"), # Retry ("monop", "Kentucky ave. (R) (0): "), ("merp", ".1"), ("monop", "Indiana ave. (R) (0): "), ("merp", ".1"), ("monop", "Illinois ave. (R) (0): "), ("merp", ".1"), ("monop", "You asked for 3 houses for $450"), ("monop", "Is that ok? "), ("merp", ".y"), ]) self.assertIsNotNone(result) self.assertEqual(result["changes"], {21: 1, 23: 1, 24: 1}) def test_too_many_retry(self): """'Too many' re-prompts same property.""" hp = HouseParser() result = feed_lines(hp, [ ("monop", "Houses will cost $150"), ("monop", "How many houses do you wish to buy for"), ("monop", "Kentucky ave. (R) (3): "), ("merp", ".5"), ("monop", "That's too many. The most you can buy is 2"), ("monop", "Kentucky ave. (R) (3): "), ("merp", ".2"), ("monop", "Indiana ave. (R) (3): "), ("merp", ".2"), ("monop", "Illinois ave. (R) (3): "), ("merp", ".2"), ("monop", "You asked for 6 houses for $900"), ("monop", "Is that ok? "), ("merp", ".y"), ]) self.assertEqual(result["changes"], {21: 5, 23: 5, 24: 5}) def test_resets_after_completion(self): """Parser resets to idle after completion.""" hp = HouseParser() feed_lines(hp, [ ("monop", "Houses will cost $50"), ("monop", "How many houses do you wish to buy for"), ("monop", "Mediterranean ave. (P) (0): "), ("fbs", ".1"), ("monop", "Baltic ave. (P) (0): "), ("fbs", ".1"), ("monop", "You asked for 2 houses for $100"), ("monop", "Is that ok? "), ("fbs", ".y"), ]) self.assertFalse(hp.active) self.assertEqual(hp.state, "idle") class TestRealLog(unittest.TestCase): """Replay real log segments through the sub-parser.""" def _replay_segment(self, start, end): """Replay lines start..end from test log through HouseParser.""" hp = HouseParser() results = [] with open("test_data/monop.log") as f: for i, line in enumerate(f, 1): if i < start: continue if i > end: break parts = line.rstrip('\n').split('\t', 2) if len(parts) < 3: continue sender = parts[1].strip().lstrip('@') msg = parts[2] r = hp.feed(sender, msg) if r is not None: results.append(r) return results, hp def test_real_buy_line_59(self): """Real log: merp buys 1+1+2 houses on Red (lines 59-73).""" results, hp = self._replay_segment(59, 73) self.assertEqual(len(results), 1) r = results[0] self.assertEqual(r["action"], "buy") # Kentucky=21, Indiana=23, Illinois=24 self.assertEqual(r["changes"], {21: 1, 23: 1, 24: 2}) self.assertEqual(r["cost"], 600) def test_real_sell_line_354(self): """Real log: merp sells 1+1+2 houses from Red (lines 354-366).""" results, hp = self._replay_segment(354, 366) self.assertEqual(len(results), 1) r = results[0] self.assertEqual(r["action"], "sell") self.assertEqual(r["changes"], {21: 0, 23: 0, 24: 0}) def test_real_sell_hotel_line_4386(self): """Real log: Derecho sells 1 hotel from Oriental (lines 4386-4399).""" results, hp = self._replay_segment(4386, 4399) self.assertEqual(len(results), 1) r = results[0] self.assertEqual(r["action"], "sell") # Oriental (6): H(5) - 1 = 4 self.assertEqual(r["changes"][6], 4) class TestActiveState(unittest.TestCase): """Test the active property.""" def test_not_active_initially(self): hp = HouseParser() self.assertFalse(hp.active) def test_active_during_prompts(self): hp = HouseParser() hp.feed("monop", "Houses will cost $150") hp.feed("monop", "How many houses do you wish to buy for") self.assertTrue(hp.active) def test_active_during_confirm(self): hp = HouseParser() feed_lines(hp, [ ("monop", "Houses will cost $50"), ("monop", "How many houses do you wish to buy for"), ("monop", "Mediterranean ave. (P) (0): "), ("fbs", ".1"), ("monop", "Baltic ave. (P) (0): "), ("fbs", ".1"), ("monop", "You asked for 2 houses for $100"), ]) self.assertTrue(hp.active) if __name__ == "__main__": unittest.main(verbosity=2)