def test_raises_claims_missing(client: Client, entity: Entity) -> None: """Raise TyepError if no claims are provided for endorsement.""" with pytest.raises(TypeError) as excinfo: client.put(entity, endorse=True) assert (str( excinfo.value ) == "missing required keyword argument needed for endorsement: 'claims'")
def test_create_request_with_content_claims( client: Client, endorser: PrivateIdentity, mocked_requests_200: respx.MockTransport, entity: Entity, ) -> None: """Create endorsement request provided by a 3rd party endorser.""" claims = [b"claim-1", b"claim-2"] # Create endorsement request with authorisation of endorser content, authorisation = endorser.endorse(entity, claims) # This will also add the subject holders authorisation client.put( entity, claims=claims, content=content, authorisations=[authorisation], create_claims=True # endorse=True # TODO - provide test that this does not have any effect ) http_request, _ = mocked_requests_200["create_entity"].calls[0] claims_header = json.loads( iov42_decode(http_request.headers["x-iov42-claims"])) assert claims_header == {hashed_claim(c): c.decode() for c in claims}
def test_raise_identity_already_exists_2(client: Client) -> None: """Raise exception when an identity (with an other key) already exists.""" client.identity = PrivateIdentity( CryptoProtocol.SHA256WithECDSA.generate_private_key(), "test-1234") with respx.mock(base_url=PLATFORM_URL) as respx_mock: respx_mock.put( re.compile("/api/v1/requests/.*$"), status_code=400, content= ('{"errors":[{"errorCode":2503,"errorType":"Authorisation",' '"message":"Signature L-IjTeba3wvJn4hHR40GPCG-H7iIeDWOzBo3hCK7x1mLZgif' "SdgR-YVxOZtvPzHaI86WdhIL3y-sNOwYUf2c0j7OfT31dAX71W9le-Cp2Mx1PgjjqI09f" "i0Nku-h5lgipQ07VKAm3gUx0foeG9GdDQe_I85QuCqtJsaAXWDVc8r0NeWpa3dnQEflIm" "W0-gecjO6pYDeyXPALcvp9h8Q_TxkuGVvreqpWvgKzdPMlXHMbN3wYoLNNLM3gpqrqAp" "Eze1aTqtlK6gCQUuhsJlKe4Bb2Nj8MRxXXXNpxIJqjJHM0IRps5J0U8gsnEEcny8Zf0tB" 'h7NGkTteNv554QUbNVA cannot be verified with public credentials of identity test-1234."},' '{"errorCode":2503,"errorType":"Authorisation","message":"Signature ' "L2PIREIx1MZsjV-j0fSMoN3u1eHP2wyqUpAs1mOWdp8k8yrnoBTbyH2Uxw8_9zYTzDHrz" "rI16fNKeRFuLlHosWqzoUf41M0Nip5zbW6gmPYiL05AWPdH1pg9qS-cgQa9IFXiMUkZh9" "EZltT7HHl9aRn35kcwoJYAoPm96Up1YPI0JWISx1iXXEAcxVOA1N_k-l0tT5Tb7lWNOI4" "5eh6flW_vVEeBQDjQhkl94rlP3qDFlDYZ9HZS2A3lTkiIo6MsU57pxeTD9FqwZ8uofJ3O" "Yx05TJKl106GPsscf2mnpnQGEzgS20QsJyqUs_u7dpZbAcjfBsaHucVz8gwkz_PoNg " 'cannot be verified with public credentials of identity test-1234."},' '{"errorCode":2602,"errorType":"AssetExistence",' '"message":"Another identity with address test-1234 already exists"}],' '"requestId":"23343439","proof":"/api/v1/proofs/23343439"}'), ) with pytest.raises(EntityAlreadyExists) as excinfo: client.put(client.identity.public_identity, request_id="1234567") assert str(excinfo.value) == "identity 'test-1234' already exists" assert excinfo.value.request_id == "1234567"
def test_create_request_with_content( client: Client, endorser: PrivateIdentity, mocked_requests_200: respx.MockTransport, entity: Entity, ) -> None: """Create endorsement request provided by a 3rd party endorser.""" claims = [b"claim-1", b"claim-2"] # Create endorsement request with authorisation of endorser content, authorisation = endorser.endorse(entity, claims) # This will also add the subject holders authorisation client.put( entity, claims=claims, content=content, authorisations=[authorisation], # endorse=True # TODO - provide test that this does not have any effect ) http_request, _ = mocked_requests_200["create_entity"].calls[0] request_id = json.loads(content.decode())["requestId"] assert http_request.url.path.rsplit("/", 1)[1] == request_id claims_header = json.loads( iov42_decode(http_request.headers["x-iov42-claims"])) assert claims_header == {} authorisations = json.loads( iov42_decode(http_request.headers["x-iov42-authorisations"].encode())) expected_identities = [a["identityId"] for a in authorisations] assert client.identity.identity_id in expected_identities assert endorser.identity_id in expected_identities
def existing_asset_claims(alice_client: Client, existing_asset: Asset) -> List[bytes]: """Return a list of claims endorsed against an asset owned by Alice.""" claims = [b"asset-claim-1", b"asset-claim-2"] alice_client.put(existing_asset, claims=claims, endorse=True, create_claims=True) return claims
def register_product(client: Client, tag_type: AssetType, product: Tuple[str, str]) -> None: """Register product on iov42 platform and add product information as claim.""" tag_id, claim = product tag = Asset(asset_id=tag_id, asset_type_id=tag_type.asset_type_id) client.put(tag) print(f"Created tag: {tag_id}") client.put(tag, claims=[claim.encode()], endorse=True) print(f"Tag [{tag_id}]: added enrosement on claim '{claim}'")
def test_invalid_request_id(client: Client, entity: Entity, invalid_request_id: str) -> None: """Raise exception if the provided request ID contains invalid charatcers.""" with pytest.raises(ValueError) as excinfo: client.put(entity, request_id=invalid_request_id) # No request is sent assert not respx.calls assert ( str(excinfo.value) == f"invalid identifier '{invalid_request_id}' - valid characters are [a-zA-Z0-9._\\-+]" )
def test_raise_on_request_error(client: Client) -> None: """If raise exception on a request error.""" respx.put( re.compile(PLATFORM_URL + "/api/v1/requests/.*$"), content=httpcore.ConnectError(), ) # TODO: do we really want to leak httpx to our clients? # We could catch all exception thrown by httpx, wrap it in a few library # exceptions and rethrow those. with pytest.raises(httpx.ConnectError): client.put(client.identity.public_identity)
def test_raise_duplicate_request_id(client: Client) -> None: """Raise exception when the request_id already exists.""" with respx.mock(base_url=PLATFORM_URL) as respx_mock: respx_mock.put( re.compile("/api/v1/requests/.*$"), status_code=409, content= ('{"errors":[{"errorCode":2701,"errorType":"RequestId",' '"message":"Found duplicate request id"}],"requestId":"1234567"}' ), ) with pytest.raises(DuplicateRequestId) as excinfo: client.put(client.identity.public_identity, request_id="1234567") assert str(excinfo.value) == "request ID already exists" assert excinfo.value.request_id == "1234567"
def test_header_content_type(client: Client, mocked_requests_200: respx.MockTransport, entity: Entity) -> None: """PUT request content-type is JSON.""" _ = client.put(entity) http_request, _ = mocked_requests_200["create_entity"].calls[0] assert http_request.headers["content-type"] == "application/json"
def test_create_asset_claims_with_endorsement(alice_client: Client, existing_asset: Asset) -> None: """Create asset claims and (self-) endorsements on an unique asset all at once.""" claims = [b"claim-1", b"claim-2"] response = alice_client.put(existing_asset, claims=claims, endorse=True, create_claims=True) prefix = "/".join(( "/api/v1/asset-types", existing_asset.asset_type_id, "assets", existing_asset.asset_id, "claims", )) # Affected resources: for each endorsements we also created the claim. assert len( response.resources) == 2 * len(claims) # type: ignore[union-attr] for c in [hashed_claim(c) for c in claims]: assert "/".join( (prefix, c)) in response.resources # type: ignore[union-attr] assert ("/".join( (prefix, c, "endorsements", alice_client.identity.identity_id)) in response.resources # type: ignore[union-attr] )
def test_response(client: Client, mocked_requests_200: respx.MockTransport, entity: Entity) -> None: """Platform response to the create an entity request.""" request_id = str(uuid.uuid4()) response = client.put(entity, request_id=request_id) assert response.proof == "/api/v1/proofs/" + request_id assert len(response.resources) == 1 # type: ignore[union-attr]
def test_create_asset_type(alice_client: Client, asset_type: AssetType) -> None: """Create an asset types on an iov42 platform.""" response = alice_client.put(asset_type) assert ("/".join(("/api/v1/asset-types", asset_type.asset_type_id )) == response.resources[0] # type: ignore[union-attr] )
def test_call_to_put_endpoint(client: Client, mocked_requests_200: respx.MockTransport, entity: Entity) -> None: """Corret endpoint is called once.""" request_id = str(uuid.uuid4()) _ = client.put(entity, request_id=request_id) http_request, _ = mocked_requests_200["create_entity"].calls[0] assert str(http_request.url).rsplit("/", 1)[1] == request_id
def create_asset_type(ctx: click.core.Context, identity: str, asset_type_id: str, scale: int) -> None: """Create an asset type.""" asset_type = AssetType(asset_type_id) id = _load_identity(identity) client = Client(ctx.obj["url"], id) _ = client.put(asset_type, request_id=ctx.obj["request_id"]) print(f"asset_type_id: {asset_type_id}")
def test_raise_identity_already_exists(client: Client) -> None: """Raise exception when an identity already exists.""" client.identity = PrivateIdentity( CryptoProtocol.SHA256WithECDSA.generate_private_key(), "test-1234") with respx.mock(base_url=PLATFORM_URL) as respx_mock: respx_mock.put( re.compile("/api/v1/requests/.*$"), status_code=400, content= ('{"errors":[{"errorCode":2602,"errorType":"AssetExistence",' '"message":"Another identity with address test-1234 already exists"}],' '"requestId":"1234567","proof":"/api/v1/proofs/23343456"}'), ) with pytest.raises(EntityAlreadyExists) as excinfo: client.put(client.identity.public_identity, request_id="1234567") assert str(excinfo.value) == "identity 'test-1234' already exists" assert excinfo.value.request_id == "1234567"
def create_asset(ctx: click.core.Context, identity: str, asset_type_id: str, asset_id: str) -> None: """Create an asset.""" asset = Asset(asset_type_id=asset_type_id, asset_id=asset_id) id = _load_identity(identity) client = Client(ctx.obj["url"], id) _ = client.put(asset, request_id=ctx.obj["request_id"]) print(f"asset_id: {asset}")
def test_iov42_headers(client: Client, mocked_requests_200: respx.MockTransport, entity: Entity) -> None: """Authentication and authorisations are created with the request.""" _ = client.put(entity) http_request, _ = mocked_requests_200["create_entity"].calls[0] assert "x-iov42-authorisations" in [*http_request.headers] assert "x-iov42-authentication" in [*http_request.headers]
def test_response_identity( client: Client, mocked_requests_200: respx.MockTransport, ) -> None: """Platform response to the request to create an identity.""" response = client.put(client.identity.public_identity) assert response.resources == [ # type: ignore[union-attr] "/api/v1/identities/" + client.identity.identity_id ]
def test_response_asset_type( client: Client, mocked_requests_200: respx.MockTransport, ) -> None: """Platform response to the request to create an asset type.""" entity = AssetType() response = client.put(entity) assert response.resources == [ "/api/v1/asset-types/" + entity.asset_type_id ] # type: ignore[union-attr]
def create_identity(ctx: click.core.Context, identity_id: str, crypto_protocol: str) -> None: """Create an identity.""" private_key = generate_private_key(crypto_protocol) identity = PrivateIdentity(private_key, identity_id) client = Client(ctx.obj["url"], identity) _ = client.put(identity.public_identity, request_id=ctx.obj["request_id"]) print(_identity_json(identity))
def test_generated_request_id(client: Client, mocked_requests_200: respx.MockTransport, entity: Entity) -> None: """If no request ID is provided a UUID is generated.""" _ = client.put(entity) http_request, _ = mocked_requests_200["create_entity"].calls[0] # We have to call read(), otherwise we get an httpx.RequestNoRead reading # the content (see https://github.com/lundberg/respx/issues/83). content = json.loads(http_request.read()) assert uuid.UUID(content["requestId"])
def test_create_asset(alice_client: Client, existing_asset_type_id: str) -> None: """Create an unique asset on an iov42 platform.""" asset = Asset(asset_type_id=existing_asset_type_id) response = alice_client.put(asset) assert ("/".join( ("/api/v1/asset-types", asset.asset_type_id, "assets", asset.asset_id)) == response.resources[0] # type: ignore[union-attr] )
def test_response_asset( client: Client, mocked_requests_200: respx.MockTransport, ) -> None: """Platform response to the request to create an asset type.""" entity = Asset(asset_type_id=str(uuid.uuid4())) response = client.put(entity) assert response.resources == [ # type: ignore[union-attr] "/".join(("/api/v1/asset-types", entity.asset_type_id, "assets", entity.asset_id)) ]
def test_create_account(alice_client: Client, existing_quantifiable_asset_type_id: str) -> None: """Create an account on an iov42 platform.""" account = Asset(asset_type_id=existing_quantifiable_asset_type_id, quantity=0) # type: ignore[arg-type] response = alice_client.put(account) assert ("/".join(( "/api/v1/asset-types", account.asset_type_id, "assets", account.asset_id)) == response.resources[0] # type: ignore[union-attr] )
def test_authentication_header(client: Client, mocked_requests_200: respx.MockTransport, entity: Entity) -> None: """x-iov42-authentication header is signed by the client's identity.""" _ = client.put(entity) http_request, _ = mocked_requests_200["create_entity"].calls[0] authentication = json.loads( iov42_decode(http_request.headers["x-iov42-authentication"].encode())) assert authentication["identityId"] == client.identity.identity_id assert authentication[ "protocolId"] == client.identity.private_key.protocol.name
def test_empty_claims_header( client: Client, mocked_requests_200: respx.MockTransport, entity: Entity, ) -> None: """Request to create endorsements against an entity contains empty 'x-iov42-claims' header.""" claims = [b"claim-1"] _ = client.put(entity, claims=claims, endorse=True) http_request, _ = mocked_requests_200["create_entity"].calls[0] claims_header = json.loads( iov42_decode(http_request.headers["x-iov42-claims"])) assert claims_header == {}
def test_3rd_party_endorsements_on_asset( bob_client: Client, existing_asset: Asset, existing_asset_claims: List[bytes], ) -> None: """Bob endorrses claims on Alice's unique asset.""" response = bob_client.put(existing_asset, claims=existing_asset_claims, endorse=True) for r in response.resources: # type: ignore[union-attr] if "endorsements/" in r: assert "endorsements/" + bob_client.identity.identity_id in r
def test_create_identity_claims_and_endorsements(alice_client: Client) -> None: """Create claims and endorsements against its own identity.""" claims = [b"alice-claim-27", b"alice-claim-28"] response = alice_client.put( alice_client.identity.public_identity, claims=claims, endorse=True, create_claims=True, ) # Affected resources: for each endorsements we also created the claim. assert len( response.resources) == 2 * len(claims) # type: ignore[union-attr]
def test_authorisations_signature(client: Client, mocked_requests_200: respx.MockTransport, entity: Entity) -> None: """Signature of x-iov42-authorisations header is the signed request content.""" _ = client.put(entity) http_request, _ = mocked_requests_200["create_entity"].calls[0] authorisations = json.loads( iov42_decode(http_request.headers["x-iov42-authorisations"].encode())) try: content = http_request.read() client.identity.verify_signature(authorisations[0]["signature"], content) except InvalidSignature: pytest.fail("Signature verification failed")