Recently Written · git

subplz-web

git clone https://github.com/equwal/subplz-web

Log | Files | Refs


tests/conftest.py (5001 bytes)

1 """Test harness: a throwaway data dir, billing on, Stripe faked at the SDK edge.
2 
3 Settings are read once at import, so the environment has to be in place before
4 anything under `backend` is imported - hence doing it here, at module load.
5 """
6 
7 from __future__ import annotations
8 
9 import hashlib
10 import hmac
11 import json
12 import os
13 import tempfile
14 import time
15 from pathlib import Path
16 
17 _TMP = tempfile.mkdtemp(prefix="subplz-web-tests-")
18 os.environ.update(
19     SUBPLZ_WEB_DATA_DIR=_TMP,
20     SUBPLZ_WEB_BILLING_ENABLED="true",
21     SUBPLZ_WEB_STRIPE_SECRET_KEY="sk_test_dummy",
22     SUBPLZ_WEB_STRIPE_WEBHOOK_SECRET="whsec_test_secret",
23     SUBPLZ_WEB_PUBLIC_BASE_URL="https://example.test",
24     SUBPLZ_WEB_MATCH_CHECK="false",
25     SUBPLZ_WEB_SMTP_HOST="",
26 )
27 
28 import pytest  # noqa: E402
29 from fastapi.testclient import TestClient  # noqa: E402
30 
31 from backend import api  # noqa: E402
32 from backend.db import (  # noqa: E402
33     Account, Artifact, Job, JobStatus, SessionLocal, init_db,
34 )
35 from backend.main import app  # noqa: E402
36 from backend.storage import storage  # noqa: E402
37 
38 WEBHOOK_SECRET = "whsec_test_secret"
39 
40 init_db()
41 
42 
43 @pytest.fixture(autouse=True)
44 def _no_real_work(monkeypatch):
45     """Starting a job must not actually launch an alignment."""
46     monkeypatch.setattr(api.queue, "enqueue", lambda job_id: None)
47 
48 
49 @pytest.fixture
50 def client():
51     """A browser: keeps its cookie, so it stays one account across requests."""
52     with TestClient(app, base_url="http://testserver") as c:
53         yield c
54 
55 
56 @pytest.fixture
57 def second_client():
58     with TestClient(app, base_url="http://testserver") as c:
59         yield c
60 
61 
62 def account_id(client: TestClient) -> str:
63     return client.get("/api/account").json()["id"]
64 
65 
66 def make_job(
67     client: TestClient, status: JobStatus = JobStatus.draft, with_files: bool = False
68 ) -> str:
69     """A job row owned by `client`, skipping the upload (which needs real audio)."""
70     acct = account_id(client)
71     with SessionLocal() as s:
72         job = Job(
73             account_id=acct, status=status, language="en", splitter="pysbd",
74             model="tiny", audio_filename="book.m4b", text_filename="book.epub",
75         )
76         s.add(job)
77         s.commit()
78         job_id = job.id
79 
80         if with_files:
81             for kind, name in [("srt", "book.en.srt"), ("video", "book.en.mp4"),
82                                ("video_embedded", "book.en.mkv")]:
83                 src = Path(_TMP) / f"{job_id}-{name}"
84                 src.write_bytes(b"x" * 10)
85                 key = f"{job_id}/{name}"
86                 storage.put_file(key, src)
87                 s.add(Artifact(job_id=job_id, kind=kind, filename=name,
88                                storage_key=key, size_bytes=10))
89             s.commit()
90     return job_id
91 
92 
93 def get_account_row(account_id_: str) -> Account:
94     with SessionLocal() as s:
95         return s.get(Account, account_id_)
96 
97 
98 def get_job_row(job_id: str) -> Job:
99     with SessionLocal() as s:
100         return s.get(Job, job_id)
101 
102 
103 def signed(payload: dict, secret: str = WEBHOOK_SECRET) -> tuple[bytes, dict]:
104     """A webhook body plus the header Stripe would have sent with it."""
105     body = json.dumps(payload).encode()
106     stamp = int(time.time())
107     digest = hmac.new(
108         secret.encode(), f"{stamp}.".encode() + body, hashlib.sha256
109     ).hexdigest()
110     return body, {
111         "stripe-signature": f"t={stamp},v1={digest}",
112         "content-type": "application/json",
113     }
114 
115 
116 def checkout_event(
117     session_id: str, account: str, plan: str, email: str | None = "buyer@example.com",
118     job_id: str = "", payment_status: str = "paid", subscription: str | None = None,
119     customer: str | None = "cus_test",
120 ) -> dict:
121     return {
122         "id": f"evt_{session_id}",
123         "type": "checkout.session.completed",
124         "data": {"object": checkout_object(
125             session_id, account, plan, email, job_id, payment_status,
126             subscription, customer,
127         )},
128     }
129 
130 
131 def checkout_object(session_id, account, plan, email="buyer@example.com",
132                     job_id="", payment_status="paid", subscription=None,
133                     customer="cus_test") -> dict:
134     return {
135         "id": session_id,
136         "object": "checkout.session",
137         "client_reference_id": account,
138         "customer": customer,
139         "customer_details": {"email": email} if email else None,
140         "payment_status": payment_status,
141         "amount_total": 1299,
142         "currency": "usd",
143         "subscription": subscription,
144         "metadata": {"account_id": account, "plan_id": plan, "job_id": job_id},
145     }
146 
147 
148 def post_webhook(client: TestClient, event: dict, secret: str = WEBHOOK_SECRET):
149     body, headers = signed(event, secret)
150     return client.post("/api/billing/webhook", content=body, headers=headers)
151 
152 
153 class FakeStripeObject:
154     """Stands in for an SDK return value: not a dict, but has to_dict()."""
155 
156     def __init__(self, data: dict):
157         self._data = data
158 
159     def to_dict(self) -> dict:
160         return self._data