test_alarm_system.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. #!/usr/bin/env python3
  2. """
  3. Test suite for the Price Alarm System
  4. Tests alarm creation, management, triggering, and integration.
  5. """
  6. import unittest
  7. import tempfile
  8. import os
  9. from datetime import datetime
  10. from alarm_manager import AlarmManager
  11. class TestAlarmManager(unittest.TestCase):
  12. """Test the AlarmManager class."""
  13. def setUp(self):
  14. """Set up test fixtures."""
  15. # Use temporary file for testing
  16. self.temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.json')
  17. self.temp_file.close()
  18. self.alarm_manager = AlarmManager(self.temp_file.name)
  19. def tearDown(self):
  20. """Clean up after tests."""
  21. # Remove temporary file
  22. if os.path.exists(self.temp_file.name):
  23. os.unlink(self.temp_file.name)
  24. def test_create_alarm_above(self):
  25. """Test creating an alarm for price going above current price."""
  26. # Current price $50, target $55 (above)
  27. alarm = self.alarm_manager.create_alarm("BTC", 55.0, 50.0)
  28. self.assertEqual(alarm['id'], 1)
  29. self.assertEqual(alarm['token'], 'BTC')
  30. self.assertEqual(alarm['target_price'], 55.0)
  31. self.assertEqual(alarm['current_price_when_set'], 50.0)
  32. self.assertEqual(alarm['direction'], 'above')
  33. self.assertEqual(alarm['status'], 'active')
  34. def test_create_alarm_below(self):
  35. """Test creating an alarm for price going below current price."""
  36. # Current price $50, target $45 (below)
  37. alarm = self.alarm_manager.create_alarm("ETH", 45.0, 50.0)
  38. self.assertEqual(alarm['id'], 1)
  39. self.assertEqual(alarm['token'], 'ETH')
  40. self.assertEqual(alarm['target_price'], 45.0)
  41. self.assertEqual(alarm['current_price_when_set'], 50.0)
  42. self.assertEqual(alarm['direction'], 'below')
  43. self.assertEqual(alarm['status'], 'active')
  44. def test_multiple_alarms_increment_id(self):
  45. """Test that multiple alarms get incrementing IDs."""
  46. alarm1 = self.alarm_manager.create_alarm("BTC", 55.0, 50.0)
  47. alarm2 = self.alarm_manager.create_alarm("ETH", 3500.0, 3000.0)
  48. alarm3 = self.alarm_manager.create_alarm("BTC", 45.0, 50.0)
  49. self.assertEqual(alarm1['id'], 1)
  50. self.assertEqual(alarm2['id'], 2)
  51. self.assertEqual(alarm3['id'], 3)
  52. def test_get_alarms_by_token(self):
  53. """Test filtering alarms by token."""
  54. self.alarm_manager.create_alarm("BTC", 55.0, 50.0)
  55. self.alarm_manager.create_alarm("ETH", 3500.0, 3000.0)
  56. self.alarm_manager.create_alarm("BTC", 45.0, 50.0)
  57. btc_alarms = self.alarm_manager.get_alarms_by_token("BTC")
  58. eth_alarms = self.alarm_manager.get_alarms_by_token("ETH")
  59. self.assertEqual(len(btc_alarms), 2)
  60. self.assertEqual(len(eth_alarms), 1)
  61. self.assertEqual(btc_alarms[0]['token'], 'BTC')
  62. self.assertEqual(eth_alarms[0]['token'], 'ETH')
  63. def test_remove_alarm(self):
  64. """Test removing an alarm by ID."""
  65. alarm = self.alarm_manager.create_alarm("BTC", 55.0, 50.0)
  66. alarm_id = alarm['id']
  67. # Verify alarm exists
  68. self.assertIsNotNone(self.alarm_manager.get_alarm_by_id(alarm_id))
  69. # Remove alarm
  70. result = self.alarm_manager.remove_alarm(alarm_id)
  71. self.assertTrue(result)
  72. # Verify alarm is gone
  73. self.assertIsNone(self.alarm_manager.get_alarm_by_id(alarm_id))
  74. def test_remove_nonexistent_alarm(self):
  75. """Test removing an alarm that doesn't exist."""
  76. result = self.alarm_manager.remove_alarm(999)
  77. self.assertFalse(result)
  78. def test_check_alarms_trigger_above(self):
  79. """Test triggering an alarm when price goes above target."""
  80. # Create alarm: trigger when BTC goes above $55
  81. self.alarm_manager.create_alarm("BTC", 55.0, 50.0)
  82. # Price data: BTC is now $56 (above $55)
  83. price_data = {"BTC": 56.0}
  84. triggered = self.alarm_manager.check_alarms(price_data)
  85. self.assertEqual(len(triggered), 1)
  86. self.assertEqual(triggered[0]['token'], 'BTC')
  87. self.assertEqual(triggered[0]['triggered_price'], 56.0)
  88. self.assertEqual(triggered[0]['status'], 'triggered')
  89. def test_check_alarms_trigger_below(self):
  90. """Test triggering an alarm when price goes below target."""
  91. # Create alarm: trigger when ETH goes below $45
  92. self.alarm_manager.create_alarm("ETH", 45.0, 50.0)
  93. # Price data: ETH is now $44 (below $45)
  94. price_data = {"ETH": 44.0}
  95. triggered = self.alarm_manager.check_alarms(price_data)
  96. self.assertEqual(len(triggered), 1)
  97. self.assertEqual(triggered[0]['token'], 'ETH')
  98. self.assertEqual(triggered[0]['triggered_price'], 44.0)
  99. self.assertEqual(triggered[0]['status'], 'triggered')
  100. def test_check_alarms_no_trigger(self):
  101. """Test that alarms don't trigger when conditions aren't met."""
  102. # Create alarm: trigger when BTC goes above $55
  103. self.alarm_manager.create_alarm("BTC", 55.0, 50.0)
  104. # Price data: BTC is now $54 (still below $55)
  105. price_data = {"BTC": 54.0}
  106. triggered = self.alarm_manager.check_alarms(price_data)
  107. self.assertEqual(len(triggered), 0)
  108. # Verify alarm is still active
  109. active_alarms = self.alarm_manager.get_all_active_alarms()
  110. self.assertEqual(len(active_alarms), 1)
  111. self.assertEqual(active_alarms[0]['status'], 'active')
  112. def test_check_alarms_exact_price(self):
  113. """Test that alarms trigger at exact target price."""
  114. # Create alarm: trigger when BTC goes above $55
  115. self.alarm_manager.create_alarm("BTC", 55.0, 50.0)
  116. # Price data: BTC is exactly $55
  117. price_data = {"BTC": 55.0}
  118. triggered = self.alarm_manager.check_alarms(price_data)
  119. self.assertEqual(len(triggered), 1)
  120. self.assertEqual(triggered[0]['triggered_price'], 55.0)
  121. def test_alarm_only_triggers_once(self):
  122. """Test that triggered alarms don't trigger again."""
  123. # Create alarm
  124. self.alarm_manager.create_alarm("BTC", 55.0, 50.0)
  125. # Trigger alarm
  126. price_data = {"BTC": 56.0}
  127. triggered1 = self.alarm_manager.check_alarms(price_data)
  128. self.assertEqual(len(triggered1), 1)
  129. # Try to trigger again with even higher price
  130. price_data = {"BTC": 60.0}
  131. triggered2 = self.alarm_manager.check_alarms(price_data)
  132. self.assertEqual(len(triggered2), 0) # Should not trigger again
  133. def test_multiple_alarms_selective_trigger(self):
  134. """Test that only applicable alarms trigger."""
  135. # Create multiple alarms
  136. self.alarm_manager.create_alarm("BTC", 55.0, 50.0) # Above $55
  137. self.alarm_manager.create_alarm("ETH", 45.0, 50.0) # Below $45
  138. self.alarm_manager.create_alarm("BTC", 40.0, 50.0) # Below $40
  139. # Price data: BTC $56 (triggers first), ETH $46 (doesn't trigger)
  140. price_data = {"BTC": 56.0, "ETH": 46.0}
  141. triggered = self.alarm_manager.check_alarms(price_data)
  142. self.assertEqual(len(triggered), 1)
  143. self.assertEqual(triggered[0]['token'], 'BTC')
  144. self.assertEqual(triggered[0]['target_price'], 55.0)
  145. def test_persistence(self):
  146. """Test that alarms persist across manager instances."""
  147. # Create alarm
  148. alarm = self.alarm_manager.create_alarm("BTC", 55.0, 50.0)
  149. alarm_id = alarm['id']
  150. # Create new manager instance with same file
  151. new_manager = AlarmManager(self.temp_file.name)
  152. # Check that alarm persists
  153. restored_alarm = new_manager.get_alarm_by_id(alarm_id)
  154. self.assertIsNotNone(restored_alarm)
  155. self.assertEqual(restored_alarm['token'], 'BTC')
  156. self.assertEqual(restored_alarm['target_price'], 55.0)
  157. self.assertEqual(restored_alarm['status'], 'active')
  158. def test_format_alarm_list_empty(self):
  159. """Test formatting empty alarm list."""
  160. message = self.alarm_manager.format_alarm_list([])
  161. self.assertIn("No alarms found", message)
  162. def test_format_alarm_list_with_alarms(self):
  163. """Test formatting alarm list with alarms."""
  164. self.alarm_manager.create_alarm("BTC", 55.0, 50.0)
  165. self.alarm_manager.create_alarm("ETH", 45.0, 50.0)
  166. alarms = self.alarm_manager.get_all_active_alarms()
  167. message = self.alarm_manager.format_alarm_list(alarms)
  168. self.assertIn("BTC", message)
  169. self.assertIn("ETH", message)
  170. self.assertIn("ID 1", message)
  171. self.assertIn("ID 2", message)
  172. self.assertIn("55.00", message)
  173. self.assertIn("45.00", message)
  174. def test_format_triggered_alarm(self):
  175. """Test formatting triggered alarm notification."""
  176. # Create and trigger alarm
  177. self.alarm_manager.create_alarm("BTC", 55.0, 50.0)
  178. price_data = {"BTC": 56.0}
  179. triggered = self.alarm_manager.check_alarms(price_data)
  180. message = self.alarm_manager.format_triggered_alarm(triggered[0])
  181. self.assertIn("Price Alert Triggered", message)
  182. self.assertIn("BTC", message)
  183. self.assertIn("56.00", message)
  184. self.assertIn("55.00", message)
  185. self.assertIn("50.00", message)
  186. def test_statistics(self):
  187. """Test alarm statistics."""
  188. # Create some alarms
  189. self.alarm_manager.create_alarm("BTC", 55.0, 50.0)
  190. self.alarm_manager.create_alarm("BTC", 45.0, 50.0)
  191. self.alarm_manager.create_alarm("ETH", 3500.0, 3000.0)
  192. # Trigger one alarm
  193. price_data = {"BTC": 56.0}
  194. self.alarm_manager.check_alarms(price_data)
  195. stats = self.alarm_manager.get_statistics()
  196. self.assertEqual(stats['total_active'], 2) # 2 still active
  197. self.assertEqual(stats['total_triggered'], 1) # 1 triggered
  198. self.assertEqual(stats['tokens_tracked'], 2) # BTC and ETH
  199. self.assertEqual(stats['token_breakdown']['BTC'], 1) # 1 BTC alarm left
  200. self.assertEqual(stats['token_breakdown']['ETH'], 1) # 1 ETH alarm
  201. if __name__ == '__main__':
  202. unittest.main()