Files
aza/AzA march 2026 - Kopie (28)/tests/test_wc_period_sync_phase1d.py

262 lines
9.0 KiB
Python
Raw Normal View History

2026-05-23 21:31:34 +02:00
# -*- coding: utf-8 -*-
"""Phase 1d WooCommerce Perioden-Sync (ohne Netzwerk gegen echtes Woo)."""
from __future__ import annotations
import os
import sqlite3
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
from aza_wc_period_sync import (
compute_period_unix_from_wc_subscription,
extract_wc_subscription_numeric_id,
sync_active_license_periods_from_woocommerce_only,
_subtract_billing_period,
)
class TestExtractWcSub(unittest.TestCase):
def test_numeric(self):
self.assertEqual(extract_wc_subscription_numeric_id("wc_sub_1234"), 1234)
def test_reject_non_wc(self):
self.assertIsNone(extract_wc_subscription_numeric_id("sub_abc"))
self.assertIsNone(extract_wc_subscription_numeric_id("wc_sub_"))
def test_case_insensitive_prefix(self):
self.assertEqual(extract_wc_subscription_numeric_id("WC_SUB_99"), 99)
class TestPeriodCompute(unittest.TestCase):
def test_monthly_next_payment(self):
d = {
"status": "active",
"next_payment_date": "2026-07-01T12:00:00",
"billing_period": "month",
"billing_interval": 1,
}
ps, pe = compute_period_unix_from_wc_subscription(d)
self.assertIsNotNone(ps)
self.assertIsNotNone(pe)
self.assertLess(ps, pe)
def test_yearly(self):
d = {
"next_payment_date": "2027-05-18T00:00:00+00:00",
"billing_period": "year",
"billing_interval": 1,
}
ps, pe = compute_period_unix_from_wc_subscription(d)
self.assertLess(ps, pe)
self.assertGreaterEqual(pe - ps, 3600 * 24 * 300)
def test_meta_schedule_next_payment(self):
d = {
"billing_period": "month",
"billing_interval": 1,
"meta_data": [{"key": "_schedule_next_payment", "value": "2026-08-10T10:00:00"}],
}
ps, pe = compute_period_unix_from_wc_subscription(d)
self.assertIsNotNone(pe)
self.assertIsNotNone(ps)
self.assertLess(ps, int(pe))
def test_subtract_month_consistency(self):
end = 1_700_000_000
s = _subtract_billing_period(end, "month", 1)
self.assertLess(s, end)
class TestSyncNoCredentials(unittest.TestCase):
def test_no_env_no_touch_db(self):
env_keys = (
"AZA_WOOCOMMERCE_URL",
"AZA_WOOCOMMERCE_CONSUMER_KEY",
"AZA_WOOCOMMERCE_CONSUMER_SECRET",
"WOOCOMMERCE_URL",
"WOOCOMMERCE_CONSUMER_KEY",
"WOOCOMMERCE_CONSUMER_SECRET",
)
saved = {k: os.environ.pop(k, None) for k in env_keys}
try:
tmp = tempfile.NamedTemporaryFile(suffix=".sqlite", delete=False)
tmp.close()
db = Path(tmp.name)
con = sqlite3.connect(str(db))
con.execute(
"""
CREATE TABLE licenses (
subscription_id TEXT PRIMARY KEY,
customer_id TEXT,
status TEXT,
current_period_start INTEGER,
current_period_end INTEGER,
updated_at INTEGER
)
"""
)
con.execute(
"INSERT INTO licenses VALUES ('wc_sub_1','wc_order_1','active',NULL,NULL,0)"
)
con.commit()
con.close()
with patch("aza_wc_period_sync._stripe_db_path", return_value=db):
out = sync_active_license_periods_from_woocommerce_only()
self.assertFalse(out.get("ok"))
self.assertEqual(out.get("error_code"), "WOO_CREDENTIALS_MISSING")
con = sqlite3.connect(str(db))
row = con.execute(
"SELECT current_period_start, current_period_end FROM licenses"
).fetchone()
con.close()
self.assertIsNone(row[0])
self.assertIsNone(row[1])
finally:
for k, v in saved.items():
if v is not None:
os.environ[k] = v
try:
db.unlink()
except OSError:
pass
class TestSyncWithMockFetch(unittest.TestCase):
def test_updates_period_columns_only(self):
env_keys = (
"AZA_WOOCOMMERCE_URL",
"AZA_WOOCOMMERCE_CONSUMER_KEY",
"AZA_WOOCOMMERCE_CONSUMER_SECRET",
)
for k in env_keys:
os.environ[k] = "x" if "URL" not in k else "https://example.test"
tmp = tempfile.NamedTemporaryFile(suffix=".sqlite", delete=False)
tmp.close()
db = Path(tmp.name)
try:
con = sqlite3.connect(str(db))
con.execute(
"""
CREATE TABLE licenses (
subscription_id TEXT PRIMARY KEY,
customer_id TEXT,
status TEXT,
lookup_key TEXT,
current_period_start INTEGER,
current_period_end INTEGER,
updated_at INTEGER
)
"""
)
con.execute(
"""INSERT INTO licenses VALUES (
'wc_sub_42','wc_order_41','active','aza_basic_monthly',NULL,NULL,0
)"""
)
con.commit()
con.close()
fake_sub = {
"status": "active",
"next_payment_date": "2026-07-15T08:00:00",
"billing_period": "month",
"billing_interval": 1,
}
def fake_fetch(wid: int):
self.assertEqual(wid, 42)
return fake_sub
with patch("aza_wc_period_sync._stripe_db_path", return_value=db):
with patch("aza_wc_period_sync._fetch_wc_subscription", fake_fetch):
out = sync_active_license_periods_from_woocommerce_only()
self.assertTrue(out.get("ok"))
self.assertEqual(out.get("updated"), 1)
con = sqlite3.connect(str(db))
row = con.execute(
"SELECT status, lookup_key, current_period_start, current_period_end FROM licenses"
).fetchone()
con.close()
self.assertEqual(row[0], "active")
self.assertEqual(row[1], "aza_basic_monthly")
self.assertIsNotNone(row[2])
self.assertIsNotNone(row[3])
self.assertLess(row[2], row[3])
finally:
for k in env_keys:
os.environ.pop(k, None)
try:
db.unlink()
except OSError:
pass
class TestAdminWooEndpointAuth(unittest.TestCase):
def test_requires_admin_header(self):
os.environ["AZA_ADMIN_TOKEN"] = "adm_test_secret_unit"
try:
from fastapi import FastAPI
from fastapi.testclient import TestClient
from admin_routes import router as admin_router
app = FastAPI()
app.include_router(admin_router, prefix="/admin")
c = TestClient(app)
with patch(
"aza_wc_period_sync.sync_active_license_periods_from_woocommerce_only",
return_value={"ok": True, "updated": 0},
):
r0 = c.post("/admin/woocommerce_sync_periods")
r1 = c.post(
"/admin/woocommerce_sync_periods",
headers={"X-Admin-Token": "wrong"},
)
r2 = c.post(
"/admin/woocommerce_sync_periods",
headers={"X-Admin-Token": "adm_test_secret_unit"},
)
self.assertEqual(r0.status_code, 401)
self.assertEqual(r1.status_code, 401)
self.assertEqual(r2.status_code, 200)
self.assertNotIn("secret", (r2.text or "").lower())
finally:
os.environ.pop("AZA_ADMIN_TOKEN", None)
class TestCredentials503Response(unittest.TestCase):
def test_woo_missing_returns_503_json(self):
env_keys = (
"AZA_WOOCOMMERCE_URL",
"AZA_WOOCOMMERCE_CONSUMER_KEY",
"AZA_WOOCOMMERCE_CONSUMER_SECRET",
)
saved = {k: os.environ.pop(k, None) for k in env_keys}
os.environ["AZA_ADMIN_TOKEN"] = "adm_x"
try:
from fastapi import FastAPI
from fastapi.testclient import TestClient
from admin_routes import router as admin_router
app = FastAPI()
app.include_router(admin_router, prefix="/admin")
c = TestClient(app)
r = c.post(
"/admin/woocommerce_sync_periods",
headers={"X-Admin-Token": "adm_x"},
)
self.assertEqual(r.status_code, 503)
body = r.json()
self.assertEqual(body.get("error_code"), "WOO_CREDENTIALS_MISSING")
finally:
os.environ.pop("AZA_ADMIN_TOKEN", None)
for k, v in saved.items():
if v is not None:
os.environ[k] = v
if __name__ == "__main__":
unittest.main()