#!/usr/bin/env python3 """ Tests for the conditional ability parsing in ability_processor.py Run with: python -m pytest tests/test_ability_processor_conditionals.py -v Or directly: python tests/test_ability_processor_conditionals.py """ import sys import os import unittest # Add tools directory to path sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'tools')) from ability_processor import ( parse_condition, parse_conditional_ability, parse_chained_effect, parse_scaling_effect, parse_effects, ) class TestParseCondition(unittest.TestCase): """Tests for parse_condition function""" def test_control_card_basic(self): """Should parse 'control [Card Name]' condition""" condition = parse_condition("you control Cloud") self.assertIsNotNone(condition) self.assertEqual(condition["type"], "CONTROL_CARD") self.assertEqual(condition["card_name"], "Cloud") def test_control_card_with_prefix(self): """Should parse 'control a Card Name X' condition""" condition = parse_condition("you control a Card Name Tidus") self.assertIsNotNone(condition) self.assertEqual(condition["type"], "CONTROL_CARD") def test_control_count(self): """Should parse 'control X or more Forwards' condition""" condition = parse_condition("you control 3 or more Forwards") self.assertIsNotNone(condition) self.assertEqual(condition["type"], "CONTROL_COUNT") self.assertEqual(condition["comparison"], "GTE") self.assertEqual(condition["value"], 3) self.assertEqual(condition["card_type"], "FORWARD") def test_control_count_backups(self): """Should parse 'control X or more Backups' condition""" condition = parse_condition("you control 5 or more Backups") self.assertIsNotNone(condition) self.assertEqual(condition["type"], "CONTROL_COUNT") self.assertEqual(condition["value"], 5) self.assertEqual(condition["card_type"], "BACKUP") def test_damage_received(self): """Should parse 'have received X or more damage' condition""" condition = parse_condition("you have received 5 or more damage") self.assertIsNotNone(condition) self.assertEqual(condition["type"], "DAMAGE_RECEIVED") self.assertEqual(condition["comparison"], "GTE") self.assertEqual(condition["value"], 5) def test_damage_received_points_of(self): """Should parse 'X points of damage or more' condition""" condition = parse_condition("you have received 6 points of damage or more") self.assertIsNotNone(condition) self.assertEqual(condition["type"], "DAMAGE_RECEIVED") self.assertEqual(condition["value"], 6) def test_break_zone_count(self): """Should parse break zone count condition""" condition = parse_condition("you have 5 or more Ifrit in your break zone") self.assertIsNotNone(condition) self.assertEqual(condition["type"], "BREAK_ZONE_COUNT") self.assertEqual(condition["comparison"], "GTE") self.assertEqual(condition["value"], 5) def test_forward_state_dull(self): """Should parse 'this forward is dull' condition""" condition = parse_condition("this forward is dull") self.assertIsNotNone(condition) self.assertEqual(condition["type"], "FORWARD_STATE") self.assertEqual(condition["state"], "DULL") self.assertTrue(condition["check_self"]) def test_forward_state_active(self): """Should parse 'this forward is active' condition""" condition = parse_condition("this forward is active") self.assertIsNotNone(condition) self.assertEqual(condition["type"], "FORWARD_STATE") self.assertEqual(condition["state"], "ACTIVE") def test_cost_comparison_less(self): """Should parse 'of cost X or less' condition""" condition = parse_condition("of cost 3 or less") self.assertIsNotNone(condition) self.assertEqual(condition["type"], "COST_COMPARISON") self.assertEqual(condition["comparison"], "LTE") self.assertEqual(condition["value"], 3) def test_cost_comparison_more(self): """Should parse 'of cost X or more' condition""" condition = parse_condition("of cost 4 or more") self.assertIsNotNone(condition) self.assertEqual(condition["type"], "COST_COMPARISON") self.assertEqual(condition["comparison"], "GTE") self.assertEqual(condition["value"], 4) def test_opponent_no_forwards(self): """Should parse 'opponent doesn't control any forwards' condition""" condition = parse_condition("your opponent doesn't control any Forwards") self.assertIsNotNone(condition) self.assertEqual(condition["type"], "CONTROL_COUNT") self.assertEqual(condition["comparison"], "EQ") self.assertEqual(condition["value"], 0) self.assertEqual(condition["owner"], "OPPONENT") def test_unknown_condition(self): """Should return None for unparseable conditions""" condition = parse_condition("something random and complex") self.assertIsNone(condition) class TestParseConditionalAbility(unittest.TestCase): """Tests for parse_conditional_ability function""" def test_if_control_deal_damage(self): """Should parse 'If you control X, deal Y damage' pattern""" text = "If you control Cloud, deal 5000 damage to target Forward." result = parse_conditional_ability(text) self.assertIsNotNone(result) self.assertEqual(result["type"], "CONDITIONAL") self.assertIn("condition", result) self.assertIn("then_effects", result) self.assertTrue(len(result["then_effects"]) > 0) def test_if_damage_received_gain_brave(self): """Should parse damage received condition with ability grant""" text = "If you have received 5 or more damage, this Forward gains Brave." result = parse_conditional_ability(text) self.assertIsNotNone(result) self.assertEqual(result["type"], "CONDITIONAL") # Even if condition is UNKNOWN, the structure should be correct self.assertIn("condition", result) self.assertIn("then_effects", result) def test_not_if_you_do(self): """Should NOT parse 'If you do so' as conditional (that's chained)""" text = "If you do so, draw 2 cards." result = parse_conditional_ability(text) self.assertIsNone(result) def test_non_conditional_text(self): """Should return None for non-conditional text""" text = "Deal 5000 damage to target Forward." result = parse_conditional_ability(text) self.assertIsNone(result) class TestParseChainedEffect(unittest.TestCase): """Tests for parse_chained_effect function""" def test_discard_if_you_do_damage(self): """Should parse 'Discard 1 card. If you do so, deal damage' pattern""" text = "Discard 1 card from your hand. If you do so, deal 7000 damage to target Forward." result = parse_chained_effect(text) self.assertIsNotNone(result) self.assertEqual(result["type"], "CHAINED_EFFECT") self.assertEqual(result["chain_condition"], "IF_YOU_DO") self.assertIn("primary_effect", result) self.assertIn("chain_effects", result) self.assertEqual(result["primary_effect"]["type"], "DISCARD") def test_when_you_do(self): """Should parse 'When you do so' variant""" text = "Dull 1 Backup. When you do so, draw 1 card." result = parse_chained_effect(text) self.assertIsNotNone(result) self.assertEqual(result["type"], "CHAINED_EFFECT") def test_no_chain_pattern(self): """Should return None for text without chain pattern""" text = "Deal 5000 damage. Draw 1 card." result = parse_chained_effect(text) self.assertIsNone(result) class TestParseScalingEffect(unittest.TestCase): """Tests for parse_scaling_effect function""" def test_damage_for_each_damage_received(self): """Should parse 'Deal X damage for each damage received' pattern""" text = "Deal it 1000 damage for each point of damage you have received." result = parse_scaling_effect(text) self.assertIsNotNone(result) self.assertEqual(result["type"], "SCALING_EFFECT") self.assertEqual(result["scale_by"], "DAMAGE_RECEIVED") self.assertEqual(result["multiplier"], 1000) self.assertEqual(result["base_effect"]["type"], "DAMAGE") def test_power_for_each_forward(self): """Should parse '+X power for each Forward you control' pattern""" text = "This Forward gains +1000 power for each Forward you control until the end of the turn." result = parse_scaling_effect(text) self.assertIsNotNone(result) self.assertEqual(result["type"], "SCALING_EFFECT") self.assertEqual(result["scale_by"], "FORWARDS_CONTROLLED") self.assertEqual(result["multiplier"], 1000) self.assertEqual(result["base_effect"]["type"], "POWER_MOD") def test_draw_for_each_forward(self): """Should parse 'Draw X cards for each Forward' pattern""" text = "Draw 1 card for each Forward your opponent controls." result = parse_scaling_effect(text) self.assertIsNotNone(result) self.assertEqual(result["type"], "SCALING_EFFECT") self.assertEqual(result["multiplier"], 1) self.assertEqual(result["base_effect"]["type"], "DRAW") def test_no_scaling_pattern(self): """Should return None for text without scaling pattern""" text = "Deal 5000 damage to target Forward." result = parse_scaling_effect(text) self.assertIsNone(result) class TestParseEffectsIntegration(unittest.TestCase): """Integration tests for parse_effects with conditionals""" def test_chained_effect_detected(self): """parse_effects should return CHAINED_EFFECT for 'if you do' text""" text = "Discard 1 card from your hand. If you do, draw 2 cards." effects = parse_effects(text) self.assertEqual(len(effects), 1) self.assertEqual(effects[0]["type"], "CHAINED_EFFECT") def test_scaling_effect_detected(self): """parse_effects should return SCALING_EFFECT for 'for each' text""" text = "Deal it 1000 damage for each point of damage you have received." effects = parse_effects(text) self.assertEqual(len(effects), 1) self.assertEqual(effects[0]["type"], "SCALING_EFFECT") def test_conditional_effect_detected(self): """parse_effects should return CONDITIONAL for 'If X, Y' text""" text = "If you control Cloud, deal 5000 damage to target Forward." effects = parse_effects(text) self.assertEqual(len(effects), 1) self.assertEqual(effects[0]["type"], "CONDITIONAL") def test_normal_effect_not_conditional(self): """parse_effects should NOT return CONDITIONAL for normal effects""" text = "Draw 2 cards." effects = parse_effects(text) self.assertEqual(len(effects), 1) self.assertEqual(effects[0]["type"], "DRAW") class TestRealCardExamples(unittest.TestCase): """Tests using real card text examples""" def test_warrior_damage_scaling(self): """Test Warrior card with damage scaling""" text = "Deal it 1000 damage for each point of damage you have received." result = parse_scaling_effect(text) self.assertIsNotNone(result) self.assertEqual(result["type"], "SCALING_EFFECT") self.assertEqual(result["scale_by"], "DAMAGE_RECEIVED") self.assertEqual(result["multiplier"], 1000) def test_zidane_chained_effect(self): """Test Zidane-style chained effect""" text = "Discard 1 card from your hand. If you do so, choose 1 Forward. Deal it 7000 damage." result = parse_chained_effect(text) self.assertIsNotNone(result) self.assertEqual(result["type"], "CHAINED_EFFECT") self.assertEqual(result["primary_effect"]["type"], "DISCARD") self.assertTrue(len(result["chain_effects"]) > 0) if __name__ == "__main__": unittest.main(verbosity=2)