306b5c1e5d
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
155 lines
6.2 KiB
Python
155 lines
6.2 KiB
Python
"""SLICE-3 storefront creation — service + endpoint scenario tests (SD-0001 §6.5 PUC-4/7)."""
|
|
import re
|
|
from contextlib import contextmanager
|
|
|
|
import psycopg
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
from app.main import create_app
|
|
from app.domains import storefronts
|
|
from app.platform import db
|
|
|
|
|
|
@pytest.fixture()
|
|
def migrated_conn(fresh_db_url):
|
|
with psycopg.connect(fresh_db_url) as conn:
|
|
db.migrate(conn)
|
|
yield conn
|
|
|
|
|
|
def _account_id(conn, email="merchant@example.com"):
|
|
return conn.execute(
|
|
"INSERT INTO account (email) VALUES (%s) RETURNING id", (email,)
|
|
).fetchone()[0]
|
|
|
|
|
|
def test_create_storefront_with_name(migrated_conn):
|
|
acct = _account_id(migrated_conn)
|
|
sf = storefronts.create_storefront(migrated_conn, acct, "merchant@example.com", "Ben's Bets")
|
|
assert sf.name == "Ben's Bets"
|
|
assert storefronts.storefront_for(migrated_conn, acct) == sf
|
|
|
|
|
|
def test_14_01_0026_blank_name_gets_generated_default(migrated_conn):
|
|
acct = _account_id(migrated_conn)
|
|
sf = storefronts.create_storefront(migrated_conn, acct, "merchant@example.com", None)
|
|
assert sf.name == "merchant's storefront"
|
|
|
|
|
|
def test_whitespace_name_treated_as_blank(migrated_conn):
|
|
acct = _account_id(migrated_conn)
|
|
sf = storefronts.create_storefront(migrated_conn, acct, "ben@bensbets.com", " ")
|
|
assert sf.name == "ben's storefront"
|
|
|
|
|
|
def test_second_create_refused_inv_4(migrated_conn):
|
|
acct = _account_id(migrated_conn)
|
|
storefronts.create_storefront(migrated_conn, acct, "merchant@example.com", "First")
|
|
with pytest.raises(storefronts.AlreadyOwnsStorefront):
|
|
storefronts.create_storefront(migrated_conn, acct, "merchant@example.com", "Second")
|
|
|
|
|
|
def test_membership_row_is_owner(migrated_conn):
|
|
acct = _account_id(migrated_conn)
|
|
sf = storefronts.create_storefront(migrated_conn, acct, "merchant@example.com", None)
|
|
role = migrated_conn.execute(
|
|
"SELECT role FROM storefront_membership WHERE account_id = %s AND storefront_id = %s",
|
|
(acct, sf.id),
|
|
).fetchone()[0]
|
|
assert role == "owner"
|
|
|
|
|
|
def test_storefront_for_none_when_absent(migrated_conn):
|
|
acct = _account_id(migrated_conn)
|
|
assert storefronts.storefront_for(migrated_conn, acct) is None
|
|
|
|
|
|
# ── endpoint scenario tests (§6.4 POST /api/storefronts; corpus 14.01.*) ──────────
|
|
|
|
|
|
@contextmanager
|
|
def _signed_in_client(fresh_db_url, email="merchant@example.com"):
|
|
with TestClient(create_app(database_url=fresh_db_url)) as client:
|
|
client.post("/api/auth/request-code", json={"email": email})
|
|
code = re.search(r"\b(\d{6})\b", client.app.state.mailer.outbox[-1].body).group(1)
|
|
client.post("/api/auth/verify", json={"email": email, "code": code})
|
|
yield client
|
|
|
|
|
|
def test_14_01_0013_create_storefront_with_name(fresh_db_url):
|
|
with _signed_in_client(fresh_db_url) as client:
|
|
resp = client.post("/api/storefronts", json={"name": "Ben's Bets"})
|
|
assert resp.status_code == 201
|
|
assert resp.json()["name"] == "Ben's Bets"
|
|
assert isinstance(resp.json()["id"], int)
|
|
|
|
|
|
def test_14_01_0015_0016_nothing_but_a_name_is_asked(fresh_db_url):
|
|
# No payment method, plan, or commitment: the request body needs nothing at all and
|
|
# the response carries only the storefront — no plan/trial/billing fields.
|
|
with _signed_in_client(fresh_db_url) as client:
|
|
resp = client.post("/api/storefronts", json={})
|
|
assert resp.status_code == 201
|
|
assert set(resp.json().keys()) == {"id", "name"}
|
|
|
|
|
|
def test_14_01_0025_create_lands_on_admin(fresh_db_url):
|
|
# After create, the entry-routing answer carries the storefront -> admin (PUC-6).
|
|
with _signed_in_client(fresh_db_url) as client:
|
|
created = client.post("/api/storefronts", json={"name": "Ben's Bets"}).json()
|
|
me = client.get("/api/auth/me").json()
|
|
assert me["storefront"] == created
|
|
|
|
|
|
def test_puc_05_returning_without_storefront_routes_to_create(fresh_db_url):
|
|
with _signed_in_client(fresh_db_url) as client:
|
|
me = client.get("/api/auth/me").json()
|
|
assert me["storefront"] is None
|
|
|
|
|
|
def test_14_01_0027_returning_with_storefront_straight_to_admin(fresh_db_url):
|
|
# PUC-6: a returning merchant's verify response already carries the storefront.
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
email = "returning@example.com"
|
|
with _signed_in_client(fresh_db_url, email) as client:
|
|
client.post("/api/storefronts", json={"name": "Ben's Bets"})
|
|
# fresh login: backdate the consumed code to clear the 60s resend cooldown
|
|
with psycopg.connect(fresh_db_url) as c:
|
|
c.execute(
|
|
"UPDATE auth_code SET created_at = %s WHERE email = %s",
|
|
(datetime.now(timezone.utc) - timedelta(minutes=2), email),
|
|
)
|
|
c.commit()
|
|
client.post("/api/auth/request-code", json={"email": email})
|
|
code = re.search(r"\b(\d{6})\b", client.app.state.mailer.outbox[-1].body).group(1)
|
|
resp = client.post("/api/auth/verify", json={"email": email, "code": code})
|
|
assert resp.status_code == 200
|
|
assert resp.json()["storefront"]["name"] == "Ben's Bets"
|
|
assert resp.json()["created"] is False
|
|
|
|
|
|
def test_puc_07_second_create_refused_409(fresh_db_url):
|
|
with _signed_in_client(fresh_db_url) as client:
|
|
client.post("/api/storefronts", json={"name": "First"})
|
|
resp = client.post("/api/storefronts", json={"name": "Second"})
|
|
assert resp.status_code == 409
|
|
assert resp.json()["error"]["code"] == "already_owns_storefront"
|
|
|
|
|
|
def test_puc_08_admin_shell_answer(fresh_db_url):
|
|
# The shell renders from /me alone: storefront name + signed-in email (§6.5 PUC-8).
|
|
with _signed_in_client(fresh_db_url) as client:
|
|
client.post("/api/storefronts", json={"name": "Ben's Bets"})
|
|
me = client.get("/api/auth/me").json()
|
|
assert me["account"]["email"] == "merchant@example.com"
|
|
assert me["storefront"]["name"] == "Ben's Bets"
|
|
|
|
|
|
def test_create_storefront_requires_session(fresh_db_url):
|
|
with TestClient(create_app(database_url=fresh_db_url)) as client:
|
|
resp = client.post("/api/storefronts", json={"name": "Nope"})
|
|
assert resp.status_code == 401
|
|
assert resp.json()["error"]["code"] == "unauthenticated"
|