Skip to content

Commit 6dbcb73

Browse files
authored
Merge pull request #1387 from sechkova/merge-develop-to-experimental-client
2 parents 622a54b + cd60e81 commit 6dbcb73

File tree

8 files changed

+550
-162
lines changed

8 files changed

+550
-162
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ jobs:
6969
run: tox
7070

7171
- name: Publish on coveralls.io
72+
# A failure to publish coverage results on coveralls should not
73+
# be a reason for a job failure.
74+
continue-on-error: true
7275
# TODO: Maybe make 'lint' a separate job instead of case handling here
7376
if: ${{ env.TOXENV != 'lint' }}
7477
env:

requirements-pinned.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ idna==2.10 # via requests
66
pycparser==2.20 # via cffi
77
pynacl==1.4.0 # via securesystemslib
88
requests==2.25.1
9-
securesystemslib[crypto,pynacl]==0.20.0
9+
securesystemslib[crypto,pynacl]==0.20.1
1010
urllib3==1.26.4 # via requests

tests/test_api.py

Lines changed: 198 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,14 @@
2323
import tuf.exceptions
2424
from 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

3136
from 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

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ deps =
2424
# installation (see `skipsdist`), to get relative paths in coverage reports
2525
--editable {toxinidir}
2626

27-
install_command = pip install --pre {opts} {packages}
27+
install_command = pip install {opts} {packages}
2828

2929
# Develop test env to run tests against securesystemslib's master branch
3030
# Must to be invoked explicitly with, e.g. `tox -e with-sslib-master`

0 commit comments

Comments
 (0)