import pytest @pytest.mark.asyncio async def test_license_plate_can_be_saved_and_edited(client, auth_headers) -> None: car = ( await client.post( "/api/cars", headers=auth_headers, json={"name": "Plate car", "plate_number": "12 가 3456"}, ) ).json() updated = await client.patch( f"/api/cars/{car['id']}", headers=auth_headers, json={"plate_number": "34 나 7890", "body_type": "SUV"}, ) assert updated.status_code == 200 assert updated.json()["plate_number"] == "34 나 7890" assert updated.json()["body_type"] == "SUV" @pytest.mark.asyncio async def test_insurance_six_months_allocates_proportionally(client, auth_headers) -> None: car = (await client.post("/api/cars", headers=auth_headers, json={"name": "Insurance 6"})).json() await client.post( "/api/expenses", headers=auth_headers, json={ "car_id": car["id"], "entry_date": "2026-01-01", "category": "insurance", "title": "Insurance", "total_cost": 600, "period_start": "2026-01-01", "period_months": 6, "payment_period_months": 6, "is_recurring": True, }, ) stats = await client.get( f"/api/cars/{car['id']}/stats?date_from=2026-02-01&date_to=2026-02-28", headers=auth_headers, ) assert stats.status_code == 200 assert stats.json()["cost_by_category"]["insurance"] == "100.00" @pytest.mark.asyncio async def test_custom_insurance_period_allocates_by_overlap(client, auth_headers) -> None: car = (await client.post("/api/cars", headers=auth_headers, json={"name": "Insurance custom"})).json() await client.post( "/api/expenses", headers=auth_headers, json={ "car_id": car["id"], "entry_date": "2026-01-15", "category": "insurance", "title": "Short insurance", "total_cost": 310, "period_start": "2026-01-15", "period_end": "2026-02-14", "is_recurring": True, }, ) stats = await client.get( f"/api/cars/{car['id']}/stats?date_from=2026-02-01&date_to=2026-02-28", headers=auth_headers, ) assert stats.status_code == 200 assert stats.json()["cost_by_category"]["insurance"] == "140.00" @pytest.mark.asyncio async def test_loan_calculator_regular_and_zero_rate(client, auth_headers) -> None: regular = await client.post( "/api/loans/calculate", headers=auth_headers, json={"principal": 6000000, "term_months": 36, "annual_interest_rate": 5.5}, ) zero = await client.post( "/api/loans/calculate", headers=auth_headers, json={"principal": 1200, "term_months": 12, "annual_interest_rate": 0}, ) assert regular.status_code == 200 assert float(regular.json()["monthly_payment"]) > 0 assert zero.json()["monthly_payment"] == "100.00" assert zero.json()["total_interest"] == "0.00" @pytest.mark.asyncio async def test_ownership_cost_includes_credit_and_fixed_variable_split(client, auth_headers) -> None: car = ( await client.post( "/api/cars", headers=auth_headers, json={ "name": "Credit car", "purchase_type": "credit", "loan_principal": 1200, "loan_term_months": 12, "loan_annual_interest_rate": 0, "loan_first_payment_date": "2026-01-01", }, ) ).json() await client.post( "/api/fuel", headers=auth_headers, json={ "car_id": car["id"], "entry_date": "2026-01-10", "odometer": 1000, "liters": 20, "price_per_liter": 2, }, ) await client.post( "/api/expenses", headers=auth_headers, json={ "car_id": car["id"], "entry_date": "2026-01-12", "category": "fine", "title": "Fine", "total_cost": 30, }, ) stats = await client.get( f"/api/cars/{car['id']}/stats?date_from=2026-01-01&date_to=2026-01-31", headers=auth_headers, ) payload = stats.json() assert payload["loan_principal_cost"] == "100.00" assert payload["loan_interest_cost"] == "0.00" assert payload["fixed_costs"] == "100.00" assert payload["variable_costs"] == "70.00" assert payload["total_cost_with_credit"] == "170.00" assert payload["total_cost_without_credit"] == "70.00" @pytest.mark.asyncio async def test_odometer_lower_fuel_requires_confirmation_and_delete_recalculates(client, auth_headers) -> None: car = (await client.post("/api/cars", headers=auth_headers, json={"name": "Odo car"})).json() first = ( await client.post( "/api/fuel", headers=auth_headers, json={ "car_id": car["id"], "entry_date": "2026-01-01", "odometer": 1000, "liters": 20, "price_per_liter": 2, }, ) ).json() second = ( await client.post( "/api/fuel", headers=auth_headers, json={ "car_id": car["id"], "entry_date": "2026-01-02", "odometer": 2000, "liters": 20, "price_per_liter": 2, }, ) ).json() blocked = await client.post( "/api/fuel", headers=auth_headers, json={ "car_id": car["id"], "entry_date": "2026-01-03", "odometer": 900, "liters": 20, "price_per_liter": 2, }, ) await client.delete(f"/api/fuel/{second['id']}", headers=auth_headers) refreshed = await client.get(f"/api/cars/{car['id']}", headers=auth_headers) history = await client.get(f"/api/cars/{car['id']}/odometer-history", headers=auth_headers) assert first["odometer"] == 1000 assert blocked.status_code == 409 assert refreshed.json()["current_odometer"] == 1000 assert len(history.json()) >= 2 @pytest.mark.asyncio async def test_service_lower_odometer_can_be_confirmed(client, auth_headers) -> None: car = ( await client.post( "/api/cars", headers=auth_headers, json={"name": "Service odo", "current_odometer": 1000}, ) ).json() blocked = await client.post( "/api/service", headers=auth_headers, json={ "car_id": car["id"], "entry_date": "2026-01-02", "odometer": 900, "service_type": "maintenance", "title": "Correction", "total_cost": 0, }, ) confirmed = await client.post( "/api/service", headers=auth_headers, json={ "car_id": car["id"], "entry_date": "2026-01-02", "odometer": 900, "service_type": "maintenance", "title": "Correction", "total_cost": 0, "confirm_lower_odometer": True, }, ) assert blocked.status_code == 409 assert confirmed.status_code == 201 @pytest.mark.asyncio async def test_full_tank_analysis_uses_full_tank_intervals_and_warns(client, auth_headers) -> None: car = (await client.post("/api/cars", headers=auth_headers, json={"name": "Tank car"})).json() for odometer, liters, full in [ (1000, 40, True), (1200, 10, False), (1500, 35, True), (1600, 40, True), ]: await client.post( "/api/fuel", headers=auth_headers, json={ "car_id": car["id"], "entry_date": "2026-01-01", "odometer": odometer, "liters": liters, "price_per_liter": 2, "is_full_tank": full, }, ) analytics = await client.get(f"/api/cars/{car['id']}/analytics", headers=auth_headers) payload = analytics.json() assert payload["average_full_tank_distance"] == 300.0 assert payload["last_full_tank_distance"] == 100 assert payload["full_tank_warning"] is not None @pytest.mark.asyncio async def test_parser_recognizes_full_tank_and_credit_purchase(client, auth_headers) -> None: fuel = await client.post( "/api/parse/record", headers=auth_headers, json={"text": "заправил полный бак 43 литра на 72000, пробег 184230"}, ) purchase = await client.post( "/api/parse/record", headers=auth_headers, json={"text": "купил машину за 8500000 вон, кредит 6000000 на 36 месяцев под 5.5%"}, ) assert fuel.json()["event_type"] == "fuel" assert fuel.json()["data"]["is_full_tank"] is True assert purchase.json()["event_type"] == "vehicle_purchase" assert purchase.json()["data"]["purchase_type"] == "credit" assert purchase.json()["data"]["loan_term_months"] == 36 @pytest.mark.asyncio async def test_admin_request_changes_keeps_application_visible_to_moderation( client, auth_headers, admin_auth_headers, internal_headers ) -> None: center = ( await client.post( "/api/service-centers", headers=auth_headers, json={"display_name": "Needs Work Service", "country": "KR"}, ) ).json() await client.post( "/api/users", headers=internal_headers, json={"telegram_id": 9001, "platform_role": "admin"}, ) response = await client.post( f"/api/admin/service-centers/{center['id']}/request-changes", headers=admin_auth_headers, json={"reason": "Добавьте документы", "comment": "Нужны фото регистрации"}, ) assert response.status_code == 200 assert response.json()["verification_status"] == "needs_changes" @pytest.mark.asyncio async def test_service_center_application_is_visible_to_moderators( client, auth_headers, admin_auth_headers, internal_headers ) -> None: center = ( await client.post( "/api/service-centers", headers=auth_headers, json={"display_name": "Pending Review Service", "country": "KR"}, ) ).json() await client.post( "/api/users", headers=internal_headers, json={"telegram_id": 9001, "platform_role": "moderator"}, ) pending = await client.get("/api/admin/service-centers/pending", headers=admin_auth_headers) assert pending.status_code == 200 assert center["id"] in [item["id"] for item in pending.json()]