2323import tuf .exceptions
2424from tuf .api .metadata import (
2525 Metadata ,
26+ Root ,
2627 Snapshot ,
2728 Timestamp ,
28- Targets
29+ Targets ,
30+ Key ,
31+ Role ,
32+ Delegations ,
33+ DelegatedRole ,
2934)
3035
3136from tuf .api .serialization import (
@@ -96,6 +101,7 @@ def tearDownClass(cls):
96101
97102 def test_generic_read (self ):
98103 for metadata , inner_metadata_cls in [
104+ ('root' , Root ),
99105 ('snapshot' , Snapshot ),
100106 ('timestamp' , Timestamp ),
101107 ('targets' , Targets )]:
@@ -142,7 +148,7 @@ def test_compact_json(self):
142148
143149
144150 def test_read_write_read_compare (self ):
145- for metadata in ['snapshot' , 'timestamp' , 'targets' ]:
151+ for metadata in ['root' , ' snapshot' , 'timestamp' , 'targets' ]:
146152 path = os .path .join (self .repo_dir , 'metadata' , metadata + '.json' )
147153 metadata_obj = Metadata .from_file (path )
148154
@@ -256,6 +262,18 @@ def test_metadata_snapshot(self):
256262 snapshot .signed .update ('role1' , 2 , 123 , hashes )
257263 self .assertEqual (snapshot .signed .meta , fileinfo )
258264
265+ # Update only version. Length and hashes are optional.
266+ snapshot .signed .update ('role2' , 3 )
267+ fileinfo ['role2.json' ] = {'version' : 3 }
268+ self .assertEqual (snapshot .signed .meta , fileinfo )
269+
270+ # Test from_dict and to_dict without hashes and length.
271+ snapshot_dict = snapshot .to_dict ()
272+ test_dict = snapshot_dict ['signed' ].copy ()
273+ del test_dict ['meta' ]['role1.json' ]['length' ]
274+ del test_dict ['meta' ]['role1.json' ]['hashes' ]
275+ snapshot = Snapshot .from_dict (test_dict )
276+ self .assertEqual (snapshot_dict ['signed' ], snapshot .to_dict ())
259277
260278 def test_metadata_timestamp (self ):
261279 timestamp_path = os .path .join (
@@ -291,6 +309,81 @@ def test_metadata_timestamp(self):
291309 timestamp .signed .update (2 , 520 , hashes )
292310 self .assertEqual (timestamp .signed .meta ['snapshot.json' ], fileinfo )
293311
312+ # Test from_dict and to_dict without hashes and length.
313+ timestamp_dict = timestamp .to_dict ()
314+ test_dict = timestamp_dict ['signed' ].copy ()
315+ del test_dict ['meta' ]['snapshot.json' ]['length' ]
316+ del test_dict ['meta' ]['snapshot.json' ]['hashes' ]
317+ timestamp_test = Timestamp .from_dict (test_dict )
318+ self .assertEqual (timestamp_dict ['signed' ], timestamp_test .to_dict ())
319+
320+ # Update only version. Length and hashes are optional.
321+ timestamp .signed .update (3 )
322+ fileinfo = {'version' : 3 }
323+ self .assertEqual (timestamp .signed .meta ['snapshot.json' ], fileinfo )
324+
325+ def test_key_class (self ):
326+ keys = {
327+ "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d" :{
328+ "keytype" : "ed25519" ,
329+ "keyval" : {
330+ "public" : "edcd0a32a07dce33f7c7873aaffbff36d20ea30787574ead335eefd337e4dacd"
331+ },
332+ "scheme" : "ed25519"
333+ },
334+ }
335+ for key_dict in keys .values ():
336+ # Testing that the workflow of deserializing and serializing
337+ # a key dictionary doesn't change the content.
338+ test_key_dict = key_dict .copy ()
339+ key_obj = Key .from_dict (test_key_dict )
340+ self .assertEqual (key_dict , key_obj .to_dict ())
341+ # Test creating an instance without a required attribute.
342+ for key in key_dict .keys ():
343+ test_key_dict = key_dict .copy ()
344+ del test_key_dict [key ]
345+ with self .assertRaises (KeyError ):
346+ Key .from_dict (test_key_dict )
347+ # Test creating a Key instance with wrong keyval format.
348+ key_dict ["keyval" ] = {}
349+ with self .assertRaises (ValueError ):
350+ Key .from_dict (key_dict )
351+
352+
353+ def test_role_class (self ):
354+ roles = {
355+ "root" : {
356+ "keyids" : [
357+ "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb"
358+ ],
359+ "threshold" : 1
360+ },
361+ "snapshot" : {
362+ "keyids" : [
363+ "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d"
364+ ],
365+ "threshold" : 1
366+ },
367+ }
368+ for role_dict in roles .values ():
369+ # Testing that the workflow of deserializing and serializing
370+ # a role dictionary doesn't change the content.
371+ test_role_dict = role_dict .copy ()
372+ role_obj = Role .from_dict (test_role_dict )
373+ self .assertEqual (role_dict , role_obj .to_dict ())
374+ # Test creating an instance without a required attribute.
375+ for role_attr in role_dict .keys ():
376+ test_role_dict = role_dict .copy ()
377+ del test_role_dict [role_attr ]
378+ with self .assertRaises (KeyError ):
379+ Key .from_dict (test_role_dict )
380+ # Test creating a Role instance with keyid dublicates.
381+ # for keyid in role_dict["keyids"]:
382+ role_dict ["keyids" ].append (role_dict ["keyids" ][0 ])
383+ test_role_dict = role_dict .copy ()
384+ with self .assertRaises (ValueError ):
385+ Role .from_dict (test_role_dict )
386+
294387
295388 def test_metadata_root (self ):
296389 root_path = os .path .join (
@@ -306,23 +399,100 @@ def test_metadata_root(self):
306399 root_key2 ['keytype' ], root_key2 ['scheme' ], root_key2 ['keyval' ])
307400
308401 # Assert that root does not contain the new key
309- self .assertNotIn (keyid , root .signed .roles ['root' ][ ' keyids' ] )
402+ self .assertNotIn (keyid , root .signed .roles ['root' ]. keyids )
310403 self .assertNotIn (keyid , root .signed .keys )
311404
312405 # Add new root key
313406 root .signed .add_key ('root' , keyid , key_metadata )
314407
315408 # Assert that key is added
316- self .assertIn (keyid , root .signed .roles ['root' ][ ' keyids' ] )
409+ self .assertIn (keyid , root .signed .roles ['root' ]. keyids )
317410 self .assertIn (keyid , root .signed .keys )
318411
412+ # Try adding the same key again and assert its ignored.
413+ pre_add_keyid = root .signed .roles ['root' ].keyids .copy ()
414+ root .signed .add_key ('root' , keyid , key_metadata )
415+ self .assertEqual (pre_add_keyid , root .signed .roles ['root' ].keyids )
416+
319417 # Remove the key
320418 root .signed .remove_key ('root' , keyid )
321419
322420 # Assert that root does not contain the new key anymore
323- self .assertNotIn (keyid , root .signed .roles ['root' ][ ' keyids' ] )
421+ self .assertNotIn (keyid , root .signed .roles ['root' ]. keyids )
324422 self .assertNotIn (keyid , root .signed .keys )
325423
424+ with self .assertRaises (KeyError ):
425+ root .signed .remove_key ('root' , 'nosuchkey' )
426+
427+ def test_delegated_role_class (self ):
428+ roles = [
429+ {
430+ "keyids" : [
431+ "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a"
432+ ],
433+ "name" : "role1" ,
434+ "paths" : [
435+ "file3.txt"
436+ ],
437+ "terminating" : False ,
438+ "threshold" : 1
439+ }
440+ ]
441+ for role in roles :
442+ # Testing that the workflow of deserializing and serializing
443+ # a delegation role dictionary doesn't change the content.
444+ key_obj = DelegatedRole .from_dict (role .copy ())
445+ self .assertEqual (role , key_obj .to_dict ())
446+
447+ # Test creating a DelegatedRole object with both "paths" and
448+ # "path_hash_prefixes" set.
449+ role ["path_hash_prefixes" ] = "foo"
450+ with self .assertRaises (ValueError ):
451+ DelegatedRole .from_dict (role .copy ())
452+
453+ # Test creating DelegatedRole only with "path_hash_prefixes"
454+ del role ["paths" ]
455+ DelegatedRole .from_dict (role .copy ())
456+ role ["paths" ] = "foo"
457+
458+ # Test creating DelegatedRole only with "paths"
459+ del role ["path_hash_prefixes" ]
460+ DelegatedRole .from_dict (role .copy ())
461+ role ["path_hash_prefixes" ] = "foo"
462+
463+ # Test creating DelegatedRole without "paths" and
464+ # "path_hash_prefixes" set
465+ del role ["paths" ]
466+ del role ["path_hash_prefixes" ]
467+ DelegatedRole .from_dict (role )
468+
469+
470+ def test_delegation_class (self ):
471+ roles = [
472+ {
473+ "keyids" : [
474+ "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a"
475+ ],
476+ "name" : "role1" ,
477+ "paths" : [
478+ "file3.txt"
479+ ],
480+ "terminating" : False ,
481+ "threshold" : 1
482+ }
483+ ]
484+ keys = {
485+ "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d" :{
486+ "keytype" : "ed25519" ,
487+ "keyval" : {
488+ "public" : "edcd0a32a07dce33f7c7873aaffbff36d20ea30787574ead335eefd337e4dacd"
489+ },
490+ "scheme" : "ed25519"
491+ },
492+ }
493+ delegations_dict = {"keys" : keys , "roles" : roles }
494+ delegations = Delegations .from_dict (copy .deepcopy (delegations_dict ))
495+ self .assertEqual (delegations_dict , delegations .to_dict ())
326496
327497
328498 def test_metadata_targets (self ):
@@ -349,6 +519,13 @@ def test_metadata_targets(self):
349519 # Verify that data is updated
350520 self .assertEqual (targets .signed .targets [filename ], fileinfo )
351521
522+ # Test from_dict/to_dict Targets without delegations
523+ targets_dict = targets .to_dict ()
524+ del targets_dict ["signed" ]["delegations" ]
525+ tmp_dict = targets_dict ["signed" ].copy ()
526+ targets_obj = Targets .from_dict (tmp_dict )
527+ self .assertEqual (targets_dict ["signed" ], targets_obj .to_dict ())
528+
352529 def setup_dict_with_unrecognized_field (self , file_path , field , value ):
353530 json_dict = {}
354531 with open (file_path ) as f :
@@ -365,6 +542,22 @@ def test_support_for_unrecognized_fields(self):
365542 # Test that the metadata classes store unrecognized fields when
366543 # initializing and passes them when casting the instance to a dict.
367544
545+ # Add unrecognized fields to all metadata sub (helper) classes.
546+ if metadata == "root" :
547+ for keyid in dict1 ["signed" ]["keys" ].keys ():
548+ dict1 ["signed" ]["keys" ][keyid ]["d" ] = "c"
549+ for role_str in dict1 ["signed" ]["roles" ].keys ():
550+ dict1 ["signed" ]["roles" ][role_str ]["e" ] = "g"
551+ elif metadata == "targets" and dict1 ["signed" ].get ("delegations" ):
552+ for keyid in dict1 ["signed" ]["delegations" ]["keys" ].keys ():
553+ dict1 ["signed" ]["delegations" ]["keys" ][keyid ]["d" ] = "c"
554+ new_roles = []
555+ for role in dict1 ["signed" ]["delegations" ]["roles" ]:
556+ role ["e" ] = "g"
557+ new_roles .append (role )
558+ dict1 ["signed" ]["delegations" ]["roles" ] = new_roles
559+ dict1 ["signed" ]["delegations" ]["foo" ] = "bar"
560+
368561 temp_copy = copy .deepcopy (dict1 )
369562 metadata_obj = Metadata .from_dict (temp_copy )
370563
0 commit comments