|
10 | 10 | ) |
11 | 11 | from fastapi.testclient import TestClient |
12 | 12 | from pytest_mock import MockerFixture |
| 13 | +from sqlalchemy.ext.asyncio import AsyncSession |
13 | 14 |
|
| 15 | +from app.core.checkout import schemas_checkout |
14 | 16 | from app.core.groups import models_groups |
15 | | -from app.core.groups.groups_type import GroupType |
| 17 | +from app.core.groups.groups_type import AccountType, GroupType |
16 | 18 | from app.core.memberships import models_memberships |
17 | | -from app.core.mypayment import cruds_mypayment, models_mypayment |
| 19 | +from app.core.mypayment import cruds_mypayment, models_mypayment, schemas_mypayment |
18 | 20 | from app.core.mypayment.coredata_mypayment import ( |
19 | 21 | MyPaymentBankAccountHolder, |
20 | 22 | ) |
21 | | -from app.core.mypayment.schemas_mypayment import QRCodeContentData |
| 23 | +from app.core.mypayment.endpoints_mypayment import MyPaymentPermissions |
| 24 | +from app.core.mypayment.schemas_mypayment import ( |
| 25 | + QRCodeContentData, |
| 26 | + RequestValidation, |
| 27 | + RequestValidationData, |
| 28 | +) |
22 | 29 | from app.core.mypayment.types_mypayment import ( |
| 30 | + MyPaymentCallType, |
| 31 | + RequestStatus, |
23 | 32 | TransactionStatus, |
24 | 33 | TransactionType, |
25 | 34 | TransferType, |
26 | 35 | WalletDeviceStatus, |
27 | 36 | WalletType, |
28 | 37 | ) |
29 | | -from app.core.mypayment.utils_mypayment import LATEST_TOS |
| 38 | +from app.core.mypayment.utils_mypayment import LATEST_TOS, validate_transfer_callback |
| 39 | +from app.core.permissions import models_permissions |
30 | 40 | from app.core.users import models_users |
| 41 | +from app.types.module import Module |
31 | 42 | from tests.commons import ( |
32 | 43 | add_coredata_to_db, |
33 | 44 | add_object_to_db, |
|
37 | 48 | get_TestingSessionLocal, |
38 | 49 | ) |
39 | 50 |
|
| 51 | +TEST_MODULE_ROOT = "tests" |
| 52 | + |
40 | 53 | bde_group: models_groups.CoreGroup |
41 | 54 |
|
42 | 55 | admin_user: models_users.CoreUser |
|
72 | 85 | store3: models_mypayment.Store |
73 | 86 | store_wallet_device_private_key: Ed25519PrivateKey |
74 | 87 | store_wallet_device: models_mypayment.WalletDevice |
| 88 | +store_direct_transfer: models_mypayment.Transfer |
75 | 89 |
|
76 | 90 |
|
77 | 91 | transaction_from_ecl_user_to_store: models_mypayment.Transaction |
|
88 | 102 | invoice2_detail: models_mypayment.InvoiceDetail |
89 | 103 | invoice3_detail: models_mypayment.InvoiceDetail |
90 | 104 |
|
| 105 | +proposed_request: models_mypayment.Request |
| 106 | +expired_request: models_mypayment.Request |
| 107 | +refused_request: models_mypayment.Request |
| 108 | + |
91 | 109 | store_seller_can_bank_user: models_users.CoreUser |
92 | 110 | store_seller_no_permission_user_access_token: str |
93 | 111 | store_seller_can_bank_user_access_token: str |
|
103 | 121 |
|
104 | 122 | @pytest_asyncio.fixture(scope="module", autouse=True) |
105 | 123 | async def init_objects() -> None: |
| 124 | + for account_type in AccountType: |
| 125 | + await add_object_to_db( |
| 126 | + models_permissions.CorePermissionAccountType( |
| 127 | + permission_name=MyPaymentPermissions.access_payment.value, |
| 128 | + account_type=account_type, |
| 129 | + ), |
| 130 | + ) |
| 131 | + |
106 | 132 | global bde_group |
107 | 133 | bde_group = await create_groups_with_permissions( |
108 | 134 | [], |
@@ -352,6 +378,21 @@ async def init_objects() -> None: |
352 | 378 | ) |
353 | 379 | await add_object_to_db(store_wallet_device) |
354 | 380 |
|
| 381 | + global store_direct_transfer |
| 382 | + store_direct_transfer = models_mypayment.Transfer( |
| 383 | + id=uuid4(), |
| 384 | + type=TransferType.HELLO_ASSO, |
| 385 | + transfer_identifier=str(uuid4()), |
| 386 | + approver_user_id=None, |
| 387 | + wallet_id=store_wallet.id, |
| 388 | + total=1500, # 15€ |
| 389 | + creation=datetime.now(UTC), |
| 390 | + confirmed=False, |
| 391 | + module=TEST_MODULE_ROOT, |
| 392 | + object_id=uuid4(), |
| 393 | + ) |
| 394 | + await add_object_to_db(store_direct_transfer) |
| 395 | + |
355 | 396 | # Create test transactions |
356 | 397 | global transaction_from_ecl_user_to_store |
357 | 398 | transaction_from_ecl_user_to_store = models_mypayment.Transaction( |
@@ -430,6 +471,8 @@ async def init_objects() -> None: |
430 | 471 | total=1000, # 10€ |
431 | 472 | creation=datetime.now(UTC), |
432 | 473 | confirmed=True, |
| 474 | + module=None, |
| 475 | + object_id=None, |
433 | 476 | ) |
434 | 477 | await add_object_to_db(ecl_user_transfer) |
435 | 478 |
|
@@ -599,6 +642,50 @@ async def init_objects() -> None: |
599 | 642 | ) |
600 | 643 | await add_object_to_db(invoice3_detail) |
601 | 644 |
|
| 645 | + global proposed_request, expired_request, refused_request |
| 646 | + proposed_request = models_mypayment.Request( |
| 647 | + id=uuid4(), |
| 648 | + wallet_id=ecl_user_wallet.id, |
| 649 | + store_id=store.id, |
| 650 | + total=1000, |
| 651 | + name="Proposed Request", |
| 652 | + store_note="Proposed Request Note", |
| 653 | + status=RequestStatus.PROPOSED, |
| 654 | + module=TEST_MODULE_ROOT, |
| 655 | + object_id=uuid4(), |
| 656 | + transaction_id=None, |
| 657 | + creation=datetime.now(UTC), |
| 658 | + ) |
| 659 | + await add_object_to_db(proposed_request) |
| 660 | + expired_request = models_mypayment.Request( |
| 661 | + id=uuid4(), |
| 662 | + wallet_id=ecl_user_wallet.id, |
| 663 | + store_id=store.id, |
| 664 | + total=1000, |
| 665 | + name="Expired Request", |
| 666 | + store_note="Expired Request Note", |
| 667 | + status=RequestStatus.EXPIRED, |
| 668 | + module=TEST_MODULE_ROOT, |
| 669 | + object_id=uuid4(), |
| 670 | + transaction_id=None, |
| 671 | + creation=datetime.now(UTC) - timedelta(days=30), |
| 672 | + ) |
| 673 | + await add_object_to_db(expired_request) |
| 674 | + refused_request = models_mypayment.Request( |
| 675 | + id=uuid4(), |
| 676 | + wallet_id=ecl_user_wallet.id, |
| 677 | + store_id=store.id, |
| 678 | + total=1000, |
| 679 | + name="Refused Request", |
| 680 | + store_note="Refused Request Note", |
| 681 | + status=RequestStatus.REFUSED, |
| 682 | + module=TEST_MODULE_ROOT, |
| 683 | + object_id=uuid4(), |
| 684 | + transaction_id=None, |
| 685 | + creation=datetime.now(UTC) - timedelta(days=30), |
| 686 | + ) |
| 687 | + await add_object_to_db(refused_request) |
| 688 | + |
602 | 689 |
|
603 | 690 | async def test_get_structures(client: TestClient): |
604 | 691 | response = client.get( |
@@ -3211,3 +3298,130 @@ async def test_delete_invoice( |
3211 | 3298 | ) |
3212 | 3299 | assert response.status_code == 200 |
3213 | 3300 | assert not any(invoice["id"] == invoice3.id for invoice in response.json()) |
| 3301 | + |
| 3302 | + |
| 3303 | +async def mypayment_callback( |
| 3304 | + object_id: UUID, |
| 3305 | + db: AsyncSession, |
| 3306 | +) -> None: |
| 3307 | + pass |
| 3308 | + |
| 3309 | + |
| 3310 | +async def test_get_request( |
| 3311 | + client: TestClient, |
| 3312 | +): |
| 3313 | + response = client.get( |
| 3314 | + "/mypayment/requests", |
| 3315 | + headers={"Authorization": f"Bearer {ecl_user_access_token}"}, |
| 3316 | + ) |
| 3317 | + assert response.status_code == 200 |
| 3318 | + assert len(response.json()) == 1 |
| 3319 | + assert response.json()[0]["id"] == str(proposed_request.id) |
| 3320 | + |
| 3321 | + |
| 3322 | +async def test_get_request_with_used_filter( |
| 3323 | + client: TestClient, |
| 3324 | +): |
| 3325 | + response = client.get( |
| 3326 | + "/mypayment/requests?used=true", |
| 3327 | + headers={"Authorization": f"Bearer {ecl_user_access_token}"}, |
| 3328 | + ) |
| 3329 | + assert response.status_code == 200 |
| 3330 | + assert len(response.json()) == 3 |
| 3331 | + |
| 3332 | + |
| 3333 | +async def test_accept_request( |
| 3334 | + mocker: MockerFixture, |
| 3335 | + client: TestClient, |
| 3336 | +): |
| 3337 | + # We patch the callback to be able to check if it was called |
| 3338 | + mocked_callback = mocker.patch( |
| 3339 | + "tests.core.test_mypayment.mypayment_callback", |
| 3340 | + ) |
| 3341 | + |
| 3342 | + # We patch the module_list to inject our custom test module |
| 3343 | + test_module = Module( |
| 3344 | + root=TEST_MODULE_ROOT, |
| 3345 | + tag="Tests", |
| 3346 | + default_allowed_groups_ids=[], |
| 3347 | + mypayment_callback=mypayment_callback, |
| 3348 | + factory=None, |
| 3349 | + permissions=None, |
| 3350 | + ) |
| 3351 | + mocker.patch( |
| 3352 | + "app.core.mypayment.utils_mypayment.all_modules", |
| 3353 | + [test_module], |
| 3354 | + ) |
| 3355 | + |
| 3356 | + validation_data = RequestValidationData( |
| 3357 | + request_id=proposed_request.id, |
| 3358 | + key=ecl_user_wallet_device.id, |
| 3359 | + iat=datetime.now(UTC), |
| 3360 | + tot=proposed_request.total, |
| 3361 | + ) |
| 3362 | + validation_data_signature = ecl_user_wallet_device_private_key.sign( |
| 3363 | + validation_data.model_dump_json().encode("utf-8"), |
| 3364 | + ) |
| 3365 | + validation = RequestValidation( |
| 3366 | + **validation_data.model_dump(), |
| 3367 | + signature=base64.b64encode(validation_data_signature).decode("utf-8"), |
| 3368 | + ) |
| 3369 | + response = client.post( |
| 3370 | + f"/mypayment/requests/{proposed_request.id}/accept", |
| 3371 | + headers={"Authorization": f"Bearer {ecl_user_access_token}"}, |
| 3372 | + json=validation.model_dump(mode="json"), |
| 3373 | + ) |
| 3374 | + assert response.status_code == 204 |
| 3375 | + |
| 3376 | + responser = client.get( |
| 3377 | + "/mypayment/requests?used=true", |
| 3378 | + headers={"Authorization": f"Bearer {ecl_user_access_token}"}, |
| 3379 | + ) |
| 3380 | + assert responser.status_code == 200 |
| 3381 | + accepted = next( |
| 3382 | + ( |
| 3383 | + request |
| 3384 | + for request in responser.json() |
| 3385 | + if request["id"] == str(proposed_request.id) |
| 3386 | + ), |
| 3387 | + None, |
| 3388 | + ) |
| 3389 | + assert accepted is not None |
| 3390 | + assert accepted["status"] == RequestStatus.ACCEPTED |
| 3391 | + mocked_callback.assert_called_once() |
| 3392 | + |
| 3393 | + |
| 3394 | +async def test_direct_transfer_callback( |
| 3395 | + mocker: MockerFixture, |
| 3396 | + client: TestClient, |
| 3397 | +): |
| 3398 | + # We patch the callback to be able to check if it was called |
| 3399 | + mocked_callback = mocker.patch( |
| 3400 | + "tests.core.test_mypayment.mypayment_callback", |
| 3401 | + ) |
| 3402 | + |
| 3403 | + # We patch the module_list to inject our custom test module |
| 3404 | + test_module = Module( |
| 3405 | + root=TEST_MODULE_ROOT, |
| 3406 | + tag="Tests", |
| 3407 | + default_allowed_groups_ids=[], |
| 3408 | + mypayment_callback=mypayment_callback, |
| 3409 | + factory=None, |
| 3410 | + permissions=None, |
| 3411 | + ) |
| 3412 | + mocker.patch( |
| 3413 | + "app.core.mypayment.utils_mypayment.all_modules", |
| 3414 | + [test_module], |
| 3415 | + ) |
| 3416 | + |
| 3417 | + async with get_TestingSessionLocal()() as db: |
| 3418 | + await validate_transfer_callback( |
| 3419 | + checkout_payment=schemas_checkout.CheckoutPayment( |
| 3420 | + id=uuid4(), |
| 3421 | + paid_amount=1500, |
| 3422 | + checkout_id=UUID(store_direct_transfer.transfer_identifier), |
| 3423 | + ), |
| 3424 | + db=db, |
| 3425 | + ) |
| 3426 | + |
| 3427 | + mocked_callback.assert_called_once() |
0 commit comments