venthrive-api / tests /test_bsa.py
GitHub Actions
Sync from GitHub 路 2026-10-22 23:15 UTC
b324c71
Raw History Blame Contribute Delete
3.07 kB
"""
Unit tests for the Business Strength Archetype: server-side scoring (bsa/scoring.py)
and the public results route (routes/bsa.py). Firestore is mocked.
"""
from unittest.mock import MagicMock, patch
import pytest
from fastapi import HTTPException
from bsa import scoring
from routes import bsa as bsa_routes
from routes.bsa import ResultIn, submit_result
@pytest.fixture(autouse=True)
def _reset_limits():
bsa_routes.limiter.reset()
def _natural():
return {qid: q["naturalFit"] for qid, q in scoring.QUESTIONS.items()}
def _body(answers=None, **over):
answers = answers or _natural()
base = dict(firstName=" Ada ", email=" Ada@Example.com ", phone=" +234 803 123 4567 ", ageRange=scoring.AGES[1], stage=scoring.STAGES[2],
consent=True, answers=[{"q": q, "dim": d} for q, d in answers.items()],
startedAt=1, sessionId="s-1")
base.update(over)
return ResultIn(**base)
def test_scoring_matches_the_documented_rules():
r = scoring.score(_natural())
assert set(r["raw"].values()) == {6}
assert (r["primary"], r["secondary"], r["blendKey"]) == ("ST", "AN", "ST-AN")
assert sum(r["pct"].values()) == 100
op = {qid: ("OP" if "OP" in q["dims"] else q["dims"][0]) for qid, q in scoring.QUESTIONS.items()}
r = scoring.score(op)
assert r["primary"] == "OP" and r["raw"]["OP"] == 24
def test_rejects_impossible_or_missing_answers():
answers = _natural()
first = min(answers)
bad = next(d for d in scoring.ORDER if d not in scoring.QUESTIONS[first]["dims"])
with pytest.raises(ValueError):
scoring.score({**answers, first: bad})
answers.pop(first)
with pytest.raises(ValueError):
scoring.score(answers)
def test_stores_details_and_server_computed_score():
db = MagicMock()
db.collection.return_value.add.return_value = (None, MagicMock(id="doc1"))
with patch.object(bsa_routes.firestore, "client", return_value=db):
out = submit_result(_body())
assert out["id"] == "doc1" and out["blendKey"] == "ST-AN"
db.collection.assert_called_with("bsa_results")
stored = db.collection.return_value.add.call_args[0][0]
assert stored["firstName"] == "Ada" and stored["email"] == "ada@example.com"
assert stored["phone"] == "+234 803 123 4567"
assert len(stored["answers"]) == 42 and stored["primary"] == "ST"
@pytest.mark.parametrize("over", [{"consent": False}, {"email": "not-an-email"}, {"phone": "call me maybe"}, {"phone": "123456"}, {"stage": "made up"}])
def test_rejects_bad_details(over):
with pytest.raises(HTTPException) as e:
submit_result(_body(**over))
assert e.value.status_code == 400
def test_rate_limits_per_browser():
db = MagicMock()
db.collection.return_value.add.return_value = (None, MagicMock(id="x"))
with patch.object(bsa_routes.firestore, "client", return_value=db):
for _ in range(5):
submit_result(_body())
with pytest.raises(HTTPException) as e:
submit_result(_body())
assert e.value.status_code == 429