update
This commit is contained in:
@@ -10,8 +10,10 @@ from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from aza_ai_budget import (
|
||||
_norm_model,
|
||||
compute_budget_snapshot,
|
||||
ensure_ai_budget_schema,
|
||||
estimate_openai_cost_usd,
|
||||
insert_usage_event,
|
||||
resolve_license_for_empfang,
|
||||
sum_usage_usd_for_period,
|
||||
@@ -67,6 +69,57 @@ def _mk_db(path: Path) -> None:
|
||||
con.close()
|
||||
|
||||
|
||||
class TestModelNormalizationAndBudgetDefault(unittest.TestCase):
|
||||
def test_norm_dated_gpt_4o_mini(self):
|
||||
self.assertEqual(_norm_model("gpt-4o-mini-2024-07-18"), "gpt-4o-mini")
|
||||
|
||||
def test_dated_gpt_4o_mini_uses_cheap_price(self):
|
||||
cost = estimate_openai_cost_usd(
|
||||
model="gpt-4o-mini-2024-07-18",
|
||||
input_tokens=1_000_000,
|
||||
output_tokens=0,
|
||||
)
|
||||
self.assertAlmostEqual(cost, 0.15, places=6)
|
||||
self.assertEqual(
|
||||
cost,
|
||||
estimate_openai_cost_usd(
|
||||
model="gpt-4o-mini", input_tokens=1_000_000, output_tokens=0
|
||||
),
|
||||
)
|
||||
|
||||
def test_gpt_4o_mini_transcribe_unchanged(self):
|
||||
self.assertEqual(_norm_model("gpt-4o-mini-transcribe"), "gpt-4o-mini-transcribe")
|
||||
cost = estimate_openai_cost_usd(
|
||||
model="gpt-4o-mini-transcribe", audio_seconds=60.0
|
||||
)
|
||||
self.assertAlmostEqual(cost, 0.012, places=6)
|
||||
|
||||
def test_unknown_model_stays_conservative_fallback(self):
|
||||
cost = estimate_openai_cost_usd(
|
||||
model="totally-unknown-model-xyz",
|
||||
input_tokens=1_000_000,
|
||||
output_tokens=0,
|
||||
)
|
||||
self.assertAlmostEqual(cost, 5.0, places=6)
|
||||
|
||||
def test_env_overrides_budget_default(self):
|
||||
import importlib
|
||||
import aza_ai_budget as mod
|
||||
|
||||
key = "AZA_AI_BUDGET_USD_DEFAULT"
|
||||
old = os.environ.get(key)
|
||||
try:
|
||||
os.environ[key] = "42.5"
|
||||
importlib.reload(mod)
|
||||
self.assertEqual(mod.DEFAULT_MONTHLY_AI_BUDGET_USD, 42.5)
|
||||
finally:
|
||||
if old is None:
|
||||
os.environ.pop(key, None)
|
||||
else:
|
||||
os.environ[key] = old
|
||||
importlib.reload(mod)
|
||||
|
||||
|
||||
class TestBackendGatePracticeFallback(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.NamedTemporaryFile(suffix=".sqlite", delete=False)
|
||||
|
||||
@@ -123,23 +123,71 @@ class TestPhase1bResolve(unittest.TestCase):
|
||||
self.assertIsNotNone(lic)
|
||||
self.assertEqual(lic.subscription_id, "sub_ef")
|
||||
|
||||
def test_empfang_prefers_device_then_practice(self):
|
||||
def test_empfang_prefers_practice_when_both_set(self):
|
||||
with sqlite3.connect(str(self.db_path)) as con:
|
||||
lic = resolve_license_for_empfang(
|
||||
con, x_device_id="dev-ef-test", session_practice_id="prac_ef_test"
|
||||
)
|
||||
self.assertIsNotNone(lic)
|
||||
self.assertEqual(lic.customer_email, "ef@example.test")
|
||||
self.assertEqual(lic.subscription_id, "sub_ef")
|
||||
self.assertEqual(lic.practice_id, "prac_ef_test")
|
||||
|
||||
def test_empfang_device_practice_conflict_drops_device(self):
|
||||
def test_empfang_practice_row_used_even_if_email_points_newer(self):
|
||||
"""Session practice_id mit eigener Zeile schlägt neuere E-Mail-Lizenz ohne practice_id."""
|
||||
now = 1_700_000_000
|
||||
with sqlite3.connect(str(self.db_path)) as con:
|
||||
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_other', 'cus_z', 'active', 'aza_basic_monthly', 1, 2,
|
||||
'ef@example.test', NULL, ?, ?, ?, 'KEY3', 'prac_other')
|
||||
""",
|
||||
(now + 86400 * 60, now + 86400 * 30, now + 86400 * 5),
|
||||
)
|
||||
lic = resolve_license_for_empfang(
|
||||
con, x_device_id="dev-ef-test", session_practice_id="prac_other"
|
||||
)
|
||||
self.assertIsNone(lic)
|
||||
self.assertIsNotNone(lic)
|
||||
self.assertEqual(lic.subscription_id, "sub_other")
|
||||
self.assertEqual(lic.practice_id, "prac_other")
|
||||
|
||||
def test_empfang_device_only_without_practice(self):
|
||||
with sqlite3.connect(str(self.db_path)) as con:
|
||||
lic = resolve_license_for_empfang(
|
||||
con, x_device_id="dev-ef-test", session_practice_id=None
|
||||
)
|
||||
self.assertIsNotNone(lic)
|
||||
self.assertEqual(lic.subscription_id, "sub_ef")
|
||||
|
||||
def test_empfang_device_fallback_when_practice_missing(self):
|
||||
with sqlite3.connect(str(self.db_path)) as con:
|
||||
lic = resolve_license_for_empfang(
|
||||
con, x_device_id="dev-ef-test", session_practice_id="prac_unknown"
|
||||
)
|
||||
self.assertIsNotNone(lic)
|
||||
self.assertEqual(lic.subscription_id, "sub_ef")
|
||||
|
||||
def test_empfang_practice_wins_over_newer_email_license(self):
|
||||
"""Device/E-Mail würde neuere Lizenz ohne practice_id wählen; Practice-ID gewinnt."""
|
||||
now = 1_700_000_000
|
||||
with sqlite3.connect(str(self.db_path)) as con:
|
||||
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_newer_email', 'cus_y', 'active', 'aza_basic_monthly', 1, 2,
|
||||
'ef@example.test', NULL, ?, ?, ?, 'KEY2', NULL)
|
||||
""",
|
||||
(now + 86400 * 60, now + 86400 * 30, now + 86400 * 10),
|
||||
)
|
||||
lic = resolve_license_for_empfang(
|
||||
con, x_device_id="dev-ef-test", session_practice_id="prac_ef_test"
|
||||
)
|
||||
self.assertIsNotNone(lic)
|
||||
self.assertEqual(lic.subscription_id, "sub_ef")
|
||||
self.assertEqual(lic.practice_id, "prac_ef_test")
|
||||
|
||||
class TestPhase1bGateAndAudio(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.NamedTemporaryFile(suffix=".sqlite", delete=False)
|
||||
self.tmp.close()
|
||||
|
||||
1222
AzA march 2026/tests/test_ai_credit_topup.py
Normal file
1222
AzA march 2026/tests/test_ai_credit_topup.py
Normal file
File diff suppressed because it is too large
Load Diff
213
AzA march 2026/tests/test_sync_items.py
Normal file
213
AzA march 2026/tests/test_sync_items.py
Normal file
@@ -0,0 +1,213 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Tests für practice_id-Sync (Textblöcke, Korrekturen, Autotext) und renewal_date_de."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sqlite3
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from aza_sync_items import (
|
||||
ensure_sync_items_schema,
|
||||
items_to_local_textblocks,
|
||||
list_sync_items,
|
||||
local_autotext_to_items,
|
||||
local_korrekturen_to_items,
|
||||
local_textblocks_to_items,
|
||||
soft_delete_sync_item,
|
||||
upsert_sync_item,
|
||||
)
|
||||
from aza_ai_budget import budget_json_for_client
|
||||
|
||||
|
||||
class TestSyncItemsSchema(unittest.TestCase):
|
||||
def test_schema_idempotent(self):
|
||||
td = tempfile.mkdtemp()
|
||||
try:
|
||||
db = Path(td) / "t.sqlite"
|
||||
con = sqlite3.connect(db)
|
||||
try:
|
||||
ensure_sync_items_schema(con)
|
||||
ensure_sync_items_schema(con)
|
||||
cols = {
|
||||
r[1]
|
||||
for r in con.execute("PRAGMA table_info(synced_user_items)").fetchall()
|
||||
}
|
||||
finally:
|
||||
con.close()
|
||||
self.assertIn("practice_id", cols)
|
||||
self.assertIn("item_type", cols)
|
||||
finally:
|
||||
try:
|
||||
os.remove(Path(td) / "t.sqlite")
|
||||
except OSError:
|
||||
pass
|
||||
os.rmdir(td)
|
||||
|
||||
|
||||
class TestSyncItemsDb(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
import shutil
|
||||
|
||||
try:
|
||||
shutil.rmtree(self.td, ignore_errors=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def setUp(self):
|
||||
self.td = tempfile.mkdtemp()
|
||||
self.db = Path(self.td) / "stripe_webhook.sqlite"
|
||||
with sqlite3.connect(self.db) as con:
|
||||
ensure_sync_items_schema(con)
|
||||
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,
|
||||
updated_at INTEGER NOT NULL,
|
||||
practice_id TEXT,
|
||||
current_period_start INTEGER
|
||||
)
|
||||
"""
|
||||
)
|
||||
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, updated_at,
|
||||
practice_id, current_period_start
|
||||
) VALUES ('sub_a', 'c1', 'active', 'k1', 5, 2, 'a@test.ch',
|
||||
'', 2000000000, 1, 'prac_alpha', 1900000000)
|
||||
"""
|
||||
)
|
||||
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, updated_at,
|
||||
practice_id, current_period_start
|
||||
) VALUES ('sub_b', 'c2', 'active', 'k2', 5, 2, 'b@test.ch',
|
||||
'', 2000000000, 1, 'prac_beta', 1900000000)
|
||||
"""
|
||||
)
|
||||
con.commit()
|
||||
|
||||
def test_practice_isolation_and_soft_delete(self):
|
||||
with sqlite3.connect(self.db) as con:
|
||||
upsert_sync_item(
|
||||
con,
|
||||
"prac_alpha",
|
||||
{
|
||||
"id": "tb_1",
|
||||
"item_type": "textblock",
|
||||
"title": "A",
|
||||
"content": "alpha",
|
||||
"sort_order": 1,
|
||||
},
|
||||
)
|
||||
upsert_sync_item(
|
||||
con,
|
||||
"prac_beta",
|
||||
{
|
||||
"id": "tb_beta_1",
|
||||
"item_type": "textblock",
|
||||
"title": "B",
|
||||
"content": "beta",
|
||||
"sort_order": 1,
|
||||
},
|
||||
)
|
||||
a_items = list_sync_items(con, "prac_alpha", "textblock")
|
||||
b_items = list_sync_items(con, "prac_beta", "textblock")
|
||||
self.assertEqual(len(a_items), 1)
|
||||
self.assertEqual(a_items[0]["content"], "alpha")
|
||||
self.assertEqual(b_items[0]["content"], "beta")
|
||||
soft_delete_sync_item(con, "prac_alpha", "tb_1")
|
||||
a2 = list_sync_items(con, "prac_alpha", "textblock")
|
||||
self.assertEqual(len(a2), 0)
|
||||
|
||||
def test_item_types_separate(self):
|
||||
with sqlite3.connect(self.db) as con:
|
||||
upsert_sync_item(
|
||||
con,
|
||||
"prac_alpha",
|
||||
{
|
||||
"id": "at_x",
|
||||
"item_type": "autotext",
|
||||
"trigger": "kg",
|
||||
"content": "Krankengeschichte",
|
||||
"sort_order": 1,
|
||||
},
|
||||
)
|
||||
upsert_sync_item(
|
||||
con,
|
||||
"prac_alpha",
|
||||
{
|
||||
"id": "corr_1",
|
||||
"item_type": "correction",
|
||||
"title": "medikamente",
|
||||
"trigger": "asprin",
|
||||
"content": "Aspirin",
|
||||
"sort_order": 1,
|
||||
},
|
||||
)
|
||||
at = list_sync_items(con, "prac_alpha", "autotext")
|
||||
co = list_sync_items(con, "prac_alpha", "correction")
|
||||
self.assertEqual(len(at), 1)
|
||||
self.assertEqual(len(co), 1)
|
||||
|
||||
|
||||
class TestLocalMapping(unittest.TestCase):
|
||||
def test_textblock_roundtrip(self):
|
||||
local = {
|
||||
"1": {"name": "Urtikaria", "content": "Haut", "updated_at": "t"},
|
||||
"2": {"name": "TB2", "content": "", "updated_at": "t"},
|
||||
}
|
||||
items = local_textblocks_to_items(local)
|
||||
back = items_to_local_textblocks(items)
|
||||
self.assertEqual(back["1"]["name"], "Urtikaria")
|
||||
self.assertGreaterEqual(len(back), 2)
|
||||
|
||||
def test_korrekturen_mapping(self):
|
||||
k = {"medikamente": {"asprin": "Aspirin"}}
|
||||
items = local_korrekturen_to_items(k)
|
||||
self.assertEqual(items[0]["item_type"], "correction")
|
||||
|
||||
def test_autotext_mapping(self):
|
||||
at = {"entries": {"kg": "Krankengeschichte"}}
|
||||
items = local_autotext_to_items(at)
|
||||
self.assertEqual(items[0]["trigger"], "kg")
|
||||
|
||||
|
||||
class TestBudgetRenewalField(unittest.TestCase):
|
||||
def test_renewal_date_de_in_client_json(self):
|
||||
snap = {
|
||||
"ok": True,
|
||||
"active": True,
|
||||
"available_percent": 76,
|
||||
"period_end": 1781817600,
|
||||
"show_warning": False,
|
||||
"user_label": "KI-Kontingent: 76 % verfügbar",
|
||||
}
|
||||
body = budget_json_for_client(snap)
|
||||
self.assertIn("renewal_date_de", body)
|
||||
self.assertTrue(body["renewal_date_de"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user