328 lines
17 KiB
Python
328 lines
17 KiB
Python
"""
|
|
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}")
|
|
|
|
# ── Phase 1: Migration ────────────────────────────────────────────────────────
|
|
print("\n--- Phase 1: Migration ---")
|
|
|
|
try:
|
|
with open(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
|
"modules", "migration", "migrateRootUsers.py"), "r") as f:
|
|
source = f.read()
|
|
_check("Migration script exists", True)
|
|
_check("Migration has _isMigrationCompleted", "_isMigrationCompleted" in source)
|
|
_check("Migration has migrateRootUsers", "migrateRootUsers" in source)
|
|
except Exception as e:
|
|
errors.append(f"Phase 1 Migration: {e}")
|
|
print(f" [FAIL] Phase 1 Migration: {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)
|