|
9 | 9 |
|
10 | 10 | import debug # pyflakes:ignore |
11 | 11 |
|
| 12 | +from django.test import RequestFactory |
| 13 | +from django.utils.text import slugify |
12 | 14 | from django.urls import reverse as urlreverse |
13 | 15 |
|
14 | | -from ietf.doc.models import ( Document, State, DocEvent, |
15 | | - BallotPositionDocEvent, LastCallDocEvent, WriteupDocEvent, TelechatDocEvent ) |
16 | | -from ietf.doc.factories import DocumentFactory, IndividualDraftFactory, IndividualRfcFactory, WgDraftFactory |
| 16 | +from ietf.doc.models import (Document, State, DocEvent, |
| 17 | + BallotPositionDocEvent, LastCallDocEvent, WriteupDocEvent, TelechatDocEvent) |
| 18 | +from ietf.doc.factories import (DocumentFactory, IndividualDraftFactory, IndividualRfcFactory, WgDraftFactory, |
| 19 | + BallotPositionDocEventFactory, BallotDocEventFactory) |
17 | 20 | from ietf.doc.utils import create_ballot_if_not_open |
| 21 | +from ietf.doc.views_doc import document_ballot_content |
18 | 22 | from ietf.group.models import Group, Role |
19 | 23 | from ietf.group.factories import GroupFactory, RoleFactory, ReviewTeamFactory |
20 | 24 | from ietf.ipr.factories import HolderIprDisclosureFactory |
21 | 25 | from ietf.name.models import BallotPositionName |
22 | 26 | from ietf.iesg.models import TelechatDate |
23 | 27 | from ietf.person.models import Person, PersonalApiKey |
24 | 28 | from ietf.person.factories import PersonFactory |
| 29 | +from ietf.person.utils import get_active_ads |
25 | 30 | from ietf.utils.test_utils import TestCase, login_testing_unauthorized |
26 | 31 | from ietf.utils.mail import outbox, empty_outbox, get_payload_text |
27 | 32 | from ietf.utils.text import unwrap |
@@ -1100,11 +1105,278 @@ def test_regenerate_last_call(self): |
1100 | 1105 | self.assertTrue("rfc6666" in lc_text) |
1101 | 1106 | self.assertTrue("Independent Submission" in lc_text) |
1102 | 1107 |
|
1103 | | - draft.relateddocument_set.create(target=rfc.docalias.get(name='rfc6666'),relationship_id='downref-approval') |
| 1108 | + draft.relateddocument_set.create(target=rfc.docalias.get(name='rfc6666'), relationship_id='downref-approval') |
1104 | 1109 |
|
1105 | 1110 | r = self.client.post(url, dict(regenerate_last_call_text="1")) |
1106 | 1111 | self.assertEqual(r.status_code, 200) |
1107 | 1112 | draft = Document.objects.get(name=draft.name) |
1108 | 1113 | lc_text = draft.latest_event(WriteupDocEvent, type="changed_last_call_text").text |
1109 | 1114 | self.assertFalse("contains these normative down" in lc_text) |
1110 | 1115 | self.assertFalse("rfc6666" in lc_text) |
| 1116 | + |
| 1117 | + |
| 1118 | +class BallotContentTests(TestCase): |
| 1119 | + def test_ballotpositiondocevent_any_email_sent(self): |
| 1120 | + now = datetime.datetime.now() # be sure event timestamps are at distinct times |
| 1121 | + bpde_with_null_send_email = BallotPositionDocEventFactory( |
| 1122 | + time=now - datetime.timedelta(minutes=30), |
| 1123 | + send_email=None, |
| 1124 | + ) |
| 1125 | + ballot = bpde_with_null_send_email.ballot |
| 1126 | + balloter = bpde_with_null_send_email.balloter |
| 1127 | + self.assertIsNone( |
| 1128 | + bpde_with_null_send_email.any_email_sent(), |
| 1129 | + 'Result is None when only send_email is None', |
| 1130 | + ) |
| 1131 | + |
| 1132 | + self.assertIsNone( |
| 1133 | + BallotPositionDocEventFactory( |
| 1134 | + ballot=ballot, |
| 1135 | + balloter=balloter, |
| 1136 | + time=now - datetime.timedelta(minutes=29), |
| 1137 | + send_email=None, |
| 1138 | + ).any_email_sent(), |
| 1139 | + 'Result is None when all send_email values are None', |
| 1140 | + ) |
| 1141 | + |
| 1142 | + # test with assertIs instead of assertFalse to distinguish None from False |
| 1143 | + self.assertIs( |
| 1144 | + BallotPositionDocEventFactory( |
| 1145 | + ballot=ballot, |
| 1146 | + balloter=balloter, |
| 1147 | + time=now - datetime.timedelta(minutes=28), |
| 1148 | + send_email=False, |
| 1149 | + ).any_email_sent(), |
| 1150 | + False, |
| 1151 | + 'Result is False when current send_email is False' |
| 1152 | + ) |
| 1153 | + |
| 1154 | + self.assertIs( |
| 1155 | + BallotPositionDocEventFactory( |
| 1156 | + ballot=ballot, |
| 1157 | + balloter=balloter, |
| 1158 | + time=now - datetime.timedelta(minutes=27), |
| 1159 | + send_email=None, |
| 1160 | + ).any_email_sent(), |
| 1161 | + False, |
| 1162 | + 'Result is False when earlier send_email is False' |
| 1163 | + ) |
| 1164 | + |
| 1165 | + self.assertIs( |
| 1166 | + BallotPositionDocEventFactory( |
| 1167 | + ballot=ballot, |
| 1168 | + balloter=balloter, |
| 1169 | + time=now - datetime.timedelta(minutes=26), |
| 1170 | + send_email=True, |
| 1171 | + ).any_email_sent(), |
| 1172 | + True, |
| 1173 | + 'Result is True when current send_email is True' |
| 1174 | + ) |
| 1175 | + |
| 1176 | + self.assertIs( |
| 1177 | + BallotPositionDocEventFactory( |
| 1178 | + ballot=ballot, |
| 1179 | + balloter=balloter, |
| 1180 | + time=now - datetime.timedelta(minutes=25), |
| 1181 | + send_email=None, |
| 1182 | + ).any_email_sent(), |
| 1183 | + True, |
| 1184 | + 'Result is True when earlier send_email is True and current is None' |
| 1185 | + ) |
| 1186 | + |
| 1187 | + self.assertIs( |
| 1188 | + BallotPositionDocEventFactory( |
| 1189 | + ballot=ballot, |
| 1190 | + balloter=balloter, |
| 1191 | + time=now - datetime.timedelta(minutes=24), |
| 1192 | + send_email=False, |
| 1193 | + ).any_email_sent(), |
| 1194 | + True, |
| 1195 | + 'Result is True when earlier send_email is True and current is False' |
| 1196 | + ) |
| 1197 | + |
| 1198 | + def _assertBallotMessage(self, q, balloter, expected): |
| 1199 | + heading = q(f'h4[id$="_{slugify(balloter.plain_name())}"]') |
| 1200 | + self.assertEqual(len(heading), 1) |
| 1201 | + # <h4/> is followed by a panel with the message of interest, so use next() |
| 1202 | + self.assertEqual( |
| 1203 | + len(heading.next().find( |
| 1204 | + f'*[title="{expected}"]' |
| 1205 | + )), |
| 1206 | + 1, |
| 1207 | + ) |
| 1208 | + |
| 1209 | + def test_document_ballot_content_email_sent(self): |
| 1210 | + """Ballot content correctly describes whether email is requested for each position""" |
| 1211 | + ballot = BallotDocEventFactory() |
| 1212 | + balloters = get_active_ads() |
| 1213 | + self.assertGreaterEqual(len(balloters), 6, |
| 1214 | + 'Oops! Need to create additional active balloters for test') |
| 1215 | + |
| 1216 | + # send_email is True |
| 1217 | + BallotPositionDocEventFactory( |
| 1218 | + ballot=ballot, |
| 1219 | + balloter=balloters[0], |
| 1220 | + pos_id='discuss', |
| 1221 | + discuss='Discussion text', |
| 1222 | + discuss_time=datetime.datetime.now(), |
| 1223 | + send_email=True, |
| 1224 | + ) |
| 1225 | + BallotPositionDocEventFactory( |
| 1226 | + ballot=ballot, |
| 1227 | + balloter=balloters[1], |
| 1228 | + pos_id='noobj', |
| 1229 | + comment='Commentary', |
| 1230 | + comment_time=datetime.datetime.now(), |
| 1231 | + send_email=True, |
| 1232 | + ) |
| 1233 | + |
| 1234 | + # send_email False |
| 1235 | + BallotPositionDocEventFactory( |
| 1236 | + ballot=ballot, |
| 1237 | + balloter=balloters[2], |
| 1238 | + pos_id='discuss', |
| 1239 | + discuss='Discussion text', |
| 1240 | + discuss_time=datetime.datetime.now(), |
| 1241 | + send_email=False, |
| 1242 | + ) |
| 1243 | + BallotPositionDocEventFactory( |
| 1244 | + ballot=ballot, |
| 1245 | + balloter=balloters[3], |
| 1246 | + pos_id='noobj', |
| 1247 | + comment='Commentary', |
| 1248 | + comment_time=datetime.datetime.now(), |
| 1249 | + send_email=False, |
| 1250 | + ) |
| 1251 | + |
| 1252 | + # send_email False but earlier position had send_email True |
| 1253 | + BallotPositionDocEventFactory( |
| 1254 | + ballot=ballot, |
| 1255 | + balloter=balloters[4], |
| 1256 | + pos_id='discuss', |
| 1257 | + discuss='Discussion text', |
| 1258 | + discuss_time=datetime.datetime.now() - datetime.timedelta(days=1), |
| 1259 | + send_email=True, |
| 1260 | + ) |
| 1261 | + BallotPositionDocEventFactory( |
| 1262 | + ballot=ballot, |
| 1263 | + balloter=balloters[4], |
| 1264 | + pos_id='discuss', |
| 1265 | + discuss='Discussion text', |
| 1266 | + discuss_time=datetime.datetime.now(), |
| 1267 | + send_email=False, |
| 1268 | + ) |
| 1269 | + BallotPositionDocEventFactory( |
| 1270 | + ballot=ballot, |
| 1271 | + balloter=balloters[5], |
| 1272 | + pos_id='noobj', |
| 1273 | + comment='Commentary', |
| 1274 | + comment_time=datetime.datetime.now() - datetime.timedelta(days=1), |
| 1275 | + send_email=True, |
| 1276 | + ) |
| 1277 | + BallotPositionDocEventFactory( |
| 1278 | + ballot=ballot, |
| 1279 | + balloter=balloters[5], |
| 1280 | + pos_id='noobj', |
| 1281 | + comment='Commentary', |
| 1282 | + comment_time=datetime.datetime.now(), |
| 1283 | + send_email=False, |
| 1284 | + ) |
| 1285 | + |
| 1286 | + # Create a few positions with non-active-ad people. These will be treated |
| 1287 | + # as "old" ballot positions because the people are not in the list returned |
| 1288 | + # by get_active_ads() |
| 1289 | + # |
| 1290 | + # Some faked non-ASCII names wind up with plain names that cannot be slugified. |
| 1291 | + # This causes test failure because that slug is used in an HTML element ID. |
| 1292 | + # Until that's fixed, set the plain names to something guaranteed unique so |
| 1293 | + # the test does not randomly fail. |
| 1294 | + no_email_balloter = BallotPositionDocEventFactory( |
| 1295 | + ballot=ballot, |
| 1296 | + balloter__plain='plain name1', |
| 1297 | + pos_id='discuss', |
| 1298 | + discuss='Discussion text', |
| 1299 | + discuss_time=datetime.datetime.now(), |
| 1300 | + send_email=False, |
| 1301 | + ).balloter |
| 1302 | + send_email_balloter = BallotPositionDocEventFactory( |
| 1303 | + ballot=ballot, |
| 1304 | + balloter__plain='plain name2', |
| 1305 | + pos_id='discuss', |
| 1306 | + discuss='Discussion text', |
| 1307 | + discuss_time=datetime.datetime.now(), |
| 1308 | + send_email=True, |
| 1309 | + ).balloter |
| 1310 | + prev_send_email_balloter = BallotPositionDocEventFactory( |
| 1311 | + ballot=ballot, |
| 1312 | + balloter__plain='plain name3', |
| 1313 | + pos_id='discuss', |
| 1314 | + discuss='Discussion text', |
| 1315 | + discuss_time=datetime.datetime.now() - datetime.timedelta(days=1), |
| 1316 | + send_email=True, |
| 1317 | + ).balloter |
| 1318 | + BallotPositionDocEventFactory( |
| 1319 | + ballot=ballot, |
| 1320 | + balloter=prev_send_email_balloter, |
| 1321 | + pos_id='discuss', |
| 1322 | + discuss='Discussion text', |
| 1323 | + discuss_time=datetime.datetime.now(), |
| 1324 | + send_email=False, |
| 1325 | + ) |
| 1326 | + |
| 1327 | + content = document_ballot_content( |
| 1328 | + request=RequestFactory(), |
| 1329 | + doc=ballot.doc, |
| 1330 | + ballot_id=ballot.pk, |
| 1331 | + ) |
| 1332 | + q = PyQuery(content) |
| 1333 | + self._assertBallotMessage(q, balloters[0], 'Email requested to be sent for this discuss') |
| 1334 | + self._assertBallotMessage(q, balloters[1], 'Email requested to be sent for this comment') |
| 1335 | + self._assertBallotMessage(q, balloters[2], 'No email send requests for this discuss') |
| 1336 | + self._assertBallotMessage(q, balloters[3], 'No email send requests for this comment') |
| 1337 | + self._assertBallotMessage(q, balloters[4], 'Email requested to be sent for earlier discuss') |
| 1338 | + self._assertBallotMessage(q, balloters[5], 'Email requested to be sent for earlier comment') |
| 1339 | + self._assertBallotMessage(q, no_email_balloter, 'No email send requests for this ballot position') |
| 1340 | + self._assertBallotMessage(q, send_email_balloter, 'Email requested to be sent for this ballot position') |
| 1341 | + self._assertBallotMessage(q, prev_send_email_balloter, 'Email requested to be sent for earlier ballot position') |
| 1342 | + |
| 1343 | + def test_document_ballot_content_without_send_email_values(self): |
| 1344 | + """Ballot content correctly indicates lack of send_email field in records""" |
| 1345 | + ballot = BallotDocEventFactory() |
| 1346 | + balloters = get_active_ads() |
| 1347 | + self.assertGreaterEqual(len(balloters), 2, |
| 1348 | + 'Oops! Need to create additional active balloters for test') |
| 1349 | + BallotPositionDocEventFactory( |
| 1350 | + ballot=ballot, |
| 1351 | + balloter=balloters[0], |
| 1352 | + pos_id='discuss', |
| 1353 | + discuss='Discussion text', |
| 1354 | + discuss_time=datetime.datetime.now(), |
| 1355 | + send_email=None, |
| 1356 | + ) |
| 1357 | + BallotPositionDocEventFactory( |
| 1358 | + ballot=ballot, |
| 1359 | + balloter=balloters[1], |
| 1360 | + pos_id='noobj', |
| 1361 | + comment='Commentary', |
| 1362 | + comment_time=datetime.datetime.now(), |
| 1363 | + send_email=None, |
| 1364 | + ) |
| 1365 | + old_balloter = BallotPositionDocEventFactory( |
| 1366 | + ballot=ballot, |
| 1367 | + balloter__plain='plain name', # ensure plain name is slugifiable |
| 1368 | + pos_id='discuss', |
| 1369 | + discuss='Discussion text', |
| 1370 | + discuss_time=datetime.datetime.now(), |
| 1371 | + send_email=None, |
| 1372 | + ).balloter |
| 1373 | + |
| 1374 | + content = document_ballot_content( |
| 1375 | + request=RequestFactory(), |
| 1376 | + doc=ballot.doc, |
| 1377 | + ballot_id=ballot.pk, |
| 1378 | + ) |
| 1379 | + q = PyQuery(content) |
| 1380 | + self._assertBallotMessage(q, balloters[0], 'No email send requests for this discuss') |
| 1381 | + self._assertBallotMessage(q, balloters[1], 'No ballot position send log available') |
| 1382 | + self._assertBallotMessage(q, old_balloter, 'No ballot position send log available') |
0 commit comments