feat: add tests for import polling, login page, money utilities, and query keys
- Implemented tests for import polling functionality, ensuring correct behavior for active and terminal import statuses. - Created tests for the login page, validating user interactions and error handling. - Added unit tests for money utility functions, covering parsing, formatting, and summation of monetary values. - Developed tests for query keys, ensuring proper scoping and invalidation roots. chore: setup testing environment and configuration - Added setup file for testing with Vitest and Testing Library. - Configured TypeScript and Vite for the frontend project. - Created Docker and Nginx configurations for local development and production environments. - Established a PostgreSQL initialization script for setting up application roles. docs: add README and environment configuration for infra - Included README for the infra directory, detailing local development setup and service descriptions. - Created an example environment configuration file for local development with placeholders for sensitive information.
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
from datetime import date
|
||||
|
||||
import pytest
|
||||
from hypothesis import HealthCheck, given, settings
|
||||
from hypothesis import strategies as st
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.errors import AppError
|
||||
from app.identity.models import User
|
||||
from app.ledger.models import Posting
|
||||
from app.ledger.service import (
|
||||
account_balance,
|
||||
create_account,
|
||||
create_expense,
|
||||
create_income,
|
||||
create_refund,
|
||||
create_transfer,
|
||||
get_account,
|
||||
)
|
||||
from app.spaces.service import create_space
|
||||
|
||||
|
||||
def seed(db: Session):
|
||||
user = User(email="owner@example.test", password_hash="unused-in-domain-tests")
|
||||
db.add(user)
|
||||
db.flush()
|
||||
space = create_space(db, user=user, name="Personal")
|
||||
return user, space
|
||||
|
||||
|
||||
@given(amount=st.integers(min_value=1, max_value=10**12))
|
||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture])
|
||||
def test_transfer_postings_conserve_money(db: Session, amount: int) -> None:
|
||||
db.rollback()
|
||||
user, space = seed(db)
|
||||
source = create_account(
|
||||
db,
|
||||
space_id=space.id,
|
||||
user=user,
|
||||
name="Debit",
|
||||
kind="debit",
|
||||
opened_on=date(2026, 1, 1),
|
||||
opening_amount_kopecks=amount,
|
||||
)
|
||||
target = create_account(
|
||||
db,
|
||||
space_id=space.id,
|
||||
user=user,
|
||||
name="Cash",
|
||||
kind="cash",
|
||||
opened_on=date(2026, 1, 1),
|
||||
)
|
||||
operation = create_transfer(
|
||||
db,
|
||||
space_id=space.id,
|
||||
user=user,
|
||||
source_account_id=source.id,
|
||||
target_account_id=target.id,
|
||||
amount_kopecks=amount,
|
||||
occurred_on=date(2026, 1, 2),
|
||||
)
|
||||
db.flush()
|
||||
postings = db.query(Posting).filter_by(operation_id=operation.id).all()
|
||||
assert len(postings) == 2
|
||||
assert sum(item.amount_kopecks for item in postings) == 0
|
||||
assert account_balance(db, source) == 0
|
||||
assert account_balance(db, target) == amount
|
||||
db.rollback()
|
||||
|
||||
|
||||
def test_credit_expense_is_negative_balance_and_refund_is_capped(db: Session) -> None:
|
||||
user, space = seed(db)
|
||||
card = create_account(
|
||||
db,
|
||||
space_id=space.id,
|
||||
user=user,
|
||||
name="Credit",
|
||||
kind="credit",
|
||||
opened_on=date(2026, 2, 1),
|
||||
)
|
||||
expense = create_expense(
|
||||
db,
|
||||
space_id=space.id,
|
||||
user=user,
|
||||
account_id=card.id,
|
||||
amount_kopecks=12_345,
|
||||
occurred_on=date(2026, 2, 2),
|
||||
)
|
||||
assert account_balance(db, card) == -12_345
|
||||
create_refund(
|
||||
db,
|
||||
space_id=space.id,
|
||||
user=user,
|
||||
parent_operation_id=expense.id,
|
||||
amount_kopecks=2_345,
|
||||
occurred_on=date(2026, 2, 3),
|
||||
)
|
||||
assert account_balance(db, card) == -10_000
|
||||
with pytest.raises(AppError, match="Refund exceeds"):
|
||||
create_refund(
|
||||
db,
|
||||
space_id=space.id,
|
||||
user=user,
|
||||
parent_operation_id=expense.id,
|
||||
amount_kopecks=10_001,
|
||||
occurred_on=date(2026, 2, 4),
|
||||
)
|
||||
|
||||
|
||||
def test_cross_space_lookup_fails_closed(db: Session) -> None:
|
||||
user, first = seed(db)
|
||||
other = create_space(db, user=user, name="Other")
|
||||
account = create_account(
|
||||
db,
|
||||
space_id=first.id,
|
||||
user=user,
|
||||
name="Cash",
|
||||
kind="cash",
|
||||
opened_on=date(2026, 1, 1),
|
||||
)
|
||||
with pytest.raises(AppError) as exc:
|
||||
get_account(db, space_id=other.id, account_id=account.id)
|
||||
assert exc.value.code == "LEDGER_ACCOUNT_NOT_FOUND"
|
||||
|
||||
|
||||
def test_idempotency_key_prevents_duplicate_income(db: Session) -> None:
|
||||
user, space = seed(db)
|
||||
account = create_account(
|
||||
db,
|
||||
space_id=space.id,
|
||||
user=user,
|
||||
name="Debit",
|
||||
kind="debit",
|
||||
opened_on=date(2026, 1, 1),
|
||||
)
|
||||
first = create_income(
|
||||
db,
|
||||
space_id=space.id,
|
||||
user=user,
|
||||
account_id=account.id,
|
||||
amount_kopecks=50_000,
|
||||
occurred_on=date(2026, 1, 5),
|
||||
request_key="retry-1",
|
||||
)
|
||||
second = create_income(
|
||||
db,
|
||||
space_id=space.id,
|
||||
user=user,
|
||||
account_id=account.id,
|
||||
amount_kopecks=50_000,
|
||||
occurred_on=date(2026, 1, 5),
|
||||
request_key="retry-1",
|
||||
)
|
||||
assert first.id == second.id
|
||||
assert account_balance(db, account) == 50_000
|
||||
Reference in New Issue
Block a user