# -*- 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()