d582f15681
- 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.
101 lines
3.5 KiB
Python
101 lines
3.5 KiB
Python
from datetime import date
|
|
|
|
import pytest
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.budgeting.models import PeriodClosure
|
|
from app.budgeting.service import (
|
|
close_period,
|
|
compute_period_state,
|
|
copy_previous_month,
|
|
create_category,
|
|
get_or_create_period,
|
|
reopen_period,
|
|
set_assignments,
|
|
)
|
|
from app.errors import AppError
|
|
from app.identity.models import User
|
|
from app.ledger.service import create_account, create_expense
|
|
from app.spaces.service import create_space
|
|
|
|
|
|
def seed(db: Session):
|
|
user = User(email="budget@example.test", password_hash="unused-in-domain-tests")
|
|
db.add(user)
|
|
db.flush()
|
|
return user, create_space(db, user=user, name="Budget")
|
|
|
|
|
|
def test_uncategorized_expense_blocks_close(db: Session) -> None:
|
|
user, space = seed(db)
|
|
account = create_account(
|
|
db,
|
|
space_id=space.id,
|
|
user=user,
|
|
name="Cash",
|
|
kind="cash",
|
|
opened_on=date(2026, 3, 1),
|
|
opening_amount_kopecks=10_000,
|
|
)
|
|
create_expense(
|
|
db,
|
|
space_id=space.id,
|
|
user=user,
|
|
account_id=account.id,
|
|
amount_kopecks=1_000,
|
|
occurred_on=date(2026, 3, 2),
|
|
)
|
|
period = get_or_create_period(db, space_id=space.id, year=2026, month=3)
|
|
with pytest.raises(AppError) as exc:
|
|
close_period(db, space_id=space.id, user=user, period=period)
|
|
assert exc.value.code == "BUDGET_CLOSE_BLOCKED"
|
|
|
|
|
|
def test_copy_previous_month_clamps_expected_day(db: Session) -> None:
|
|
from app.budgeting.service import add_expected_income
|
|
|
|
user, space = seed(db)
|
|
category = create_category(db, space_id=space.id, user=user, kind="monthly", name="Food")
|
|
january = get_or_create_period(db, space_id=space.id, year=2026, month=1)
|
|
february = get_or_create_period(db, space_id=space.id, year=2026, month=2)
|
|
set_assignments(
|
|
db,
|
|
space_id=space.id,
|
|
user=user,
|
|
period=january,
|
|
items=[{"category_id": category.id, "planned_kopecks": 30_000}],
|
|
)
|
|
add_expected_income(
|
|
db,
|
|
space_id=space.id,
|
|
user=user,
|
|
period=january,
|
|
expected_on=date(2026, 1, 31),
|
|
amount_kopecks=100_000,
|
|
source_name="Salary",
|
|
)
|
|
result = copy_previous_month(db, space_id=space.id, user=user, period=february)
|
|
assert result == {"assignments_copied": 1, "expected_incomes_copied": 1}
|
|
state = compute_period_state(db, space_id=space.id, period=february)
|
|
assert state["expected_incomes"][0]["expected_on"] == "2026-02-28"
|
|
|
|
|
|
def test_reopen_preserves_immutable_closure_history(db: Session) -> None:
|
|
user, space = seed(db)
|
|
period = get_or_create_period(db, space_id=space.id, year=2026, month=4)
|
|
close_period(db, space_id=space.id, user=user, period=period)
|
|
reopen_period(db, space_id=space.id, user=user, period=period)
|
|
close_period(db, space_id=space.id, user=user, period=period)
|
|
assert db.query(PeriodClosure).filter_by(period_id=period.id).count() == 2
|
|
|
|
|
|
def test_periods_must_close_in_calendar_order(db: Session) -> None:
|
|
user, space = seed(db)
|
|
august = get_or_create_period(db, space_id=space.id, year=2026, month=8)
|
|
september = get_or_create_period(db, space_id=space.id, year=2026, month=9)
|
|
with pytest.raises(AppError) as exc:
|
|
close_period(db, space_id=space.id, user=user, period=september)
|
|
assert exc.value.code == "BUDGET_CLOSE_OUT_OF_ORDER"
|
|
close_period(db, space_id=space.id, user=user, period=august)
|
|
close_period(db, space_id=space.id, user=user, period=september)
|