/ tests / test_widget_rate_limit.py
test_widget_rate_limit.py
 1  """Tests for widget chat rate limiting."""
 2  import random
 3  import pytest
 4  from fastapi.testclient import TestClient
 5  
 6  from restai.config import RESTAI_DEFAULT_PASSWORD
 7  from restai.main import app
 8  
 9  suffix = str(random.randint(0, 1000000))
10  team_name = f"wrl_team_{suffix}"
11  project_name = f"wrl_project_{suffix}"
12  team_id = None
13  project_id = None
14  widget_key = None
15  
16  
17  @pytest.fixture(scope="module")
18  def client():
19      with TestClient(app) as c:
20          yield c
21  
22  
23  def test_setup(client):
24      """Create resources for widget rate limit tests."""
25      global team_id, project_id, widget_key
26      auth = ("admin", RESTAI_DEFAULT_PASSWORD)
27      resp = client.post("/teams", json={"name": team_name}, auth=auth)
28      assert resp.status_code in (200, 201)
29      team_id = resp.json()["id"]
30  
31      resp = client.post("/projects", json={"name": project_name, "type": "block", "team_id": team_id}, auth=auth)
32      assert resp.status_code == 201
33      project_id = resp.json()["project"]
34  
35      resp = client.post(f"/projects/{project_id}/widgets", json={"name": "RL Widget", "allowed_domains": []}, auth=auth)
36      assert resp.status_code == 201
37      widget_key = resp.json()["widget_key"]
38  
39  
40  def test_widget_rate_limit_triggers(client):
41      """After 30 requests in a minute, widget gets 429."""
42      from restai.routers.widgets import _widget_requests, _widget_lock
43      with _widget_lock:
44          _widget_requests.clear()
45  
46      for i in range(30):
47          client.post("/widget/chat", json={"question": "hi"}, headers={"X-Widget-Key": widget_key})
48  
49      resp = client.post("/widget/chat", json={"question": "hi"}, headers={"X-Widget-Key": widget_key})
50      assert resp.status_code == 429
51      assert "rate limit" in resp.json()["detail"].lower()
52  
53      with _widget_lock:
54          _widget_requests.clear()
55  
56  
57  def test_widget_works_after_rate_limit_reset(client):
58      """After clearing state, widget works again."""
59      from restai.routers.widgets import _widget_requests, _widget_lock
60      with _widget_lock:
61          _widget_requests.clear()
62  
63      resp = client.post("/widget/chat", json={"question": "hi"}, headers={"X-Widget-Key": widget_key})
64      # Block project will error on chat (no LLM), but should NOT be 429
65      assert resp.status_code != 429
66  
67  
68  def test_cleanup(client):
69      """Clean up."""
70      auth = ("admin", RESTAI_DEFAULT_PASSWORD)
71      client.delete(f"/projects/{project_name}", auth=auth)
72      client.delete(f"/teams/{team_id}", auth=auth)