python-tuf/tests/test_metadata_serialization.py
Martin Vrachev f34cc7e2cb Metadata API: simplify testing unrecognized_fields
We have merged ADR 8 allowing for unrecognized fields and we have
added tests for that which are too specific and not scalable.

Now, I use table testing which we have used initially in https://github.com/theupdateframework/tuf/pull/1416
to test unrecognized fields support in a cleaner and much more readable
way.

Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
2021-07-01 15:40:47 +03:00

229 lines
9.8 KiB
Python

# Copyright New York University and the TUF contributors
# SPDX-License-Identifier: MIT OR Apache-2.0
""" Unit tests testing tuf/api/metadata.py classes
serialization and deserialization.
"""
import json
import sys
import logging
import unittest
import copy
from typing import Dict, Callable
from tests import utils
from tuf.api.metadata import (
Root,
Snapshot,
Timestamp,
Targets,
Key,
Role,
MetaFile,
TargetFile,
Delegations,
DelegatedRole,
)
logger = logging.getLogger(__name__)
# DataSet is only here so type hints can be used:
# It is a dict of name to test dict
DataSet = Dict[str, str]
# Test runner decorator: Runs the test as a set of N SubTests,
# (where N is number of items in dataset), feeding the actual test
# function one test case at a time
def run_sub_tests_with_dataset(dataset: DataSet):
def real_decorator(function: Callable[["TestSerialization", str], None]):
def wrapper(test_cls: "TestSerialization"):
for case, data in dataset.items():
with test_cls.subTest(case=case):
function(test_cls, data)
return wrapper
return real_decorator
class TestSerialization(unittest.TestCase):
valid_keys: DataSet = {
"all": '{"keytype": "rsa", "scheme": "rsassa-pss-sha256", \
"keyval": {"public": "foo"}}',
"unrecognized field": '{"keytype": "rsa", "scheme": "rsassa-pss-sha256", \
"keyval": {"public": "foo"}, "foo": "bar"}',
"unrecognized field in keyval": '{"keytype": "rsa", "scheme": "rsassa-pss-sha256", \
"keyval": {"public": "foo", "foo": "bar"}}',
}
@run_sub_tests_with_dataset(valid_keys)
def test_key_serialization(self, test_case_data: str):
case_dict = json.loads(test_case_data)
key = Key.from_dict("id", copy.copy(case_dict))
self.assertDictEqual(case_dict, key.to_dict())
valid_roles: DataSet = {
"all": '{"keyids": ["keyid"], "threshold": 3}',
"unrecognized field": '{"keyids": ["keyid"], "threshold": 3, "foo": "bar"}',
}
@run_sub_tests_with_dataset(valid_roles)
def test_role_serialization(self, test_case_data: str):
case_dict = json.loads(test_case_data)
role = Role.from_dict(copy.deepcopy(case_dict))
self.assertDictEqual(case_dict, role.to_dict())
valid_roots: DataSet = {
"all": '{"_type": "root", "spec_version": "1.0.0", "version": 1, \
"expires": "2030-01-01T00:00:00Z", "consistent_snapshot": false, \
"keys": {"keyid" : {"keytype": "rsa", "scheme": "rsassa-pss-sha256", "keyval": {"public": "foo"}}}, \
"roles": { "targets": {"keyids": ["keyid"], "threshold": 3}} \
}',
"no consistent_snapshot": '{ "_type": "root", "spec_version": "1.0.0", "version": 1, \
"expires": "2030-01-01T00:00:00Z", \
"keys": {"keyid" : {"keytype": "rsa", "scheme": "rsassa-pss-sha256", "keyval": {"public": "foo"} }}, \
"roles": { "targets": {"keyids": ["keyid"], "threshold": 3} } \
}',
"unrecognized field": '{"_type": "root", "spec_version": "1.0.0", "version": 1, \
"expires": "2030-01-01T00:00:00Z", "consistent_snapshot": false, \
"keys": {"keyid" : {"keytype": "rsa", "scheme": "rsassa-pss-sha256", "keyval": {"public": "foo"}}}, \
"roles": { "targets": {"keyids": ["keyid"], "threshold": 3}}, \
"foo": "bar"}',
}
@run_sub_tests_with_dataset(valid_roots)
def test_root_serialization(self, test_case_data: str):
case_dict = json.loads(test_case_data)
root = Root.from_dict(copy.deepcopy(case_dict))
self.assertDictEqual(case_dict, root.to_dict())
valid_metafiles: DataSet = {
"all": '{"hashes": {"sha256" : "abc"}, "length": 12, "version": 1}',
"no length": '{"hashes": {"sha256" : "abc"}, "version": 1 }',
"no hashes": '{"length": 12, "version": 1}',
"unrecognized field": '{"hashes": {"sha256" : "abc"}, "length": 12, "version": 1, \
"foo": "bar"}',
}
@run_sub_tests_with_dataset(valid_metafiles)
def test_metafile_serialization(self, test_case_data: str):
case_dict = json.loads(test_case_data)
metafile = MetaFile.from_dict(copy.copy(case_dict))
self.assertDictEqual(case_dict, metafile.to_dict())
valid_timestamps: DataSet = {
"all": '{ "_type": "timestamp", "spec_version": "1.0.0", "version": 1, "expires": "2030-01-01T00:00:00Z", \
"meta": {"snapshot.json": {"hashes": {"sha256" : "abc"}, "version": 1}}}',
"unrecognized field": '{ "_type": "timestamp", "spec_version": "1.0.0", "version": 1, "expires": "2030-01-01T00:00:00Z", \
"meta": {"snapshot.json": {"hashes": {"sha256" : "abc"}, "version": 1}}, "foo": "bar"}',
}
@run_sub_tests_with_dataset(valid_timestamps)
def test_timestamp_serialization(self, test_case_data: str):
case_dict = json.loads(test_case_data)
timestamp = Timestamp.from_dict(copy.deepcopy(case_dict))
self.assertDictEqual(case_dict, timestamp.to_dict())
valid_snapshots: DataSet = {
"all": '{ "_type": "snapshot", "spec_version": "1.0.0", "version": 1, "expires": "2030-01-01T00:00:00Z", \
"meta": { "file.txt": { "hashes": {"sha256" : "abc"}, "version": 1 }}}',
"unrecognized field": '{ "_type": "snapshot", "spec_version": "1.0.0", "version": 1, "expires": "2030-01-01T00:00:00Z", \
"meta": { "file.txt": { "hashes": {"sha256" : "abc"}, "version": 1 }}, "foo": "bar"}',
}
@run_sub_tests_with_dataset(valid_snapshots)
def test_snapshot_serialization(self, test_case_data: str):
case_dict = json.loads(test_case_data)
snapshot = Snapshot.from_dict(copy.deepcopy(case_dict))
self.assertDictEqual(case_dict, snapshot.to_dict())
valid_delegated_roles: DataSet = {
"no hash prefix attribute":
'{"keyids": ["keyid"], "name": "a", "paths": ["fn1", "fn2"], \
"terminating": false, "threshold": 1}',
"no path attribute":
'{"keyids": ["keyid"], "name": "a", "terminating": false, \
"path_hash_prefixes": ["h1", "h2"], "threshold": 99}',
"no hash or path prefix":
'{"keyids": ["keyid"], "name": "a", "terminating": true, "threshold": 3}',
"unrecognized field":
'{"keyids": ["keyid"], "name": "a", "terminating": true, "threshold": 3, "foo": "bar"}',
}
@run_sub_tests_with_dataset(valid_delegated_roles)
def test_delegated_role_serialization(self, test_case_data: str):
case_dict = json.loads(test_case_data)
deserialized_role = DelegatedRole.from_dict(copy.copy(case_dict))
self.assertDictEqual(case_dict, deserialized_role.to_dict())
valid_delegations: DataSet = {
"all": '{"keys": {"keyid" : {"keytype": "rsa", "scheme": "rsassa-pss-sha256", "keyval": {"public": "foo"}}}, \
"roles": [ {"keyids": ["keyid"], "name": "a", "terminating": true, "threshold": 3} ]}',
"unrecognized field":
'{"keys": {"keyid" : {"keytype": "rsa", "scheme": "rsassa-pss-sha256", "keyval": {"public": "foo"}}}, \
"roles": [ {"keyids": ["keyid"], "name": "a", "terminating": true, "threshold": 3} ], \
"foo": "bar"}',
}
@run_sub_tests_with_dataset(valid_delegations)
def test_delegation_serialization(self, test_case_data: str):
case_dict = json.loads(test_case_data)
delegation = Delegations.from_dict(copy.deepcopy(case_dict))
self.assertDictEqual(case_dict, delegation.to_dict())
valid_targetfiles: DataSet = {
"all": '{"length": 12, "hashes": {"sha256" : "abc"}, \
"custom" : {"foo": "bar"} }',
"no custom": '{"length": 12, "hashes": {"sha256" : "abc"}}',
"unrecognized field": '{"length": 12, "hashes": {"sha256" : "abc"}, \
"custom" : {"foo": "bar"}, "foo": "bar"}',
}
@run_sub_tests_with_dataset(valid_targetfiles)
def test_targetfile_serialization(self, test_case_data: str):
case_dict = json.loads(test_case_data)
target_file = TargetFile.from_dict(copy.copy(case_dict))
self.assertDictEqual(case_dict, target_file.to_dict())
valid_targets: DataSet = {
"all attributes": '{"_type": "targets", "spec_version": "1.0.0", "version": 1, "expires": "2030-01-01T00:00:00Z", \
"targets": { "file.txt": {"length": 12, "hashes": {"sha256" : "abc"} } }, \
"delegations": {"keys": {"keyid" : {"keytype": "rsa", \
"scheme": "rsassa-pss-sha256", "keyval": {"public": "foo"} }}, \
"roles": [ {"keyids": ["keyid"], "name": "a", "terminating": true, "threshold": 3} ]} \
}',
"empty targets": '{"_type": "targets", "spec_version": "1.0.0", "version": 1, "expires": "2030-01-01T00:00:00Z", \
"targets": {}, \
"delegations": {"keys": {"keyid" : {"keytype": "rsa", \
"scheme": "rsassa-pss-sha256", "keyval": {"public": "foo"} }}, \
"roles": [ {"keyids": ["keyid"], "name": "a", "terminating": true, "threshold": 3} ]} \
}',
"no delegations": '{"_type": "targets", "spec_version": "1.0.0", "version": 1, "expires": "2030-01-01T00:00:00Z", \
"targets": { "file.txt": {"length": 12, "hashes": {"sha256" : "abc"} } } \
}',
"unrecognized_field": '{"_type": "targets", "spec_version": "1.0.0", "version": 1, "expires": "2030-01-01T00:00:00Z", \
"targets": {}, "foo": "bar"}',
}
@run_sub_tests_with_dataset(valid_targets)
def test_targets_serialization(self, test_case_data):
case_dict = json.loads(test_case_data)
targets = Targets.from_dict(copy.deepcopy(case_dict))
self.assertDictEqual(case_dict, targets.to_dict())
# Run unit test.
if __name__ == '__main__':
utils.configure_test_logging(sys.argv)
unittest.main()