test_events.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. from urllib.error import URLError
  2. from mock import patch, MagicMock
  3. import json
  4. import pytest
  5. from backend.ecs_tasks.delete_files.events import (
  6. sanitize_message,
  7. emit_deletion_event,
  8. emit_failure_event,
  9. emit_skipped_event,
  10. get_emitter_id,
  11. )
  12. pytestmark = [pytest.mark.unit, pytest.mark.ecs_tasks]
  13. @patch("backend.ecs_tasks.delete_files.events.emit_event")
  14. @patch("backend.ecs_tasks.delete_files.events.get_emitter_id")
  15. def test_it_emits_deletions(mock_get_id, mock_emit, message_stub):
  16. mock_get_id.return_value = "ECSTask_4567"
  17. stats_stub = {"Some": "stats"}
  18. msg = json.loads(message_stub())
  19. emit_deletion_event(msg, stats_stub)
  20. mock_emit.assert_called_with(
  21. "1234",
  22. "ObjectUpdated",
  23. {
  24. "Statistics": stats_stub,
  25. "Object": "s3://bucket/path/basic.parquet",
  26. },
  27. "ECSTask_4567",
  28. )
  29. @patch("backend.ecs_tasks.delete_files.events.emit_event")
  30. @patch("backend.ecs_tasks.delete_files.events.get_emitter_id")
  31. def test_it_emits_skips(mock_get_id, mock_emit, message_stub):
  32. mock_get_id.return_value = "ECSTask_4567"
  33. reason_stub = "Object not found"
  34. msg = json.loads(message_stub())
  35. emit_skipped_event(msg, reason_stub)
  36. mock_emit.assert_called_with(
  37. "1234",
  38. "ObjectUpdateSkipped",
  39. {
  40. "Reason": reason_stub,
  41. "Object": "s3://bucket/path/basic.parquet",
  42. },
  43. "ECSTask_4567",
  44. )
  45. @patch("backend.ecs_tasks.delete_files.events.emit_event")
  46. @patch("backend.ecs_tasks.delete_files.events.get_emitter_id")
  47. def test_it_emits_failed_deletions(mock_get_id, mock_emit, message_stub):
  48. mock_get_id.return_value = "ECSTask_4567"
  49. msg = message_stub()
  50. emit_failure_event(msg, "Some error", "ObjectUpdateFailed")
  51. mock_emit.assert_called_with(
  52. "1234",
  53. "ObjectUpdateFailed",
  54. {"Error": "Some error", "Message": json.loads(msg)},
  55. "ECSTask_4567",
  56. )
  57. @patch("backend.ecs_tasks.delete_files.events.emit_event")
  58. @patch("backend.ecs_tasks.delete_files.events.get_emitter_id")
  59. def test_it_emits_failed_rollback(mock_get_id, mock_emit, message_stub):
  60. mock_get_id.return_value = "ECSTask_4567"
  61. msg = message_stub()
  62. emit_failure_event(msg, "Some error", "ObjectRollbackFailed")
  63. mock_emit.assert_called_with(
  64. "1234",
  65. "ObjectRollbackFailed",
  66. {"Error": "Some error", "Message": json.loads(msg)},
  67. "ECSTask_4567",
  68. )
  69. def test_it_raises_for_missing_job_id():
  70. with pytest.raises(ValueError):
  71. emit_failure_event("{}", "Some error", "deletion")
  72. @patch("os.getenv", MagicMock(return_value="http://metadatauri/path"))
  73. @patch("urllib.request.urlopen")
  74. def test_it_fetches_task_id_from_metadata_uri(url_open_mock):
  75. get_emitter_id.cache_clear()
  76. res = MagicMock()
  77. url_open_mock.return_value = res
  78. res.read.return_value = (
  79. b'{"Labels": {"com.amazonaws.ecs.task-arn": "arn/task-id"}}\n'
  80. )
  81. resp = get_emitter_id()
  82. assert "ECSTask_task-id" == resp
  83. url_open_mock.assert_called_with("http://metadatauri/path", timeout=1)
  84. @patch("os.getenv", MagicMock(return_value="http://metadatauri/path"))
  85. @patch("urllib.request.urlopen")
  86. @patch("backend.ecs_tasks.delete_files.events.logger")
  87. def test_it_defaults_task_id_if_urlerror(logger_mock, url_open_mock):
  88. get_emitter_id.cache_clear()
  89. res = MagicMock()
  90. url_open_mock.return_value = res
  91. res.read.side_effect = URLError("foo")
  92. resp = get_emitter_id()
  93. assert "ECSTask" == resp
  94. logger_mock.warning.assert_called_with(
  95. "Error when accessing the metadata service: foo"
  96. )
  97. @patch("os.getenv", MagicMock(return_value="http://metadatauri/path"))
  98. @patch("urllib.request.urlopen")
  99. @patch("backend.ecs_tasks.delete_files.events.logger")
  100. def test_it_defaults_task_id_if_malformed_response(logger_mock, url_open_mock):
  101. get_emitter_id.cache_clear()
  102. res = MagicMock()
  103. url_open_mock.return_value = res
  104. res.read.return_value = b"{}\n"
  105. resp = get_emitter_id()
  106. assert "ECSTask" == resp
  107. logger_mock.warning.assert_called_with(
  108. "Malformed response from the metadata service: b'{}\\n'"
  109. )
  110. @patch("os.getenv", MagicMock(return_value="http://metadatauri/path"))
  111. @patch("urllib.request.urlopen")
  112. @patch("backend.ecs_tasks.delete_files.events.logger")
  113. def test_it_defaults_task_id_if_generic_error(logger_mock, url_open_mock):
  114. get_emitter_id.cache_clear()
  115. res = MagicMock()
  116. url_open_mock.return_value = res
  117. res.read.side_effect = NameError("error")
  118. resp = get_emitter_id()
  119. assert "ECSTask" == resp
  120. logger_mock.warning.assert_called_with(
  121. "Error when getting emitter id from metadata service: error"
  122. )
  123. @patch("os.getenv", MagicMock(return_value=None))
  124. @patch("urllib.request.urlopen")
  125. @patch("backend.ecs_tasks.delete_files.events.logger")
  126. def test_it_defaults_task_id_if_env_variable_not_set(logger_mock, url_open_mock):
  127. get_emitter_id.cache_clear()
  128. resp = get_emitter_id()
  129. assert "ECSTask" == resp
  130. logger_mock.warning.assert_not_called()
  131. url_open_mock.assert_not_called()
  132. def test_it_sanitises_matches(message_stub):
  133. assert (
  134. "This message contains ID *** MATCH ID *** and *** MATCH ID ***"
  135. == sanitize_message(
  136. "This message contains ID 12345 and 23456",
  137. message_stub(
  138. Columns=[{"Column": "a", "MatchIds": ["12345", "23456", "34567"]}]
  139. ),
  140. )
  141. )
  142. def test_it_sanitises_int_matches(message_stub):
  143. assert (
  144. "This message contains ID *** MATCH ID *** and *** MATCH ID ***"
  145. == sanitize_message(
  146. "This message contains ID 12345 and 23456",
  147. message_stub(Columns=[{"Column": "a", "MatchIds": [12345, 23456, 34567]}]),
  148. )
  149. )
  150. def test_sanitiser_handles_malformed_messages():
  151. assert "an error message" == sanitize_message("an error message", "not json")