diff --git a/soitool/serialize_export_import_soi.py b/soitool/serialize_export_import_soi.py index e2cce5be6d4ae8add2f2eaf9d3b73d01ed696bef..18bdecad6b845be7f6b8ac1f20dbde1804589032 100644 --- a/soitool/serialize_export_import_soi.py +++ b/soitool/serialize_export_import_soi.py @@ -2,10 +2,63 @@ import json from datetime import datetime from ast import literal_eval +from schema import Schema, And, Or from soitool.soi import SOI from soitool.compressor import compress, decompress from soitool.modules.module_table import TableModule +# Valid schema for serialized SOI +SERIALIZED_SOI_SCHEMA = Schema( + { + "title": And(str, len), + "description": str, + "version": And(str, len), + "date": Or(str, None), + "valid": {"from_date": Or(str, None), "to_date": Or(str, None)}, + "icon": Or(str, None), + "classification": And(str, len), + "orientation": And(str, len, Or("portrait", "landscape")), + "placement_strategy": And(str, len, Or("manual", "auto")), + "algorithm_bin": And(str, len, Or("BFF", "BBF")), + "algorithm_pack": And( + str, len, Or("MaxRectsBl", "SkylineBl", "GuillotineBssfSas") + ), + "algorithm_sort": And(str, len, Or("none", "area", "width", "height")), + "modules": [ + { + "type": And(str, len), + "data": object, + "size": { + "width": And(Or(int, float), lambda w: w > 0), + "height": And(Or(int, float), lambda h: h > 0), + }, + "meta": { + "x": And(Or(int, float), lambda x: x >= 0), + "y": And(Or(int, float), lambda y: y >= 0), + "page": And(int, lambda page: page >= 0), + "name": And(str, len), + }, + } + ], + "attachments": [ + { + "type": And(str, len), + "data": object, + "size": { + "width": And(Or(int, float), lambda w: w > 0), + "height": And(Or(int, float), lambda h: h > 0), + }, + "meta": { + "x": And(Or(int, float), lambda x: x >= 0), + "y": And(Or(int, float), lambda y: y >= 0), + "page": And(int, lambda page: page >= 0), + "name": And(str, len), + }, + } + ], + } +) + def serialize_soi(soi): """Serialize SOI to JSON-string. @@ -130,6 +183,8 @@ def import_soi(file_path): Raises ------ + ValueError + If file content is invalid against SERIALIZED_SOI_SCHEMA. TypeError If 'type' of module or attachment is not implemented. """ @@ -139,6 +194,10 @@ def import_soi(file_path): else: serialized = literal_eval(file.read()) + # Raise error if file content is invalid + if not SERIALIZED_SOI_SCHEMA.is_valid(serialized): + raise ValueError("Serialized SOI does not have correct format.") + # Create dict for modules with instantiated widget(s) modules = [] for module in serialized["modules"]: diff --git a/test/test_serialize_export_import.py b/test/test_serialize_export_import.py index 5fe77efbae8b8b66f70a6329752000effc3aa2e6..46f46bfe850e732b98e66b53492a3423323bea1c 100644 --- a/test/test_serialize_export_import.py +++ b/test/test_serialize_export_import.py @@ -5,13 +5,13 @@ import json from pathlib import Path from datetime import datetime from PySide2.QtWidgets import QApplication -from schema import Schema, And, Or from soitool.soi import SOI from soitool.modules.module_table import TableModule from soitool.serialize_export_import_soi import ( serialize_soi, export_soi, import_soi, + SERIALIZED_SOI_SCHEMA, ) app = QApplication.instance() @@ -50,57 +50,6 @@ MODULES = [ "meta": {"x": 200, "y": 150, "page": 1, "name": "Table1"}, }, ] -# Valid schema for serialized SOI -SCHEMA = Schema( - { - "title": And(str, len), - "description": str, - "version": And(str, len), - "date": Or(str, None), - "valid": {"from_date": Or(str, None), "to_date": Or(str, None)}, - "icon": Or(str, None), - "classification": And(str, len), - "orientation": And(str, len, Or("portrait", "landscape")), - "placement_strategy": And(str, len, Or("manual", "auto")), - "algorithm_bin": And(str, len, Or("BFF", "BBF")), - "algorithm_pack": And( - str, len, Or("MaxRectsBl", "SkylineBl", "GuillotineBssfSas") - ), - "algorithm_sort": And(str, len, Or("none", "area", "width", "height")), - "modules": [ - { - "type": And(str, len), - "data": object, - "size": { - "width": And(Or(int, float), lambda w: w > 0), - "height": And(Or(int, float), lambda h: h > 0), - }, - "meta": { - "x": And(Or(int, float), lambda x: x >= 0), - "y": And(Or(int, float), lambda y: y >= 0), - "page": And(int, lambda page: page >= 0), - "name": And(str, len), - }, - } - ], - "attachments": [ - { - "type": And(str, len), - "data": object, - "size": { - "width": And(Or(int, float), lambda w: w > 0), - "height": And(Or(int, float), lambda h: h > 0), - }, - "meta": { - "x": And(Or(int, float), lambda x: x >= 0), - "y": And(Or(int, float), lambda y: y >= 0), - "page": And(int, lambda page: page >= 0), - "name": And(str, len), - }, - } - ], - } -) class SerializeTest(unittest.TestCase): @@ -142,10 +91,26 @@ class SerializeTest(unittest.TestCase): serialized = serialize_soi(self.soi) json_data = json.loads(serialized) # Assert serialized SOI format matches schema - self.assertTrue(SCHEMA.is_valid(json_data)) + self.assertTrue(SERIALIZED_SOI_SCHEMA.is_valid(json_data)) def test_import_soi(self): """Import serialized SOI, check SOI content and delete SOI-file.""" + # Write invalid serialization to file + invalid_soi_file_path = "test_file.json" + invalid_serialization = { + "title": "SOI-test", + "invalid_key": "All keys do not exist", + } + file = open(invalid_soi_file_path, "w") + file.write(str(invalid_serialization)) + file.close() + # Assert invalid serialization throws error on import + with self.assertRaises(ValueError) as err: + import_soi(invalid_soi_file_path) + self.assertEqual( + str(err.exception), "Serialized SOI does not have correct format." + ) + # Import SOI soi = import_soi(self.file_path) @@ -168,5 +133,6 @@ class SerializeTest(unittest.TestCase): self.assertEqual(MODULES[0]["meta"], soi.modules[0]["meta"]) self.assertEqual(MODULES[1]["meta"], soi.modules[1]["meta"]) - # Delete exported SOI-file + # Delete exported SOI-files os.remove(self.file_path) + os.remove(invalid_soi_file_path)