"""The optional starting credit for new organizations (``BILLING_STARTING_CREDIT``, D-B). What these tests defend: * with the setting at 0, an organization created over HTTP or by the wizard starts with an empty wallet and no ``starting_credit`` history row, so the platform owner credits it by hand * with a positive setting, both paths give the new organization that credit and exactly one ``starting_credit`` history row, and no other organization is credited """ from __future__ import annotations from httpx import AsyncClient from app.core.config import settings from app.domain.billing.models import CreditKind from app.infrastructure.repositories.billing import CreditWalletRepository from app.infrastructure.repositories.credit import CreditLedgerRepository from tests.conftest import auth_header, signup STARTING_REASON = "اعتبار آغازین" def _payload(name: str, slug: str) -> dict: return { "org_name": name, "slug": slug, "app_name": f"app-{slug}", "app_type": "web_app", "language": "node", "framework": "next", "git_source": "platform", } async def _http_org(client: AsyncClient, token: str, name: str) -> str: response = await client.post( "/api/organizations", json={"name": name}, headers=auth_header(token) ) assert response.status_code == 201, response.text return response.json()["id"] async def _wizard_org(client: AsyncClient, token: str, name: str, slug: str) -> str: response = await client.post( "/api/setup/complete", json=_payload(name, slug), headers=auth_header(token) ) assert response.status_code == 201, response.text return response.json()["organization"]["id"] async def _credit_state(session_factory, organization_id: str): """The wallet balance and the starting-credit history of one organization.""" async with session_factory() as session: wallet = await CreditWalletRepository(session).get_for_org(organization_id) starting = await CreditLedgerRepository(session).history( organization_id, kinds=(CreditKind.STARTING_CREDIT,) ) return (wallet.balance if wallet else 0.0), starting async def test_with_the_default_setting_a_new_organization_starts_empty( client, session_factory, seeded, monkeypatch ): monkeypatch.setattr(settings, "billing_starting_credit", 0.0) http_manager = await signup(client, "start-http@example.com") wizard_manager = await signup(client, "start-wizard@example.com") http_org = await _http_org(client, http_manager["access_token"], "سازمان HTTP") wizard_org = await _wizard_org( client, wizard_manager["access_token"], "سازمان ویزارد", "start-wizard-org" ) for organization_id in (http_org, wizard_org): balance, starting = await _credit_state(session_factory, organization_id) assert balance == 0.0 assert starting == [] async def test_a_positive_setting_credits_both_paths_once( client, session_factory, seeded, monkeypatch ): monkeypatch.setattr(settings, "billing_starting_credit", 3.0) http_manager = await signup(client, "credit-http@example.com") wizard_manager = await signup(client, "credit-wizard@example.com") http_org = await _http_org(client, http_manager["access_token"], "سازمان الف") wizard_org = await _wizard_org( client, wizard_manager["access_token"], "سازمان ب", "credit-wizard-org" ) for organization_id in (http_org, wizard_org): balance, starting = await _credit_state(session_factory, organization_id) assert balance == 3.0 assert len(starting) == 1 assert starting[0].amount == 3.0 assert starting[0].balance_after == 3.0 assert starting[0].reason == STARTING_REASON