|
1 | 1 | # Copyright The IETF Trust 2015-2020, All Rights Reserved |
2 | 2 | # -*- coding: utf-8 -*- |
3 | | - |
| 3 | +import base64 |
4 | 4 | import datetime |
5 | 5 | import json |
6 | 6 | import html |
|
36 | 36 | from ietf.person.models import Email, User |
37 | 37 | from ietf.person.models import PersonalApiKey |
38 | 38 | from ietf.stats.models import MeetingRegistration |
39 | | -from ietf.utils.mail import outbox, get_payload_text |
| 39 | +from ietf.utils.mail import empty_outbox, outbox, get_payload_text |
40 | 40 | from ietf.utils.models import DumpInfo |
41 | 41 | from ietf.utils.test_utils import TestCase, login_testing_unauthorized, reload_db_objects |
42 | 42 |
|
43 | 43 | from .ietf_utils import is_valid_token, requires_api_token |
| 44 | +from .views import EmailIngestionError |
44 | 45 |
|
45 | 46 | OMITTED_APPS = ( |
46 | 47 | 'ietf.secr.meetings', |
@@ -1013,6 +1014,194 @@ def test_role_holder_addresses(self): |
1013 | 1014 | sorted(e.address for e in emails), |
1014 | 1015 | ) |
1015 | 1016 |
|
| 1017 | + @override_settings(APP_API_TOKENS={"ietf.api.views.ingest_email": "valid-token"}) |
| 1018 | + @mock.patch("ietf.api.views.iana_ingest_review_email") |
| 1019 | + @mock.patch("ietf.api.views.ipr_ingest_response_email") |
| 1020 | + @mock.patch("ietf.api.views.nomcom_ingest_feedback_email") |
| 1021 | + def test_ingest_email( |
| 1022 | + self, mock_nomcom_ingest, mock_ipr_ingest, mock_iana_ingest |
| 1023 | + ): |
| 1024 | + mocks = {mock_nomcom_ingest, mock_ipr_ingest, mock_iana_ingest} |
| 1025 | + empty_outbox() |
| 1026 | + url = urlreverse("ietf.api.views.ingest_email") |
| 1027 | + |
| 1028 | + # test various bad calls |
| 1029 | + r = self.client.get(url) |
| 1030 | + self.assertEqual(r.status_code, 403) |
| 1031 | + self.assertFalse(any(m.called for m in mocks)) |
| 1032 | + |
| 1033 | + r = self.client.post(url) |
| 1034 | + self.assertEqual(r.status_code, 403) |
| 1035 | + self.assertFalse(any(m.called for m in mocks)) |
| 1036 | + |
| 1037 | + r = self.client.get(url, headers={"X-Api-Key": "valid-token"}) |
| 1038 | + self.assertEqual(r.status_code, 405) |
| 1039 | + self.assertFalse(any(m.called for m in mocks)) |
| 1040 | + |
| 1041 | + r = self.client.post(url, headers={"X-Api-Key": "valid-token"}) |
| 1042 | + self.assertEqual(r.status_code, 415) |
| 1043 | + self.assertFalse(any(m.called for m in mocks)) |
| 1044 | + |
| 1045 | + r = self.client.post( |
| 1046 | + url, content_type="application/json", headers={"X-Api-Key": "valid-token"} |
| 1047 | + ) |
| 1048 | + self.assertEqual(r.status_code, 400) |
| 1049 | + self.assertFalse(any(m.called for m in mocks)) |
| 1050 | + |
| 1051 | + r = self.client.post( |
| 1052 | + url, |
| 1053 | + "this is not JSON!", |
| 1054 | + content_type="application/json", |
| 1055 | + headers={"X-Api-Key": "valid-token"}, |
| 1056 | + ) |
| 1057 | + self.assertEqual(r.status_code, 400) |
| 1058 | + self.assertFalse(any(m.called for m in mocks)) |
| 1059 | + |
| 1060 | + r = self.client.post( |
| 1061 | + url, |
| 1062 | + {"json": "yes", "valid_schema": False}, |
| 1063 | + content_type="application/json", |
| 1064 | + headers={"X-Api-Key": "valid-token"}, |
| 1065 | + ) |
| 1066 | + self.assertEqual(r.status_code, 400) |
| 1067 | + self.assertFalse(any(m.called for m in mocks)) |
| 1068 | + |
| 1069 | + # test that valid requests call handlers appropriately |
| 1070 | + message_b64 = base64.b64encode(b"This is a message").decode() |
| 1071 | + r = self.client.post( |
| 1072 | + url, |
| 1073 | + {"dest": "iana-review", "message": message_b64}, |
| 1074 | + content_type="application/json", |
| 1075 | + headers={"X-Api-Key": "valid-token"}, |
| 1076 | + ) |
| 1077 | + self.assertEqual(r.status_code, 200) |
| 1078 | + self.assertTrue(mock_iana_ingest.called) |
| 1079 | + self.assertEqual(mock_iana_ingest.call_args, mock.call(b"This is a message")) |
| 1080 | + self.assertFalse(any(m.called for m in (mocks - {mock_iana_ingest}))) |
| 1081 | + mock_iana_ingest.reset_mock() |
| 1082 | + |
| 1083 | + r = self.client.post( |
| 1084 | + url, |
| 1085 | + {"dest": "ipr-response", "message": message_b64}, |
| 1086 | + content_type="application/json", |
| 1087 | + headers={"X-Api-Key": "valid-token"}, |
| 1088 | + ) |
| 1089 | + self.assertEqual(r.status_code, 200) |
| 1090 | + self.assertTrue(mock_ipr_ingest.called) |
| 1091 | + self.assertEqual(mock_ipr_ingest.call_args, mock.call(b"This is a message")) |
| 1092 | + self.assertFalse(any(m.called for m in (mocks - {mock_ipr_ingest}))) |
| 1093 | + mock_ipr_ingest.reset_mock() |
| 1094 | + |
| 1095 | + r = self.client.post( |
| 1096 | + url, |
| 1097 | + {"dest": "nomcom-feedback", "message": message_b64, "year": 2024}, # arbitrary year |
| 1098 | + content_type="application/json", |
| 1099 | + headers={"X-Api-Key": "valid-token"}, |
| 1100 | + ) |
| 1101 | + self.assertEqual(r.status_code, 200) |
| 1102 | + self.assertTrue(mock_nomcom_ingest.called) |
| 1103 | + self.assertEqual(mock_nomcom_ingest.call_args, mock.call(b"This is a message", 2024)) |
| 1104 | + self.assertFalse(any(m.called for m in (mocks - {mock_nomcom_ingest}))) |
| 1105 | + mock_nomcom_ingest.reset_mock() |
| 1106 | + |
| 1107 | + # test that exceptions lead to email being sent - assumes that iana-review handling is representative |
| 1108 | + mock_iana_ingest.side_effect = EmailIngestionError("Error: don't send email") |
| 1109 | + r = self.client.post( |
| 1110 | + url, |
| 1111 | + {"dest": "iana-review", "message": message_b64}, |
| 1112 | + content_type="application/json", |
| 1113 | + headers={"X-Api-Key": "valid-token"}, |
| 1114 | + ) |
| 1115 | + self.assertEqual(r.status_code, 400) |
| 1116 | + self.assertTrue(mock_iana_ingest.called) |
| 1117 | + self.assertEqual(mock_iana_ingest.call_args, mock.call(b"This is a message")) |
| 1118 | + self.assertFalse(any(m.called for m in (mocks - {mock_iana_ingest}))) |
| 1119 | + self.assertEqual(len(outbox), 0) # implicitly tests that _none_ of the earlier tests sent email |
| 1120 | + mock_iana_ingest.reset_mock() |
| 1121 | + |
| 1122 | + # test default recipients and attached original message |
| 1123 | + mock_iana_ingest.side_effect = EmailIngestionError( |
| 1124 | + "Error: do send email", |
| 1125 | + email_body="This is my email\n", |
| 1126 | + email_original_message=b"This is the original message" |
| 1127 | + ) |
| 1128 | + with override_settings(ADMINS=[("Some Admin", "admin@example.com")]): |
| 1129 | + r = self.client.post( |
| 1130 | + url, |
| 1131 | + {"dest": "iana-review", "message": message_b64}, |
| 1132 | + content_type="application/json", |
| 1133 | + headers={"X-Api-Key": "valid-token"}, |
| 1134 | + ) |
| 1135 | + self.assertEqual(r.status_code, 400) |
| 1136 | + self.assertTrue(mock_iana_ingest.called) |
| 1137 | + self.assertEqual(mock_iana_ingest.call_args, mock.call(b"This is a message")) |
| 1138 | + self.assertFalse(any(m.called for m in (mocks - {mock_iana_ingest}))) |
| 1139 | + self.assertEqual(len(outbox), 1) |
| 1140 | + self.assertIn("admin@example.com", outbox[0]["To"]) |
| 1141 | + self.assertEqual("Error: do send email", outbox[0]["Subject"]) |
| 1142 | + self.assertEqual("This is my email\n", get_payload_text(outbox[0].get_body())) |
| 1143 | + attachments = list(a for a in outbox[0].iter_attachments()) |
| 1144 | + self.assertEqual(len(attachments), 1) |
| 1145 | + self.assertEqual(attachments[0].get_filename(), "original-message") |
| 1146 | + self.assertEqual(attachments[0].get_content_type(), "application/octet-stream") |
| 1147 | + self.assertEqual(attachments[0].get_content(), b"This is the original message") |
| 1148 | + mock_iana_ingest.reset_mock() |
| 1149 | + empty_outbox() |
| 1150 | + |
| 1151 | + # test overridden recipients and no attached original message |
| 1152 | + mock_iana_ingest.side_effect = EmailIngestionError( |
| 1153 | + "Error: do send email", |
| 1154 | + email_body="This is my email\n", |
| 1155 | + email_recipients=("thatguy@example.com") |
| 1156 | + ) |
| 1157 | + with override_settings(ADMINS=[("Some Admin", "admin@example.com")]): |
| 1158 | + r = self.client.post( |
| 1159 | + url, |
| 1160 | + {"dest": "iana-review", "message": message_b64}, |
| 1161 | + content_type="application/json", |
| 1162 | + headers={"X-Api-Key": "valid-token"}, |
| 1163 | + ) |
| 1164 | + self.assertEqual(r.status_code, 400) |
| 1165 | + self.assertTrue(mock_iana_ingest.called) |
| 1166 | + self.assertEqual(mock_iana_ingest.call_args, mock.call(b"This is a message")) |
| 1167 | + self.assertFalse(any(m.called for m in (mocks - {mock_iana_ingest}))) |
| 1168 | + self.assertEqual(len(outbox), 1) |
| 1169 | + self.assertNotIn("admin@example.com", outbox[0]["To"]) |
| 1170 | + self.assertIn("thatguy@example.com", outbox[0]["To"]) |
| 1171 | + self.assertEqual("Error: do send email", outbox[0]["Subject"]) |
| 1172 | + self.assertEqual("This is my email\n", get_payload_text(outbox[0])) |
| 1173 | + mock_iana_ingest.reset_mock() |
| 1174 | + empty_outbox() |
| 1175 | + |
| 1176 | + # test attached traceback |
| 1177 | + mock_iana_ingest.side_effect = EmailIngestionError( |
| 1178 | + "Error: do send email", |
| 1179 | + email_body="This is my email\n", |
| 1180 | + email_attach_traceback=True, |
| 1181 | + ) |
| 1182 | + with override_settings(ADMINS=[("Some Admin", "admin@example.com")]): |
| 1183 | + r = self.client.post( |
| 1184 | + url, |
| 1185 | + {"dest": "iana-review", "message": message_b64}, |
| 1186 | + content_type="application/json", |
| 1187 | + headers={"X-Api-Key": "valid-token"}, |
| 1188 | + ) |
| 1189 | + self.assertEqual(r.status_code, 400) |
| 1190 | + self.assertTrue(mock_iana_ingest.called) |
| 1191 | + self.assertEqual(mock_iana_ingest.call_args, mock.call(b"This is a message")) |
| 1192 | + self.assertFalse(any(m.called for m in (mocks - {mock_iana_ingest}))) |
| 1193 | + self.assertEqual(len(outbox), 1) |
| 1194 | + self.assertIn("admin@example.com", outbox[0]["To"]) |
| 1195 | + self.assertEqual("Error: do send email", outbox[0]["Subject"]) |
| 1196 | + self.assertEqual("This is my email\n", get_payload_text(outbox[0].get_body())) |
| 1197 | + attachments = list(a for a in outbox[0].iter_attachments()) |
| 1198 | + self.assertEqual(len(attachments), 1) |
| 1199 | + self.assertEqual(attachments[0].get_filename(), "traceback.txt") |
| 1200 | + self.assertEqual(attachments[0].get_content_type(), "text/plain") |
| 1201 | + self.assertIn("ietf.api.views.EmailIngestionError: Error: do send email", attachments[0].get_content()) |
| 1202 | + mock_iana_ingest.reset_mock() |
| 1203 | + empty_outbox() |
| 1204 | + |
1016 | 1205 |
|
1017 | 1206 | class DirectAuthApiTests(TestCase): |
1018 | 1207 |
|
|
0 commit comments