import hashlib import hmac import json import time from urllib.parse import quote, urlencode import pytest from fastapi.testclient import TestClient from app.auth import validate_telegram_init_data from app.config import Settings from app.main import create_app def signed_init_data(bot_token: str, user_id: int = 123) -> str: pairs = { "query_id": "AAEAAAE", "user": json.dumps({"id": user_id, "first_name": "Test"}, separators=(",", ":")), "auth_date": str(int(time.time())), } data_check_string = "\n".join(f"{k}={v}" for k, v in sorted(pairs.items())) secret_key = hmac.new(b"WebAppData", bot_token.encode(), hashlib.sha256).digest() pairs["hash"] = hmac.new(secret_key, data_check_string.encode(), hashlib.sha256).hexdigest() return urlencode(pairs, quote_via=quote) class FakeAdapter: def __init__(self): self.calls = [] self.tasks = [ {"id": "t_1", "title": "Ready task", "status": "ready", "assignee": "dev", "priority": 7}, {"id": "t_2", "title": "Blocked task", "status": "blocked", "assignee": "worker", "priority": 1}, ] def boards(self): return [{"slug": "default", "current": True}] def list_tasks(self, board=None): self.calls.append(("list", board)) return self.tasks def show_task(self, task_id, board=None): return {"id": task_id, "title": "Ready task", "body": "Full body", "status": "ready", "comments": [], "events": [], "runs": []} def task_events(self, task_id, board=None): return [{"kind": "created"}] def task_runs(self, task_id, board=None): return [{"id": 1, "status": "completed"}] def task_log(self, task_id, board=None, lines=200): return {"task_id": task_id, "log": "", "missing": True} def create_task(self, payload, board=None): self.calls.append(("create", payload.title)) return {"id": "t_new", "title": payload.title, "status": "todo"} def comment(self, task_id, body, board=None): self.calls.append(("comment", task_id, body)) return {"ok": True} def assign(self, task_id, assignee, board=None): self.calls.append(("assign", task_id, assignee)) return {"ok": True} def promote(self, task_id, board=None): self.calls.append(("promote", task_id)) return {"ok": True} def block(self, task_id, reason, board=None): self.calls.append(("block", task_id, reason)) return {"ok": True} def unblock(self, task_id, board=None): self.calls.append(("unblock", task_id)) return {"ok": True} def archive(self, task_id, board=None): self.calls.append(("archive", task_id)) return {"ok": True} def dispatch(self, board=None): self.calls.append(("dispatch", board)) return {"ok": True} @pytest.fixture def fake_adapter(): return FakeAdapter() @pytest.fixture def client(fake_adapter): app = create_app(Settings(auth_mode="none", allowed_origins=["http://localhost:5173"]), fake_adapter) return TestClient(app) def test_health_returns_ok(client): assert client.get("/api/health").json()["status"] == "ok" def test_telegram_init_data_accepts_correct_signature(): data = signed_init_data("token123", 123) user = validate_telegram_init_data(data, "token123", max_age_seconds=3600) assert user["id"] == 123 def test_telegram_init_data_rejects_wrong_hash(): data = signed_init_data("token123", 123).replace("hash=", "hash=bad") with pytest.raises(ValueError): validate_telegram_init_data(data, "token123", max_age_seconds=3600) def test_allowed_user_filter_rejects_unauthorized(fake_adapter): settings = Settings(auth_mode="telegram", telegram_bot_token="token123", telegram_allowed_user_ids=[999]) app = create_app(settings, fake_adapter) response = TestClient(app).get("/api/tasks", headers={"X-Telegram-Init-Data": signed_init_data("token123", 123)}) assert response.status_code == 403 def test_env_list_settings_accept_json_strings(): settings = Settings( telegram_allowed_user_ids='[123, "456"]', allowed_origins='["https://miniapp.example", "http://localhost:5173"]', ) assert settings.telegram_allowed_user_ids == [123, 456] assert settings.allowed_origins == ["https://miniapp.example", "http://localhost:5173"] def test_env_list_settings_accept_comma_separated_strings(): settings = Settings( telegram_allowed_user_ids="123,456", allowed_origins="https://miniapp.example,http://localhost:5173", ) assert settings.telegram_allowed_user_ids == [123, 456] assert settings.allowed_origins == ["https://miniapp.example", "http://localhost:5173"] def test_static_frontend_can_be_served_from_backend(tmp_path, fake_adapter): (tmp_path / "index.html").write_text("