Files
OSIT-AE-API-FastAPI/tests/e2e/test_e2e_v3_action_novi_mailman.py
Scott Idem 6b25cf9c6d feat: add Novi AMS → Mailman 3 cron-based mirror sync bridge (IDAA)
Implements a full proof-of-concept for syncing IDAA's Novi AMS membership
groups to Mailman 3 mailing lists via a cron-triggered reconciliation approach.

Key changes:
- methods: rewrote sync engine around confirmed Novi API shape — group-based
  member fetch (/groups/{guid}/members + /customers/{uuid}), respects
  Active=false and UnsubscribeFromEmails=true flags
- methods: mirror_novi_group_to_mailman_list() diffs Novi group against
  Mailman roster and subscribes/unsubscribes accordingly (full mirror)
- methods: mirror_all_configured_mappings() iterates novi_mailman_sync
  config array in IDAA site cfg_json — this is the cron target
- router: replaced old /sync endpoint with POST /sync (all mappings) and
  POST /sync/group/{guid} (single mapping); removed webhook endpoint
  (sync is cron-based, not event-driven)
- router: added GET/POST/DELETE endpoints for list member inspection
  and manual subscribe/unsubscribe
- tests: two new e2e scripts covering connection checks and full member
  lifecycle; old webhook integration test archived

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 16:36:32 -04:00

100 lines
3.3 KiB
Python

"""
E2E tests for the Novi-Mailman Bridge — API connection checks.
Verifies that both the Novi AMS API and Mailman 3 REST API are reachable
and that the credentials stored in IDAA site cfg_json are valid.
Run from project root:
./environment/bin/python3 tests/e2e/test_e2e_v3_action_novi_mailman.py
Environment:
AE_API_BASE — override the target API base URL (default: https://dev-api.oneskyit.com)
Related tests:
test_e2e_v3_action_novi_mailman_lists.py — member read/subscribe/unsubscribe lifecycle
test_e2e_v3_action_novi_mailman_sync.py — full Novi → Mailman mirror sync (TODO)
Credential storage:
All Novi and Mailman credentials live in IDAA site cfg_json (id_random='58_gJESdlUh').
See project memory for key names.
"""
import os
import sys
import time
import requests
BASE_URL = os.environ.get('AE_API_BASE', 'https://dev-api.oneskyit.com')
ACTION_BASE = f"{BASE_URL}/v3/action/e_novi_mailman"
API_KEY = "PMM4n50teUCaOMMTN8qOJA"
AUTH_HEADERS = {
"X-Aether-API-Key": API_KEY,
"x-no-account-id": "bypass",
}
pass_count = 0
fail_count = 0
def print_result(label: str, success: bool, message: str = ""):
global pass_count, fail_count
status = "✅ PASS" if success else "❌ FAIL"
msg = f" {message}" if message else ""
print(f" [{status}] {label}{msg}")
if success:
pass_count += 1
else:
fail_count += 1
def test_novi_connection():
print("\n[1] Novi API Connection")
try:
resp = requests.get(f"{ACTION_BASE}/test_connection/novi", headers=AUTH_HEADERS, timeout=15)
data = resp.json().get('data', {})
if resp.status_code == 200 and data.get('ok'):
print_result("Novi credentials valid", True)
elif resp.status_code == 401:
print_result("Novi credentials valid", False, f"401 — {data.get('error', resp.text[:120])}")
else:
print_result("Novi credentials valid", False, f"HTTP {resp.status_code}: {resp.text[:120]}")
except Exception as e:
print_result("Novi credentials valid", False, f"Exception: {e}")
def test_mailman_connection():
print("\n[2] Mailman 3 API Connection")
try:
resp = requests.get(f"{ACTION_BASE}/test_connection/mailman", headers=AUTH_HEADERS, timeout=15)
data = resp.json().get('data', {})
if resp.status_code == 200 and data.get('ok'):
print_result("Mailman credentials valid", True, f"version={data.get('version', 'unknown')}")
elif resp.status_code == 401:
print_result("Mailman credentials valid", False,
f"401 — {data.get('error', resp.text[:120])} "
f"(add mailman_* keys to IDAA cfg_json)")
else:
print_result("Mailman credentials valid", False, f"HTTP {resp.status_code}: {resp.text[:120]}")
except Exception as e:
print_result("Mailman credentials valid", False, f"Exception: {e}")
if __name__ == "__main__":
print("=" * 60)
print(" Novi-Mailman Bridge — Connection Tests")
print(f" Target: {ACTION_BASE}")
print("=" * 60)
t_start = time.time()
test_novi_connection()
test_mailman_connection()
elapsed = time.time() - t_start
print(f"\n{'=' * 60}")
print(f" Results: {pass_count} passed, {fail_count} failed ({elapsed:.2f}s)")
print("=" * 60)
sys.exit(0 if fail_count == 0 else 1)