diff --git a/scripts/prepare_program_solutions_browser.py b/scripts/prepare_program_solutions_browser.py index 333f58b4..739cae7a 100644 --- a/scripts/prepare_program_solutions_browser.py +++ b/scripts/prepare_program_solutions_browser.py @@ -9,6 +9,12 @@ from unittest.mock import patch +def stable_json_value(value): + if isinstance(value, (memoryview, bytes, bytearray)): + return {"binary_hex": bytes(value).hex()} + return str(value) + + def main(): sys.path.insert(0, os.getcwd()) os.environ.setdefault("DJANGO_SETTINGS_MODULE", "procollab.settings") @@ -39,7 +45,9 @@ def main(): manager = get_user_model().objects.get(pk=manager_id, is_active=True) def digest(value): - return hashlib.sha256(json.dumps(value, sort_keys=True, default=str).encode()).hexdigest() + return hashlib.sha256( + json.dumps(value, sort_keys=True, default=stable_json_value).encode() + ).hexdigest() def rows(model, ids): return list(model._base_manager.filter(pk__in=ids).order_by("pk").values()) diff --git a/scripts/verify_program_solutions_dev.py b/scripts/verify_program_solutions_dev.py index 8f072ff0..e1b03fd7 100644 --- a/scripts/verify_program_solutions_dev.py +++ b/scripts/verify_program_solutions_dev.py @@ -1,6 +1,12 @@ """Приемка API на dev; новые записи всегда откатываются транзакцией.""" +def stable_json_value(value): + if isinstance(value, (memoryview, bytes, bytearray)): + return {"binary_hex": bytes(value).hex()} + return str(value) + + def main(): import hashlib import io @@ -42,7 +48,9 @@ def main(): checks, wire, programs = [], [], [] def digest(value): - return hashlib.sha256(json.dumps(value, sort_keys=True, default=str).encode()).hexdigest() + return hashlib.sha256( + json.dumps(value, sort_keys=True, default=stable_json_value).encode() + ).hexdigest() models = [ model diff --git a/tests/test_solution_acceptance_retention.py b/tests/test_solution_acceptance_retention.py new file mode 100644 index 00000000..5bc18b1f --- /dev/null +++ b/tests/test_solution_acceptance_retention.py @@ -0,0 +1,36 @@ +"""Бинарные данные сертификата должны сравниваться по содержимому.""" +import hashlib +import json +import runpy +from pathlib import Path +from unittest import TestCase + + +class AcceptanceRetentionTests(TestCase): + scripts = ("prepare_program_solutions_browser.py", "verify_program_solutions_dev.py") + + def functions(self): + root = Path(__file__).resolve().parents[1] + for name in self.scripts: + yield name, runpy.run_path(str(root / "scripts" / name))["stable_json_value"] + + def test_independent_binary_values_with_equal_content_have_equal_hashes(self): + payload = bytes(range(256)) + for name, encode in self.functions(): + with self.subTest(script=name): + variants = [memoryview(payload), memoryview(bytes(payload)), payload, bytearray(payload)] + hashes = { + hashlib.sha256(json.dumps({"content": value}, default=encode).encode()).hexdigest() + for value in variants + } + self.assertEqual(len(hashes), 1) + + def test_changed_content_and_false_zero_are_distinguished(self): + for name, encode in self.functions(): + with self.subTest(script=name): + values = [memoryview(b"before"), memoryview(b"after"), 0, False, None] + hashes = { + hashlib.sha256(json.dumps({"value": value}, default=encode).encode()).hexdigest() + for value in values + } + self.assertEqual(len(hashes), len(values))