Skip to content

Commit 50fa93e

Browse files
committed
feat: migration and tests
1 parent b7ac09d commit 50fa93e

File tree

2 files changed

+280
-4
lines changed

2 files changed

+280
-4
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
"""empty message
2+
3+
Create Date: 2026-03-19 15:49:33.554684
4+
"""
5+
6+
from collections.abc import Sequence
7+
from typing import TYPE_CHECKING
8+
9+
if TYPE_CHECKING:
10+
from pytest_alembic import MigrationContext
11+
12+
import sqlalchemy as sa
13+
from alembic import op
14+
15+
# revision identifiers, used by Alembic.
16+
revision: str = "46fbbcee7237"
17+
down_revision: str | None = "562adbd796ae"
18+
branch_labels: str | Sequence[str] | None = None
19+
depends_on: str | Sequence[str] | None = None
20+
21+
22+
def upgrade() -> None:
23+
# ### commands auto generated by Alembic - please adjust! ###
24+
op.add_column("mypayment_request", sa.Column("module", sa.String(), nullable=False))
25+
op.add_column(
26+
"mypayment_request",
27+
sa.Column("object_id", sa.Uuid(), nullable=False),
28+
)
29+
op.drop_column("mypayment_request", "callback")
30+
op.add_column("mypayment_transfer", sa.Column("module", sa.String(), nullable=True))
31+
op.add_column(
32+
"mypayment_transfer",
33+
sa.Column("object_id", sa.Uuid(), nullable=True),
34+
)
35+
# ### end Alembic commands ###
36+
37+
38+
def downgrade() -> None:
39+
# ### commands auto generated by Alembic - please adjust! ###
40+
op.drop_column("mypayment_transfer", "object_id")
41+
op.drop_column("mypayment_transfer", "module")
42+
op.add_column(
43+
"mypayment_request",
44+
sa.Column("callback", sa.VARCHAR(), autoincrement=False, nullable=False),
45+
)
46+
op.drop_column("mypayment_request", "module")
47+
op.drop_column("mypayment_request", "object_id")
48+
# ### end Alembic commands ###
49+
50+
51+
def pre_test_upgrade(
52+
alembic_runner: "MigrationContext",
53+
alembic_connection: sa.Connection,
54+
) -> None:
55+
pass
56+
57+
58+
def test_upgrade(
59+
alembic_runner: "MigrationContext",
60+
alembic_connection: sa.Connection,
61+
) -> None:
62+
pass

tests/core/test_mypayment.py

Lines changed: 218 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,35 @@
1010
)
1111
from fastapi.testclient import TestClient
1212
from pytest_mock import MockerFixture
13+
from sqlalchemy.ext.asyncio import AsyncSession
1314

15+
from app.core.checkout import schemas_checkout
1416
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
1618
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
1820
from app.core.mypayment.coredata_mypayment import (
1921
MyPaymentBankAccountHolder,
2022
)
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+
)
2229
from app.core.mypayment.types_mypayment import (
30+
MyPaymentCallType,
31+
RequestStatus,
2332
TransactionStatus,
2433
TransactionType,
2534
TransferType,
2635
WalletDeviceStatus,
2736
WalletType,
2837
)
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
3040
from app.core.users import models_users
41+
from app.types.module import Module
3142
from tests.commons import (
3243
add_coredata_to_db,
3344
add_object_to_db,
@@ -37,6 +48,8 @@
3748
get_TestingSessionLocal,
3849
)
3950

51+
TEST_MODULE_ROOT = "tests"
52+
4053
bde_group: models_groups.CoreGroup
4154

4255
admin_user: models_users.CoreUser
@@ -72,6 +85,7 @@
7285
store3: models_mypayment.Store
7386
store_wallet_device_private_key: Ed25519PrivateKey
7487
store_wallet_device: models_mypayment.WalletDevice
88+
store_direct_transfer: models_mypayment.Transfer
7589

7690

7791
transaction_from_ecl_user_to_store: models_mypayment.Transaction
@@ -88,6 +102,10 @@
88102
invoice2_detail: models_mypayment.InvoiceDetail
89103
invoice3_detail: models_mypayment.InvoiceDetail
90104

105+
proposed_request: models_mypayment.Request
106+
expired_request: models_mypayment.Request
107+
refused_request: models_mypayment.Request
108+
91109
store_seller_can_bank_user: models_users.CoreUser
92110
store_seller_no_permission_user_access_token: str
93111
store_seller_can_bank_user_access_token: str
@@ -103,6 +121,14 @@
103121

104122
@pytest_asyncio.fixture(scope="module", autouse=True)
105123
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+
106132
global bde_group
107133
bde_group = await create_groups_with_permissions(
108134
[],
@@ -352,6 +378,21 @@ async def init_objects() -> None:
352378
)
353379
await add_object_to_db(store_wallet_device)
354380

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+
355396
# Create test transactions
356397
global transaction_from_ecl_user_to_store
357398
transaction_from_ecl_user_to_store = models_mypayment.Transaction(
@@ -430,6 +471,8 @@ async def init_objects() -> None:
430471
total=1000, # 10€
431472
creation=datetime.now(UTC),
432473
confirmed=True,
474+
module=None,
475+
object_id=None,
433476
)
434477
await add_object_to_db(ecl_user_transfer)
435478

@@ -599,6 +642,50 @@ async def init_objects() -> None:
599642
)
600643
await add_object_to_db(invoice3_detail)
601644

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+
602689

603690
async def test_get_structures(client: TestClient):
604691
response = client.get(
@@ -3211,3 +3298,130 @@ async def test_delete_invoice(
32113298
)
32123299
assert response.status_code == 200
32133300
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

Comments
 (0)