from __future__ import annotations import io from datetime import date from decimal import Decimal from app.extensions import db from app.models import CostParticipant, Entry, InAppNotification, Month, User def test_share_logic_with_external_guest(app): month = Month.query.filter_by(label="2026-04").first() service = app.extensions["saldo.share_service"] shared_value = next(item for item in month.entry_values if item.entry.name == "Streaming 1") result = service.calculate_entry_shares(shared_value) guest_share = next(item for item in result["shares"] if item["participant_name"] == "Gast") assert guest_share["amount"] == Decimal("5.99") assert result["external_total"] == guest_share["amount"] def test_external_guest_shares_do_not_reduce_distribution_pool(app): month = Month.query.filter_by(label="2026-04").first() service = app.extensions["saldo.month_service"] shared_value = next(item for item in month.entry_values if item.entry.name == "Streaming 1") summary = service.compute_summary(month) assert shared_value.entry.share_rules assert summary.fixed_costs < summary.total_costs def test_push_reminder_logic_creates_in_app_notifications(app): notification_service = app.extensions["saldo.notification_service"] count = notification_service.run_monthly_checks(date(2026, 4, 27)) assert count >= 1 assert InAppNotification.query.count() >= 1 def test_setup_route_can_create_first_admin(empty_client, empty_app): response = empty_client.post( "/auth/setup", data={ "username": "setup-admin", "display_name": "Setup Admin", "email": "setup-admin@example.invalid", "password": "supersecret", "password_confirm": "supersecret", }, follow_redirects=True, ) with empty_app.app_context(): user = User.query.filter_by(username="setup-admin").first() assert user is not None assert user.role == "admin" assert response.status_code == 200 assert "2026-04" in response.get_data(as_text=True) or "2026-05" in response.get_data(as_text=True) def test_admin_route_requires_admin(client, app, editor_user): client.post( "/auth/login", data={"username": editor_user.username, "password": "testpass"}, follow_redirects=True, ) response = client.get("/admin/") assert response.status_code == 403 def test_admin_can_access_admin_route(logged_in_client): response = logged_in_client.get("/admin/") assert response.status_code == 200 def test_admin_can_create_entry_and_backfill_existing_month(logged_in_client, app): from app.models import Category, Entry, Month, MonthlyEntryValue category = Category.query.filter_by(slug="haushalt").first() month = Month.query.filter_by(label="2026-04").first() before = MonthlyEntryValue.query.filter_by(month_id=month.id).count() response = logged_in_client.post( "/admin/entries", data={ "category_id": category.id, "name": "Tierfutter", "slug": "tierfutter", "default_amount": "45.00", "amount_type": "fixed", "sort_order": "99", "description": "", "is_active": "on", }, follow_redirects=True, ) assert response.status_code == 200 entry = Entry.query.filter_by(slug="tierfutter").first() assert entry is not None assert MonthlyEntryValue.query.filter_by(month_id=month.id).count() == before + 1 def test_participant_avatar_can_be_uploaded(logged_in_client, app): response = logged_in_client.post( "/planning/2026-04/participants", data={ "name": "Mika", "is_external": "on", "return_dialog": "split-people-dialog", "avatar_file": (io.BytesIO(b"fake-image-bytes"), "mika.png"), }, content_type="multipart/form-data", follow_redirects=True, ) participant = CostParticipant.query.filter_by(name="Mika").first() assert response.status_code == 200 assert participant is not None assert participant.avatar_url is not None assert participant.avatar_url.startswith("/media/avatars/")