Skip to content

Commit f8bfeb8

Browse files
authored
Merge pull request #2635 from niklasmohrin/json-import-idempotent
Make JSONImporter pass assert_no_database_modifications on nop runs
2 parents 9f10df8 + 689390b commit f8bfeb8

File tree

3 files changed

+38
-36
lines changed

3 files changed

+38
-36
lines changed

evap/cms/json_importer.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -479,9 +479,13 @@ def _import_evaluation( # noqa: PLR0912, PLR0915
479479
wait_for_grade_upload_before_publishing = True
480480
else:
481481
wait_for_grade_upload_before_publishing = any(grade["scale"] for grade in data["courses"])
482-
course.evaluations.all().update(
482+
483+
if course.evaluations.exclude(
483484
wait_for_grade_upload_before_publishing=wait_for_grade_upload_before_publishing
484-
)
485+
).exists():
486+
course.evaluations.all().update(
487+
wait_for_grade_upload_before_publishing=wait_for_grade_upload_before_publishing
488+
)
485489

486490
is_rewarded = False
487491
else:

evap/cms/tests/test_json_importer.py

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
from contextlib import nullcontext
23
from copy import deepcopy
34
from datetime import date, datetime, timedelta
45
from pathlib import Path
@@ -23,6 +24,7 @@
2324
UserProfile,
2425
)
2526
from evap.evaluation.models_logging import LogEntry
27+
from evap.evaluation.tests.tools import assert_no_database_modifications
2628

2729
EXAMPLE_DATA = json.loads(
2830
Path(evap.cms.fixtures.__file__).with_name("import_example_data.json").read_text(encoding="utf-8")
@@ -313,17 +315,21 @@ class TestImportEvents(TestCase):
313315
def setUpTestData(cls):
314316
cls.semester = baker.make(Semester)
315317

316-
def _import(self, data=None):
318+
def _import(self, data=None, assert_nop=False):
317319
if not data:
318320
data = EXAMPLE_DATA
319321
data = json.dumps(data)
320-
importer = JSONImporter(self.semester, date(2000, 1, 1))
321-
importer.import_json(data)
322+
323+
cm = assert_no_database_modifications() if assert_nop else nullcontext()
324+
with cm:
325+
importer = JSONImporter(self.semester, date(2000, 1, 1))
326+
importer.import_json(data)
322327
return importer
323328

324329
@override_settings(EXAM_EVALUATION_DEFAULT_DURATION=timedelta(days=3))
325330
def test_import_courses(self):
326331
importer = self._import()
332+
self._import(assert_nop=True)
327333

328334
course = Course.objects.get()
329335

@@ -632,11 +638,7 @@ def test_import_courses_evaluation_not_new(self):
632638
evaluation.course.name_en = "Change"
633639
evaluation.course.save()
634640

635-
importer = self._import()
636-
637-
evaluation = Evaluation.objects.get(pk=evaluation.pk)
638-
self.assertFalse(evaluation.is_rewarded)
639-
self.assertEqual(evaluation.course.name_en, "Change")
641+
importer = self._import(assert_nop=True)
640642
self.assertEqual(len(importer.statistics.attempted_evaluation_changes), 1)
641643
self.assertEqual(len(importer.statistics.attempted_course_changes), 1)
642644

@@ -663,12 +665,8 @@ def test_import_courses_evaluation_approved(self):
663665
evaluation.is_rewarded = False
664666
evaluation.save()
665667

666-
importer = self._import()
667-
668-
evaluation = Evaluation.objects.get(pk=evaluation.pk)
669-
self.assertFalse(evaluation.is_rewarded)
668+
importer = self._import(assert_nop=True)
670669
self.assertEqual(len(importer.statistics.attempted_evaluation_changes), 1)
671-
self.assertEqual(evaluation.participants.count(), 2)
672670

673671
evaluation.participants.clear()
674672
evaluation = Evaluation.objects.get(pk=evaluation.pk)
@@ -695,12 +693,10 @@ def test_import_courses_evaluation_running(self):
695693

696694
self.assertEqual(evaluation.participants.count(), 0)
697695

698-
importer = self._import()
699-
696+
importer = self._import(assert_nop=True)
700697
self.assertEqual(len(importer.statistics.attempted_evaluation_changes), 1)
701698
self.assertEqual(len(importer.statistics.updated_participants), 0)
702699
self.assertEqual(len(importer.statistics.attempted_participant_changes), 1)
703-
self.assertEqual(evaluation.participants.count(), 0)
704700

705701
def test_import_courses_update(self):
706702
self._import()
@@ -740,7 +736,8 @@ def test_importer_log_email_sent_no_recipients(self):
740736
def test_importer_wrong_data(self):
741737
wrong_data = deepcopy(EXAMPLE_DATA)
742738
wrong_data["events"][0]["isexam"] = "false"
743-
with self.assertRaises(ValidationError):
739+
# Note: not using assert_nop because the assert_no_database_modifications should include the assertRaises
740+
with assert_no_database_modifications(), self.assertRaises(ValidationError):
744741
self._import(wrong_data)
745742

746743
data_with_additional_attribute = deepcopy(EXAMPLE_DATA)

evap/evaluation/tests/tools.py

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -272,26 +272,27 @@ def assert_no_database_modifications(*args, **kwargs):
272272
assert len(connections.all()) == 1, "Found more than one connection, so the decorator might monitor the wrong one"
273273

274274
# may be extended with other non-modifying verbs
275-
allowed_prefixes = ["select", "savepoint", "release savepoint"]
275+
allowed_prefixes = ["select", "savepoint", "release savepoint", "rollback to savepoint"]
276276

277277
conn = connections[DEFAULT_DB_ALIAS]
278278
with CaptureQueriesContext(conn):
279-
yield
280-
281-
for query in conn.queries_log:
282-
if (
283-
query["sql"].startswith('INSERT INTO "testing_cache_sessions"')
284-
or query["sql"].startswith('UPDATE "testing_cache_sessions"')
285-
or query["sql"].startswith('DELETE FROM "testing_cache_sessions"')
286-
or query["sql"].startswith('UPDATE "evaluation_userprofile" SET "last_login" = ')
287-
):
288-
# These queries are caused by interacting with the test-app (self.app.get()), since that opens a session.
289-
# That's not what we want to test for here
290-
continue
291-
292-
lower_sql = query["sql"].lower()
293-
if not any(lower_sql.startswith(prefix) for prefix in allowed_prefixes):
294-
raise AssertionError("Unexpected modifying query found: " + query["sql"])
279+
try:
280+
yield
281+
finally:
282+
for query in conn.queries_log:
283+
if (
284+
query["sql"].startswith('INSERT INTO "testing_cache_sessions"')
285+
or query["sql"].startswith('UPDATE "testing_cache_sessions"')
286+
or query["sql"].startswith('DELETE FROM "testing_cache_sessions"')
287+
or query["sql"].startswith('UPDATE "evaluation_userprofile" SET "last_login" = ')
288+
):
289+
# These queries are caused by interacting with the test-app (self.app.get()), since that opens a session.
290+
# That's not what we want to test for here
291+
continue
292+
293+
lower_sql = query["sql"].lower()
294+
if not any(lower_sql.startswith(prefix) for prefix in allowed_prefixes):
295+
raise AssertionError("Unexpected modifying query found: " + query["sql"])
295296

296297

297298
class LiveServerTest(SeleniumTestCase):

0 commit comments

Comments
 (0)