Skip to content

Commit de3b9b5

Browse files
authored
Merge branch 'main' into dc_demo_local_ngrok
2 parents 94a2f2e + 2bd6f27 commit de3b9b5

File tree

15 files changed

+282
-46
lines changed

15 files changed

+282
-46
lines changed

acapy_agent/ledger/multiple_ledger/ledger_config_schema.py

Lines changed: 72 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,25 @@ def __init__(
2121
self,
2222
*,
2323
id: Optional[str] = None,
24-
is_production: str = True,
25-
genesis_transactions: Optional[str] = None,
26-
genesis_file: Optional[str] = None,
27-
genesis_url: Optional[str] = None,
24+
is_production: bool = True,
25+
is_write: bool = False,
26+
keepalive: int = 5,
27+
read_only: bool = False,
28+
socks_proxy: Optional[str] = None,
29+
pool_name: Optional[str] = None,
30+
endorser_alias: Optional[str] = None,
31+
endorser_did: Optional[str] = None,
2832
):
2933
"""Initialize LedgerConfigInstance."""
30-
self.id = id
34+
self.id = id or str(uuid4())
3135
self.is_production = is_production
32-
self.genesis_transactions = genesis_transactions
33-
self.genesis_file = genesis_file
34-
self.genesis_url = genesis_url
36+
self.is_write = is_write
37+
self.keepalive = keepalive
38+
self.read_only = read_only
39+
self.socks_proxy = socks_proxy
40+
self.pool_name = pool_name or self.id
41+
self.endorser_alias = endorser_alias
42+
self.endorser_did = endorser_did
3543

3644

3745
class LedgerConfigInstanceSchema(BaseModelSchema):
@@ -43,13 +51,46 @@ class Meta:
4351
model_class = LedgerConfigInstance
4452
unknown = EXCLUDE
4553

