release: publish saldo 0.1.0
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
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/")
|
||||
Reference in New Issue
Block a user