""" Basic verification tests for Phase 1-3 implementation. Run with: python tests/test_phase123_basic.py Requires: gateway running on localhost:8000 """ import sys import os sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) print("=" * 60) print("PHASE 1-3 BASIC VERIFICATION") print("=" * 60) errors = [] passes = [] def _check(label, condition, detail=""): if condition: passes.append(label) print(f" [PASS] {label}") else: errors.append(f"{label}: {detail}") print(f" [FAIL] {label} — {detail}") # ── Phase 1: Data Models ────────────────────────────────────────────────────── print("\n--- Phase 1: Data Models ---") try: from modules.datamodels.datamodelUam import Mandate m = Mandate(name="test", label="test") _check("Mandate has isSystem field", hasattr(m, "isSystem")) _check("Mandate isSystem default False", m.isSystem is False) _check("Mandate no mandateType field", not hasattr(m, "mandateType")) except Exception as e: errors.append(f"Phase 1 DataModel: {e}") print(f" [FAIL] Phase 1 DataModel import: {e}") try: from modules.datamodels.datamodelSubscription import SubscriptionStatusEnum, BUILTIN_PLANS, SubscriptionPlan _check("PENDING status exists", hasattr(SubscriptionStatusEnum, "PENDING")) _check("BUILTIN_PLANS has TRIAL_14D", "TRIAL_14D" in BUILTIN_PLANS) trial = BUILTIN_PLANS["TRIAL_14D"] _check("TRIAL_14D has maxDataVolumeMB", hasattr(trial, "maxDataVolumeMB")) _check("TRIAL_14D maxDataVolumeMB=1024", trial.maxDataVolumeMB == 1024) _check("TRIAL_14D has includedModules", hasattr(trial, "includedModules")) _check("TRIAL_14D includedModules=2", trial.includedModules == 2) _check("TRIAL_14D trialDays=14", trial.trialDays == 14) except Exception as e: errors.append(f"Phase 1 Subscription: {e}") print(f" [FAIL] Phase 1 Subscription: {e}") # ── Phase 2: Scope Fields ───────────────────────────────────────────────────── print("\n--- Phase 2: Scope Fields on Models ---") try: from modules.datamodels.datamodelFiles import FileItem fi = FileItem(fileName="test.txt", mimeType="text/plain", fileHash="abc", fileSize=100) _check("FileItem has scope field", hasattr(fi, "scope")) _check("FileItem scope default=personal", fi.scope == "personal") _check("FileItem has neutralize field", hasattr(fi, "neutralize")) _check("FileItem neutralize default=False", fi.neutralize == False) except Exception as e: errors.append(f"Phase 2 FileItem: {e}") print(f" [FAIL] Phase 2 FileItem: {e}") try: from modules.datamodels.datamodelDataSource import DataSource ds = DataSource(connectionId="c1", sourceType="sharepoint", path="/test", label="Test") _check("DataSource has scope field", hasattr(ds, "scope")) _check("DataSource scope default=personal", ds.scope == "personal") _check("DataSource has neutralize field", hasattr(ds, "neutralize")) _check("DataSource neutralize default=False", ds.neutralize == False) except Exception as e: errors.append(f"Phase 2 DataSource: {e}") print(f" [FAIL] Phase 2 DataSource: {e}") try: from modules.datamodels.datamodelKnowledge import FileContentIndex fci = FileContentIndex(userId="u1", fileName="test.txt", mimeType="text/plain") _check("FileContentIndex has scope field", hasattr(fci, "scope")) _check("FileContentIndex scope default=personal", fci.scope == "personal") _check("FileContentIndex has neutralizationStatus", hasattr(fci, "neutralizationStatus")) _check("FileContentIndex neutralizationStatus default=None", fci.neutralizationStatus is None) except Exception as e: errors.append(f"Phase 2 FileContentIndex: {e}") print(f" [FAIL] Phase 2 FileContentIndex: {e}") # ── Phase 2: RAG Scope Filtering ────────────────────────────────────────────── print("\n--- Phase 2: RAG Scope Logic ---") try: from modules.interfaces.interfaceDbKnowledge import KnowledgeObjects _check("KnowledgeObjects has _getScopedFileIds", hasattr(KnowledgeObjects, "_getScopedFileIds")) _check("KnowledgeObjects has _buildScopeFilter", hasattr(KnowledgeObjects, "_buildScopeFilter")) import inspect sig = inspect.signature(KnowledgeObjects._getScopedFileIds) params = list(sig.parameters.keys()) _check("_getScopedFileIds has isSysAdmin param", "isSysAdmin" in params) sig2 = inspect.signature(KnowledgeObjects.semanticSearch) params2 = list(sig2.parameters.keys()) _check("semanticSearch has scope param", "scope" in params2) _check("semanticSearch has isSysAdmin param", "isSysAdmin" in params2) except Exception as e: errors.append(f"Phase 2 RAG: {e}") print(f" [FAIL] Phase 2 RAG: {e}") # ── Phase 3: Neutralization Methods ─────────────────────────────────────────── print("\n--- Phase 3: Neutralization Integration ---") try: from modules.workflows.workflowManager import WorkflowManager _check("WorkflowManager has _neutralizePromptIfRequired", hasattr(WorkflowManager, "_neutralizePromptIfRequired")) _check("WorkflowManager has _rehydrateResponseIfNeeded", hasattr(WorkflowManager, "_rehydrateResponseIfNeeded")) import inspect sig_n = inspect.signature(WorkflowManager._neutralizePromptIfRequired) _check("_neutralizePromptIfRequired is async", inspect.iscoroutinefunction(WorkflowManager._neutralizePromptIfRequired)) sig_r = inspect.signature(WorkflowManager._rehydrateResponseIfNeeded) _check("_rehydrateResponseIfNeeded is async", inspect.iscoroutinefunction(WorkflowManager._rehydrateResponseIfNeeded)) except Exception as e: errors.append(f"Phase 3 WorkflowManager: {e}") print(f" [FAIL] Phase 3 WorkflowManager: {e}") # ── Phase 3: Fail-Safe Logic ────────────────────────────────────────────────── print("\n--- Phase 3: Fail-Safe Logic ---") try: import ast with open(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "modules", "workflows", "methods", "methodContext", "actions", "neutralizeData.py"), "r") as f: source = f.read() _check("neutralizeData.py has 'SKIPPING' fail-safe", "SKIPPING" in source) _check("neutralizeData.py has 'do NOT pass original' comment", "do NOT pass original" in source.lower() or "not passing original" in source.lower()) _check("neutralizeData.py uses continue for skip", "continue" in source) except Exception as e: errors.append(f"Phase 3 Fail-Safe: {e}") print(f" [FAIL] Phase 3 Fail-Safe: {e}") # ── Phase 2: Route Endpoints ────────────────────────────────────────────────── print("\n--- Phase 2: API Endpoints ---") try: import ast with open(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "modules", "routes", "routeDataFiles.py"), "r") as f: source = f.read() _check("routeDataFiles has PATCH scope endpoint", "updateFileScope" in source) _check("routeDataFiles has PATCH neutralize endpoint", "updateFileNeutralize" in source) _check("routeDataFiles checks global sysAdmin", "hasSysAdminRole" in source or "sysadmin" in source.lower()) except Exception as e: errors.append(f"Phase 2 Routes: {e}") print(f" [FAIL] Phase 2 Routes: {e}") # ── Phase 1: Store Endpoints ────────────────────────────────────────────────── print("\n--- Phase 1: Store Endpoints ---") try: with open(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "modules", "routes", "routeStore.py"), "r") as f: source = f.read() _check("routeStore has listUserMandates", "listUserMandates" in source or "list_user_mandates" in source) _check("routeStore has getSubscriptionInfo", "getSubscriptionInfo" in source or "get_subscription_info" in source) _check("routeStore has orphan control", "orphan" in source.lower() or "last" in source.lower()) except Exception as e: errors.append(f"Phase 1 Store: {e}") print(f" [FAIL] Phase 1 Store: {e}") # ── Phase 1: Provisioning ───────────────────────────────────────────────────── print("\n--- Phase 1: Provisioning ---") try: with open(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "modules", "interfaces", "interfaceDbApp.py"), "r") as f: source = f.read() _check("interfaceDbApp has _provisionMandateForUser", "_provisionMandateForUser" in source) _check("interfaceDbApp has _activatePendingSubscriptions", "_activatePendingSubscriptions" in source) _check("interfaceDbApp has deleteMandate cascade", "deleteMandate" in source and "cascade" in source.lower()) except Exception as e: errors.append(f"Phase 1 Provisioning: {e}") print(f" [FAIL] Phase 1 Provisioning: {e}") # ── Phase 1: Registration Routes ────────────────────────────────────────────── print("\n--- Phase 1: Registration ---") try: with open(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "modules", "routes", "routeSecurityLocal.py"), "r") as f: source = f.read() _check("routeSecurityLocal has registrationType", "registrationType" in source) _check("routeSecurityLocal has companyName", "companyName" in source) _check("routeSecurityLocal has onboarding endpoint", "onboarding" in source) except Exception as e: errors.append(f"Phase 1 Registration: {e}") print(f" [FAIL] Phase 1 Registration: {e}") # ── Fix 1: OnboardingWizard Integration ──────────────────────────────────────── print("\n--- Fix 1: OnboardingWizard Integration ---") try: loginPath = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "..", "frontend_nyla", "src", "pages", "Login.tsx") with open(loginPath, "r", encoding="utf-8") as f: source = f.read() _check("Login.tsx imports OnboardingWizard", "OnboardingWizard" in source) _check("Login.tsx has showOnboardingWizard state", "showOnboardingWizard" in source) _check("Login.tsx checks isNewUser", "isNewUser" in source) except Exception as e: errors.append(f"Fix 1: {e}") print(f" [FAIL] Fix 1: {e}") # ── Fix 2: CommCoach UDB Integration ────────────────────────────────────────── print("\n--- Fix 2: CommCoach UDB Integration ---") try: dossierPath = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "..", "frontend_nyla", "src", "pages", "views", "commcoach", "CommcoachDossierView.tsx") with open(dossierPath, "r", encoding="utf-8") as f: source = f.read() _check("CommCoach imports UnifiedDataBar", "UnifiedDataBar" in source) _check("CommCoach imports FilesTab", "FilesTab" in source) _check("CommCoach no longer imports getDocumentsApi", "getDocumentsApi" not in source) _check("CommCoach has UDB sidebar", "udbSidebar" in source or "UnifiedDataBar" in source) except Exception as e: errors.append(f"Fix 2: {e}") print(f" [FAIL] Fix 2: {e}") # ── Fix 3: Neutralization Backend Endpoints ─────────────────────────────────── print("\n--- Fix 3: Neutralization Backend Endpoints ---") try: routePath = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "modules", "features", "neutralization", "routeFeatureNeutralizer.py") with open(routePath, "r") as f: source = f.read() _check("Neutralization has deleteAttribute endpoint", "deleteAttribute" in source or "delete_attribute" in source) _check("Neutralization has retrigger endpoint", "retrigger" in source) _check("Neutralization has single attribute delete", "single" in source or "attributeId" in source) except Exception as e: errors.append(f"Fix 3: {e}") print(f" [FAIL] Fix 3: {e}") # ── Fix 4: Central AI Neutralization ────────────────────────────────────────── print("\n--- Fix 4: Central AI Neutralization ---") try: aiPath = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "modules", "serviceCenter", "services", "serviceAi", "mainServiceAi.py") with open(aiPath, "r") as f: source = f.read() _check("AiService has _shouldNeutralize", "_shouldNeutralize" in source) _check("AiService has _neutralizeRequest", "_neutralizeRequest" in source) _check("AiService has _rehydrateResponse", "_rehydrateResponse" in source) _check("callAi uses neutralization", "_shouldNeutralize" in source and "_neutralizeRequest" in source) except Exception as e: errors.append(f"Fix 4: {e}") print(f" [FAIL] Fix 4: {e}") # ── Fix 5: Voice Settings User Level ────────────────────────────────────────── print("\n--- Fix 5: Voice Settings User Level ---") try: from modules.datamodels.datamodelUam import UserVoicePreferences uvp = UserVoicePreferences(userId="u1") _check("UserVoicePreferences model exists", True) _check("UserVoicePreferences has sttLanguage", hasattr(uvp, "sttLanguage")) _check("UserVoicePreferences default sttLanguage=de-DE", uvp.sttLanguage == "de-DE") _check("UserVoicePreferences has ttsVoice", hasattr(uvp, "ttsVoice")) except Exception as e: errors.append(f"Fix 5: {e}") print(f" [FAIL] Fix 5: {e}") try: voiceUserPath = os.path.join( os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "modules", "routes", "routeVoiceUser.py", ) with open(voiceUserPath, "r") as f: source = f.read() _check("Voice preferences GET endpoint", '"/preferences"' in source and "getVoicePreferences" in source) _check("Voice preferences PUT endpoint", "updateVoicePreferences" in source) except Exception as e: errors.append(f"Fix 5 Routes: {e}") print(f" [FAIL] Fix 5 Routes: {e}") # ── Fix 6: RAG mandate-wide scope ───────────────────────────────────────────── print("\n--- Fix 6: RAG mandate-wide scope ---") try: knowledgePath = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "modules", "serviceCenter", "services", "serviceKnowledge", "mainServiceKnowledge.py") with open(knowledgePath, "r") as f: source = f.read() _check("buildAgentContext passes mandateId to semanticSearch", "mandateId=mandateId" in source) _check("buildAgentContext has isSysAdmin param", "isSysAdmin" in source) except Exception as e: errors.append(f"Fix 6: {e}") print(f" [FAIL] Fix 6: {e}") # ── Summary ─────────────────────────────────────────────────────────────────── print("\n" + "=" * 60) print(f"RESULTS: {len(passes)} passed, {len(errors)} failed") print("=" * 60) if errors: print("\nFAILURES:") for e in errors: print(f" - {e}") sys.exit(1) else: print("\nALL CHECKS PASSED!") sys.exit(0)