Release 2026-06-15/16: B2 billing-orphan fix + demo conversion (B1/B3/B4/B5) + conversation history (72h) + per-tenant teach-the-bot + onboarding videos
4778987 verified | """Security hardening: refuse the default signing key in prod; throttle /chat.""" | |
| from __future__ import annotations | |
| from types import SimpleNamespace | |
| import pytest | |
| from app.main import _check_security | |
| from app.routes import chat | |
| def _settings(**over): | |
| base = dict(testing=False, admin_token="tok", secret_key="a-real-strong-key") | |
| base.update(over) | |
| return SimpleNamespace(**base) | |
| def test_default_secret_key_refuses_to_boot_in_prod(): | |
| with pytest.raises(RuntimeError, match="SECRET_KEY"): | |
| _check_security(_settings(secret_key="dev-insecure-change-me")) | |
| def test_strong_secret_key_boots(): | |
| _check_security(_settings()) # no raise | |
| def test_testing_mode_skips_the_guard(): | |
| # tests run with the default key; testing=True must never crash them | |
| _check_security(_settings(testing=True, secret_key="dev-insecure-change-me")) | |
| # ---- /chat rate limit ---- | |
| def _req(ip="1.2.3.4"): | |
| return SimpleNamespace(headers={"x-forwarded-for": ip}, client=SimpleNamespace(host=ip)) | |
| def test_chat_rate_limit_blocks_after_burst(monkeypatch): | |
| # fresh limiter so other tests don't pollute the window | |
| from app.ratelimit import RateLimiter | |
| monkeypatch.setattr(chat, "_chat_limiter", RateLimiter(chat._CHAT_PER_IP_PER_MIN, 60)) | |
| req = _req("9.9.9.9") | |
| for _ in range(chat._CHAT_PER_IP_PER_MIN): | |
| chat._enforce_chat_rate(req) # allowed | |
| with pytest.raises(Exception) as ei: | |
| chat._enforce_chat_rate(req) # one over -> 429 | |
| assert getattr(ei.value, "status_code", None) == 429 | |
| def test_chat_rate_limit_is_per_ip(monkeypatch): | |
| from app.ratelimit import RateLimiter | |
| monkeypatch.setattr(chat, "_chat_limiter", RateLimiter(2, 60)) | |
| chat._enforce_chat_rate(_req("1.1.1.1")) | |
| chat._enforce_chat_rate(_req("1.1.1.1")) | |
| # a different IP still has its own fresh budget | |
| chat._enforce_chat_rate(_req("2.2.2.2")) | |