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