Source-agnostic license issuance service. FastAPI app fronts a Postgres `licenses` table; the only currently-wired source is `manual` (operator mints via /internal/mint). Gumroad webhook adapter lands in PR 2. Key design points: - Signing reuses src/license/crypto.py via a COPY into the image (single source of truth — blobs minted server-side verify against the same embedded pubkey on the buyer's machine). - Source adapter Protocol (app/adapters/base.py) is the seam for Gumroad / Lemon Squeezy / Stripe in later PRs; Mint API speaks only SaleEvent / RefundEvent. - (source, source_order_id) UNIQUE composite gives idempotent webhook retries without double-mint. - JSONB type uses with_variant(JSON, 'sqlite') so the same models drive both Postgres prod and SQLite tests (no testcontainers dep). - Bearer-token auth on /internal/*; the IP-loopback guard was removed after the docker bridge made it fight legitimate prod traffic (nginx defense + Bearer remain). - Secrets resolved via *_FILE env vars pointing at /run/secrets/<name>, so passwords never appear in `docker inspect`. 21 unit tests (SQLite in-memory, StaticPool) plus a real-Postgres docker-compose smoke test in server/scripts/smoke.sh that builds the image, runs the alembic migration, mints a license, verifies the signature against the host dev pubkey, and checks the DB row. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
53 lines
1.5 KiB
Python
53 lines
1.5 KiB
Python
"""ManualAdapter — building a SaleEvent from CLI-style kwargs."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from decimal import Decimal
|
|
|
|
from app.adapters.manual import ManualAdapter
|
|
|
|
|
|
def test_build_sale_minimal_defaults():
|
|
a = ManualAdapter()
|
|
sale = a.build_sale(name="Jane Doe", email="jane@example.com", tier="core")
|
|
assert sale.source == "manual"
|
|
assert sale.source_order_id is None
|
|
assert sale.buyer_name == "Jane Doe"
|
|
assert sale.buyer_email == "jane@example.com"
|
|
assert sale.tier == "core"
|
|
assert sale.years == 1
|
|
assert sale.currency == "USD"
|
|
assert sale.promotion is None
|
|
assert sale.amount_paid is None
|
|
assert sale.notes is None
|
|
|
|
|
|
def test_build_sale_full_metadata():
|
|
a = ManualAdapter()
|
|
sale = a.build_sale(
|
|
name="Acme",
|
|
email="ops@acme.example",
|
|
tier="pro",
|
|
years=2,
|
|
promotion="LAUNCH50",
|
|
amount_paid=Decimal("249.00"),
|
|
currency="EUR",
|
|
notes="comp for beta tester",
|
|
)
|
|
assert sale.years == 2
|
|
assert sale.promotion == "LAUNCH50"
|
|
assert sale.amount_paid == Decimal("249.00")
|
|
assert sale.currency == "EUR"
|
|
assert sale.notes == "comp for beta tester"
|
|
|
|
|
|
def test_verify_webhook_always_false():
|
|
"""Manual flow never originates from a webhook."""
|
|
a = ManualAdapter()
|
|
assert a.verify_webhook(body=b"{}", headers={}) is False
|
|
|
|
|
|
def test_parse_refund_returns_none():
|
|
a = ManualAdapter()
|
|
assert a.parse_refund({"any": "payload"}) is None
|