Initial commit: CI Agent competitive-intelligence monitoring app
FastAPI + Celery + Next.js + Postgres/Redis app with company monitoring, source collection, LLM-based change analysis, enrichment, and account security (Turnstile, escalating lockout, email verification).
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
"""POST /companies/discover: persists nothing, returns a proposed profile,
|
||||
rate-limited tightly since it costs a real search + LLM call."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import uuid
|
||||
from unittest.mock import patch
|
||||
|
||||
import httpx
|
||||
import respx
|
||||
|
||||
from app.core.rate_limit import limiter
|
||||
|
||||
|
||||
def _register_and_login(client) -> dict[str, str]:
|
||||
email = f"user-{uuid.uuid4().hex[:12]}@example.com"
|
||||
client.post(
|
||||
"/api/v1/auth/register",
|
||||
json={"email": email, "password": "correct-horse-1", "display_name": "Test User"},
|
||||
)
|
||||
tokens = client.post(
|
||||
"/api/v1/auth/login", json={"email": email, "password": "correct-horse-1"}
|
||||
).json()
|
||||
return {"Authorization": f"Bearer {tokens['access_token']}"}
|
||||
|
||||
|
||||
def _mock_empty_github_sec():
|
||||
respx.get("https://api.github.com/search/users").mock(
|
||||
return_value=httpx.Response(200, text=json.dumps({"items": []}))
|
||||
)
|
||||
respx.get("https://www.sec.gov/cgi-bin/browse-edgar").mock(
|
||||
return_value=httpx.Response(
|
||||
200, text='<?xml version="1.0"?><feed xmlns="http://www.w3.org/2005/Atom"></feed>'
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_discover_returns_a_profile_without_persisting_a_company(client):
|
||||
headers = _register_and_login(client)
|
||||
|
||||
with patch("socket.getaddrinfo", return_value=[(2, 1, 6, "", ("93.184.216.34", 0))]):
|
||||
with respx.mock:
|
||||
_mock_empty_github_sec()
|
||||
respx.get("https://acmewidgets.com/robots.txt").mock(return_value=httpx.Response(404))
|
||||
respx.get("https://acmewidgets.com").mock(
|
||||
return_value=httpx.Response(200, html="<html><body>Acme Widgets</body></html>")
|
||||
)
|
||||
resp = client.post(
|
||||
"/api/v1/companies/discover",
|
||||
json={"name": "Acme Widgets"},
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
assert resp.status_code == 200
|
||||
body = resp.json()
|
||||
assert body["name"] == "Acme Widgets"
|
||||
assert body["official_website"] == "https://acmewidgets.com"
|
||||
assert "potential_sources" in body
|
||||
assert "sources_consulted" in body
|
||||
|
||||
companies = client.get("/api/v1/companies", headers=headers).json()
|
||||
assert companies == []
|
||||
|
||||
|
||||
def test_discover_requires_auth(client):
|
||||
resp = client.post("/api/v1/companies/discover", json={"name": "Acme"})
|
||||
assert resp.status_code in (401, 403)
|
||||
|
||||
|
||||
def test_discover_enforces_rate_limit(client):
|
||||
headers = _register_and_login(client)
|
||||
|
||||
limiter.enabled = True
|
||||
try:
|
||||
with patch("socket.getaddrinfo", return_value=[(2, 1, 6, "", ("93.184.216.34", 0))]):
|
||||
with respx.mock:
|
||||
_mock_empty_github_sec()
|
||||
respx.route(host="acme0.com").mock(return_value=httpx.Response(404))
|
||||
respx.route(host="acme1.com").mock(return_value=httpx.Response(404))
|
||||
respx.route(host="acme2.com").mock(return_value=httpx.Response(404))
|
||||
respx.route(host="acme3.com").mock(return_value=httpx.Response(404))
|
||||
respx.route(host="acme4.com").mock(return_value=httpx.Response(404))
|
||||
respx.route(host="acme5.com").mock(return_value=httpx.Response(404))
|
||||
statuses = [
|
||||
client.post(
|
||||
"/api/v1/companies/discover",
|
||||
json={"name": f"Acme{i}"},
|
||||
headers=headers,
|
||||
).status_code
|
||||
for i in range(6)
|
||||
]
|
||||
assert 429 in statuses, f"Expected a 429 among {statuses} after 6 rapid discover calls"
|
||||
finally:
|
||||
limiter.enabled = False
|
||||
Reference in New Issue
Block a user