-
Notifications
You must be signed in to change notification settings - Fork 86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Verifiable Credentials #759
base: main
Are you sure you want to change the base?
Changes from 6 commits
c7e1a15
9d549c1
8375c42
8d810fc
0a33121
fd8157d
1d85bbb
8ea12c4
e4293be
60b7663
509ecd1
c0151c2
48b8abc
f476a51
8b82c3d
a24ca5b
4402f6d
22de7a3
f848bbd
cb63c6d
ffcf1bd
e498721
e5a0779
ad4331a
1a974fb
880a670
af67d78
c50d611
2c887aa
8468fcd
bba054d
6c0b958
7f68440
1485ae9
27f8719
485d392
562aa49
d83f0d7
a188c1a
b97d6f3
379620a
e63b49d
5425b37
3e8b825
4106236
6e42798
0719107
f1d2b4e
bf5114a
6d8f1be
8e496e1
5656080
b43aa12
6392351
4ad028b
1f25b01
a870f2d
a6ac0f4
111e12a
8593111
7fa47e1
3d66a36
2d86ea2
f58dc2c
0f7f745
b02f902
b70ac84
7f79596
3814a94
c721a87
c76b755
9b7d17c
5068266
c7a6e2e
344bb49
ee01fc9
09b7d18
87d2af7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
from tests.integration.integration_test_case import IntegrationTestCase | ||
from tests.integration.it_utils import ( | ||
sign_and_reliable_submission_async, | ||
test_async_and_sync, | ||
) | ||
from tests.integration.reusable_values import DESTINATION, WALLET | ||
from xrpl.models.response import ResponseStatus | ||
from xrpl.models.transactions.credential_accept import CredentialAccept | ||
from xrpl.models.transactions.credential_create import CredentialCreate | ||
from xrpl.models.transactions.credential_delete import CredentialDelete | ||
from xrpl.utils import str_to_hex | ||
|
||
_URI = "www.my-id.com/username" | ||
|
||
|
||
class TestCredentialCreate(IntegrationTestCase): | ||
ckeshava marked this conversation as resolved.
Show resolved
Hide resolved
|
||
@test_async_and_sync(globals()) | ||
async def test_valid(self, client): | ||
mvadari marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# Define entities helpful for Credential lifecycle | ||
_ISSUER = WALLET.address | ||
_SUBJECT = DESTINATION.address | ||
|
||
# Disambiguate the sync/async, json/websocket tests with different | ||
# credential type values -- this avoids tecDUPLICATE error | ||
# self.value is defined inside the above decorator | ||
cred_type = str_to_hex("Passport_" + str(self.value)) | ||
|
||
tx = CredentialCreate( | ||
account=_ISSUER, | ||
subject=_SUBJECT, | ||
credential_type=cred_type, | ||
uri=str_to_hex(_URI), | ||
) | ||
response = await sign_and_reliable_submission_async(tx, WALLET, client) | ||
self.assertEqual(response.status, ResponseStatus.SUCCESS) | ||
self.assertEqual(response.result["engine_result"], "tesSUCCESS") | ||
|
||
mvadari marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# Execute the CredentialAccept transaction on the above Credential ledger object | ||
tx = CredentialAccept( | ||
issuer=_ISSUER, account=_SUBJECT, credential_type=cred_type | ||
) | ||
# CredentialAccept transaction is submitted by SUBJECT | ||
response = await sign_and_reliable_submission_async(tx, DESTINATION, client) | ||
self.assertEqual(response.status, ResponseStatus.SUCCESS) | ||
self.assertEqual(response.result["engine_result"], "tesSUCCESS") | ||
|
||
# Execute the CredentialDelete transaction | ||
# Subject initiates the deletion of the Credential ledger object | ||
tx = CredentialDelete( | ||
issuer=_ISSUER, account=_SUBJECT, credential_type=cred_type | ||
) | ||
|
||
response = await sign_and_reliable_submission_async(tx, DESTINATION, client) | ||
self.assertEqual(response.status, ResponseStatus.SUCCESS) | ||
self.assertEqual(response.result["engine_result"], "tesSUCCESS") |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
from unittest import TestCase | ||
|
||
from xrpl.models.exceptions import XRPLModelException | ||
from xrpl.models.transactions.credential_accept import CredentialAccept | ||
from xrpl.utils import str_to_hex | ||
|
||
_ACCOUNT_ISSUER = "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ" | ||
_ACCOUNT_SUBJECT = "rNdY9XDnQ4Dr1EgefwU3CBRuAjt3sAutGg" | ||
_VALID_CREDENTIAL_TYPE = str_to_hex("Passport") | ||
|
||
|
||
class TestCredentialAccept(TestCase): | ||
def test_valid(self): | ||
tx = CredentialAccept( | ||
issuer=_ACCOUNT_ISSUER, | ||
account=_ACCOUNT_SUBJECT, | ||
credential_type=_VALID_CREDENTIAL_TYPE, | ||
) | ||
self.assertTrue(tx.is_valid()) | ||
|
||
ckeshava marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# invalid inputs to the credential_type field | ||
def test_cred_type_field_too_long(self): | ||
with self.assertRaises(XRPLModelException) as error: | ||
CredentialAccept( | ||
issuer=_ACCOUNT_ISSUER, | ||
account=_ACCOUNT_SUBJECT, | ||
credential_type=str_to_hex("A" * 65), | ||
) | ||
self.assertEqual( | ||
error.exception.args[0], | ||
"{'credential_type': 'Length of credential_type field must not be greater " | ||
+ "than 64 bytes. '}", | ||
) | ||
|
||
def test_cred_type_field_empty(self): | ||
with self.assertRaises(XRPLModelException) as error: | ||
CredentialAccept( | ||
issuer=_ACCOUNT_ISSUER, | ||
account=_ACCOUNT_SUBJECT, | ||
credential_type="", | ||
) | ||
self.assertEqual( | ||
error.exception.args[0], | ||
"{'credential_type': 'Length of credential_type field must be greater than " | ||
+ "0. credential_type field must be encoded in base-16 format. '}", | ||
) | ||
|
||
def test_cred_type_field_not_hex(self): | ||
with self.assertRaises(XRPLModelException) as error: | ||
CredentialAccept( | ||
issuer=_ACCOUNT_ISSUER, | ||
account=_ACCOUNT_SUBJECT, | ||
credential_type="Passport", | ||
) | ||
self.assertEqual( | ||
error.exception.args[0], | ||
"{'credential_type': 'credential_type field must be encoded in base-16 " | ||
+ "format. '}", | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider adding boundary test cases. The current tests cover important invalid scenarios, but consider adding these additional test cases:
def test_cred_type_field_exact_length(self):
# Test with exactly 64 bytes
tx = CredentialAccept(
issuer=_ACCOUNT_ISSUER,
account=_ACCOUNT_SUBJECT,
credential_type=str_to_hex("A" * 32), # 32 chars = 64 bytes in hex
)
self.assertTrue(tx.is_valid())
def test_cred_type_field_special_hex(self):
# Test with valid hex containing special characters
with self.assertRaises(XRPLModelException) as error:
CredentialAccept(
issuer=_ACCOUNT_ISSUER,
account=_ACCOUNT_SUBJECT,
credential_type="!@#$%^",
)
def test_cred_type_field_mixed_case(self):
# Test with mixed case hex string
tx = CredentialAccept(
issuer=_ACCOUNT_ISSUER,
account=_ACCOUNT_SUBJECT,
credential_type="aAbBcCdD",
)
self.assertTrue(tx.is_valid()) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
from unittest import TestCase | ||
|
||
from xrpl.models.exceptions import XRPLModelException | ||
from xrpl.models.transactions.credential_create import CredentialCreate | ||
from xrpl.utils import str_to_hex | ||
|
||
_ACCOUNT_ISSUER = "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ" | ||
_ACCOUNT_SUBJECT = "rNdY9XDnQ4Dr1EgefwU3CBRuAjt3sAutGg" | ||
_VALID_CREDENTIAL_TYPE = str_to_hex("Passport") | ||
_VALID_URI = str_to_hex("www.my-id.com/username") | ||
|
||
|
||
class TestCredentialCreate(TestCase): | ||
def test_valid(self): | ||
tx = CredentialCreate( | ||
account=_ACCOUNT_ISSUER, | ||
subject=_ACCOUNT_SUBJECT, | ||
credential_type=_VALID_CREDENTIAL_TYPE, | ||
uri=_VALID_URI, | ||
) | ||
self.assertTrue(tx.is_valid()) | ||
|
||
mvadari marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# invalid URI field inputs | ||
def test_uri_field_too_long(self): | ||
with self.assertRaises(XRPLModelException) as error: | ||
CredentialCreate( | ||
account=_ACCOUNT_ISSUER, | ||
subject=_ACCOUNT_SUBJECT, | ||
credential_type=_VALID_CREDENTIAL_TYPE, | ||
uri=str_to_hex("A" * 257), | ||
) | ||
self.assertEqual( | ||
error.exception.args[0], | ||
"{'uri': 'Length of URI field must not be greater than 256 characters. '}", | ||
) | ||
|
||
def test_uri_field_empty(self): | ||
with self.assertRaises(XRPLModelException) as error: | ||
CredentialCreate( | ||
account=_ACCOUNT_ISSUER, | ||
subject=_ACCOUNT_SUBJECT, | ||
credential_type=_VALID_CREDENTIAL_TYPE, | ||
uri=str_to_hex(""), | ||
) | ||
self.assertEqual( | ||
error.exception.args[0], | ||
"{'uri': 'Length of URI field must be greater than 0. URI field must be " | ||
+ "encoded in base-16 format. '}", | ||
) | ||
|
||
def test_uri_field_not_hex(self): | ||
with self.assertRaises(XRPLModelException) as error: | ||
CredentialCreate( | ||
account=_ACCOUNT_ISSUER, | ||
subject=_ACCOUNT_SUBJECT, | ||
credential_type=_VALID_CREDENTIAL_TYPE, | ||
uri="www.identity-repo.com/username", | ||
) | ||
self.assertEqual( | ||
error.exception.args[0], | ||
"{'uri': 'URI field must be encoded in base-16 format. '}", | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add test for URI with invalid hex characters. The current tests verify empty, too long, and non-hex URIs, but don't test for URIs with invalid hex characters (e.g., "0x1G"). def test_uri_field_invalid_hex(self):
"""Test that a URI with invalid hex characters raises an exception."""
with self.assertRaises(XRPLModelException) as error:
CredentialCreate(
account=_ACCOUNT_ISSUER,
subject=_ACCOUNT_SUBJECT,
credential_type=_VALID_CREDENTIAL_TYPE,
uri="1G", # Invalid hex character
)
self.assertEqual(
error.exception.args[0],
"{'uri': 'URI field must be encoded in base-16 format. '}"
) |
||
|
||
# invalid inputs to the credential_type field | ||
def test_cred_type_field_too_long(self): | ||
with self.assertRaises(XRPLModelException) as error: | ||
CredentialCreate( | ||
account=_ACCOUNT_ISSUER, | ||
subject=_ACCOUNT_SUBJECT, | ||
credential_type=str_to_hex("A" * 65), | ||
uri=_VALID_URI, | ||
) | ||
self.assertEqual( | ||
error.exception.args[0], | ||
"{'credential_type': 'Length of credential_type field must not be greater " | ||
+ "than 64 bytes. '}", | ||
) | ||
|
||
def test_cred_type_field_empty(self): | ||
with self.assertRaises(XRPLModelException) as error: | ||
CredentialCreate( | ||
account=_ACCOUNT_ISSUER, | ||
subject=_ACCOUNT_SUBJECT, | ||
credential_type="", | ||
uri=_VALID_URI, | ||
) | ||
self.assertEqual( | ||
error.exception.args[0], | ||
"{'credential_type': 'Length of credential_type field must be greater than " | ||
+ "0. credential_type field must be encoded in base-16 format. '}", | ||
) | ||
|
||
def test_cred_type_field_not_hex(self): | ||
with self.assertRaises(XRPLModelException) as error: | ||
CredentialCreate( | ||
account=_ACCOUNT_ISSUER, | ||
subject=_ACCOUNT_SUBJECT, | ||
credential_type="Passport", | ||
uri=_VALID_URI, | ||
) | ||
self.assertEqual( | ||
error.exception.args[0], | ||
"{'credential_type': 'credential_type field must be encoded in base-16 " | ||
+ "format. '}", | ||
) | ||
|
||
def test_create_cred_type_object_all_empty_fields(self): | ||
with self.assertRaises(XRPLModelException): | ||
CredentialCreate( | ||
account=_ACCOUNT_ISSUER, | ||
subject=_ACCOUNT_SUBJECT, | ||
credential_type="", | ||
uri="", | ||
) | ||
ckeshava marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
from unittest import TestCase | ||
|
||
from xrpl.models.exceptions import XRPLModelException | ||
from xrpl.models.transactions.credential_delete import CredentialDelete | ||
from xrpl.utils import str_to_hex | ||
|
||
_ACCOUNT_ISSUER = "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ" | ||
_ACCOUNT_SUBJECT = "rNdY9XDnQ4Dr1EgefwU3CBRuAjt3sAutGg" | ||
_VALID_CREDENTIAL_TYPE = str_to_hex("Passport") | ||
|
||
|
||
class TestCredentialDelete(TestCase): | ||
def test_valid(self): | ||
tx = CredentialDelete( | ||
issuer=_ACCOUNT_ISSUER, | ||
account=_ACCOUNT_SUBJECT, | ||
credential_type=_VALID_CREDENTIAL_TYPE, | ||
) | ||
self.assertTrue(tx.is_valid()) | ||
|
||
# alternative specification of the CredentialDelete transaction | ||
tx = CredentialDelete( | ||
account=_ACCOUNT_ISSUER, | ||
subject=_ACCOUNT_SUBJECT, | ||
credential_type=_VALID_CREDENTIAL_TYPE, | ||
) | ||
self.assertTrue(tx.is_valid()) | ||
|
||
def test_unspecified_subject_and_issuer(self): | ||
with self.assertRaises(XRPLModelException) as error: | ||
CredentialDelete( | ||
account=_ACCOUNT_SUBJECT, | ||
credential_type=str_to_hex("DMV_ID"), | ||
) | ||
self.assertEqual( | ||
error.exception.args[0], | ||
"{'invalid_params': 'CredentialDelete transaction requires at least one " | ||
+ "input amongst issuer or subject. '}", | ||
) | ||
|
||
# invalid inputs to the credential_type field | ||
def test_cred_type_field_too_long(self): | ||
with self.assertRaises(XRPLModelException) as error: | ||
CredentialDelete( | ||
issuer=_ACCOUNT_ISSUER, | ||
account=_ACCOUNT_SUBJECT, | ||
credential_type=str_to_hex("A" * 65), | ||
) | ||
self.assertEqual( | ||
error.exception.args[0], | ||
"{'credential_type': 'Length of credential_type field must not be greater " | ||
+ "than 64 bytes. '}", | ||
) | ||
|
||
def test_cred_type_field_empty(self): | ||
with self.assertRaises(XRPLModelException) as error: | ||
CredentialDelete( | ||
issuer=_ACCOUNT_ISSUER, | ||
account=_ACCOUNT_SUBJECT, | ||
credential_type="", | ||
) | ||
self.assertEqual( | ||
error.exception.args[0], | ||
"{'credential_type': 'Length of credential_type field must be greater than " | ||
+ "0. credential_type field must be encoded in base-16 format. '}", | ||
) | ||
|
||
def test_cred_type_field_not_hex(self): | ||
with self.assertRaises(XRPLModelException) as error: | ||
CredentialDelete( | ||
issuer=_ACCOUNT_ISSUER, | ||
account=_ACCOUNT_SUBJECT, | ||
credential_type="Passport", | ||
) | ||
self.assertEqual( | ||
error.exception.args[0], | ||
"{'credential_type': 'credential_type field must be encoded in base-16 " | ||
+ "format. '}", | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider adding more edge cases to improve test coverage. The current error case tests are well-implemented, but consider adding these additional test cases for more comprehensive coverage:
Example additional test: def test_invalid_account_address(self):
with self.assertRaises(XRPLModelException) as error:
CredentialDelete(
issuer="invalid_address",
account=_ACCOUNT_SUBJECT,
credential_type=_VALID_CREDENTIAL_TYPE,
)
self.assertEqual(
error.exception.args[0],
"{'issuer': 'Invalid account address format'}"
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is unnecessary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in f2651ca