Files
aza/AzA march 2026/tests/test_ai_credit_topup.py

1223 lines
46 KiB
Python
Raw Normal View History

2026-05-23 21:31:34 +02:00
# -*- coding: utf-8 -*-
"""Tests KI-Zusatzguthaben / Ledger (Mocks, keine Stripe-Charges)."""
from __future__ import annotations
import json
import os
import sqlite3
import tempfile
import unittest
from pathlib import Path
from aza_ai_budget import (
LicenseBudgetRow,
budget_gate_blocked_payload_or_none,
check_allows_openai_call,
compute_budget_snapshot,
ensure_ai_budget_schema,
insert_usage_event,
)
from aza_ai_credit import (
AUTO_TOPUP_COOLDOWN_SEC,
DEFAULT_INTERNAL_USD,
DEFAULT_MONTHLY_LIMIT_CHF,
DEFAULT_SETUP_SUCCESS_URL,
DEFAULT_TOPUP_SUCCESS_URL,
DEFAULT_TRIGGER_PERCENT,
apply_auto_topup_user_settings,
apply_extra_credit_to_snapshot,
auto_topup_is_fully_configured,
auto_topup_settings_status,
normalize_stripe_id,
chf_to_internal_usd,
compute_extra_credit_remaining,
create_setup_checkout_session,
create_topup_checkout_session,
ensure_ai_credit_schema,
get_topup_settings,
has_pending_auto_topup,
insert_ledger_event,
last_successful_auto_topup_ts,
ledger_exists_for_checkout,
ledger_exists_for_payment_intent,
list_ledger_for_practice,
list_user_credit_history,
mask_stripe_reference,
maybe_run_auto_topup_for_practice,
process_auto_topup_payment_intent_succeeded,
process_setup_checkout_completed,
process_setup_intent_succeeded,
process_topup_checkout_completed,
process_topup_payment_intent_succeeded,
save_topup_settings,
sum_auto_topups_chf_this_month,
TopupSettings,
)
def _mk_db(path: Path) -> None:
con = sqlite3.connect(str(path))
con.execute(
"""
CREATE TABLE licenses (
subscription_id TEXT PRIMARY KEY,
customer_id TEXT,
status TEXT,
lookup_key TEXT,
allowed_users INTEGER,
devices_per_user INTEGER,
customer_email TEXT,
client_reference_id TEXT,
current_period_end INTEGER,
current_period_start INTEGER,
updated_at INTEGER NOT NULL,
license_key TEXT,
practice_id TEXT
)
"""
)
now = 1_700_000_000
ps = now
pe = now + 86400 * 30
con.execute(
"""
INSERT INTO licenses(subscription_id, customer_id, status, lookup_key, allowed_users, devices_per_user,
customer_email, client_reference_id, current_period_end, current_period_start, updated_at, license_key, practice_id)
VALUES ('sub_top', 'cus_x', 'active', 'aza_basic_monthly', 1, 2,
'cli@example.test', NULL, ?, ?, ?, 'KEY', 'prac_top')
""",
(pe, ps, now),
)
con.commit()
con.close()
def _lic() -> LicenseBudgetRow:
now = 1_700_000_000
return LicenseBudgetRow(
subscription_id="sub_top",
customer_email="cli@example.test",
customer_id="cus_x",
practice_id="prac_top",
lookup_key="aza_basic_monthly",
status="active",
period_start=now,
period_end=now + 86400 * 30,
)
class TestAiCreditTopup(unittest.TestCase):
def setUp(self) -> None:
self.tmp = tempfile.NamedTemporaryFile(suffix=".sqlite", delete=False)
self.tmp.close()
self.db = Path(self.tmp.name)
_mk_db(self.db)
self.con = sqlite3.connect(str(self.db))
ensure_ai_budget_schema(self.con)
ensure_ai_credit_schema(self.con)
def tearDown(self) -> None:
self.con.close()
self.db.unlink(missing_ok=True)
def test_chf30_gives_10_usd(self) -> None:
self.assertAlmostEqual(chf_to_internal_usd(30), 10.0, places=2)
def test_chf10_gives_3333_usd(self) -> None:
self.assertAlmostEqual(chf_to_internal_usd(10), 3.3333, places=4)
def test_custom_amount_formula(self) -> None:
self.assertAlmostEqual(chf_to_internal_usd(45), 15.0, places=1)
self.assertAlmostEqual(chf_to_internal_usd(50), 16.6667, places=4)
self.assertAlmostEqual(chf_to_internal_usd(300), 100.0, places=4)
def test_ledger_schema_idempotent(self) -> None:
ensure_ai_credit_schema(self.con)
ensure_ai_credit_schema(self.con)
cols = [r[1] for r in self.con.execute("PRAGMA table_info(ai_credit_ledger)").fetchall()]
self.assertIn("event_type", cols)
def test_webhook_writes_single_credit(self) -> None:
session = {
"id": "cs_test_001",
"payment_status": "paid",
"metadata": {
"aza_purpose": "ai_topup",
"practice_id": "prac_top",
"subscription_id": "sub_top",
"internal_credit_usd": "20",
"paid_chf": "30",
},
}
r1 = process_topup_checkout_completed(self.con, session=session)
r2 = process_topup_checkout_completed(self.con, session=session)
self.assertTrue(r1.get("credited"))
self.assertTrue(r2.get("duplicate"))
cnt = self.con.execute(
"SELECT COUNT(*) FROM ai_credit_ledger WHERE stripe_checkout_session_id='cs_test_001'"
).fetchone()[0]
self.assertEqual(cnt, 1)
def test_failed_payment_no_credit(self) -> None:
session = {
"id": "cs_test_fail",
"payment_status": "unpaid",
"metadata": {
"aza_purpose": "ai_topup",
"practice_id": "prac_top",
"subscription_id": "sub_top",
"internal_credit_usd": "20",
"paid_chf": "30",
},
}
r = process_topup_checkout_completed(self.con, session=session)
self.assertFalse(r.get("credited"))
row = self.con.execute(
"SELECT status, amount_internal_usd FROM ai_credit_ledger WHERE stripe_checkout_session_id='cs_test_fail'"
).fetchone()
self.assertEqual(row[0], "failed")
self.assertEqual(row[1], 0.0)
def test_budget_includes_extra_when_monthly_exhausted(self) -> None:
lic = _lic()
insert_ledger_event(
self.con,
practice_id="prac_top",
subscription_id="sub_top",
customer_email=None,
event_type="topup_purchase",
amount_internal_usd=20.0,
amount_paid_chf=30.0,
stripe_checkout_session_id="cs_paid",
status="succeeded",
)
ps, pe = lic.period_start, lic.period_end
insert_usage_event(
self.con,
lic=lic,
device_id=None,
period_start=ps,
period_end=pe,
operation_type="chat",
model="gpt-4o-mini",
input_tokens=1,
output_tokens=1,
total_tokens=2,
audio_seconds=0.0,
estimated_cost_usd=20.0,
request_id="u1",
status="success",
)
snap = compute_budget_snapshot(self.con, lic)
self.assertEqual(snap["remaining_usd"], 0.0)
self.assertGreater(snap.get("extra_credit_remaining_usd", 0), 0)
ok, _ = check_allows_openai_call(self.con, lic)
self.assertTrue(ok)
def test_month_rollover_extra_persists(self) -> None:
lic = _lic()
insert_ledger_event(
self.con,
practice_id="prac_top",
subscription_id="sub_top",
customer_email=None,
event_type="topup_purchase",
amount_internal_usd=15.0,
amount_paid_chf=22.5,
stripe_checkout_session_id="cs_roll",
status="succeeded",
)
extra_before = compute_extra_credit_remaining(
self.con,
practice_id="prac_top",
subscription_id="sub_top",
monthly_budget_usd=20.0,
)
lic_new = LicenseBudgetRow(
subscription_id="sub_top",
customer_email="cli@example.test",
customer_id="cus_x",
practice_id="prac_top",
lookup_key="aza_basic_monthly",
status="active",
period_start=lic.period_end + 1,
period_end=lic.period_end + 86400 * 30,
)
extra_after = compute_extra_credit_remaining(
self.con,
practice_id="prac_top",
subscription_id="sub_top",
monthly_budget_usd=20.0,
)
self.assertAlmostEqual(extra_before, extra_after, places=2)
snap = compute_budget_snapshot(self.con, lic_new)
self.assertEqual(snap["used_usd"], 0.0)
self.assertAlmostEqual(snap.get("extra_credit_remaining_usd", 0), 15.0, places=2)
def test_auto_topup_settings_save(self) -> None:
settings = TopupSettings(
practice_id="prac_top",
auto_topup_enabled=True,
topup_amount_chf=30.0,
internal_credit_usd=10.0,
trigger_below_percent=10,
monthly_limit_chf=300.0,
stripe_customer_id=None,
default_payment_method_id=None,
)
save_topup_settings(self.con, settings)
loaded = get_topup_settings(self.con, "prac_top")
self.assertTrue(loaded.auto_topup_enabled)
self.assertEqual(loaded.monthly_limit_chf, 300.0)
def test_setup_session_dry_run_no_charge(self) -> None:
os.environ["AZA_AI_TOPUP_DRY_RUN"] = "1"
r = create_setup_checkout_session(
practice_id="prac_top",
customer_email="cli@example.test",
success_url="https://example/s",
cancel_url="https://example/c",
)
self.assertTrue(r.get("dry_run"))
self.assertIsNone(r.get("checkout_url"))
def test_topup_checkout_dry_run(self) -> None:
os.environ["AZA_AI_TOPUP_DRY_RUN"] = "1"
r = create_topup_checkout_session(
practice_id="prac_top",
subscription_id="sub_top",
customer_email="cli@example.test",
paid_chf=30.0,
internal_usd=20.0,
success_url="https://example/s",
cancel_url="https://example/c",
)
self.assertTrue(r.get("dry_run"))
self.assertIsNone(r.get("checkout_url"))
def test_auto_topup_respects_monthly_limit(self) -> None:
save_topup_settings(
self.con,
TopupSettings(
practice_id="prac_top",
auto_topup_enabled=True,
topup_amount_chf=30.0,
internal_credit_usd=20.0,
trigger_below_percent=50,
monthly_limit_chf=60.0,
stripe_customer_id="cus_x",
default_payment_method_id="pm_test",
),
)
for i in range(2):
insert_ledger_event(
self.con,
practice_id="prac_top",
subscription_id="sub_top",
customer_email=None,
event_type="auto_topup_purchase",
amount_internal_usd=20.0,
amount_paid_chf=30.0,
stripe_checkout_session_id=f"cs_auto_{i}",
status="succeeded",
)
r = maybe_run_auto_topup_for_practice(self.con, practice_id="prac_top", available_percent=5, dry_run=True)
self.assertEqual(r.get("reason"), "auto_topup_monthly_limit_reached")
self.assertEqual(r.get("error_code"), "AUTO_TOPUP_MONTHLY_LIMIT_REACHED")
def test_no_auto_charge_without_payment_method(self) -> None:
save_topup_settings(
self.con,
TopupSettings(
practice_id="prac_top",
auto_topup_enabled=True,
topup_amount_chf=30.0,
internal_credit_usd=10.0,
trigger_below_percent=10,
monthly_limit_chf=300.0,
stripe_customer_id="cus_x",
default_payment_method_id=None,
),
)
r = maybe_run_auto_topup_for_practice(self.con, practice_id="prac_top", available_percent=5, dry_run=False)
self.assertEqual(r.get("reason"), "no_payment_method")
def test_meta_json_no_secrets(self) -> None:
eid = insert_ledger_event(
self.con,
practice_id="prac_top",
subscription_id="sub_top",
customer_email=None,
event_type="topup_purchase",
amount_internal_usd=1.0,
meta={"note": "ok", "api_secret_key": "must_not_store", "default_payment_method_id": "pm_x"},
)
row = self.con.execute("SELECT meta_json FROM ai_credit_ledger WHERE id=?", (eid,)).fetchone()
meta = json.loads(row[0])
self.assertNotIn("api_secret_key", meta)
self.assertNotIn("default_payment_method_id", meta)
def test_usage_events_unchanged_schema(self) -> None:
cols = [r[1] for r in self.con.execute("PRAGMA table_info(ai_usage_events)").fetchall()]
self.assertIn("estimated_cost_usd", cols)
self.assertIn("subscription_id", cols)
def test_ledger_exists_for_checkout(self) -> None:
self.assertFalse(ledger_exists_for_checkout(self.con, "cs_new"))
insert_ledger_event(
self.con,
practice_id="prac_top",
subscription_id="sub_top",
customer_email=None,
event_type="topup_purchase",
amount_internal_usd=1.0,
stripe_checkout_session_id="cs_new",
status="succeeded",
)
self.assertTrue(ledger_exists_for_checkout(self.con, "cs_new"))
def test_setup_webhook_enables_auto_with_defaults(self) -> None:
session = {
"id": "cs_setup_1",
"customer": "cus_setup",
"setup_intent": {"id": "seti_setup_1", "payment_method": "pm_setup_1"},
"metadata": {"aza_purpose": "ai_topup_setup", "practice_id": "prac_top"},
}
r = process_setup_checkout_completed(self.con, session=session)
self.assertTrue(r.get("handled"))
self.assertTrue(r.get("auto_topup_enabled"))
loaded = get_topup_settings(self.con, "prac_top")
self.assertTrue(loaded.auto_topup_enabled)
self.assertEqual(loaded.stripe_customer_id, "cus_setup")
self.assertEqual(loaded.topup_amount_chf, 30.0)
self.assertEqual(loaded.internal_credit_usd, 10.0)
self.assertEqual(loaded.trigger_below_percent, 5)
self.assertEqual(loaded.monthly_limit_chf, 300.0)
ledger_count = self.con.execute("SELECT COUNT(*) FROM ai_credit_ledger").fetchone()[0]
self.assertEqual(ledger_count, 0)
def test_setup_intent_succeeded_saves_payment_method(self) -> None:
setup_intent = {
"id": "seti_test_1",
"customer": "cus_setup2",
"payment_method": "pm_setup_test",
"metadata": {"aza_purpose": "ai_topup_setup", "practice_id": "prac_top"},
}
r = process_setup_intent_succeeded(self.con, setup_intent=setup_intent)
self.assertTrue(r.get("handled"))
loaded = get_topup_settings(self.con, "prac_top")
self.assertTrue(loaded.auto_topup_enabled)
self.assertEqual(loaded.stripe_customer_id, "cus_setup2")
self.assertEqual(loaded.default_payment_method_id, "pm_setup_test")
def test_setup_checkout_with_embedded_setup_intent_pm(self) -> None:
session = {
"id": "cs_setup_2",
"customer": "cus_setup3",
"setup_intent": {
"id": "seti_embedded",
"payment_method": {"id": "pm_embedded"},
},
"metadata": {"aza_purpose": "ai_topup_setup", "practice_id": "prac_top"},
}
r = process_setup_checkout_completed(self.con, session=session)
self.assertTrue(r.get("handled"))
loaded = get_topup_settings(self.con, "prac_top")
self.assertEqual(loaded.stripe_customer_id, "cus_setup3")
self.assertEqual(loaded.default_payment_method_id, "pm_embedded")
self.assertTrue(loaded.auto_topup_enabled)
def test_normalize_stripe_id_from_dict_and_json(self) -> None:
self.assertEqual(normalize_stripe_id("pm_abc123", "pm_"), "pm_abc123")
self.assertEqual(normalize_stripe_id({"id": "pm_from_dict"}, "pm_"), "pm_from_dict")
self.assertEqual(
normalize_stripe_id('{"id": "pm_from_json", "object": "payment_method"}', "pm_"),
"pm_from_json",
)
class _FakePm:
id = "pm_from_obj"
self.assertEqual(normalize_stripe_id(_FakePm(), "pm_"), "pm_from_obj")
self.assertIsNone(normalize_stripe_id("not_an_id", "pm_"))
def test_setup_incomplete_without_customer(self) -> None:
session = {
"id": "cs_setup_nc",
"customer": None,
"setup_intent": {"id": "seti_x", "payment_method": "pm_only"},
"metadata": {"aza_purpose": "ai_topup_setup", "practice_id": "prac_top"},
}
r = process_setup_checkout_completed(self.con, session=session)
self.assertEqual(r.get("reason"), "AUTO_TOPUP_SETUP_INCOMPLETE")
loaded = get_topup_settings(self.con, "prac_top")
self.assertFalse(loaded.auto_topup_enabled)
self.assertEqual(loaded.default_payment_method_id, "pm_only")
self.assertIsNone(loaded.stripe_customer_id)
def test_setup_incomplete_without_payment_method(self) -> None:
session = {
"id": "cs_setup_np",
"customer": "cus_only",
"setup_intent": None,
"metadata": {"aza_purpose": "ai_topup_setup", "practice_id": "prac_top"},
}
r = process_setup_checkout_completed(self.con, session=session)
self.assertEqual(r.get("reason"), "AUTO_TOPUP_SETUP_INCOMPLETE")
loaded = get_topup_settings(self.con, "prac_top")
self.assertFalse(loaded.auto_topup_enabled)
self.assertEqual(loaded.stripe_customer_id, "cus_only")
def test_setup_stripe_object_pm_normalized_not_json(self) -> None:
class _FakePm:
id = "pm_stripe_obj"
session = {
"id": "cs_setup_obj",
"customer": "cus_obj",
"setup_intent": {"id": "seti_obj", "payment_method": _FakePm()},
"metadata": {"aza_purpose": "ai_topup_setup", "practice_id": "prac_top"},
}
r = process_setup_checkout_completed(self.con, session=session)
self.assertTrue(r.get("auto_topup_enabled"))
loaded = get_topup_settings(self.con, "prac_top")
self.assertEqual(loaded.default_payment_method_id, "pm_stripe_obj")
self.assertFalse(str(loaded.default_payment_method_id or "").startswith("{"))
def test_payment_method_configured_requires_cus_and_pm(self) -> None:
save_topup_settings(
self.con,
TopupSettings(
practice_id="prac_top",
auto_topup_enabled=True,
topup_amount_chf=30.0,
internal_credit_usd=10.0,
trigger_below_percent=5,
monthly_limit_chf=300.0,
stripe_customer_id=None,
default_payment_method_id="pm_only",
),
)
self.assertFalse(auto_topup_settings_status(self.con, practice_id="prac_top").get("payment_method_configured"))
save_topup_settings(
self.con,
TopupSettings(
practice_id="prac_top",
auto_topup_enabled=True,
topup_amount_chf=30.0,
internal_credit_usd=10.0,
trigger_below_percent=5,
monthly_limit_chf=300.0,
stripe_customer_id="cus_ok",
default_payment_method_id="pm_ok",
),
)
self.assertTrue(auto_topup_settings_status(self.con, practice_id="prac_top").get("payment_method_configured"))
def test_setup_session_response_has_checkout_url_field(self) -> None:
os.environ["AZA_AI_TOPUP_DRY_RUN"] = "1"
r = create_setup_checkout_session(
practice_id="prac_top",
customer_email="cli@example.test",
success_url="https://example/s",
cancel_url="https://example/c",
)
self.assertIn("checkout_url", r)
self.assertTrue(r.get("ok"))
def test_setup_url_field_aliases_accepted(self) -> None:
for key in ("checkout_url", "setup_url", "url"):
payload = {key: "https://checkout.stripe.test/setup"}
picked = (
payload.get("checkout_url")
or payload.get("setup_url")
or payload.get("url")
or ""
).strip()
self.assertEqual(picked, "https://checkout.stripe.test/setup")
def test_24h_cooldown_blocks_second_auto_topup(self) -> None:
save_topup_settings(
self.con,
TopupSettings(
practice_id="prac_top",
auto_topup_enabled=True,
topup_amount_chf=30.0,
internal_credit_usd=10.0,
trigger_below_percent=10,
monthly_limit_chf=300.0,
stripe_customer_id="cus_x",
default_payment_method_id="pm_test",
),
)
now = int(__import__("time").time())
insert_ledger_event(
self.con,
practice_id="prac_top",
subscription_id="sub_top",
customer_email=None,
event_type="auto_topup_purchase",
amount_internal_usd=20.0,
amount_paid_chf=30.0,
stripe_payment_intent_id="pi_recent",
status="succeeded",
)
self.con.execute(
"UPDATE ai_credit_ledger SET created_at=? WHERE stripe_payment_intent_id='pi_recent'",
(now - 3600,),
)
self.con.commit()
self.assertLess(_now_delta(last_successful_auto_topup_ts(self.con, "prac_top")), AUTO_TOPUP_COOLDOWN_SEC)
r = maybe_run_auto_topup_for_practice(self.con, practice_id="prac_top", available_percent=5, dry_run=False)
self.assertEqual(r.get("reason"), "cooldown_24h")
def test_pending_auto_topup_blocks_parallel(self) -> None:
insert_ledger_event(
self.con,
practice_id="prac_top",
subscription_id="sub_top",
customer_email=None,
event_type="auto_topup_purchase",
amount_internal_usd=20.0,
amount_paid_chf=30.0,
status="pending",
)
self.assertTrue(has_pending_auto_topup(self.con, "prac_top"))
save_topup_settings(
self.con,
TopupSettings(
practice_id="prac_top",
auto_topup_enabled=True,
topup_amount_chf=30.0,
internal_credit_usd=10.0,
trigger_below_percent=10,
monthly_limit_chf=300.0,
stripe_customer_id="cus_x",
default_payment_method_id="pm_test",
),
)
r = maybe_run_auto_topup_for_practice(self.con, practice_id="prac_top", available_percent=5, dry_run=False)
self.assertEqual(r.get("reason"), "pending_auto_topup")
def test_double_payment_intent_no_double_credit(self) -> None:
pi = {
"id": "pi_manual_1",
"customer": "cus_x",
"metadata": {
"aza_purpose": "ai_topup",
"practice_id": "prac_top",
"subscription_id": "sub_top",
"internal_credit_usd": "20",
"paid_chf": "30",
},
}
r1 = process_topup_payment_intent_succeeded(self.con, payment_intent=pi)
r2 = process_topup_payment_intent_succeeded(self.con, payment_intent=pi)
self.assertTrue(r1.get("credited"))
self.assertTrue(r2.get("duplicate"))
self.assertTrue(ledger_exists_for_payment_intent(self.con, "pi_manual_1"))
cnt = self.con.execute(
"SELECT COUNT(*) FROM ai_credit_ledger WHERE stripe_payment_intent_id='pi_manual_1'"
).fetchone()[0]
self.assertEqual(cnt, 1)
def test_auto_topup_disabled_when_env_off(self) -> None:
os.environ["AZA_AI_AUTO_TOPUP_ENABLED"] = "0"
os.environ["AZA_AI_TOPUP_ALLOW_LIVE"] = "1"
os.environ["AZA_AI_TOPUP_DRY_RUN"] = "0"
save_topup_settings(
self.con,
TopupSettings(
practice_id="prac_top",
auto_topup_enabled=True,
topup_amount_chf=30.0,
internal_credit_usd=10.0,
trigger_below_percent=10,
monthly_limit_chf=300.0,
stripe_customer_id="cus_x",
default_payment_method_id="pm_test",
),
)
r = maybe_run_auto_topup_for_practice(self.con, practice_id="prac_top", available_percent=5, dry_run=False)
self.assertTrue(r.get("dry_run"))
def test_no_live_topup_when_allow_live_off(self) -> None:
os.environ["AZA_AI_TOPUP_ALLOW_LIVE"] = "0"
os.environ["AZA_AI_TOPUP_DRY_RUN"] = "0"
os.environ["STRIPE_SECRET_KEY"] = "sk_live_test_only"
r = create_topup_checkout_session(
practice_id="prac_top",
subscription_id="sub_top",
customer_email="cli@example.test",
paid_chf=30.0,
internal_usd=20.0,
success_url="https://example/s",
cancel_url="https://example/c",
)
self.assertFalse(r.get("ok", True))
self.assertEqual(r.get("error_code"), "LIVE_TOPUP_BLOCKED")
def test_gate_retries_after_mock_auto_topup_credit(self) -> None:
from unittest.mock import patch
lic = _lic()
ps, pe = lic.period_start, lic.period_end
insert_usage_event(
self.con,
lic=lic,
device_id=None,
period_start=ps,
period_end=pe,
operation_type="chat",
model="gpt-4o-mini",
input_tokens=1,
output_tokens=1,
total_tokens=2,
audio_seconds=0.0,
estimated_cost_usd=20.0,
request_id="gate_block",
status="success",
)
os.environ["AZA_AI_AUTO_TOPUP_ENABLED"] = "1"
def _fake_auto(*_a, **_k):
insert_ledger_event(
self.con,
practice_id="prac_top",
subscription_id="sub_top",
customer_email=None,
event_type="auto_topup_purchase",
amount_internal_usd=20.0,
amount_paid_chf=30.0,
stripe_payment_intent_id="pi_gate",
status="succeeded",
)
return {"ok": True, "charged": True}
with patch("aza_ai_credit.maybe_run_auto_topup_for_practice", side_effect=_fake_auto):
blocked = budget_gate_blocked_payload_or_none(
self.con,
lic,
device_id=None,
request_id="req_gate",
operation_type="chat",
model="gpt-4o-mini",
)
self.assertIsNone(blocked)
def test_failed_auto_topup_pauses_settings(self) -> None:
from unittest.mock import MagicMock, patch
os.environ["AZA_AI_AUTO_TOPUP_ENABLED"] = "1"
os.environ["AZA_AI_TOPUP_ALLOW_LIVE"] = "1"
os.environ["AZA_AI_TOPUP_DRY_RUN"] = "0"
os.environ["STRIPE_SECRET_KEY"] = "sk_live_test_only"
save_topup_settings(
self.con,
TopupSettings(
practice_id="prac_top",
auto_topup_enabled=True,
topup_amount_chf=30.0,
internal_credit_usd=10.0,
trigger_below_percent=10,
monthly_limit_chf=300.0,
stripe_customer_id="cus_x",
default_payment_method_id="pm_test",
),
)
mock_pi = MagicMock()
mock_pi.id = "pi_fail"
mock_pi.status = "requires_action"
with patch("stripe.PaymentIntent.create", return_value=mock_pi):
r = maybe_run_auto_topup_for_practice(
self.con, practice_id="prac_top", available_percent=5, dry_run=False
)
self.assertFalse(r.get("charged", True))
self.assertTrue(r.get("paused"))
loaded = get_topup_settings(self.con, "prac_top")
self.assertFalse(loaded.auto_topup_enabled)
failed_cnt = self.con.execute(
"SELECT COUNT(*) FROM ai_credit_ledger WHERE event_type='failed_auto_topup'"
).fetchone()[0]
self.assertEqual(failed_cnt, 1)
def test_live_auto_topup_mock_success(self) -> None:
from unittest.mock import MagicMock, patch
os.environ["AZA_AI_AUTO_TOPUP_ENABLED"] = "1"
os.environ["AZA_AI_TOPUP_ALLOW_LIVE"] = "1"
os.environ["AZA_AI_TOPUP_DRY_RUN"] = "0"
os.environ["STRIPE_SECRET_KEY"] = "sk_live_test_only"
save_topup_settings(
self.con,
TopupSettings(
practice_id="prac_top",
auto_topup_enabled=True,
topup_amount_chf=30.0,
internal_credit_usd=10.0,
trigger_below_percent=10,
monthly_limit_chf=300.0,
stripe_customer_id="cus_x",
default_payment_method_id="pm_test",
),
)
mock_pi = MagicMock()
mock_pi.id = "pi_ok"
mock_pi.status = "succeeded"
with patch("stripe.PaymentIntent.create", return_value=mock_pi):
r = maybe_run_auto_topup_for_practice(
self.con,
practice_id="prac_top",
available_percent=5,
dry_run=False,
subscription_id="sub_top",
)
self.assertTrue(r.get("charged"))
row = self.con.execute(
"SELECT status, amount_internal_usd FROM ai_credit_ledger WHERE stripe_payment_intent_id='pi_ok'"
).fetchone()
self.assertEqual(row[0], "succeeded")
self.assertEqual(row[1], 10.0)
def test_auto_pi_webhook_idempotent(self) -> None:
pi = {
"id": "pi_auto_wh",
"customer": "cus_x",
"metadata": {
"aza_purpose": "ai_auto_topup",
"practice_id": "prac_top",
"subscription_id": "sub_top",
"internal_credit_usd": "20",
"paid_chf": "30",
},
}
r1 = process_auto_topup_payment_intent_succeeded(self.con, payment_intent=pi)
r2 = process_auto_topup_payment_intent_succeeded(self.con, payment_intent=pi)
self.assertTrue(r1.get("credited"))
self.assertTrue(r2.get("duplicate"))
def test_topup_success_url_is_succes2(self) -> None:
from unittest.mock import MagicMock, patch
os.environ["AZA_AI_TOPUP_DRY_RUN"] = "0"
os.environ["AZA_AI_TOPUP_ALLOW_LIVE"] = "1"
os.environ["STRIPE_SECRET_KEY"] = "sk_live_test_only"
mock_session = MagicMock(url="https://checkout.stripe.com/test", id="cs_test")
with patch("stripe.checkout.Session.create", return_value=mock_session) as mock_create:
create_topup_checkout_session(
practice_id="prac_top",
subscription_id="sub_top",
customer_email="cli@example.test",
paid_chf=30.0,
internal_usd=20.0,
save_payment_method=False,
success_url=DEFAULT_TOPUP_SUCCESS_URL,
cancel_url="https://aza-medwork.ch",
)
self.assertEqual(mock_create.call_args.kwargs["success_url"], "https://aza-medwork.ch/succes2")
def test_abo_checkout_success_url_unchanged(self) -> None:
import stripe_routes
self.assertNotEqual(
stripe_routes.STRIPE_SUCCESS_URL or "",
DEFAULT_TOPUP_SUCCESS_URL,
)
def test_history_no_token_401(self) -> None:
from fastapi import FastAPI
from fastapi.testclient import TestClient
from aza_ai_credit_routes import router
os.environ["AZA_API_TOKEN"] = "test-history-token"
app = FastAPI()
app.include_router(router)
with TestClient(app) as client:
resp = client.get(
"/v1/ai-credit/history",
headers={"X-Device-Id": "dev1", "X-Practice-Id": "prac_top"},
)
self.assertEqual(resp.status_code, 401)
def test_history_only_own_practice(self) -> None:
insert_ledger_event(
self.con,
practice_id="prac_top",
subscription_id="sub_top",
customer_email=None,
event_type="topup_purchase",
amount_internal_usd=20.0,
amount_paid_chf=30.0,
stripe_payment_intent_id="pi_prac_top_secret",
status="succeeded",
)
insert_ledger_event(
self.con,
practice_id="prac_other",
subscription_id="sub_other",
customer_email=None,
event_type="topup_purchase",
amount_internal_usd=20.0,
amount_paid_chf=30.0,
stripe_payment_intent_id="pi_other_secret",
status="succeeded",
)
items = list_user_credit_history(self.con, practice_id="prac_top")
self.assertEqual(len(items), 1)
self.assertEqual(items[0]["amount_paid_chf"], 30.0)
self.assertNotIn("pi_other_secret", json.dumps(items))
def test_history_masks_stripe_ids(self) -> None:
masked = mask_stripe_reference("pi_1234567890abcdef")
self.assertIn("", masked or "")
self.assertNotIn("abcdef", masked or "abcdef")
insert_ledger_event(
self.con,
practice_id="prac_top",
subscription_id="sub_top",
customer_email=None,
event_type="topup_purchase",
amount_internal_usd=20.0,
amount_paid_chf=30.0,
stripe_payment_intent_id="pi_1234567890abcdef",
status="succeeded",
)
items = list_user_credit_history(self.con, practice_id="prac_top")
self.assertEqual(items[0]["stripe_payment_reference"], masked)
def test_history_shows_failed_and_refund(self) -> None:
insert_ledger_event(
self.con,
practice_id="prac_top",
subscription_id="sub_top",
customer_email=None,
event_type="topup_purchase",
amount_internal_usd=0.0,
amount_paid_chf=30.0,
status="failed",
)
insert_ledger_event(
self.con,
practice_id="prac_top",
subscription_id="sub_top",
customer_email=None,
event_type="refund",
amount_internal_usd=-10.0,
amount_paid_chf=-15.0,
status="succeeded",
)
items = list_user_credit_history(self.con, practice_id="prac_top")
statuses = {i["status"] for i in items}
types = {i["event_type"] for i in items}
self.assertIn("failed", statuses)
self.assertIn("refund", types)
def test_admin_ledger_helper_unchanged(self) -> None:
insert_ledger_event(
self.con,
practice_id="prac_top",
subscription_id="sub_top",
customer_email=None,
event_type="topup_purchase",
amount_internal_usd=1.0,
stripe_checkout_session_id="cs_admin_check",
status="succeeded",
)
entries = list_ledger_for_practice(self.con, "prac_top")
self.assertEqual(len(entries), 1)
self.assertIn("checkout_session_masked", entries[0])
def test_default_trigger_percent_is_5(self) -> None:
self.assertEqual(DEFAULT_TRIGGER_PERCENT, 5)
loaded = get_topup_settings(self.con, "prac_new_defaults")
self.assertEqual(loaded.trigger_below_percent, 5)
self.assertEqual(loaded.topup_amount_chf, 30.0)
self.assertEqual(loaded.internal_credit_usd, 10.0)
self.assertEqual(loaded.monthly_limit_chf, 300.0)
def test_setup_success_url_constant(self) -> None:
self.assertEqual(
DEFAULT_SETUP_SUCCESS_URL,
"https://aza-medwork.ch/ki-topup-setup-success/",
)
def test_setup_checkout_uses_setup_success_url(self) -> None:
from unittest.mock import MagicMock, patch
os.environ["AZA_AI_TOPUP_DRY_RUN"] = "0"
os.environ["AZA_AI_TOPUP_ALLOW_LIVE"] = "1"
os.environ["STRIPE_SECRET_KEY"] = "sk_live_test_only"
mock_session = MagicMock(url="https://checkout.stripe.com/setup", id="cs_setup")
with patch("stripe.checkout.Session.create", return_value=mock_session) as mock_create:
create_setup_checkout_session(
practice_id="prac_top",
customer_email="cli@example.test",
success_url=DEFAULT_SETUP_SUCCESS_URL,
cancel_url="https://aza-medwork.ch",
)
self.assertEqual(
mock_create.call_args.kwargs["success_url"],
"https://aza-medwork.ch/ki-topup-setup-success/",
)
self.assertEqual(mock_create.call_args.kwargs["customer_creation"], "always")
def test_disable_auto_topup_preserves_payment_method(self) -> None:
save_topup_settings(
self.con,
TopupSettings(
practice_id="prac_top",
auto_topup_enabled=True,
topup_amount_chf=30.0,
internal_credit_usd=10.0,
trigger_below_percent=5,
monthly_limit_chf=300.0,
stripe_customer_id="cus_keep",
default_payment_method_id="pm_keep",
),
)
before = self.con.execute("SELECT COUNT(1) FROM ai_credit_ledger").fetchone()[0]
r = apply_auto_topup_user_settings(self.con, practice_id="prac_top", enabled=False)
after = self.con.execute("SELECT COUNT(1) FROM ai_credit_ledger").fetchone()[0]
self.assertTrue(r.get("ok"))
self.assertFalse(r.get("auto_topup_enabled"))
loaded = get_topup_settings(self.con, "prac_top")
self.assertFalse(loaded.auto_topup_enabled)
self.assertEqual(loaded.stripe_customer_id, "cus_keep")
self.assertEqual(loaded.default_payment_method_id, "pm_keep")
self.assertEqual(before, after)
def test_enable_auto_topup_without_payment_method_blocked(self) -> None:
r = apply_auto_topup_user_settings(self.con, practice_id="prac_top", enabled=True)
self.assertFalse(r.get("ok"))
self.assertEqual(r.get("error_code"), "AUTO_TOPUP_NO_PAYMENT_METHOD")
def test_auto_topup_settings_status_masks_payment_method(self) -> None:
save_topup_settings(
self.con,
TopupSettings(
practice_id="prac_top",
auto_topup_enabled=True,
topup_amount_chf=30.0,
internal_credit_usd=10.0,
trigger_below_percent=5,
monthly_limit_chf=300.0,
stripe_customer_id="cus_x",
default_payment_method_id="pm_secret123",
),
)
status = auto_topup_settings_status(self.con, practice_id="prac_top")
self.assertTrue(status.get("ok"))
self.assertTrue(status.get("auto_topup_enabled"))
self.assertTrue(status.get("payment_method_configured"))
self.assertEqual(status.get("trigger_below_percent"), 5)
self.assertNotIn("default_payment_method_id", status)
self.assertNotIn("pm_secret123", json.dumps(status))
def test_auto_topup_settings_get_no_token_401(self) -> None:
from fastapi import FastAPI
from fastapi.testclient import TestClient
from aza_ai_credit_routes import router
os.environ["AZA_API_TOKEN"] = "test-auto-topup-token"
app = FastAPI()
app.include_router(router)
with TestClient(app) as client:
resp = client.get(
"/v1/ai-credit/auto-topup/settings",
headers={"X-Device-Id": "dev1", "X-Practice-Id": "prac_top"},
)
self.assertEqual(resp.status_code, 401)
def test_default_product_constants(self) -> None:
self.assertEqual(DEFAULT_INTERNAL_USD, 10.0)
self.assertEqual(DEFAULT_MONTHLY_LIMIT_CHF, 300.0)
self.assertEqual(DEFAULT_TRIGGER_PERCENT, 5)
def test_available_percent_can_exceed_100(self) -> None:
base = {
"remaining_usd": 20.0,
"used_usd": 0.0,
"budget_usd": 20.0,
}
snap = apply_extra_credit_to_snapshot(base, extra_remaining=10.0, monthly_budget=20.0)
self.assertEqual(snap["available_percent"], 150)
def test_available_percent_extra_only_50(self) -> None:
base = {"remaining_usd": 0.0, "used_usd": 20.0, "budget_usd": 20.0}
snap = apply_extra_credit_to_snapshot(base, extra_remaining=10.0, monthly_budget=20.0)
self.assertEqual(snap["available_percent"], 50)
def test_available_percent_mixed_130(self) -> None:
base = {"remaining_usd": 6.0, "used_usd": 14.0, "budget_usd": 20.0}
snap = apply_extra_credit_to_snapshot(base, extra_remaining=20.0, monthly_budget=20.0)
self.assertEqual(snap["available_percent"], 130)
def test_auto_monthly_limit_ignores_manual_topups(self) -> None:
save_topup_settings(
self.con,
TopupSettings(
practice_id="prac_top",
auto_topup_enabled=True,
topup_amount_chf=30.0,
internal_credit_usd=10.0,
trigger_below_percent=50,
monthly_limit_chf=300.0,
stripe_customer_id="cus_x",
default_payment_method_id="pm_test",
),
)
insert_ledger_event(
self.con,
practice_id="prac_top",
subscription_id="sub_top",
customer_email=None,
event_type="topup_purchase",
amount_internal_usd=66.6667,
amount_paid_chf=200.0,
stripe_checkout_session_id="cs_manual_big",
status="succeeded",
)
self.assertAlmostEqual(sum_auto_topups_chf_this_month(self.con, "prac_top"), 0.0, places=2)
r = maybe_run_auto_topup_for_practice(self.con, practice_id="prac_top", available_percent=5, dry_run=True)
self.assertTrue(r.get("dry_run"))
def test_manual_topup_checkout_allowed_when_auto_limit_reached(self) -> None:
save_topup_settings(
self.con,
TopupSettings(
practice_id="prac_top",
auto_topup_enabled=True,
topup_amount_chf=30.0,
internal_credit_usd=10.0,
trigger_below_percent=50,
monthly_limit_chf=60.0,
stripe_customer_id="cus_x",
default_payment_method_id="pm_test",
),
)
for i in range(2):
insert_ledger_event(
self.con,
practice_id="prac_top",
subscription_id="sub_top",
customer_email=None,
event_type="auto_topup_purchase",
amount_internal_usd=10.0,
amount_paid_chf=30.0,
stripe_payment_intent_id=f"pi_auto_cap_{i}",
status="succeeded",
)
blocked = maybe_run_auto_topup_for_practice(
self.con, practice_id="prac_top", available_percent=5, dry_run=True
)
self.assertEqual(blocked.get("reason"), "auto_topup_monthly_limit_reached")
os.environ["AZA_AI_TOPUP_DRY_RUN"] = "1"
manual = create_topup_checkout_session(
practice_id="prac_top",
subscription_id="sub_top",
customer_email="cli@example.test",
paid_chf=30.0,
internal_usd=chf_to_internal_usd(30.0),
success_url="https://example/s",
cancel_url="https://example/c",
)
self.assertTrue(manual.get("dry_run"))
def test_history_shows_ledger_value_not_recalculated(self) -> None:
insert_ledger_event(
self.con,
practice_id="prac_top",
subscription_id="sub_top",
customer_email=None,
event_type="topup_purchase",
amount_internal_usd=6.6667,
amount_paid_chf=10.0,
stripe_payment_intent_id="pi_old_rate",
status="succeeded",
)
insert_ledger_event(
self.con,
practice_id="prac_top",
subscription_id="sub_top",
customer_email=None,
event_type="auto_topup_purchase",
amount_internal_usd=20.0,
amount_paid_chf=30.0,
stripe_payment_intent_id="pi_old_auto",
status="succeeded",
)
items = list_user_credit_history(self.con, practice_id="prac_top")
by_pi = {i.get("stripe_payment_reference") or "": i for i in items}
amounts = sorted(i["amount_internal_usd"] for i in items)
self.assertEqual(amounts, [6.6667, 20.0])
self.assertAlmostEqual(chf_to_internal_usd(10.0), 3.3333, places=4)
def test_gate_reports_auto_monthly_limit_message(self) -> None:
lic = _lic()
ps, pe = lic.period_start, lic.period_end
insert_usage_event(
self.con,
lic=lic,
device_id=None,
period_start=ps,
period_end=pe,
operation_type="chat",
model="gpt-4o-mini",
input_tokens=1,
output_tokens=1,
total_tokens=2,
audio_seconds=0.0,
estimated_cost_usd=40.0,
request_id="gate_limit",
status="success",
)
save_topup_settings(
self.con,
TopupSettings(
practice_id="prac_top",
auto_topup_enabled=True,
topup_amount_chf=30.0,
internal_credit_usd=10.0,
trigger_below_percent=50,
monthly_limit_chf=60.0,
stripe_customer_id="cus_x",
default_payment_method_id="pm_test",
),
)
for i in range(2):
insert_ledger_event(
self.con,
practice_id="prac_top",
subscription_id="sub_top",
customer_email=None,
event_type="auto_topup_purchase",
amount_internal_usd=10.0,
amount_paid_chf=30.0,
stripe_payment_intent_id=f"pi_gate_cap_{i}",
status="succeeded",
)
os.environ["AZA_AI_AUTO_TOPUP_ENABLED"] = "1"
blocked = budget_gate_blocked_payload_or_none(
self.con,
lic,
device_id=None,
request_id="req_limit",
operation_type="chat",
model="gpt-4o-mini",
)
self.assertIsNotNone(blocked)
self.assertEqual(blocked.get("error_code"), "AUTO_TOPUP_MONTHLY_LIMIT_REACHED")
self.assertIn("manuell", (blocked.get("message_user") or "").lower())
def _now_delta(ts: int | None) -> int:
if not ts:
return 999999
return int(__import__("time").time()) - ts
if __name__ == "__main__":
unittest.main()