TorbatYar/backend/services/accounting/app/tests/test_api.py
Mortezakoohjani 12c8615615 Ship enterprise Accounting FE/API with CRUD parity and production wiring.
Adds accounting-service PATCH/archive, fiscal helpers, COA templates and setup status, plus SuperApp Accounting UI (DS, scoreboard, masters, vouchers, ledger, ops modules) with session refresh and HTTPS public API URLs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-24 15:26:43 +03:30

229 lines
6.9 KiB
Python

import uuid
from datetime import date
import pytest
from app.tests.conftest import TENANT_A, TENANT_B, tenant_headers
@pytest.mark.asyncio
async def test_health(client):
resp = await client.get("/health")
assert resp.status_code == 200
assert resp.json()["service"] == "accounting-service"
@pytest.mark.asyncio
async def test_create_chart_of_accounts(client):
resp = await client.post(
"/api/v1/accounts/charts",
json={"name": "Default COA", "code": "COA-001", "is_default": True},
headers=tenant_headers(TENANT_A),
)
assert resp.status_code == 201
data = resp.json()
assert data["code"] == "COA-001"
assert data["tenant_id"] == str(TENANT_A)
@pytest.mark.asyncio
async def test_tenant_isolation_accounts(client):
chart_resp = await client.post(
"/api/v1/accounts/charts",
json={"name": "Tenant A COA", "code": "COA-A"},
headers=tenant_headers(TENANT_A),
)
assert chart_resp.status_code == 201
chart_id = chart_resp.json()["id"]
acc_resp = await client.post(
"/api/v1/accounts",
json={
"chart_id": chart_id,
"code": "1000",
"name": "Test Account",
"account_type": "asset",
"account_category": "current_asset",
},
headers=tenant_headers(TENANT_A),
)
assert acc_resp.status_code == 201
account_id = acc_resp.json()["id"]
resp_b_list = await client.get(
"/api/v1/accounts",
headers=tenant_headers(TENANT_B),
)
assert resp_b_list.status_code == 200
assert len(resp_b_list.json()) == 0
resp_b_get = await client.get(
f"/api/v1/accounts/{account_id}",
headers=tenant_headers(TENANT_B),
)
assert resp_b_get.status_code == 404
@pytest.mark.asyncio
async def test_posting_engine_balanced_voucher(client):
year_resp = await client.post(
"/api/v1/fiscal/years",
json={"name": "1403", "start_date": "2024-03-20", "end_date": "2025-03-19", "is_current": True},
headers=tenant_headers(TENANT_A),
)
assert year_resp.status_code == 201
year_id = year_resp.json()["id"]
period_resp = await client.post(
"/api/v1/fiscal/periods",
json={
"fiscal_year_id": year_id,
"name": "Month 1",
"period_number": 1,
"start_date": "2024-03-20",
"end_date": "2024-04-19",
"is_current": True,
},
headers=tenant_headers(TENANT_A),
)
assert period_resp.status_code == 201
period_id = period_resp.json()["id"]
chart_resp = await client.post(
"/api/v1/accounts/charts",
json={"name": "COA", "code": "COA-POST"},
headers=tenant_headers(TENANT_A),
)
chart_id = chart_resp.json()["id"]
acc1_resp = await client.post(
"/api/v1/accounts",
json={
"chart_id": chart_id,
"code": "1101",
"name": "Cash",
"account_type": "asset",
"account_category": "current_asset",
},
headers=tenant_headers(TENANT_A),
)
acc2_resp = await client.post(
"/api/v1/accounts",
json={
"chart_id": chart_id,
"code": "4101",
"name": "Revenue",
"account_type": "revenue",
"account_category": "operating_revenue",
},
headers=tenant_headers(TENANT_A),
)
acc1_id = acc1_resp.json()["id"]
acc2_id = acc2_resp.json()["id"]
voucher_resp = await client.post(
"/api/v1/posting/vouchers",
json={
"fiscal_period_id": period_id,
"voucher_number": "V-001",
"voucher_date": str(date.today()),
"description": "Test voucher",
"lines": [
{"account_id": acc1_id, "debit": "1000.0000", "credit": "0"},
{"account_id": acc2_id, "debit": "0", "credit": "1000.0000"},
],
},
headers=tenant_headers(TENANT_A),
)
assert voucher_resp.status_code == 201
voucher_id = voucher_resp.json()["id"]
post_resp = await client.post(
f"/api/v1/posting/vouchers/{voucher_id}/post",
json={},
headers=tenant_headers(TENANT_A),
)
assert post_resp.status_code == 200
assert post_resp.json()["status"] == "posted"
@pytest.mark.asyncio
async def test_unbalanced_voucher_rejected(client):
year_resp = await client.post(
"/api/v1/fiscal/years",
json={"name": "1404", "start_date": "2025-03-20", "end_date": "2026-03-19"},
headers=tenant_headers(TENANT_A),
)
year_id = year_resp.json()["id"]
period_resp = await client.post(
"/api/v1/fiscal/periods",
json={
"fiscal_year_id": year_id,
"name": "P1",
"period_number": 1,
"start_date": "2025-03-20",
"end_date": "2025-04-19",
"is_current": True,
},
headers=tenant_headers(TENANT_A),
)
period_id = period_resp.json()["id"]
chart_resp = await client.post(
"/api/v1/accounts/charts",
json={"name": "COA2", "code": "COA-UNBAL"},
headers=tenant_headers(TENANT_A),
)
chart_id = chart_resp.json()["id"]
acc_resp = await client.post(
"/api/v1/accounts",
json={
"chart_id": chart_id,
"code": "1102",
"name": "Bank",
"account_type": "asset",
"account_category": "current_asset",
},
headers=tenant_headers(TENANT_A),
)
acc_id = acc_resp.json()["id"]
voucher_resp = await client.post(
"/api/v1/posting/vouchers",
json={
"fiscal_period_id": period_id,
"voucher_number": "V-UNBAL",
"voucher_date": str(date.today()),
"lines": [
{"account_id": acc_id, "debit": "500", "credit": "0"},
{"account_id": acc_id, "debit": "0", "credit": "300"},
],
},
headers=tenant_headers(TENANT_A),
)
voucher_id = voucher_resp.json()["id"]
validate_resp = await client.post(
f"/api/v1/posting/vouchers/{voucher_id}/validate",
headers=tenant_headers(TENANT_A),
)
assert validate_resp.status_code == 422
@pytest.mark.asyncio
async def test_trial_balance(client):
tb_resp = await client.post(
"/api/v1/ledger/trial-balance",
params={"fiscal_period_id": str(uuid.uuid4())},
headers=tenant_headers(TENANT_A),
)
assert tb_resp.status_code == 200
assert "is_balanced" in tb_resp.json()
@pytest.mark.asyncio
async def test_requires_tenant_header(client):
resp = await client.get("/api/v1/accounts")
assert resp.status_code == 400