From 4cf032c21bdaa45a40d5724aa5ae96fd93c70367 Mon Sep 17 00:00:00 2001 From: Paul Hewlett <1104895+eccles@users.noreply.github.com> Date: Mon, 11 Mar 2024 20:04:06 +0000 Subject: [PATCH] Merkle log support (#259) * Merkle log support Confirmation status is now an enumerated type. Confirmation logic tests for both CONFIRMED(simple_hash) or COMMITTED (merkle_log). Added suitable unit ahd func tests. Fixes AB#9103 --- Taskfile.yml | 4 +- archivist/confirmation_status.py | 27 +++++++ archivist/confirmer.py | 41 ++++++---- archivist/constants.py | 5 +- archivist/subjects_confirmer.py | 4 +- functests/execassets.py | 85 ++++++++++++++++++++- scripts/builder.sh | 1 + scripts/functests.sh | 39 +++++----- unittests/testaccess_policies.py | 2 +- unittests/testassets.py | 109 ++++++++++++++++++++++----- unittests/testassetsconstants.py | 40 +++++++++- unittests/testassetslist.py | 14 ++-- unittests/testassetsread.py | 8 +- unittests/testconfirmation_status.py | 98 ++++++++++++++++++++++++ 14 files changed, 401 insertions(+), 76 deletions(-) create mode 100644 archivist/confirmation_status.py create mode 100644 unittests/testconfirmation_status.py diff --git a/Taskfile.yml b/Taskfile.yml index 8fee1f69..dc86435c 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -10,8 +10,6 @@ tasks: desc: Generate about.py cmds: - ./scripts/builder.sh ./scripts/version.sh - status: - - test -s archivist/about.py audit: desc: Audit the code @@ -54,7 +52,7 @@ tasks: - task: check-pyright check-pyright: - desc: Execute pyright conditinally - currently does not work in 3.12 (https://github.com/ekalinin/nodeenv/issues/341) + desc: Execute pyright conditionally - currently does not work in 3.12 (https://github.com/ekalinin/nodeenv/issues/341) cmds: - | if [ "{{.PYVERSION}}" != "3.12" ]; then diff --git a/archivist/confirmation_status.py b/archivist/confirmation_status.py new file mode 100644 index 00000000..042eb3f5 --- /dev/null +++ b/archivist/confirmation_status.py @@ -0,0 +1,27 @@ +"""Archivist Confirmation Status + + Enumerated type that allows user to select the confirmation status option when + creating an asset. + +""" + +# pylint: disable=unused-private-member + +from enum import Enum + + +class ConfirmationStatus(Enum): + """Enumerate confirmation status options""" + + UNSPECIFIED = 0 + # not yet committed + PENDING = 1 + CONFIRMED = 2 + # permanent failure + FAILED = 3 + # forestrie, "its in the db" + STORED = 4 + # forestrie, "you can know if its changed" + COMMITTED = 5 + # forestrie, "You easily prove it was publicly available to all" + UNEQUIVOCAL = 6 diff --git a/archivist/confirmer.py b/archivist/confirmer.py index ee90db91..8f146a5e 100644 --- a/archivist/confirmer.py +++ b/archivist/confirmer.py @@ -15,12 +15,8 @@ from .events import Event, _EventsPublic, _EventsRestricted -from .constants import ( - CONFIRMATION_CONFIRMED, - CONFIRMATION_FAILED, - CONFIRMATION_PENDING, - CONFIRMATION_STATUS, -) +from .confirmation_status import ConfirmationStatus +from .constants import CONFIRMATION_STATUS from .errors import ArchivistUnconfirmedError from .utils import backoff_handler @@ -54,25 +50,29 @@ def __on_giveup_confirmation(details: "Details"): @overload def _wait_for_confirmation( - self: "_AssetsRestricted", identity: str + self: "_AssetsRestricted", + identity: str, ) -> "Asset": ... # pragma: no cover @overload def _wait_for_confirmation( - self: "_AssetsPublic", identity: str + self: "_AssetsPublic", + identity: str, ) -> "Asset": ... # pragma: no cover @overload def _wait_for_confirmation( - self: "_EventsRestricted", identity: str + self: "_EventsRestricted", + identity: str, ) -> "Event": ... # pragma: no cover @overload def _wait_for_confirmation( - self: "_EventsPublic", identity: str + self: "_EventsPublic", + identity: str, ) -> "Event": ... # pragma: no cover @@ -94,12 +94,20 @@ def _wait_for_confirmation(self: Managers, identity: str) -> ReturnTypes: f"cannot confirm {identity} as confirmation_status is not present" ) - if entity[CONFIRMATION_STATUS] == CONFIRMATION_FAILED: + if entity[CONFIRMATION_STATUS] == ConfirmationStatus.FAILED.name: raise ArchivistUnconfirmedError( f"confirmation for {identity} FAILED - this is unusable" ) - if entity[CONFIRMATION_STATUS] == CONFIRMATION_CONFIRMED: + # Simple hash + if entity[CONFIRMATION_STATUS] == ConfirmationStatus.CONFIRMED.name: + return entity + + # merkle_log + if ( + ConfirmationStatus[entity[CONFIRMATION_STATUS]].value + >= ConfirmationStatus.COMMITTED.value + ): return entity return None # pyright: ignore @@ -122,13 +130,16 @@ def __on_giveup_confirmed(details: "Details"): on_giveup=__on_giveup_confirmed, ) def _wait_for_confirmed( - self: PrivateManagers, *, props: "dict[str, Any]|None" = None, **kwargs: Any + self: PrivateManagers, + *, + props: "dict[str, Any]|None" = None, + **kwargs: Any, ) -> bool: """Return False until all entities are confirmed""" # look for unconfirmed entities newprops = deepcopy(props) if props else {} - newprops[CONFIRMATION_STATUS] = CONFIRMATION_PENDING + newprops[CONFIRMATION_STATUS] = ConfirmationStatus.PENDING.name LOGGER.debug("Count unconfirmed entities %s", newprops) count = self.count(props=newprops, **kwargs) @@ -136,7 +147,7 @@ def _wait_for_confirmed( if count == 0: # did any fail newprops = deepcopy(props) if props else {} - newprops[CONFIRMATION_STATUS] = CONFIRMATION_FAILED + newprops[CONFIRMATION_STATUS] = ConfirmationStatus.FAILED.name count = self.count(props=newprops, **kwargs) if count > 0: raise ArchivistUnconfirmedError(f"There are {count} FAILED entities") diff --git a/archivist/constants.py b/archivist/constants.py index a0516d14..b48b6b67 100644 --- a/archivist/constants.py +++ b/archivist/constants.py @@ -18,10 +18,9 @@ HEADERS_TOTAL_COUNT = "X-Total-Count" HEADERS_RETRY_AFTER = "Archivist-Rate-Limit-Reset" +PROOF_MECHANISM = "proof_mechanism" + CONFIRMATION_STATUS = "confirmation_status" -CONFIRMATION_PENDING = "PENDING" -CONFIRMATION_FAILED = "FAILED" -CONFIRMATION_CONFIRMED = "CONFIRMED" APPIDP_SUBPATH = "iam/v1" APPIDP_LABEL = "appidp" diff --git a/archivist/subjects_confirmer.py b/archivist/subjects_confirmer.py index 00fe57c1..f51b3f40 100644 --- a/archivist/subjects_confirmer.py +++ b/archivist/subjects_confirmer.py @@ -9,8 +9,8 @@ if TYPE_CHECKING: from .subjects import Subject, _SubjectsClient +from .confirmation_status import ConfirmationStatus from .constants import ( - CONFIRMATION_CONFIRMED, CONFIRMATION_STATUS, ) from .errors import ArchivistUnconfirmedError @@ -48,7 +48,7 @@ def _wait_for_confirmation(self: "_SubjectsClient", identity: str) -> "Subject": if CONFIRMATION_STATUS not in subject: return None # pyright: ignore - if subject[CONFIRMATION_STATUS] == CONFIRMATION_CONFIRMED: + if subject[CONFIRMATION_STATUS] == ConfirmationStatus.CONFIRMED.name: return subject return None # pyright: ignore diff --git a/functests/execassets.py b/functests/execassets.py index 1857ced3..cc7031f0 100644 --- a/functests/execassets.py +++ b/functests/execassets.py @@ -33,6 +33,13 @@ "some_custom_attribute": "value", } +SIMPLE_HASH = { + "proof_mechanism": ProofMechanism.SIMPLE_HASH.name, +} +MERKLE_LOG = { + "proof_mechanism": ProofMechanism.MERKLE_LOG.name, +} + ASSET_NAME = "Telephone with 2 attachments - one bad or not scanned 2022-03-01" REQUEST_EXISTS_ATTACHMENTS_SIMPLE_HASH = { "selector": [ @@ -121,6 +128,10 @@ def setUp(self): self.attrs = deepcopy(ATTRS) self.traffic_light = deepcopy(ATTRS) self.traffic_light["arc_display_type"] = "Traffic light with violation camera" + self.traffic_light_merkle_log = deepcopy(ATTRS) + self.traffic_light_merkle_log["arc_display_type"] = ( + "Traffic light with violation camera (merkle_log)" + ) def tearDown(self): self.arch.close() @@ -132,6 +143,7 @@ def test_asset_create_simple_hash(self): """ Test asset creation uses simple hash proof mechanism """ + # default is simple hash so it is unspecified asset = self.arch.assets.create( attrs=self.traffic_light, confirm=True, @@ -145,7 +157,25 @@ def test_asset_create_simple_hash(self): tenancy = self.arch.tenancies.publicinfo(asset["tenant_identity"]) LOGGER.debug("tenancy %s", json_dumps(tenancy, sort_keys=True, indent=4)) - def test_asset_create_with_fixtures(self): + def test_asset_create_merkle_log(self): + """ + Test asset creation uses merkle_log proof mechanism + """ + asset = self.arch.assets.create( + props=MERKLE_LOG, + attrs=self.traffic_light_merkle_log, + confirm=True, + ) + LOGGER.debug("asset %s", json_dumps(asset, sort_keys=True, indent=4)) + self.assertEqual( + asset["proof_mechanism"], + ProofMechanism.MERKLE_LOG.name, + msg="Incorrect asset proof mechanism", + ) + tenancy = self.arch.tenancies.publicinfo(asset["tenant_identity"]) + LOGGER.debug("tenancy %s", json_dumps(tenancy, sort_keys=True, indent=4)) + + def test_asset_create_with_fixtures_simple_hash(self): """ Test creation with fixtures """ @@ -196,6 +226,59 @@ def test_asset_create_with_fixtures(self): msg="Incorrect number of fancy_traffic_lights", ) + def test_asset_create_with_fixtures_merkle_log(self): + """ + Test creation with fixtures + """ + # creates simple_hash endpoint + simple_hash = copy(self.arch) + simple_hash.fixtures = { + "assets": { + "proof_mechanism": ProofMechanism.MERKLE_LOG.name, + }, + } + + # create traffic lights endpoint from simple_hash + traffic_lights = copy(simple_hash) + traffic_lights.fixtures = { + "assets": { + "attributes": { + "arc_display_type": "Traffic light with violation camera (merkle_log)", + "arc_namespace": f"functests {uuid4()}", + }, + }, + } + traffic_lights.assets.create( + props=MERKLE_LOG, + attrs=self.attrs, + confirm=True, + ) + self.assertEqual( + traffic_lights.assets.count(), + 1, + msg="Incorrect number of traffic_lights", + ) + + # create fancy traffic lights endpoint from traffic lights + fancy_traffic_lights = copy(traffic_lights) + fancy_traffic_lights.fixtures = { + "assets": { + "attributes": { + "arc_namespace1": f"functests {uuid4()}", + }, + }, + } + fancy_traffic_lights.assets.create( + props=MERKLE_LOG, + attrs=self.attrs, + confirm=True, + ) + self.assertEqual( + fancy_traffic_lights.assets.count(), + 1, + msg="Incorrect number of fancy_traffic_lights", + ) + def test_asset_create_event_merkle_log(self): """ Test list diff --git a/scripts/builder.sh b/scripts/builder.sh index d0a6adeb..e3c38abf 100755 --- a/scripts/builder.sh +++ b/scripts/builder.sh @@ -31,6 +31,7 @@ docker run \ -e DATATRAILS_AUTHTOKEN_FILENAME_2 \ -e DATATRAILS_BLOB_IDENTITY \ -e DATATRAILS_APPREG_CLIENT \ + -e DATATRAILS_APPREG_CLIENT_FILENAME \ -e DATATRAILS_APPREG_SECRET \ -e DATATRAILS_APPREG_SECRET_FILENAME \ -e DATATRAILS_LOGLEVEL \ diff --git a/scripts/functests.sh b/scripts/functests.sh index f81c8141..bcfa8661 100755 --- a/scripts/functests.sh +++ b/scripts/functests.sh @@ -7,36 +7,35 @@ then echo "DATATRAILS_URL is undefined" exit 1 fi +if [ -n "${DATATRAILS_APPREG_CLIENT_FILENAME}" ] +then + if [ -s "${DATATRAILS_APPREG_CLIENT_FILENAME}" ] + then + export DATATRAILS_APPREG_CLIENT=$(cat ${DATATRAILS_APPREG_CLIENT_FILENAME}) + fi +fi if [ -n "${DATATRAILS_APPREG_CLIENT}" ] then if [ -n "${DATATRAILS_APPREG_SECRET_FILENAME}" ] then - if [ ! -s "${DATATRAILS_APPREG_SECRET_FILENAME}" ] + if [ -s "${DATATRAILS_APPREG_SECRET_FILENAME}" ] then - echo "${DATATRAILS_APPREG_SECRET_FILENAME} does not exist" - exit 1 + export DATATRAILS_APPREG_SECRET=$(cat ${DATATRAILS_APPREG_SECRET_FILENAME}) fi - elif [ -z "${DATATRAILS_APPREG_SECRET}" ] - then - echo "Both DATATRAILS_APPREG_SECRET_FILENAME" - echo "and DATATRAILS_APPREG_SECRET are undefined" - exit 1 fi -else - if [ -n "${DATATRAILS_AUTHTOKEN_FILENAME}" ] - then - if [ ! -s "${DATATRAILS_AUTHTOKEN_FILENAME}" ] - then - echo "${DATATRAILS_AUTHTOKEN_FILENAME} does not exist" - exit 1 - fi - elif [ -z "${DATATRAILS_AUTHTOKEN}" ] +fi +if [ -n "${DATATRAILS_AUTHTOKEN_FILENAME}" ] +then + if [ -s "${DATATRAILS_AUTHTOKEN_FILENAME}" ] then - echo "Both DATATRAILS_AUTHTOKEN_FILENAME" - echo "and DATATRAILS_AUTHTOKEN are undefined" - exit 1 + export DATATRAILS_AUTHTOKEN=$(cat ${DATATRAILS_AUTHTOKEN_FILENAME}) fi fi +if [ -z "${DATATRAILS_AUTHTOKEN}" -a -z "${DATATRAILS_APPREG_CLIENT}" -a -z "${DATATRAILS_APPREG_SECRET}" ] +then + echo "No credentials found, DATATRAILS_AUTHTOKEN, DATATRAILS_APPREG_CLIENT, DATATRAILS_APPREG_SECRET" + exit 1 +fi python3 --version diff --git a/unittests/testaccess_policies.py b/unittests/testaccess_policies.py index af661b06..45c91b1c 100644 --- a/unittests/testaccess_policies.py +++ b/unittests/testaccess_policies.py @@ -17,7 +17,7 @@ from archivist.errors import ArchivistBadRequestError from .mock_response import MockResponse -from .testassets import RESPONSE as ASSET +from .testassets import RESPONSE_SIMPLE_HASH as ASSET # pylint: disable=missing-docstring # pylint: disable=protected-access diff --git a/unittests/testassets.py b/unittests/testassets.py index 7305500b..5232ff9d 100644 --- a/unittests/testassets.py +++ b/unittests/testassets.py @@ -32,7 +32,6 @@ REQUEST_FIXTURES_KWARGS_SIMPLE_HASH, REQUEST_KWARGS_MERKLE_LOG, REQUEST_KWARGS_SIMPLE_HASH, - RESPONSE, RESPONSE_ATTACHMENTS, RESPONSE_EXISTS, RESPONSE_EXISTS_ATTACHMENTS, @@ -40,8 +39,11 @@ RESPONSE_FAILED, RESPONSE_FIXTURES, RESPONSE_LOCATION, + RESPONSE_MERKLE_LOG, + RESPONSE_MERKLE_LOG_UNEQUIVOCAL, RESPONSE_NO_CONFIRMATION, RESPONSE_PENDING, + RESPONSE_SIMPLE_HASH, SIMPLE_HASH, SUBPATH, TestAssetsBase, @@ -84,7 +86,7 @@ def test_assets_create(self): Test asset creation """ with mock.patch.object(self.arch.session, "post", autospec=True) as mock_post: - mock_post.return_value = MockResponse(200, **RESPONSE) + mock_post.return_value = MockResponse(200, **RESPONSE_SIMPLE_HASH) asset = self.arch.assets.create( props=SIMPLE_HASH, attrs=ATTRS, confirm=False @@ -102,7 +104,7 @@ def test_assets_create(self): ) self.assertEqual( asset, - RESPONSE, + RESPONSE_SIMPLE_HASH, msg="CREATE incorrect response", ) self.assertEqual( @@ -121,7 +123,7 @@ def test_assets_create_merkle_log(self): Test asset creation """ with mock.patch.object(self.arch.session, "post", autospec=True) as mock_post: - mock_post.return_value = MockResponse(200, **RESPONSE) + mock_post.return_value = MockResponse(200, **RESPONSE_MERKLE_LOG) asset = self.arch.assets.create( props=MERKLE_LOG, attrs=ATTRS, confirm=False @@ -139,7 +141,7 @@ def test_assets_create_merkle_log(self): ) self.assertEqual( asset, - RESPONSE, + RESPONSE_MERKLE_LOG, msg="CREATE incorrect response", ) self.assertEqual( @@ -186,7 +188,7 @@ def test_assets_create_with_confirmation_no_confirmed_status(self): with mock.patch.object( self.arch.session, "post" ) as mock_post, mock.patch.object(self.arch.session, "get") as mock_get: - mock_post.return_value = MockResponse(200, **RESPONSE) + mock_post.return_value = MockResponse(200, **RESPONSE_SIMPLE_HASH) mock_get.return_value = MockResponse(200, **RESPONSE_NO_CONFIRMATION) with self.assertRaises(ArchivistUnconfirmedError): @@ -199,7 +201,7 @@ def test_assets_create_with_confirmation_failed_status(self): with mock.patch.object( self.arch.session, "post" ) as mock_post, mock.patch.object(self.arch.session, "get") as mock_get: - mock_post.return_value = MockResponse(200, **RESPONSE) + mock_post.return_value = MockResponse(200, **RESPONSE_SIMPLE_HASH) mock_get.side_effect = [ MockResponse(200, **RESPONSE_PENDING), MockResponse(200, **RESPONSE_FAILED), @@ -207,6 +209,21 @@ def test_assets_create_with_confirmation_failed_status(self): with self.assertRaises(ArchivistUnconfirmedError): self.arch.assets.create(props=SIMPLE_HASH, attrs=ATTRS, confirm=True) + def test_assets_create_with_confirmation_failed_status_merkle_log(self): + """ + Test asset confirmation + """ + with mock.patch.object( + self.arch.session, "post" + ) as mock_post, mock.patch.object(self.arch.session, "get") as mock_get: + mock_post.return_value = MockResponse(200, **RESPONSE_MERKLE_LOG) + mock_get.side_effect = [ + MockResponse(200, **RESPONSE_PENDING), + MockResponse(200, **RESPONSE_FAILED), + ] + with self.assertRaises(ArchivistUnconfirmedError): + self.arch.assets.create(props=MERKLE_LOG, attrs=ATTRS, confirm=True) + def test_assets_create_with_confirmation_always_pending_status(self): """ Test asset confirmation @@ -214,7 +231,7 @@ def test_assets_create_with_confirmation_always_pending_status(self): with mock.patch.object( self.arch.session, "post" ) as mock_post, mock.patch.object(self.arch.session, "get") as mock_get: - mock_post.return_value = MockResponse(200, **RESPONSE) + mock_post.return_value = MockResponse(200, **RESPONSE_SIMPLE_HASH) mock_get.side_effect = [ MockResponse(200, **RESPONSE_PENDING), MockResponse(200, **RESPONSE_PENDING), @@ -227,27 +244,81 @@ def test_assets_create_with_confirmation_always_pending_status(self): with self.assertRaises(ArchivistUnconfirmedError): self.arch.assets.create(props=SIMPLE_HASH, attrs=ATTRS, confirm=True) + def test_assets_create_with_confirmation_always_pending_status_merkle_log(self): + """ + Test asset confirmation + """ + with mock.patch.object( + self.arch.session, "post" + ) as mock_post, mock.patch.object(self.arch.session, "get") as mock_get: + mock_post.return_value = MockResponse(200, **RESPONSE_MERKLE_LOG) + mock_get.side_effect = [ + MockResponse(200, **RESPONSE_PENDING), + MockResponse(200, **RESPONSE_PENDING), + MockResponse(200, **RESPONSE_PENDING), + MockResponse(200, **RESPONSE_PENDING), + MockResponse(200, **RESPONSE_PENDING), + MockResponse(200, **RESPONSE_PENDING), + MockResponse(200, **RESPONSE_PENDING), + ] + with self.assertRaises(ArchivistUnconfirmedError): + self.arch.assets.create(props=MERKLE_LOG, attrs=ATTRS, confirm=True) + class TestAssetsCreateConfirm(TestAssetsBaseConfirm): """ Test Archivist Assets methods with expected confirmation """ - def test_assets_create_with_confirmation(self): + def test_assets_create_with_confirmation_simple_hash(self): """ Test asset creation """ with mock.patch.object( self.arch.session, "post" ) as mock_post, mock.patch.object(self.arch.session, "get") as mock_get: - mock_post.return_value = MockResponse(200, **RESPONSE) - mock_get.return_value = MockResponse(200, **RESPONSE) + mock_post.return_value = MockResponse(200, **RESPONSE_SIMPLE_HASH) + mock_get.return_value = MockResponse(200, **RESPONSE_SIMPLE_HASH) asset = self.arch.assets.create( props=SIMPLE_HASH, attrs=ATTRS, confirm=True ) self.assertEqual( asset, - RESPONSE, + RESPONSE_SIMPLE_HASH, + msg="CREATE method called incorrectly", + ) + + def test_assets_create_with_confirmation_merkle_log(self): + """ + Test asset creation + """ + with mock.patch.object( + self.arch.session, "post" + ) as mock_post, mock.patch.object(self.arch.session, "get") as mock_get: + mock_post.return_value = MockResponse(200, **RESPONSE_MERKLE_LOG) + mock_get.return_value = MockResponse(200, **RESPONSE_MERKLE_LOG) + asset = self.arch.assets.create(props=MERKLE_LOG, attrs=ATTRS, confirm=True) + self.assertEqual( + asset, + RESPONSE_MERKLE_LOG, + msg="CREATE method called incorrectly", + ) + + def test_assets_create_with_confirmation_merkle_log_unequivocal(self): + """ + Test asset creation + """ + with mock.patch.object( + self.arch.session, "post" + ) as mock_post, mock.patch.object(self.arch.session, "get") as mock_get: + mock_post.return_value = MockResponse( + 200, **RESPONSE_MERKLE_LOG_UNEQUIVOCAL + ) + mock_get.return_value = MockResponse(200, **RESPONSE_MERKLE_LOG_UNEQUIVOCAL) + asset = self.arch.assets.create(props=MERKLE_LOG, attrs=ATTRS, confirm=True) + self.assertEqual( + asset, + RESPONSE_MERKLE_LOG_UNEQUIVOCAL, msg="CREATE method called incorrectly", ) @@ -258,15 +329,15 @@ def test_assets_create_with_explicit_confirmation(self): with mock.patch.object( self.arch.session, "post" ) as mock_post, mock.patch.object(self.arch.session, "get") as mock_get: - mock_post.return_value = MockResponse(200, **RESPONSE) - mock_get.return_value = MockResponse(200, **RESPONSE) + mock_post.return_value = MockResponse(200, **RESPONSE_SIMPLE_HASH) + mock_get.return_value = MockResponse(200, **RESPONSE_SIMPLE_HASH) asset = self.arch.assets.create( props=SIMPLE_HASH, attrs=ATTRS, confirm=False ) self.arch.assets.wait_for_confirmation(asset["identity"]) self.assertEqual( asset, - RESPONSE, + RESPONSE_SIMPLE_HASH, msg="CREATE method called incorrectly", ) @@ -277,17 +348,17 @@ def test_assets_create_with_confirmation_pending_status(self): with mock.patch.object( self.arch.session, "post" ) as mock_post, mock.patch.object(self.arch.session, "get") as mock_get: - mock_post.return_value = MockResponse(200, **RESPONSE) + mock_post.return_value = MockResponse(200, **RESPONSE_SIMPLE_HASH) mock_get.side_effect = [ MockResponse(200, **RESPONSE_PENDING), - MockResponse(200, **RESPONSE), + MockResponse(200, **RESPONSE_SIMPLE_HASH), ] asset = self.arch.assets.create( props=SIMPLE_HASH, attrs=ATTRS, confirm=True ) self.assertEqual( asset, - RESPONSE, + RESPONSE_SIMPLE_HASH, msg="CREATE method called incorrectly", ) @@ -310,7 +381,7 @@ def test_assets_create_if_not_exists_existing_asset(self): RESPONSE_EXISTS, ], ) - mock_post.return_value = MockResponse(200, **RESPONSE) + mock_post.return_value = MockResponse(200, **RESPONSE_SIMPLE_HASH) asset, existed = self.arch.assets.create_if_not_exists( data=REQUEST_EXISTS_SIMPLE_HASH, diff --git a/unittests/testassetsconstants.py b/unittests/testassetsconstants.py index e88d990e..31f98666 100644 --- a/unittests/testassetsconstants.py +++ b/unittests/testassetsconstants.py @@ -159,7 +159,7 @@ }, "verify": True, } -RESPONSE = { +RESPONSE_SIMPLE_HASH = { "identity": IDENTITY, "behaviours": ASSET_BEHAVIOURS, "attributes": { @@ -178,6 +178,44 @@ "confirmation_status": "CONFIRMED", } +RESPONSE_MERKLE_LOG = { + "identity": IDENTITY, + "behaviours": ASSET_BEHAVIOURS, + "attributes": { + "arc_firmware_version": "1.0", + "arc_serial_number": "vtl-x4-07", + "arc_description": "Traffic flow control light at A603 North East", + "arc_display_type": "Traffic light with violation camera", + "some_custom_attribute": "value", + "arc_display_name": ASSET_NAME, + "arc_attachments": [ + TERTIARY_IMAGE, + SECONDARY_IMAGE, + PRIMARY_IMAGE, + ], + }, + "confirmation_status": "COMMITTED", +} + +RESPONSE_MERKLE_LOG_UNEQUIVOCAL = { + "identity": IDENTITY, + "behaviours": ASSET_BEHAVIOURS, + "attributes": { + "arc_firmware_version": "1.0", + "arc_serial_number": "vtl-x4-07", + "arc_description": "Traffic flow control light at A603 North East", + "arc_display_type": "Traffic light with violation camera", + "some_custom_attribute": "value", + "arc_display_name": ASSET_NAME, + "arc_attachments": [ + TERTIARY_IMAGE, + SECONDARY_IMAGE, + PRIMARY_IMAGE, + ], + }, + "confirmation_status": "UNEQUIVOCAL", +} + # case 2 create if not exists REQUEST_EXISTS_SIMPLE_HASH = { "selector": [ diff --git a/unittests/testassetslist.py b/unittests/testassetslist.py index 55ed2f02..962ceb2a 100644 --- a/unittests/testassetslist.py +++ b/unittests/testassetslist.py @@ -13,7 +13,7 @@ from .mock_response import MockResponse from .testassetsconstants import ( - RESPONSE, + RESPONSE_SIMPLE_HASH, SUBPATH, TestAssetsBase, ) @@ -41,7 +41,7 @@ def test_assets_list(self): mock_get.return_value = MockResponse( 200, assets=[ - RESPONSE, + RESPONSE_SIMPLE_HASH, ], ) @@ -54,7 +54,7 @@ def test_assets_list(self): for asset in assets: self.assertEqual( asset, - RESPONSE, + RESPONSE_SIMPLE_HASH, msg="Incorrect asset listed", ) @@ -82,7 +82,7 @@ def test_assets_list_with_params(self): mock_get.return_value = MockResponse( 200, assets=[ - RESPONSE, + RESPONSE_SIMPLE_HASH, ], ) @@ -102,7 +102,7 @@ def test_assets_list_with_params(self): for asset in assets: self.assertEqual( asset, - RESPONSE, + RESPONSE_SIMPLE_HASH, msg="Incorrect asset listed", ) @@ -133,14 +133,14 @@ def test_assets_read_by_signature(self): mock_get.return_value = MockResponse( 200, assets=[ - RESPONSE, + RESPONSE_SIMPLE_HASH, ], ) asset = self.arch.assets.read_by_signature() self.assertEqual( asset, - RESPONSE, + RESPONSE_SIMPLE_HASH, msg="Incorrect asset listed", ) diff --git a/unittests/testassetsread.py b/unittests/testassetsread.py index 308aa1d0..90ad5886 100644 --- a/unittests/testassetsread.py +++ b/unittests/testassetsread.py @@ -17,8 +17,8 @@ from .mock_response import MockResponse from .testassetsconstants import ( IDENTITY, - RESPONSE, RESPONSE_NO_ATTACHMENTS, + RESPONSE_SIMPLE_HASH, SUBPATH, TestAssetsBase, ) @@ -107,7 +107,7 @@ def test_assets_count(self): 200, headers={HEADERS_TOTAL_COUNT: 1}, assets=[ - RESPONSE, + RESPONSE_SIMPLE_HASH, ], ) @@ -142,7 +142,7 @@ def test_assets_count_with_props_params(self): 200, headers={HEADERS_TOTAL_COUNT: 1}, assets=[ - RESPONSE, + RESPONSE_SIMPLE_HASH, ], ) @@ -179,7 +179,7 @@ def test_assets_count_with_attrs_params(self): 200, headers={HEADERS_TOTAL_COUNT: 1}, assets=[ - RESPONSE, + RESPONSE_SIMPLE_HASH, ], ) diff --git a/unittests/testconfirmation_status.py b/unittests/testconfirmation_status.py new file mode 100644 index 00000000..6420008f --- /dev/null +++ b/unittests/testconfirmation_status.py @@ -0,0 +1,98 @@ +""" +Test confirmation status +""" + +# pylint: disable=protected-access + +from unittest import TestCase + +from archivist.confirmation_status import ConfirmationStatus + + +class TestConfirmationStatus(TestCase): + """ + Test confirmation status for archivist + """ + + def assert_confirmation_status(self, name, index): + """ + Test confirmation_status + """ + self.assertEqual(ConfirmationStatus(index).name, name, msg="Incorrect name") + self.assertEqual(ConfirmationStatus[name].value, index, msg="Incorrect index") + + def test_confirmation_status_unspecified(self): + """ + Test confirmation_status + """ + self.assertEqual(ConfirmationStatus.UNSPECIFIED.value, 0, msg="Incorrect value") + self.assertEqual( + ConfirmationStatus.UNSPECIFIED.name, "UNSPECIFIED", msg="Incorrect value" + ) + self.assert_confirmation_status("UNSPECIFIED", 0) + + def test_confirmation_status_pending(self): + """ + Test confirmation_status + """ + self.assertEqual(ConfirmationStatus.PENDING.value, 1, msg="Incorrect value") + self.assertEqual( + ConfirmationStatus.PENDING.name, "PENDING", msg="Incorrect value" + ) + self.assert_confirmation_status("PENDING", 1) + + def test_confirmation_status_confirmed(self): + """ + Test confirmation_status + """ + self.assertEqual(ConfirmationStatus.CONFIRMED.value, 2, msg="Incorrect value") + self.assertEqual( + ConfirmationStatus.CONFIRMED.name, "CONFIRMED", msg="Incorrect value" + ) + self.assert_confirmation_status("CONFIRMED", 2) + + def test_confirmation_status_failed(self): + """ + Test confirmation_status + """ + self.assertEqual(ConfirmationStatus.FAILED.value, 3, msg="Incorrect value") + self.assertEqual( + ConfirmationStatus.FAILED.name, "FAILED", msg="Incorrect value" + ) + self.assert_confirmation_status("FAILED", 3) + + def test_confirmation_status_stored(self): + """ + Test confirmation_status + """ + self.assertEqual(ConfirmationStatus.STORED.value, 4, msg="Incorrect value") + self.assertEqual( + ConfirmationStatus.STORED.name, "STORED", msg="Incorrect value" + ) + self.assert_confirmation_status("STORED", 4) + + def test_confirmation_status_committed(self): + """ + Test confirmation_status + """ + self.assertEqual(ConfirmationStatus.COMMITTED.value, 5, msg="Incorrect value") + self.assertEqual( + ConfirmationStatus.COMMITTED.name, "COMMITTED", msg="Incorrect value" + ) + self.assert_confirmation_status("COMMITTED", 5) + + def test_confirmation_status_unequivocal(self): + """ + Test confirmation_status + """ + self.assertEqual(ConfirmationStatus.UNEQUIVOCAL.value, 6, msg="Incorrect value") + self.assertEqual( + ConfirmationStatus.UNEQUIVOCAL.name, "UNEQUIVOCAL", msg="Incorrect value" + ) + self.assert_confirmation_status("UNEQUIVOCAL", 6) + + def test_confirmation_status_length(self): + """ + Test confirmation_status + """ + self.assertEqual(len(ConfirmationStatus), 7, msg="Incorrect value")