1111import tempfile
1212import unittest
1313import warnings
14+ from pathlib import Path
1415
1516import pyecma376_2
1617from basyx .aas import model
@@ -62,14 +63,14 @@ def test_supplementary_file_container(self) -> None:
6263
6364 # Check metadata
6465 self .assertEqual ("application/pdf" , container .get_content_type ("/TestFile.pdf" ))
65- self .assertEqual ("b18229b24a4ee92c6c2b6bc6a8018563b17472f1150d35d5a5945afeb447ed44 " ,
66+ self .assertEqual ("142a0061de1ef5c22137ab05bb6001335596c0fc8693d33fa9b011ceac652342 " ,
6667 container .get_sha256 ("/TestFile.pdf" ).hex ())
6768 self .assertIn ("/TestFile.pdf" , container )
6869
6970 # Check contents
7071 file_content = io .BytesIO ()
7172 container .write_file ("/TestFile.pdf" , file_content )
72- self .assertEqual (hashlib .sha1 (file_content .getvalue ()).hexdigest (), "78450a66f59d74c073bf6858db340090ea72a8b1 " )
73+ self .assertEqual (hashlib .sha1 (file_content .getvalue ()).hexdigest (), "241e62aef8b4cdad0975f6c68a4ed8b3923d8db1 " )
7374
7475 # Add same file again with different content_type to test reference counting
7576 with open (__file__ , 'rb' ) as f :
@@ -90,6 +91,249 @@ def test_supplementary_file_container(self) -> None:
9091
9192
9293class AASXWriterTest (unittest .TestCase ):
94+ def test_write_missing_aas_objects (self ):
95+ with tempfile .TemporaryDirectory () as tmpdir :
96+ tmpdir_path = Path (tmpdir )
97+
98+ # ---- Arange ----
99+ data = example_aas .create_full_example ()
100+
101+ # ---- Act & Assert -----
102+ with self .assertLogs (level = "WARNING" ) as log :
103+ with aasx .AASXWriter (tmpdir_path / "tmp.aasx" , failsafe = True ) as writer :
104+ # try to write non-existing object
105+ writer .write_aas_objects (
106+ "/aasx/selection.xml" ,
107+ ["https://example.org/Test_AssetAdministrationShell" ,
108+ "http://false-identifier.org/" ,
109+ "http://example.org/Submodels/Assets/TestAsset/Identification" ],
110+ data , aasx .DictSupplementaryFileContainer ()
111+ )
112+
113+ self .assertIn ("Could not find identifiable http://false-identifier.org/ in IdentifiableStore" ,
114+ log .output [0 ])
115+
116+ # assert only the two existing objects have been written to aasx file
117+ object_store = model .DictIdentifiableStore ()
118+ with aasx .AASXReader (tmpdir_path / "tmp.aasx" ) as reader :
119+ reader .read_into (object_store , aasx .DictSupplementaryFileContainer ())
120+ self .assertEqual (len (object_store ), 2 )
121+
122+ def test_writing_with_missing_file (self ) -> None :
123+ with tempfile .TemporaryDirectory () as tmpdir :
124+ tmpdir_path = Path (tmpdir )
125+
126+ # ---- Arange ----
127+ # data contains a submodel with a File submodel_element
128+ # the empty_file_store does not contain the referenced file
129+ data = example_aas .create_full_example ()
130+ empty_file_store = aasx .DictSupplementaryFileContainer ()
131+
132+ # ---- Act & Assert ----
133+ # assert warning is present in failsafe mode
134+ with self .assertLogs (level = "WARNING" ) as log :
135+ with aasx .AASXWriter (tmpdir_path / "tmp.aasx" , failsafe = True ) as writer :
136+ writer .write_all_aas_objects ("/aasx/data.xml" , data , empty_file_store )
137+ self .assertIn ("Could not find file" , log .output [0 ])
138+
139+ # assert exception is rose in non-failsafe mode
140+ with self .assertRaises (KeyError ) as cm :
141+ with aasx .AASXWriter (tmpdir_path / "tmp.aasx" , failsafe = False ) as writer :
142+ writer .write_all_aas_objects ("/aasx/data.xml" , data , empty_file_store )
143+ self .assertIn ("Could not find file" , cm .exception .args [0 ])
144+
145+ def test_writing_file_twice (self ) -> None :
146+ with (tempfile .TemporaryDirectory () as tmpdir ):
147+ tmpdir_path = Path (tmpdir )
148+
149+ # ---- Arange ----
150+ file_store = aasx .DictSupplementaryFileContainer ()
151+ with open (Path (__file__ ).parent / "TestFile.pdf" , "rb" ) as pdf :
152+ resulting_file_name = file_store .add_file ("/TestFile.pdf" , pdf , "application/pdf" )
153+
154+ # create two submodels that reference the same file in file_store
155+ first_submodel = model .Submodel (
156+ id_ = "http://example.org/First_Submodel" ,
157+ submodel_element = [model .File (
158+ id_short = "ExampleFile" ,
159+ content_type = "application/pdf" ,
160+ value = resulting_file_name
161+ )]
162+ )
163+ second_submodel = model .Submodel (
164+ id_ = "http://example.org/SecondSubmodel" ,
165+ submodel_element = [model .File (
166+ id_short = "ExampleFile" ,
167+ content_type = "application/pdf" ,
168+ value = resulting_file_name
169+ )]
170+ )
171+ data : model .DictIdentifiableStore [model .Identifiable ] \
172+ = model .DictIdentifiableStore ([first_submodel , second_submodel ])
173+
174+ # ---- Act & Assert ----
175+ with self .assertNoLogs (level = "WARNING" ):
176+ with aasx .AASXWriter (tmpdir_path / "tmp.aasx" ) as writer :
177+ writer .write_all_aas_objects ("/aasx/data.xml" , data , file_store )
178+
179+ def test_write_non_aas (self ) -> None :
180+ with tempfile .TemporaryDirectory () as tmpdir :
181+ tmpdir_path = Path (tmpdir )
182+
183+ # ---- Arange ----
184+ data = example_aas .create_full_example ()
185+ file_store = aasx .DictSupplementaryFileContainer ()
186+ with open (Path (__file__ ).parent / "TestFile.pdf" , "rb" ) as pdf :
187+ file_store .add_file ("/TestFile.pdf" , pdf , "application/pdf" )
188+
189+ # ---- Act & Assert ----
190+ # assert warning is present in failsafe mode
191+ with self .assertLogs (level = "WARNING" ) as log :
192+ with aasx .AASXWriter (tmpdir_path / "tmp.aasx" , failsafe = True ) as writer :
193+ # try to write a non AAS object
194+ writer .write_aas ("https://example.org/Test_Submodel" , data , file_store )
195+ self .assertIn ("Skipping AAS https://example.org/Test_Submodel" , log .output [0 ])
196+
197+ # assert exception is rose in non-failsafe mode
198+ with self .assertRaises (TypeError ) as cm :
199+ with aasx .AASXWriter (tmpdir_path / "tmp.aasx" , failsafe = False ) as writer :
200+ # try to write a non AAS object
201+ writer .write_aas ("https://example.org/Test_Submodel" , data , file_store )
202+ self .assertIn ("Identifier https://example.org/Test_Submodel does not belong "
203+ "to an AssetAdministrationShell" , cm .exception .args [0 ])
204+
205+ def test_write_aas_missing_submodel (self ) -> None :
206+ with tempfile .TemporaryDirectory () as tmpdir :
207+ tmpdir_path = Path (tmpdir )
208+
209+ # ---- Arange ----
210+ # leave example_submodel out of object store
211+ data = model .DictIdentifiableStore ([
212+ example_aas .create_example_asset_administration_shell (),
213+ example_aas .create_example_asset_identification_submodel (),
214+ example_aas .create_example_bill_of_material_submodel ()
215+ ])
216+ empty_file_store = aasx .DictSupplementaryFileContainer ()
217+
218+ # ---- Act & Assert ----
219+ # assert warning is present in failsafe mode
220+ with self .assertLogs (level = "WARNING" ) as log :
221+ with aasx .AASXWriter (tmpdir_path / "tmp.aasx" , failsafe = True ) as writer :
222+ writer .write_aas ("https://example.org/Test_AssetAdministrationShell" , data , empty_file_store )
223+ self .assertIn ("Could not find Submodel" , log .output [0 ])
224+
225+ # assert exception is rose in non-failsafe mode
226+ with self .assertRaises (KeyError ) as cm :
227+ with aasx .AASXWriter (tmpdir_path / "tmp.aasx" , failsafe = False ) as writer :
228+ writer .write_aas ("https://example.org/Test_AssetAdministrationShell" , data , empty_file_store )
229+ self .assertIn ("Could not find Submodel" , cm .exception .args [0 ])
230+
231+ def test_write_aas_missing_concept_description (self ) -> None :
232+ with tempfile .TemporaryDirectory () as tmpdir :
233+ tmpdir_path = Path (tmpdir )
234+
235+ # ---- Arange ----
236+ # leave example_concept_description out of object store
237+ data = model .DictIdentifiableStore ([
238+ example_aas .create_example_asset_administration_shell (),
239+ example_aas .create_example_submodel (),
240+ example_aas .create_example_asset_identification_submodel (),
241+ example_aas .create_example_bill_of_material_submodel ()
242+ ])
243+ file_store = aasx .DictSupplementaryFileContainer ()
244+ with open (Path (__file__ ).parent / "TestFile.pdf" , "rb" ) as pdf :
245+ file_store .add_file ("/TestFile.pdf" , pdf , "application/pdf" )
246+
247+ # ---- Act & Assert ----
248+ # assert warning is present in failsafe mode
249+ with self .assertLogs (level = "WARNING" ) as log :
250+ with aasx .AASXWriter (tmpdir_path / "tmp.aasx" , failsafe = True ) as writer :
251+ writer .write_aas ("https://example.org/Test_AssetAdministrationShell" , data , file_store )
252+ self .assertIn ("https://example.org/Test_ConceptDescription" , log .output [0 ])
253+ self .assertRegex (log .output [0 ], "ConceptDescription .* not found" )
254+
255+ # assert exception is rose in non-failsafe mode
256+ with self .assertRaises (KeyError ) as cm :
257+ with aasx .AASXWriter (tmpdir_path / "tmp.aasx" , failsafe = False ) as writer :
258+ writer .write_aas ("https://example.org/Test_AssetAdministrationShell" , data , file_store )
259+ self .assertIn ("https://example.org/Test_ConceptDescription" , cm .exception .args [0 ])
260+ self .assertRegex (cm .exception .args [0 ], "ConceptDescription .* not found" )
261+
262+ def test_write_aas_false_semantic_id (self ) -> None :
263+ with tempfile .TemporaryDirectory () as tmpdir :
264+ tmpdir_path = Path (tmpdir )
265+
266+ # ---- Arange ----
267+ # semanticId of submodel holds reference to an object
268+ # that is no ContentDescription
269+ second_submodel = model .Submodel (
270+ id_ = "https://example.org/Second_Submodel"
271+ )
272+ submodel = model .Submodel (
273+ id_ = "https://example.org/Test_Submodel" ,
274+ semantic_id = model .ModelReference (
275+ key = (model .Key (type_ = model .KeyTypes .SUBMODEL , value = "https://example.org/Second_Submodel" ),),
276+ type_ = model .ConceptDescription
277+ )
278+ )
279+ data = model .DictIdentifiableStore ([
280+ example_aas .create_example_asset_administration_shell (),
281+ example_aas .create_example_asset_identification_submodel (),
282+ example_aas .create_example_bill_of_material_submodel (),
283+ submodel , second_submodel
284+ ])
285+ empty_file_store = aasx .DictSupplementaryFileContainer ()
286+
287+ # ---- Act & Assert ----
288+ # assert warning is present in failsafe mode
289+ with self .assertLogs (level = "WARNING" ) as log :
290+ with aasx .AASXWriter (tmpdir_path / "tmp.aasx" , failsafe = True ) as writer :
291+ writer .write_aas ("https://example.org/Test_AssetAdministrationShell" , data , empty_file_store )
292+ self .assertIn ("which is not a ConceptDescription" , log .output [0 ])
293+
294+ # assert exception is rose in non-failsafe mode
295+ with self .assertRaises (TypeError ) as cm :
296+ with aasx .AASXWriter (tmpdir_path / "tmp.aasx" , failsafe = False ) as writer :
297+ writer .write_aas ("https://example.org/Test_AssetAdministrationShell" , data , empty_file_store )
298+ self .assertIn ("which is not a ConceptDescription" , cm .exception .args [0 ])
299+
300+ def test_write_core_properties_twice (self ) -> None :
301+ with tempfile .TemporaryDirectory () as tmpdir :
302+ tmpdir_path = Path (tmpdir )
303+
304+ # ---- Arrange ----
305+ cp = pyecma376_2 .OPCCoreProperties ()
306+ cp .created = datetime .datetime .now ()
307+ cp .creator = "Eclipse BaSyx Python Testing Framework"
308+
309+ # ---- Act & Assert ----
310+ with aasx .AASXWriter (tmpdir_path / "tmp.aasx" ) as writer :
311+ writer .write_core_properties (cp )
312+
313+ # expect RuntimeError on second write
314+ with self .assertRaises (RuntimeError ) as cm :
315+ writer .write_core_properties (cp )
316+
317+ self .assertIn ("Core Properties have already been written" , cm .exception .args [0 ])
318+
319+ def test_write_thumbnail_twice (self ) -> None :
320+ with tempfile .TemporaryDirectory () as tmpdir :
321+ tmpdir_path = Path (tmpdir )
322+
323+ # ---- Arrange ----
324+ with open (Path (__file__ ).parent / "test.png" , "rb" ) as png :
325+ thumbnail = png .read ()
326+
327+ # ---- Act & Assert ----
328+ with aasx .AASXWriter (tmpdir_path / "tmp.aasx" ) as writer :
329+ writer .write_thumbnail ("/aasx/thumbnail.png" , bytearray (thumbnail ), "image/png" )
330+
331+ # expect RuntimeError on second write
332+ with self .assertRaises (RuntimeError ) as cm :
333+ writer .write_thumbnail ("/aasx/thumbnail.png" , bytearray (thumbnail ), "image/png" )
334+
335+ self .assertIn ("package thumbnail has already been written" , cm .exception .args [0 ])
336+
93337 def test_writing_reading_example_aas (self ) -> None :
94338 # Create example data and file_store
95339 data = example_aas .create_full_example () # creates a complete, valid example AAS
@@ -147,7 +391,7 @@ def test_writing_reading_example_aas(self) -> None:
147391 file_content = io .BytesIO ()
148392 new_files .write_file ("/TestFile.pdf" , file_content )
149393 self .assertEqual (hashlib .sha1 (file_content .getvalue ()).hexdigest (),
150- "78450a66f59d74c073bf6858db340090ea72a8b1 " )
394+ "241e62aef8b4cdad0975f6c68a4ed8b3923d8db1 " )
151395
152396 os .unlink (filename )
153397
@@ -207,6 +451,50 @@ def test_reading_core_properties(self) -> None:
207451 finally :
208452 os .unlink (filename )
209453
454+ def test_get_thumbnail (self ) -> None :
455+ with tempfile .TemporaryDirectory () as tmpdir :
456+ # ---- Arange ----
457+ tmpdir_path = Path (tmpdir )
458+
459+ data : model .DictIdentifiableStore [model .Identifiable ] = model .DictIdentifiableStore ([
460+ model .AssetAdministrationShell (
461+ id_ = "http://example.org/Test_AAS" ,
462+ asset_information = model .AssetInformation (
463+ global_asset_id = "http://example.org/Test_Asset"
464+ )
465+ )
466+ ])
467+
468+ with aasx .AASXWriter (tmpdir_path / "test_thumbnail.aasx" ) as writer :
469+ writer .write_aas (
470+ 'http://example.org/Test_AAS' ,
471+ data , aasx .DictSupplementaryFileContainer (), write_json = False
472+ )
473+ with open (Path (__file__ ).parent / "test.png" , "rb" ) as png :
474+ thumbnail = png .read ()
475+ writer .write_thumbnail ("/aasx/thumbnail.png" , bytearray (thumbnail ), "image/png" )
476+
477+ # ---- Act ----
478+ with aasx .AASXReader (tmpdir_path / "test_thumbnail.aasx" ) as reader :
479+ new_thumbnail = reader .get_thumbnail ()
480+
481+ # ---- Assert ----
482+ self .assertEqual (new_thumbnail , thumbnail )
483+
484+ def test_missing_thumbnail (self ) -> None :
485+ # ---- Arange ----
486+ filename = self ._create_test_aasx ()
487+
488+ try :
489+ # ---- Act ----
490+ with aasx .AASXReader (filename ) as reader :
491+ thumbnail = reader .get_thumbnail ()
492+
493+ # ---- Assert ----
494+ self .assertIsNone (thumbnail )
495+ finally :
496+ os .unlink (filename )
497+
210498 def test_read_into (self ) -> None :
211499 filename = self ._create_test_aasx ()
212500
@@ -246,7 +534,7 @@ def test_supplementary_file_integrity(self) -> None:
246534
247535 self .assertEqual (
248536 hashlib .sha1 (buf .getvalue ()).hexdigest (),
249- "78450a66f59d74c073bf6858db340090ea72a8b1 "
537+ "241e62aef8b4cdad0975f6c68a4ed8b3923d8db1 "
250538 )
251539 finally :
252540 os .unlink (filename )
0 commit comments