161 lines
5.3 KiB
Python
161 lines
5.3 KiB
Python
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from app.bot import (
|
|
parse_cancellation,
|
|
parse_category,
|
|
parse_friend_removal,
|
|
parse_transaction,
|
|
webapp_url_for_user,
|
|
)
|
|
from app.config import Settings
|
|
from app.db import Database
|
|
|
|
|
|
def make_database(tmp_path: Path) -> Database:
|
|
database = Database(tmp_path / "test.sqlite3")
|
|
database.initialize()
|
|
return database
|
|
|
|
|
|
def add_user(database: Database, user_id: int, name: str) -> None:
|
|
database.upsert_user(
|
|
user_id=user_id,
|
|
first_name=name,
|
|
last_name=None,
|
|
username=None,
|
|
)
|
|
|
|
|
|
def test_transaction_and_leaderboard(tmp_path: Path) -> None:
|
|
database = make_database(tmp_path)
|
|
add_user(database, 1, "Аня")
|
|
item = parse_transaction(
|
|
'{"v":1,"type":"transaction","kind":"income","amount":"1000.50","category":"Зарплата","occurred_at":"2026-01-01T00:00:00Z"}'
|
|
)
|
|
database.add_transaction(1, item)
|
|
expense = parse_transaction(
|
|
'{"v":1,"type":"transaction","kind":"expense","amount":"250.25","category":"Еда","occurred_at":"2026-01-02T00:00:00Z"}'
|
|
)
|
|
database.add_transaction(1, expense)
|
|
|
|
summary = database.summary(1)
|
|
assert summary is not None
|
|
assert summary["income_cents"] == 100_050
|
|
assert summary["expense_cents"] == 25_025
|
|
assert summary["saved_percent"] == 75.0
|
|
assert database.leaderboard()[0]["nickname"] == "Аня"
|
|
|
|
|
|
def test_friendship_is_symmetric_and_idempotent(tmp_path: Path) -> None:
|
|
database = make_database(tmp_path)
|
|
add_user(database, 3, "Три")
|
|
add_user(database, 7, "Семь")
|
|
|
|
assert database.add_friendship(7, 3)
|
|
assert not database.add_friendship(3, 7)
|
|
assert database.friend_ids(3) == [7]
|
|
assert database.friend_ids(7) == [3]
|
|
assert database.remove_friendship(3, 7)
|
|
assert database.friend_ids(3) == []
|
|
assert database.friend_ids(7) == []
|
|
assert not database.remove_friendship(3, 7)
|
|
assert parse_friend_removal(
|
|
'{"v":1,"type":"friend_remove","friend_user_id":7}'
|
|
) == 7
|
|
|
|
|
|
def test_filtered_leaderboard(tmp_path: Path) -> None:
|
|
database = make_database(tmp_path)
|
|
for user_id in (1, 2, 3):
|
|
add_user(database, user_id, str(user_id))
|
|
|
|
database.add_transaction(
|
|
1,
|
|
parse_transaction(
|
|
'{"v":1,"type":"transaction","kind":"income","amount":"100","category":"Работа"}'
|
|
),
|
|
)
|
|
database.add_transaction(
|
|
3,
|
|
parse_transaction(
|
|
'{"v":1,"type":"transaction","kind":"expense","amount":"40","category":"Еда"}'
|
|
),
|
|
)
|
|
|
|
result = database.leaderboard(user_ids=[1, 3])
|
|
assert [item["user_id"] for item in result] == [1, 3]
|
|
filtered = database.leaderboard(user_ids=[1, 3], categories=["Еда"])
|
|
assert filtered[0]["user_id"] == 1
|
|
assert filtered[0]["balance_cents"] == 0
|
|
assert filtered[1]["user_id"] == 3
|
|
assert filtered[1]["balance_cents"] == -4_000
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"payload",
|
|
[
|
|
"not-json",
|
|
'{"v":1,"type":"transaction","kind":"other","amount":"10","category":"Еда"}',
|
|
'{"v":1,"type":"transaction","kind":"income","amount":"-1","category":"Зарплата"}',
|
|
'{"v":1,"type":"transaction","kind":"income","amount":"10","category":""}',
|
|
],
|
|
)
|
|
def test_rejects_invalid_webapp_payload(payload: str) -> None:
|
|
with pytest.raises(ValueError):
|
|
parse_transaction(payload)
|
|
|
|
|
|
def test_personalized_keyboard_webapp_url(tmp_path: Path) -> None:
|
|
settings = Settings(
|
|
telegram_token="token",
|
|
base_app_url="https://example.com/",
|
|
data_dir=tmp_path,
|
|
)
|
|
assert webapp_url_for_user(settings, 123456) == (
|
|
"https://example.com/app/?user_id=123456&mode=keyboard"
|
|
)
|
|
url = webapp_url_for_user(
|
|
settings,
|
|
123456,
|
|
{
|
|
"nickname": "Иван Петров",
|
|
"balance_cents": 2500,
|
|
"income_cents": 10000,
|
|
"expense_cents": 7500,
|
|
},
|
|
)
|
|
assert url == (
|
|
"https://example.com/app/?user_id=123456&mode=keyboard"
|
|
"&nickname=%D0%98%D0%B2%D0%B0%D0%BD+%D0%9F%D0%B5%D1%82%D1%80%D0%BE%D0%B2"
|
|
"&balance=2500&income=10000&expense=7500"
|
|
)
|
|
|
|
|
|
def test_custom_category_and_cancellation(tmp_path: Path) -> None:
|
|
database = make_database(tmp_path)
|
|
add_user(database, 10, "Владелец")
|
|
add_user(database, 11, "Другой")
|
|
kind, name = parse_category(
|
|
'{"v":1,"type":"category_create","kind":"expense","category":"Подписки"}'
|
|
)
|
|
assert database.add_category(10, kind, name)
|
|
assert not database.add_category(10, kind, name)
|
|
assert database.user_categories(10) == [{"kind": "expense", "name": "Подписки"}]
|
|
|
|
transaction_id = database.add_transaction(
|
|
10,
|
|
parse_transaction(
|
|
'{"v":1,"type":"transaction","kind":"expense","amount":"499","category":"Подписки"}'
|
|
),
|
|
)
|
|
assert parse_cancellation(
|
|
f'{{"v":1,"type":"transaction_cancel","transaction_id":{transaction_id}}}'
|
|
) == transaction_id
|
|
assert not database.cancel_transaction(11, transaction_id)
|
|
assert database.cancel_transaction(10, transaction_id)
|
|
assert not database.cancel_transaction(10, transaction_id)
|
|
assert database.transactions(10) == []
|
|
assert database.summary(10)["expense_cents"] == 0
|
|
assert database.user_categories(10) == [{"kind": "expense", "name": "Подписки"}]
|