feature updates
This commit is contained in:
332
tests/test_ability_processor_conditionals.py
Normal file
332
tests/test_ability_processor_conditionals.py
Normal file
@@ -0,0 +1,332 @@
|
||||
#!/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)
|
||||
Reference in New Issue
Block a user