|
13 | 13 | from ...wallet.did_info import DIDInfo
|
14 | 14 | from ...wallet.did_method import SOV, DIDMethod, DIDMethods, HolderDefinedDid
|
15 | 15 | from ...wallet.did_posture import DIDPosture
|
| 16 | +from ...wallet.error import WalletNotFoundError |
16 | 17 | from ...wallet.key_type import ED25519, KeyTypes
|
17 | 18 | from ..endpoint_type import EndpointType
|
18 | 19 | from ..indy_vdr import (
|
|
26 | 27 | VdrError,
|
27 | 28 | )
|
28 | 29 |
|
| 30 | +from ...core.profile import Profile |
| 31 | +from ...storage.askar import AskarStorage |
| 32 | +from ..util import TAA_ACCEPTED_RECORD_TYPE |
| 33 | + |
29 | 34 | WEB = DIDMethod(
|
30 | 35 | name="web",
|
31 | 36 | key_types=[ED25519],
|
32 | 37 | rotation=True,
|
33 | 38 | holder_defined_did=HolderDefinedDid.REQUIRED,
|
34 | 39 | )
|
35 | 40 |
|
| 41 | +TEST_TENANT_DID = "WgWxqztrNooG92RXvxSTWv" |
| 42 | +TEST_SCHEMA_SEQ_NO = 4935 |
| 43 | +TEST_CRED_DEF_TAG = "tenant_tag" |
| 44 | + |
| 45 | +TEST_CRED_DEF_ID = f"{TEST_TENANT_DID}:3:CL:{TEST_SCHEMA_SEQ_NO}:{TEST_CRED_DEF_TAG}" |
| 46 | + |
36 | 47 |
|
37 | 48 | @pytest.fixture()
|
38 | 49 | async def ledger():
|
@@ -1265,3 +1276,245 @@ async def test_rotate_did_keypair(self, ledger: IndyVdrLedger):
|
1265 | 1276 | ):
|
1266 | 1277 | ledger.profile.context.injector.bind_instance(DIDMethods, DIDMethods())
|
1267 | 1278 | await ledger.rotate_public_did_keypair()
|
| 1279 | + |
| 1280 | + async def _create_tenant_profile( |
| 1281 | + self, main_profile: Profile, name: str = "tenant" |
| 1282 | + ) -> Profile: |
| 1283 | + """Helper to create a secondary profile instance for testing.""" |
| 1284 | + tenant_settings = { |
| 1285 | + "wallet.type": main_profile.settings.get("wallet.type", "askar-anoncreds"), |
| 1286 | + "auto_provision": True, |
| 1287 | + "wallet.name": f"{name}_wallet", |
| 1288 | + "wallet.key": f"test_tenant_key_for_{name}", |
| 1289 | + "wallet.key_derivation_method": "RAW", |
| 1290 | + "default_label": name, |
| 1291 | + } |
| 1292 | + tenant_profile = await create_test_profile(settings=tenant_settings) |
| 1293 | + tenant_profile.context.injector.bind_instance( |
| 1294 | + DIDMethods, main_profile.context.injector.inject(DIDMethods) |
| 1295 | + ) |
| 1296 | + tenant_profile.context.injector.bind_instance(BaseCache, InMemoryCache()) |
| 1297 | + tenant_profile.context.injector.bind_instance( |
| 1298 | + KeyTypes, main_profile.context.injector.inject(KeyTypes) |
| 1299 | + ) |
| 1300 | + await tenant_profile.session() |
| 1301 | + return tenant_profile |
| 1302 | + |
| 1303 | + @pytest.mark.asyncio |
| 1304 | + async def test_submit_signing_uses_passed_profile_context( |
| 1305 | + self, ledger: IndyVdrLedger |
| 1306 | + ): |
| 1307 | + """Test _submit calls sign_message using the passed profile context.""" |
| 1308 | + |
| 1309 | + tenant_profile = await self._create_tenant_profile( |
| 1310 | + ledger.profile, "submit_tenant" |
| 1311 | + ) |
| 1312 | + |
| 1313 | + mock_signing_did = DIDInfo("tenant_signer", "tenant_signer_vk", {}, SOV, ED25519) |
| 1314 | + |
| 1315 | + mock_request_obj = mock.MagicMock(spec=indy_vdr.Request) |
| 1316 | + mock_request_obj.signature_input = b"data_to_be_signed" |
| 1317 | + mock_request_obj.body = json.dumps({"req": "data"}) |
| 1318 | + mock_request_obj.set_signature = mock.Mock() |
| 1319 | + mock_request_obj.set_txn_author_agreement_acceptance = mock.Mock() |
| 1320 | + |
| 1321 | + with mock.patch( |
| 1322 | + "acapy_agent.wallet.askar.AskarWallet.sign_message", |
| 1323 | + new_callable=mock.CoroutineMock, |
| 1324 | + return_value=b"mock_signature_from_patch", |
| 1325 | + ) as mock_sign_message_patch: |
| 1326 | + ledger.get_wallet_public_did = mock.CoroutineMock( |
| 1327 | + return_value=mock_signing_did |
| 1328 | + ) |
| 1329 | + ledger.get_latest_txn_author_acceptance = mock.CoroutineMock(return_value={}) |
| 1330 | + |
| 1331 | + async with ledger: |
| 1332 | + await ledger._submit( |
| 1333 | + mock_request_obj, |
| 1334 | + sign=True, |
| 1335 | + sign_did=mock_signing_did, |
| 1336 | + taa_accept=False, |
| 1337 | + write_ledger=False, |
| 1338 | + profile=tenant_profile, |
| 1339 | + ) |
| 1340 | + |
| 1341 | + mock_sign_message_patch.assert_awaited_once_with( |
| 1342 | + message=b"data_to_be_signed", from_verkey=mock_signing_did.verkey |
| 1343 | + ) |
| 1344 | + mock_request_obj.set_signature.assert_called_once_with( |
| 1345 | + b"mock_signature_from_patch" |
| 1346 | + ) |
| 1347 | + |
| 1348 | + @pytest.mark.asyncio |
| 1349 | + async def test_get_wallet_public_did_uses_passed_profile(self, ledger: IndyVdrLedger): |
| 1350 | + """Test get_wallet_public_did uses the explicitly passed profile.""" |
| 1351 | + tenant_profile = await self._create_tenant_profile( |
| 1352 | + ledger.profile, "get_did_tenant" |
| 1353 | + ) |
| 1354 | + mock_tenant_did = DIDInfo("did:sov:tenant_pub", "vk_pub", {}, SOV, ED25519) |
| 1355 | + |
| 1356 | + with mock.patch( |
| 1357 | + "acapy_agent.wallet.askar.AskarWallet.get_public_did", |
| 1358 | + new_callable=mock.CoroutineMock, |
| 1359 | + return_value=mock_tenant_did, |
| 1360 | + ) as mock_get_public_patch: |
| 1361 | + result_did = await ledger.get_wallet_public_did(profile=tenant_profile) |
| 1362 | + |
| 1363 | + assert result_did is mock_tenant_did |
| 1364 | + mock_get_public_patch.assert_awaited_once() |
| 1365 | + |
| 1366 | + @pytest.mark.asyncio |
| 1367 | + async def test_get_latest_taa_uses_passed_profile(self, ledger: IndyVdrLedger): |
| 1368 | + """Test get_latest_txn_author_acceptance uses the explicitly passed profile.""" |
| 1369 | + tenant_profile = await self._create_tenant_profile( |
| 1370 | + ledger.profile, "get_taa_tenant" |
| 1371 | + ) |
| 1372 | + tenant_profile.context.injector.bind_instance(BaseCache, InMemoryCache()) |
| 1373 | + |
| 1374 | + with mock.patch.object( |
| 1375 | + AskarStorage, "find_all_records", return_value=[] |
| 1376 | + ) as mock_find_records_patch: |
| 1377 | + result_taa = await ledger.get_latest_txn_author_acceptance( |
| 1378 | + profile=tenant_profile |
| 1379 | + ) |
| 1380 | + |
| 1381 | + mock_find_records_patch.assert_awaited_once() |
| 1382 | + call_args, _call_kwargs = mock_find_records_patch.call_args |
| 1383 | + assert call_args[0] == TAA_ACCEPTED_RECORD_TYPE |
| 1384 | + assert call_args[1] == {"pool_name": ledger.pool_name} |
| 1385 | + |
| 1386 | + assert result_taa == {} |
| 1387 | + |
| 1388 | + @pytest.mark.asyncio |
| 1389 | + async def test_send_revoc_reg_entry_uses_passed_profile(self, ledger: IndyVdrLedger): |
| 1390 | + """Test send_revoc_reg_entry passes the correct profile to _submit.""" |
| 1391 | + tenant_profile = await self._create_tenant_profile( |
| 1392 | + ledger.profile, "rev_entry_tenant" |
| 1393 | + ) |
| 1394 | + |
| 1395 | + async with tenant_profile.session() as session: |
| 1396 | + wallet = session.inject(BaseWallet) |
| 1397 | + tenant_did = await wallet.create_public_did(SOV, ED25519) |
| 1398 | + |
| 1399 | + test_rev_reg_id = f"{tenant_did.did}:4:{TEST_CRED_DEF_ID}:CL_ACCUM:0" |
| 1400 | + test_reg_entry = {"ver": "1.0", "value": {"accum": "test_accum"}} |
| 1401 | + |
| 1402 | + with ( |
| 1403 | + mock.patch.object( |
| 1404 | + IndyVdrLedger, |
| 1405 | + "get_revoc_reg_def", |
| 1406 | + new=mock.CoroutineMock( |
| 1407 | + return_value={"txn": {"data": {"revocDefType": "CL_ACCUM"}}} |
| 1408 | + ), |
| 1409 | + ), |
| 1410 | + mock.patch.object( |
| 1411 | + IndyVdrLedger, |
| 1412 | + "_create_revoc_reg_entry_request", |
| 1413 | + new=mock.CoroutineMock( |
| 1414 | + return_value=mock.MagicMock(spec=indy_vdr.Request) |
| 1415 | + ), |
| 1416 | + ), |
| 1417 | + mock.patch.object( |
| 1418 | + IndyVdrLedger, |
| 1419 | + "_submit", |
| 1420 | + new=mock.CoroutineMock(return_value={"result": "mock_ok"}), |
| 1421 | + ) as mock_submit, |
| 1422 | + ): |
| 1423 | + async with ledger: |
| 1424 | + await ledger.send_revoc_reg_entry( |
| 1425 | + revoc_reg_id=test_rev_reg_id, |
| 1426 | + revoc_def_type="CL_ACCUM", |
| 1427 | + revoc_reg_entry=test_reg_entry, |
| 1428 | + write_ledger=True, |
| 1429 | + profile=tenant_profile, |
| 1430 | + ) |
| 1431 | + |
| 1432 | + mock_submit.assert_awaited_once() |
| 1433 | + _, submit_kwargs = mock_submit.call_args |
| 1434 | + assert submit_kwargs.get("profile") is tenant_profile |
| 1435 | + assert submit_kwargs.get("sign_did").did == tenant_did.did |
| 1436 | + |
| 1437 | + @pytest.mark.asyncio |
| 1438 | + async def test_ledger_txn_submit_uses_passed_profile(self, ledger: IndyVdrLedger): |
| 1439 | + """Test ledger txn_submit passes profile kwarg to _submit.""" |
| 1440 | + tenant_profile = await self._create_tenant_profile( |
| 1441 | + ledger.profile, "txn_submit_tenant" |
| 1442 | + ) |
| 1443 | + |
| 1444 | + ledger._submit = mock.CoroutineMock( |
| 1445 | + return_value={"op": "REPLY", "result": {"status": "ok"}} |
| 1446 | + ) |
| 1447 | + |
| 1448 | + test_txn_data = '{"req": "data"}' |
| 1449 | + test_sign_did = mock.MagicMock(spec=DIDInfo) |
| 1450 | + |
| 1451 | + await ledger.txn_submit( |
| 1452 | + test_txn_data, |
| 1453 | + sign=True, |
| 1454 | + sign_did=test_sign_did, |
| 1455 | + write_ledger=True, |
| 1456 | + profile=tenant_profile, |
| 1457 | + ) |
| 1458 | + |
| 1459 | + ledger._submit.assert_awaited_once() |
| 1460 | + _submit_args, submit_kwargs = ledger._submit.call_args |
| 1461 | + assert "profile" in submit_kwargs |
| 1462 | + assert submit_kwargs["profile"] is tenant_profile |
| 1463 | + assert _submit_args[0] == test_txn_data |
| 1464 | + assert submit_kwargs["sign"] is True |
| 1465 | + assert submit_kwargs["sign_did"] is test_sign_did |
| 1466 | + assert submit_kwargs["write_ledger"] is True |
| 1467 | + |
| 1468 | + @pytest.mark.asyncio |
| 1469 | + async def test_submit_wallet_not_found_error(self, ledger: IndyVdrLedger): |
| 1470 | + async with ledger.profile.session() as session: |
| 1471 | + wallet = session.inject(BaseWallet) |
| 1472 | + test_did = await wallet.create_public_did(SOV, ED25519) |
| 1473 | + # Create a DID not present in the wallet |
| 1474 | + invalid_did = DIDInfo( |
| 1475 | + did="did:sov:invalid", |
| 1476 | + verkey="invalid_verkey", |
| 1477 | + metadata={}, |
| 1478 | + method=SOV, |
| 1479 | + key_type=ED25519, |
| 1480 | + ) |
| 1481 | + async with ledger: |
| 1482 | + test_msg = indy_vdr.ledger.build_get_txn_request(test_did.did, 1, 1) |
| 1483 | + with pytest.raises(WalletNotFoundError): |
| 1484 | + await ledger._submit( |
| 1485 | + test_msg, sign=True, sign_did=invalid_did, write_ledger=False |
| 1486 | + ) |
| 1487 | + |
| 1488 | + @pytest.mark.asyncio |
| 1489 | + async def test_submit_unexpected_error(self, ledger: IndyVdrLedger): |
| 1490 | + async with ledger.profile.session() as session: |
| 1491 | + wallet = session.inject(BaseWallet) |
| 1492 | + test_did = await wallet.create_public_did(SOV, ED25519) |
| 1493 | + async with ledger: |
| 1494 | + test_msg = indy_vdr.ledger.build_get_txn_request(test_did.did, 1, 1) |
| 1495 | + ledger.pool_handle.submit_request.side_effect = ValueError("Unexpected error") |
| 1496 | + with pytest.raises(LedgerTransactionError) as exc_info: |
| 1497 | + await ledger._submit(test_msg) |
| 1498 | + assert "Unexpected error during ledger submission" in str(exc_info.value) |
| 1499 | + assert isinstance(exc_info.value.__cause__, ValueError) |
| 1500 | + |
| 1501 | + @pytest.mark.asyncio |
| 1502 | + async def test_txn_submit_passes_profile(self, ledger: IndyVdrLedger): |
| 1503 | + tenant_profile = await self._create_tenant_profile( |
| 1504 | + ledger.profile, "submit_tenant" |
| 1505 | + ) |
| 1506 | + test_txn_data = '{"req": "data"}' |
| 1507 | + mock_sign_did = mock.MagicMock(spec=DIDInfo) |
| 1508 | + ledger._submit = mock.CoroutineMock(return_value={"result": "ok"}) |
| 1509 | + |
| 1510 | + await ledger.txn_submit( |
| 1511 | + test_txn_data, |
| 1512 | + sign=True, |
| 1513 | + sign_did=mock_sign_did, |
| 1514 | + write_ledger=True, |
| 1515 | + profile=tenant_profile, |
| 1516 | + ) |
| 1517 | + |
| 1518 | + ledger._submit.assert_awaited_once() |
| 1519 | + _, kwargs = ledger._submit.call_args |
| 1520 | + assert kwargs.get("profile") == tenant_profile |
0 commit comments