46-
id = fields.Str(required=False, metadata={"description": "ledger_id"})
47-
is_production = fields.Bool(required=False, metadata={"description": "is_production"})
48-
genesis_transactions = fields.Str(
49-
required=False, metadata={"description": "genesis_transactions"}
54+
id = fields.Str(
55+
required=True,
56+
metadata={
57+
"description": "Ledger identifier. Auto-generated UUID4 if not provided",
58+
"example": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
59+
},
60+
)
61+
is_production = fields.Bool(
62+
required=True, metadata={"description": "Production-grade ledger (true/false)"}
63+
)
64+
is_write = fields.Bool(
65+
required=False,
66+
metadata={"description": "Write capability enabled (default: False)"},
67+
)
68+
keepalive = fields.Int(
69+
required=False,
70+
metadata={
71+
"description": "Keep-alive timeout in seconds for idle connections",
72+
"default": 5,
73+
},
74+
)
75+
read_only = fields.Bool(
76+
required=False, metadata={"description": "Read-only access (default: False)"}
77+
)
78+
socks_proxy = fields.Str(
79+
required=False, metadata={"description": "SOCKS proxy URL (optional)"}
80+
)
81+
pool_name = fields.Str(
82+
required=False,
83+
metadata={
84+
"description": "Ledger pool name (defaults to ledger ID if not specified)",
85+
"example": "bcovrin-test-pool",
86+
},
87+
)
88+
endorser_alias = fields.Str(
89+
required=False, metadata={"description": "Endorser service alias (optional)"}
90+
)
91+
endorser_did = fields.Str(
92+
required=False, metadata={"description": "Endorser DID (optional)"}
5093
)
51-
genesis_file = fields.Str(required=False, metadata={"description": "genesis_file"})
52-
genesis_url = fields.Str(required=False, metadata={"description": "genesis_url"})
5394

5495
@pre_load
5596
def validate_id(self, data, **kwargs):
@@ -58,12 +99,27 @@ def validate_id(self, data, **kwargs):
5899
data["id"] = str(uuid4())
59100
return data
60101

102+
@pre_load
103+
def set_defaults(self, data, **kwargs):
104+
"""Set default values for optional fields."""
105+
data.setdefault("is_write", False)
106+
data.setdefault("keepalive", 5)
107+
data.setdefault("read_only", False)
108+
return data
109+
61110

62111
class LedgerConfigListSchema(OpenAPISchema):
63112
"""Schema for Ledger Config List."""
64113

65-
ledger_config_list = fields.List(
66-
fields.Nested(LedgerConfigInstanceSchema(), required=True), required=True
114+
production_ledgers = fields.List( # Changed from ledger_config_list
115+
fields.Nested(LedgerConfigInstanceSchema(), required=True),
116+
required=True,
117+
metadata={"description": "Production ledgers (may be empty)"},
118+
)
119+
non_production_ledgers = fields.List( # Added new field
120+
fields.Nested(LedgerConfigInstanceSchema(), required=True),
121+
required=True,
122+
metadata={"description": "Non-production ledgers (may be empty)"},
67123
)
68124

69125

acapy_agent/ledger/tests/test_routes.py

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import json
2+
import pytest
3+
import uuid
4+
from marshmallow import ValidationError
25
from typing import Optional
36
from unittest import IsolatedAsyncioTestCase
7+
from uuid_utils import uuid4
48

59
from ...connections.models.conn_record import ConnRecord
610
from ...ledger.base import BaseLedger
@@ -9,7 +13,11 @@
913
from ...ledger.multiple_ledger.ledger_requests_executor import (
1014
IndyLedgerRequestsExecutor,
1115
)
12-
from ...ledger.multiple_ledger.ledger_config_schema import ConfigurableWriteLedgersSchema
16+
from ...ledger.multiple_ledger.ledger_config_schema import (
17+
ConfigurableWriteLedgersSchema,
18+
LedgerConfigInstanceSchema,
19+
LedgerConfigListSchema,
20+
)
1321

1422
from ...multitenant.base import BaseMultitenantManager
1523
from ...multitenant.manager import MultitenantManager
@@ -869,6 +877,110 @@ async def test_get_ledger_config_x(self):
869877
with self.assertRaises(test_module.web.HTTPForbidden):
870878
await test_module.get_ledger_config(self.request)
871879

880+
async def test_get_ledger_config_structure(self):
881+
"""Test the structure of the ledger config response."""
882+
mock_manager = mock.MagicMock(BaseMultipleLedgerManager, autospec=True)
883+
mock_manager.get_prod_ledgers = mock.CoroutineMock(return_value={"test_1": None})
884+
mock_manager.get_nonprod_ledgers = mock.CoroutineMock(
885+
return_value={"test_2": None}
886+
)
887+
self.profile.context.injector.bind_instance(
888+
BaseMultipleLedgerManager, mock_manager
889+
)
890+
891+
self.context.settings["ledger.ledger_config_list"] = [
892+
{
893+
"id": "test_1",
894+
"is_production": True,
895+
"is_write": True,
896+
"keepalive": 5,
897+
"read_only": False,
898+
"pool_name": "test_pool",
899+
"socks_proxy": None,
900+
},
901+
{
902+
"id": "test_2",
903+
"is_production": False,
904+
"is_write": False,
905+
"keepalive": 10,
906+
"read_only": True,
907+
"pool_name": "non_prod_pool",
908+
"socks_proxy": None,
909+
},
910+
]
911+
912+
with mock.patch.object(
913+
test_module.web, "json_response", mock.Mock()
914+
) as json_response:
915+
await test_module.get_ledger_config(self.request)
916+
917+
response_data = json_response.call_args[0][0]
918+
assert "production_ledgers" in response_data
919+
assert "non_production_ledgers" in response_data
920+
921+
prod_ledger = response_data["production_ledgers"][0]
922+
assert prod_ledger == {
923+
"id": "test_1",
924+
"is_production": True,
925+
"is_write": True,
926+
"keepalive": 5,
927+
"read_only": False,
928+
"pool_name": "test_pool",
929+
"socks_proxy": None,
930+
}
931+
932+
non_prod_ledger = response_data["non_production_ledgers"][0]
933+
assert non_prod_ledger == {
934+
"id": "test_2",
935+
"is_production": False,
936+
"is_write": False,
937+
"keepalive": 10,
938+
"read_only": True,
939+
"pool_name": "non_prod_pool",
940+
"socks_proxy": None,
941+
}
942+
943+
async def test_ledger_config_schema_validation(self):
944+
"""Test schema validation for required fields."""
945+
schema = LedgerConfigInstanceSchema()
946+
947+
minimal_data = {
948+
"is_production": True,
949+
"is_write": False,
950+
"keepalive": 5,
951+
"read_only": False,
952+
}
953+
loaded = schema.load(minimal_data)
954+
assert loaded.pool_name == loaded.id
955+
assert loaded.is_write is False
956+
957+
with pytest.raises(ValidationError) as exc:
958+
schema.load({"is_production": "not_bool"})
959+
assert "is_production" in exc.value.messages
960+
961+
async def test_ledger_config_id_generation(self):
962+
"""Test automatic ID generation when missing."""
963+
schema = LedgerConfigInstanceSchema()
964+
965+
data = {
966+
"is_production": True,
967+
"is_write": False, # Add required fields
968+
"keepalive": 5,
969+
"read_only": False,
970+
}
971+
loaded = schema.load(data)
972+
assert uuid.UUID(loaded.id, version=4)
973+
974+
explicit_id = str(uuid4())
975+
loaded = schema.load({"id": explicit_id, "is_production": True})
976+
assert loaded.id == explicit_id
977+
978+
async def test_empty_ledger_lists(self):
979+
schema = LedgerConfigListSchema()
980+
empty_data = {"production_ledgers": [], "non_production_ledgers": []}
981+
loaded = schema.load(empty_data)
982+
assert loaded == empty_data
983+
872984
# Multiple Ledgers Configured
873985
async def test_get_write_ledgers_multiple(self):
874986
# Mock the multiple ledger manager

acapy_agent/wallet/default_verification_key_strategy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ async def get_verification_method_id_for_did(
104104
doc_raw = await resolver.resolve(profile=profile, did=did)
105105
doc = DIDDocument.deserialize(doc_raw)
106106

107-
methods_or_refs = getattr(doc, proof_purpose, [])
107+
methods_or_refs = doc_raw.get(proof_purpose, [])
108108
# Dereference any refs in the verification relationship
109109
methods = [
110110
await resolver.dereference_verification_method(profile, method, document=doc)

acapy_agent/wallet/tests/test_default_verification_key_strategy.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import pytest
33

44
from acapy_agent.resolver.did_resolver import DIDResolver
5+
from ...resolver.tests.test_did_resolver import MockResolver
56

67
from ...did.did_key import DIDKey
78
from ...utils.testing import create_test_profile
@@ -17,6 +18,40 @@ class TestDefaultVerificationKeyStrategy(IsolatedAsyncioTestCase):
1718
async def asyncSetUp(self) -> None:
1819
self.profile = await create_test_profile()
1920
resolver = DIDResolver()
21+
resolver.register_resolver(
22+
MockResolver(
23+
["example"],
24+
resolved={
25+
"@context": [
26+
"https://www.w3.org/ns/did/v1",
27+
"https://w3id.org/security/multikey/v1",
28+
],
29+
"id": "did:example:123",
30+
"verificationMethod": [
31+
{
32+
"id": "did:example:123#key-1",
33+
"type": "Multikey",
34+
"controller": "did:example:123",
35+
"publicKeyMultibase": "z6MkjYXizfaAXTriV3h2Vc9uxJ9AMQpfG7mE1WKMnn1KJvFE",
36+
},
37+
{
38+
"id": "did:example:123#key-2",
39+
"type": "Multikey",
40+
"controller": "did:example:123",
41+
"publicKeyMultibase": "z6MkjYXizfaAXTriV3h2Vc9uxJ9AMQpfG7mE1WKMnn1KJvFE",
42+
},
43+
{
44+
"id": "did:example:123#key-3",
45+
"type": "Ed25519VerificationKey2018",
46+
"controller": "did:example:123",
47+
"publicKeyBase58": "66GgQRKjBvNFNYrKp3C57CbAXqYorEWsKVQRxW3JPhTr",
48+
},
49+
],
50+
"authentication": ["did:example:123#key-1"],
51+
"assertionMethod": ["did:example:123#key-2", "did:example:123#key-3"],
52+
},
53+
)
54+
)
2055
self.profile.context.injector.bind_instance(DIDResolver, resolver)
2156

2257
async def test_with_did_sov(self):
@@ -33,6 +68,27 @@ async def test_with_did_key(self):
3368
== DIDKey.from_did(TEST_DID_KEY).key_id
3469
)
3570

71+
async def test_with_did_for_assertion(self):
72+
strategy = DefaultVerificationKeyStrategy()
73+
assert (
74+
await strategy.get_verification_method_id_for_did(
75+
"did:example:123",
76+
self.profile,
77+
proof_type="Ed25519Signature2020",
78+
proof_purpose="assertionMethod",
79+
)
80+
== "did:example:123#key-2"
81+
)
82+
assert (
83+
await strategy.get_verification_method_id_for_did(
84+
"did:example:123",
85+
self.profile,
86+
proof_type="Ed25519Signature2018",
87+
proof_purpose="assertionMethod",
88+
)
89+
== "did:example:123#key-3"
90+
)
91+
3692
async def test_unsupported_did_method(self):
3793
strategy = DefaultVerificationKeyStrategy()
3894
with pytest.raises(Exception):

conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ def stub_ursa_bbs_signatures() -> Stub:
147147
def pytest_sessionstart(session):
148148
global STUBS, POSTGRES_URL, ENABLE_PTVSD
149149
args = sys.argv
150-
150+
151151
# copied from __main__.py:init_debug
152152
ENABLE_PTVSD = os.getenv("ENABLE_PTVSD", "").lower()
153153
ENABLE_PTVSD = ENABLE_PTVSD and ENABLE_PTVSD not in ("false", "0")

0 commit comments

Comments
 (0)