From 33c9ec1830ad8c75bbcbf2f8c412af28c0ef96ac Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Mon, 2 Jun 2025 23:12:24 +0200
Subject: [PATCH] refactored ui
---
modules/connectors/connectorDbJson.py | 10 +
modules/interfaces/serviceAppAccess.py | 62 +-
modules/interfaces/serviceAppClass.py | 61 +-
modules/interfaces/serviceAppModel.py | 14 +-
modules/routes/routeDataMandates.py | 41 +-
modules/routes/routeDataPrompts.py | 5 -
modules/routes/routeDataUsers.py | 46 +-
modules/routes/routeSecurityLocal.py | 14 +-
modules/security/auth.py | 6 +-
notes/changelog.txt | 48 ++
static/favicon.ico | Bin 3870 -> 0 bytes
static/test.txt | 1 -
tool_test01ui.py | 926 +++++++++++++++++++++++++
ui_test_report.json | 352 ++++++++++
14 files changed, 1494 insertions(+), 92 deletions(-)
delete mode 100644 static/favicon.ico
delete mode 100644 static/test.txt
create mode 100644 tool_test01ui.py
create mode 100644 ui_test_report.json
diff --git a/modules/connectors/connectorDbJson.py b/modules/connectors/connectorDbJson.py
index fe082956..006ca7e4 100644
--- a/modules/connectors/connectorDbJson.py
+++ b/modules/connectors/connectorDbJson.py
@@ -156,6 +156,11 @@ class DatabaseConnector:
# Ensure recordId is a string
recordId = str(recordId)
+ # CRITICAL: Ensure record ID matches the file name
+ if "id" in record and str(record["id"]) != recordId:
+ logger.error(f"Record ID mismatch: file name ID ({recordId}) does not match record ID ({record['id']})")
+ raise ValueError(f"Record ID mismatch: file name ID ({recordId}) does not match record ID ({record['id']})")
+
# Add metadata
currentTime = datetime.now().isoformat()
if "_createdAt" not in record:
@@ -454,6 +459,11 @@ class DatabaseConnector:
if isinstance(record, BaseModel):
record = to_dict(record)
+ # CRITICAL: Ensure we never modify the ID
+ if "id" in record and str(record["id"]) != recordId:
+ logger.error(f"Attempted to modify record ID from {recordId} to {record['id']}")
+ raise ValueError("Cannot modify record ID - it must match the file name")
+
# Update existing record with new data
existingRecord.update(record)
diff --git a/modules/interfaces/serviceAppAccess.py b/modules/interfaces/serviceAppAccess.py
index 182a3055..ec6ee3fc 100644
--- a/modules/interfaces/serviceAppAccess.py
+++ b/modules/interfaces/serviceAppAccess.py
@@ -2,10 +2,14 @@
Access control for the Application.
"""
+import logging
from typing import Dict, Any, List, Optional
from datetime import datetime
from modules.interfaces.serviceAppModel import UserPrivilege, Session, User
+# Configure logger
+logger = logging.getLogger(__name__)
+
class AppAccess:
"""
Access control class for Application interface.
@@ -38,14 +42,31 @@ class AppAccess:
"""
filtered_records = []
- # Apply filtering based on privilege
- if self.privilege == UserPrivilege.SYSADMIN:
- filtered_records = recordset # System admins see all records
+ # Only SYSADMIN can see mandates
+ if table == "mandates":
+ if self.privilege == UserPrivilege.SYSADMIN:
+ filtered_records = recordset
+ else:
+ filtered_records = []
+ # Special handling for users table
+ elif table == "users":
+ if self.privilege == UserPrivilege.SYSADMIN:
+ # SysAdmin sees all users
+ filtered_records = recordset
+ elif self.privilege == UserPrivilege.ADMIN:
+ # Admin sees all users in their mandate
+ filtered_records = [r for r in recordset if r.get("mandateId","-") == self.mandateId]
+ else:
+ # Regular users only see themselves
+ filtered_records = [r for r in recordset if r.get("id") == self.userId]
+ # System admins see all other records
+ elif self.privilege == UserPrivilege.SYSADMIN:
+ filtered_records = recordset
+ # For other records, admins see records in their mandate
elif self.privilege == UserPrivilege.ADMIN:
- # Admins see records in their mandate
filtered_records = [r for r in recordset if r.get("mandateId","-") == self.mandateId]
- else: # Regular users
- # Users only see records they own within their mandate
+ # Regular users only see records they own within their mandate
+ else:
filtered_records = [r for r in recordset
if r.get("mandateId","-") == self.mandateId and r.get("createdBy") == self.userId]
@@ -55,13 +76,23 @@ class AppAccess:
# Set access control flags based on user permissions
if table == "mandates":
- record["_hideView"] = False # Everyone can view
+ record["_hideView"] = False # SYSADMIN can view
record["_hideEdit"] = not self.canModify("mandates", record_id)
record["_hideDelete"] = not self.canModify("mandates", record_id)
elif table == "users":
- record["_hideView"] = False # Everyone can view
- record["_hideEdit"] = not self.canModify("users", record_id)
- record["_hideDelete"] = not self.canModify("users", record_id)
+ record["_hideView"] = False # Everyone can view users they have access to
+ # SysAdmin can edit/delete any user
+ if self.privilege == UserPrivilege.SYSADMIN:
+ record["_hideEdit"] = False
+ record["_hideDelete"] = False
+ # Admin can edit/delete users in their mandate
+ elif self.privilege == UserPrivilege.ADMIN:
+ record["_hideEdit"] = record.get("mandateId","-") != self.mandateId
+ record["_hideDelete"] = record.get("mandateId","-") != self.mandateId
+ # Regular users can only edit themselves
+ else:
+ record["_hideEdit"] = record.get("id") != self.userId
+ record["_hideDelete"] = True # Regular users cannot delete users
elif table == "sessions":
# Only show sessions for the current user or if admin
if self.privilege in [UserPrivilege.SYSADMIN, UserPrivilege.ADMIN]:
@@ -97,7 +128,11 @@ class AppAccess:
Returns:
Boolean indicating permission
"""
- # System admins can modify anything
+ # For mandates, only SYSADMIN can modify
+ if table == "mandates":
+ return self.privilege == UserPrivilege.SYSADMIN
+
+ # System admins can modify anything else
if self.privilege == UserPrivilege.SYSADMIN:
return True
@@ -112,9 +147,6 @@ class AppAccess:
# Admins can modify anything in their mandate
if self.privilege == UserPrivilege.ADMIN and record.get("mandateId","-") == self.mandateId:
- # Exception: Can't modify Root mandate unless you are a sysadmin
- if table == "mandates" and record.get("initialid") and self.privilege != UserPrivilege.SYSADMIN:
- return False
return True
# Users can only modify their own records
@@ -130,8 +162,6 @@ class AppAccess:
return True
# Regular users can create most entities
- if table == "mandates":
- return False # Regular users can't create mandates
return True
def validateSession(self, sessionId: str) -> bool:
diff --git a/modules/interfaces/serviceAppClass.py b/modules/interfaces/serviceAppClass.py
index a357763e..789348d2 100644
--- a/modules/interfaces/serviceAppClass.py
+++ b/modules/interfaces/serviceAppClass.py
@@ -118,7 +118,8 @@ class GatewayInterface:
logger.info("Creating Root mandate")
rootMandate = Mandate(
name="Root",
- language="en"
+ language="en",
+ enabled=True
)
createdMandate = self.db.recordCreate("mandates", rootMandate.to_dict())
logger.info(f"Root mandate created with ID {createdMandate['id']}")
@@ -140,10 +141,10 @@ class GatewayInterface:
username="admin",
email="admin@example.com",
fullName="Administrator",
- disabled=False,
+ enabled=True,
language="en",
privilege=UserPrivilege.SYSADMIN,
- authenticationAuthority=AuthAuthority.LOCAL,
+ authenticationAuthority="local", # Using lowercase value directly
hashedPassword=self._getPasswordHash("The 1st Poweron Admin"), # Use a secure password in production!
connections=[]
)
@@ -323,8 +324,8 @@ class GatewayInterface:
if not user:
raise ValueError("User not found")
- # Check if the user is disabled
- if user.disabled:
+ # Check if the user is enabled
+ if not user.enabled:
raise ValueError("User is disabled")
# Verify that the user has local authentication enabled
@@ -342,7 +343,7 @@ class GatewayInterface:
return user
def createUser(self, username: str, password: str = None, email: str = None,
- fullName: str = None, language: str = "en", disabled: bool = False,
+ fullName: str = None, language: str = "en", enabled: bool = True,
privilege: UserPrivilege = UserPrivilege.USER,
authenticationAuthority: AuthAuthority = AuthAuthority.LOCAL,
externalId: str = None, externalUsername: str = None,
@@ -368,7 +369,7 @@ class GatewayInterface:
fullName=fullName,
language=language,
mandateId=self.mandateId,
- disabled=disabled,
+ enabled=enabled,
privilege=privilege,
authenticationAuthority=authenticationAuthority,
hashedPassword=self._getPasswordHash(password) if password else None,
@@ -439,11 +440,11 @@ class GatewayInterface:
def disableUser(self, userId: str) -> User:
"""Disables a user if current user has permission."""
- return self.updateUser(userId, {"disabled": True})
+ return self.updateUser(userId, {"enabled": False})
def enableUser(self, userId: str) -> User:
"""Enables a user if current user has permission."""
- return self.updateUser(userId, {"disabled": False})
+ return self.updateUser(userId, {"enabled": True})
def _deleteUserReferencedData(self, userId: str) -> None:
"""Deletes all data associated with a user."""
@@ -479,6 +480,38 @@ class GatewayInterface:
logger.error(f"Error deleting referenced data for user {userId}: {str(e)}")
raise
+ def deleteUser(self, userId: str) -> bool:
+ """Deletes a user if current user has permission."""
+ try:
+ # Get user
+ user = self.getUser(userId)
+ if not user:
+ raise ValueError(f"User {userId} not found")
+
+ if not self._canModify("users", userId):
+ raise PermissionError(f"No permission to delete user {userId}")
+
+ # Delete all referenced data first
+ self._deleteUserReferencedData(userId)
+
+ # Delete user record
+ success = self.db.recordDelete("users", userId)
+ if not success:
+ raise ValueError(f"Failed to delete user {userId}")
+
+ # Clear both table and metadata caches
+ if hasattr(self.db, '_tablesCache') and "users" in self.db._tablesCache:
+ del self.db._tablesCache["users"]
+ if hasattr(self.db, '_tableMetadataCache') and "users" in self.db._tableMetadataCache:
+ del self.db._tableMetadataCache["users"]
+
+ logger.info(f"User {userId} successfully deleted")
+ return True
+
+ except Exception as e:
+ logger.error(f"Error deleting user: {str(e)}")
+ raise ValueError(f"Failed to delete user: {str(e)}")
+
# Mandate methods
def getAllMandates(self) -> List[Mandate]:
@@ -520,14 +553,22 @@ class GatewayInterface:
def updateMandate(self, mandateId: str, updateData: Dict[str, Any]) -> Mandate:
"""Updates a mandate if user has access."""
try:
- # Get mandate
+ logger.debug(f"Updating mandate {mandateId} with data: {updateData}")
+
+ # First check if user has permission to modify mandates
+ if not self._canModify("mandates", mandateId):
+ raise PermissionError(f"No permission to update mandate {mandateId}")
+
+ # Get mandate with access control
mandate = self.getMandate(mandateId)
+ logger.debug(f"Retrieved mandate: {mandate}")
if not mandate:
raise ValueError(f"Mandate {mandateId} not found")
# Update mandate data using model
updatedData = mandate.to_dict()
updatedData.update(updateData)
+ logger.debug(f"Updated data: {updatedData}")
updatedMandate = Mandate.from_dict(updatedData)
# Update mandate record
diff --git a/modules/interfaces/serviceAppModel.py b/modules/interfaces/serviceAppModel.py
index 979efdda..f0947195 100644
--- a/modules/interfaces/serviceAppModel.py
+++ b/modules/interfaces/serviceAppModel.py
@@ -11,9 +11,9 @@ from modules.shared.attributeUtils import register_model_labels, AttributeDefini
class AuthAuthority(str, Enum):
"""Authentication authority enum"""
- LOCAL = "Local"
- GOOGLE = "Google"
- MSFT = "Msft"
+ LOCAL = "local"
+ GOOGLE = "google"
+ MSFT = "msft"
class UserPrivilege(str, Enum):
"""User privilege levels"""
@@ -33,6 +33,7 @@ class Mandate(BaseModel, ModelMixin):
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Unique ID of the mandate")
name: str = Field(description="Name of the mandate")
language: str = Field(default="en", description="Default language of the mandate")
+ enabled: bool = Field(default=True, description="Indicates whether the mandate is enabled")
# Register labels for Mandate
register_model_labels(
@@ -41,7 +42,8 @@ register_model_labels(
{
"id": {"en": "ID", "fr": "ID"},
"name": {"en": "Name", "fr": "Nom"},
- "language": {"en": "Language", "fr": "Langue"}
+ "language": {"en": "Language", "fr": "Langue"},
+ "enabled": {"en": "Enabled", "fr": "Activé"}
}
)
@@ -131,7 +133,7 @@ class User(BaseModel, ModelMixin):
email: Optional[EmailStr] = Field(None, description="Email address of the user")
fullName: Optional[str] = Field(None, description="Full name of the user")
language: str = Field(default="en", description="Preferred language of the user")
- disabled: bool = Field(default=False, description="Indicates whether the user is disabled")
+ enabled: bool = Field(default=True, description="Indicates whether the user is enabled")
privilege: UserPrivilege = Field(default=UserPrivilege.USER, description="Permission level")
authenticationAuthority: AuthAuthority = Field(default=AuthAuthority.LOCAL, description="Primary authentication authority")
mandateId: Optional[str] = Field(None, description="ID of the mandate this user belongs to")
@@ -147,7 +149,7 @@ register_model_labels(
"email": {"en": "Email", "fr": "Email"},
"fullName": {"en": "Full Name", "fr": "Nom complet"},
"language": {"en": "Language", "fr": "Langue"},
- "disabled": {"en": "Disabled", "fr": "Désactivé"},
+ "enabled": {"en": "Enabled", "fr": "Activé"},
"privilege": {"en": "Privilege", "fr": "Privilège"},
"authenticationAuthority": {"en": "Auth Authority", "fr": "Autorité d'authentification"},
"mandateId": {"en": "Mandate ID", "fr": "ID de mandat"},
diff --git a/modules/routes/routeDataMandates.py b/modules/routes/routeDataMandates.py
index f5e708d7..c2aebea2 100644
--- a/modules/routes/routeDataMandates.py
+++ b/modules/routes/routeDataMandates.py
@@ -17,7 +17,7 @@ from pydantic import BaseModel
from modules.security.auth import limiter, getCurrentUser
# Import interfaces
-import modules.interfaces.serviceManagementClass as serviceManagementClass
+import modules.interfaces.serviceAppClass as serviceAppClass
from modules.shared.attributeUtils import getModelAttributeDefinitions, AttributeResponse, AttributeDefinition
# Import the model classes
@@ -44,9 +44,9 @@ async def get_mandates(
) -> List[Mandate]:
"""Get all mandates"""
try:
- managementInterface = serviceManagementClass.getInterface(currentUser)
- mandates = managementInterface.getMandates()
- return [Mandate.from_dict(mandate) for mandate in mandates]
+ appInterface = serviceAppClass.getInterface(currentUser)
+ mandates = appInterface.getAllMandates()
+ return mandates
except Exception as e:
logger.error(f"Error getting mandates: {str(e)}")
raise HTTPException(
@@ -63,8 +63,8 @@ async def get_mandate(
) -> Mandate:
"""Get a specific mandate by ID"""
try:
- managementInterface = serviceManagementClass.getInterface(currentUser)
- mandate = managementInterface.getMandate(mandateId)
+ appInterface = serviceAppClass.getInterface(currentUser)
+ mandate = appInterface.getMandate(mandateId)
if not mandate:
raise HTTPException(
@@ -72,7 +72,7 @@ async def get_mandate(
detail=f"Mandate with ID {mandateId} not found"
)
- return Mandate.from_dict(mandate)
+ return mandate
except HTTPException:
raise
except Exception as e:
@@ -91,13 +91,13 @@ async def create_mandate(
) -> Mandate:
"""Create a new mandate"""
try:
- managementInterface = serviceManagementClass.getInterface(currentUser)
-
- # Convert Mandate to dict for interface
- mandate_data = mandateData.to_dict()
+ appInterface = serviceAppClass.getInterface(currentUser)
# Create mandate
- newMandate = managementInterface.createMandate(mandate_data)
+ newMandate = appInterface.createMandate(
+ name=mandateData.name,
+ language=mandateData.language
+ )
if not newMandate:
raise HTTPException(
@@ -105,7 +105,7 @@ async def create_mandate(
detail="Failed to create mandate"
)
- return Mandate.from_dict(newMandate)
+ return newMandate
except HTTPException:
raise
except Exception as e:
@@ -125,21 +125,18 @@ async def update_mandate(
) -> Mandate:
"""Update an existing mandate"""
try:
- managementInterface = serviceManagementClass.getInterface(currentUser)
+ appInterface = serviceAppClass.getInterface(currentUser)
# Check if mandate exists
- existingMandate = managementInterface.getMandate(mandateId)
+ existingMandate = appInterface.getMandate(mandateId)
if not existingMandate:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Mandate with ID {mandateId} not found"
)
- # Convert Mandate to dict for interface
- update_data = mandateData.to_dict()
-
# Update mandate
- updatedMandate = managementInterface.updateMandate(mandateId, update_data)
+ updatedMandate = appInterface.updateMandate(mandateId, mandateData.to_dict())
if not updatedMandate:
raise HTTPException(
@@ -147,7 +144,7 @@ async def update_mandate(
detail="Failed to update mandate"
)
- return Mandate.from_dict(updatedMandate)
+ return updatedMandate
except HTTPException:
raise
except Exception as e:
@@ -166,10 +163,10 @@ async def delete_mandate(
) -> Dict[str, Any]:
"""Delete a mandate"""
try:
- appInterface = serviceManagementClass.getInterface(currentUser)
+ appInterface = serviceAppClass.getInterface(currentUser)
# Check if mandate exists
- existingMandate = appInterface.getMandateById(mandateId)
+ existingMandate = appInterface.getMandate(mandateId)
if not existingMandate:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
diff --git a/modules/routes/routeDataPrompts.py b/modules/routes/routeDataPrompts.py
index d816b245..6f54fc9c 100644
--- a/modules/routes/routeDataPrompts.py
+++ b/modules/routes/routeDataPrompts.py
@@ -54,11 +54,6 @@ async def create_prompt(
# Create prompt
newPrompt = managementInterface.createPrompt(prompt_data)
- # Set current time for createdAt if it exists in the model
- promptAttributes = getModelAttributeDefinitions(Prompt)
- if "createdAt" in promptAttributes["attributes"] and hasattr(newPrompt, "createdAt"):
- newPrompt["createdAt"] = datetime.now().isoformat()
-
return Prompt.from_dict(newPrompt)
@router.get("/{promptId}", response_model=Prompt)
diff --git a/modules/routes/routeDataUsers.py b/modules/routes/routeDataUsers.py
index 39f83f80..bcaeead4 100644
--- a/modules/routes/routeDataUsers.py
+++ b/modules/routes/routeDataUsers.py
@@ -14,7 +14,7 @@ import os
from pydantic import BaseModel
# Import interfaces and models
-import modules.interfaces.serviceManagementClass as serviceManagementClass
+import modules.interfaces.serviceAppClass as serviceAppClass
from modules.security.auth import getCurrentUser, limiter, getCurrentUser
# Import the attribute definition and helper functions
@@ -34,13 +34,17 @@ router = APIRouter(
@limiter.limit("30/minute")
async def get_users(
request: Request,
+ mandateId: Optional[str] = None,
currentUser: User = Depends(getCurrentUser)
) -> List[User]:
"""Get all users in the current mandate"""
try:
- managementInterface = serviceManagementClass.getInterface(currentUser)
- users = managementInterface.getUsers()
- return [User.from_dict(user) for user in users]
+ appInterface = serviceAppClass.getInterface(currentUser)
+ # If mandateId is provided, use it, otherwise use the current user's mandate
+ targetMandateId = mandateId or currentUser.mandateId
+ # Get all users without filtering by enabled status
+ users = appInterface.getUsersByMandate(targetMandateId)
+ return users
except Exception as e:
logger.error(f"Error getting users: {str(e)}")
raise HTTPException(
@@ -57,8 +61,9 @@ async def get_user(
) -> User:
"""Get a specific user by ID"""
try:
- managementInterface = serviceManagementClass.getInterface(currentUser)
- user = managementInterface.getUser(userId)
+ appInterface = serviceAppClass.getInterface(currentUser)
+ # Get user without filtering by enabled status
+ user = appInterface.getUser(userId)
if not user:
raise HTTPException(
@@ -66,7 +71,7 @@ async def get_user(
detail=f"User with ID {userId} not found"
)
- return User.from_dict(user)
+ return user
except HTTPException:
raise
except Exception as e:
@@ -80,24 +85,19 @@ async def get_user(
@limiter.limit("10/minute")
async def create_user(
request: Request,
- user: User,
+ user_data: User = Body(...),
currentUser: User = Depends(getCurrentUser)
) -> User:
"""Create a new user"""
- managementInterface = serviceManagementClass.getInterface(currentUser)
+ appInterface = serviceAppClass.getInterface(currentUser)
# Convert User to dict for interface
- user_data = user.to_dict()
+ user_dict = user_data.dict()
# Create user
- newUser = managementInterface.createUser(user_data)
+ newUser = appInterface.createUser(user_dict)
- # Set current time for createdAt if it exists in the model
- userAttributes = getModelAttributeDefinitions(User)
- if "createdAt" in userAttributes["attributes"] and hasattr(newUser, "createdAt"):
- newUser["createdAt"] = datetime.now().isoformat()
-
- return User.from_dict(newUser)
+ return newUser
@router.put("/{userId}", response_model=User)
@limiter.limit("10/minute")
@@ -108,10 +108,10 @@ async def update_user(
currentUser: User = Depends(getCurrentUser)
) -> User:
"""Update an existing user"""
- managementInterface = serviceManagementClass.getInterface(currentUser)
+ appInterface = serviceAppClass.getInterface(currentUser)
# Check if the user exists
- existingUser = managementInterface.getUser(userId)
+ existingUser = appInterface.getUser(userId)
if not existingUser:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -119,10 +119,10 @@ async def update_user(
)
# Convert User to dict for interface
- update_data = userData.to_dict()
+ update_data = userData.dict()
# Update user
- updatedUser = managementInterface.updateUser(userId, update_data)
+ updatedUser = appInterface.updateUser(userId, update_data)
if not updatedUser:
raise HTTPException(
@@ -130,7 +130,7 @@ async def update_user(
detail="Error updating the user"
)
- return User.from_dict(updatedUser)
+ return updatedUser
@router.delete("/{userId}", response_model=Dict[str, Any])
@limiter.limit("10/minute")
@@ -140,7 +140,7 @@ async def delete_user(
currentUser: User = Depends(getCurrentUser)
) -> Dict[str, Any]:
"""Delete a user"""
- appInterface = serviceManagementClass.getInterface(currentUser)
+ appInterface = serviceAppClass.getInterface(currentUser)
# Check if the user exists
existingUser = appInterface.getUser(userId)
diff --git a/modules/routes/routeSecurityLocal.py b/modules/routes/routeSecurityLocal.py
index 1829befa..441975b8 100644
--- a/modules/routes/routeSecurityLocal.py
+++ b/modules/routes/routeSecurityLocal.py
@@ -156,25 +156,25 @@ async def register_user(
# Set the mandate ID on the interface
appInterface.mandateId = defaultMandateId
- # Create user with individual parameters
- newUser = appInterface.createUser(
+ # Create user with local authentication
+ user = appInterface.createUser(
username=userData.username,
- password=password, # Pass the plain text password - createUser will hash it
+ password=password,
email=userData.email,
fullName=userData.fullName,
language=userData.language,
- disabled=userData.disabled,
+ enabled=userData.enabled,
privilege=userData.privilege,
- authenticationAuthority=userData.authenticationAuthority
+ authenticationAuthority=AuthAuthority.LOCAL
)
- if not newUser:
+ if not user:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Failed to register user"
)
- return newUser
+ return user
except ValueError as e:
raise HTTPException(
diff --git a/modules/security/auth.py b/modules/security/auth.py
index fb75c375..6eeb7bde 100644
--- a/modules/security/auth.py
+++ b/modules/security/auth.py
@@ -122,7 +122,8 @@ def _getUserBase(token: str = Depends(oauth2Scheme)) -> User:
logger.warning(f"User {username} not found")
raise credentialsException
- if user.disabled:
+ # Check if user is enabled
+ if not user.enabled:
logger.warning(f"User {username} is disabled")
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="User is disabled")
@@ -139,7 +140,8 @@ def _getUserBase(token: str = Depends(oauth2Scheme)) -> User:
def getCurrentUser(currentUser: User = Depends(_getUserBase)) -> User:
"""Get current active user with additional validation."""
- if currentUser.disabled:
+ # Check if current user is enabled
+ if not currentUser.enabled:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="User is disabled"
diff --git a/notes/changelog.txt b/notes/changelog.txt
index b128aa28..78b68c11 100644
--- a/notes/changelog.txt
+++ b/notes/changelog.txt
@@ -1,5 +1,53 @@
....................... TASKS
+FIXES:
+- can you add generic sorting by field and pagination to the table, also to have possibility to move table borders
+
+now to adapt files.js with following specifics:
+- ADD function: File upload instead of form
+- MandateId not editable, to use user's mandateId
+- Actions: Edit, Delete
+
+
+now to adapt files.js with following specifics:
+- ADD function: File upload instead of form
+- MandateId not editable, to use user's mandateId
+- Actions: Edit, Delete
+
+
+
+
+To remove unused functions in files.js and to consolidate it to match new formGeneric.
+
+FormGeneric
+- ADD prompt: no mandateid for new prompt in the form
+- ALLE: After add, modify, delete to reload data new from api
+- ADD User: Kein Passwortfeld, connections noch drin
+- DELETE User: Fehler
+- UPDATE Mandate: enabled als Text statt boolean
+- ADD Mandate: error sending
+- Connections: Missing
+
+UAC:
+- Users disabled nicht sichtbar!
+
+Workflow:
+- Liste der Prompts nicht aktualisiert nach Änderungen in FormGeneric
+-
+
+- diese seite gesucht: "GET /.well-known/appspecific/com.chrome.devtools.json HTTP/1.1" 404 ?
+
+
+Test paths:
+- Admin
+- User
+- MSFT-Google
+- Alle Management items
+- Workflow
+- Connections on/off
+- Mail 2 Connectors
+
+
Agents and Manager:
- To adapt prompts to match document handling, done by agents
- agents to use service object and to work stepwise:
diff --git a/static/favicon.ico b/static/favicon.ico
deleted file mode 100644
index a11777cc471a4344702741ab1c8a588998b1311a..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 3870
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
baj7?x%VD!5Cxk2YbYLNVeiXvvpMCWYo=by@
diff --git a/static/test.txt b/static/test.txt
deleted file mode 100644
index bebc6e70..00000000
--- a/static/test.txt
+++ /dev/null
@@ -1 +0,0 @@
-OK 1.0
\ No newline at end of file
diff --git a/tool_test01ui.py b/tool_test01ui.py
new file mode 100644
index 00000000..205976e2
--- /dev/null
+++ b/tool_test01ui.py
@@ -0,0 +1,926 @@
+"""
+UI Test Procedure for PowerOn Frontend
+Tests CRUD operations, user registration, authentication, and access control
+"""
+
+import os
+import sys
+import json
+import logging
+from datetime import datetime
+from typing import Dict, List, Any
+import requests
+from dataclasses import dataclass
+from enum import Enum
+import time
+
+
+# Configure logging
+logging.basicConfig(
+ level=logging.INFO,
+ format='%(asctime)s - %(levelname)s - %(message)s',
+ handlers=[
+ logging.FileHandler('ui_test_report.log'),
+ logging.StreamHandler(sys.stdout)
+ ]
+)
+logger = logging.getLogger(__name__)
+
+# Test configuration
+BASE_URL = "http://localhost:8080" # Adjust based on your frontend URL
+API_URL = "http://localhost:8000" # Adjust based on your backend URL
+
+class UserRole(Enum):
+ SYSADMIN = "sysadmin"
+ ADMIN = "admin"
+ USER = "user"
+
+@dataclass
+class TestResult:
+ test_name: str
+ success: bool
+ message: str
+ details: Dict[str, Any] = None
+
+@dataclass
+class TestReport:
+ timestamp: str
+ total_tests: int
+ passed_tests: int
+ failed_tests: int
+ results: List[TestResult]
+ bugs_found: List[Dict[str, str]]
+ required_adaptations: List[Dict[str, str]]
+
+class UITestSuite:
+ def __init__(self):
+ self.session = requests.Session()
+ self.test_results = []
+ self.bugs_found = []
+ self.required_adaptations = []
+ self.current_user = None
+ self.current_role = None
+
+ def run_all_tests(self):
+ """Run all test categories"""
+ logger.info("Starting UI Test Suite")
+
+ # Test user registration and authentication
+ self.test_user_registration()
+ self.test_user_authentication()
+
+ # Test CRUD operations for each module
+ self.test_files_module()
+ self.test_mandates_module()
+ self.test_prompts_module()
+ self.test_users_module()
+
+ # Generate and save report
+ self.generate_report()
+
+ def test_user_registration(self):
+ """Test user registration for different roles"""
+ logger.info("Testing User Registration")
+
+ test_users = [
+ {"username": "test_sysadmin", "password": "Test123!", "role": UserRole.SYSADMIN},
+ {"username": "test_admin", "password": "Test123!", "role": UserRole.ADMIN},
+ {"username": "test_user", "password": "Test123!", "role": UserRole.USER}
+ ]
+
+ for user in test_users:
+ try:
+ # Create userData object matching User model
+ user_data = {
+ "username": user["username"],
+ "email": f"{user['username']}@test.com",
+ "fullName": f"Test {user['role'].value}",
+ "language": "en",
+ "enabled": True,
+ "privilege": user["role"].value,
+ "authenticationAuthority": "local",
+ "mandateId": None, # Will be set by the backend
+ "connections": []
+ }
+
+ response = self.session.post(
+ f"{API_URL}/api/local/register",
+ json={
+ "userData": user_data,
+ "password": user["password"]
+ },
+ headers={
+ "X-CSRF-Token": "test-csrf-token",
+ "Content-Type": "application/json"
+ }
+ )
+
+ if response.status_code == 200:
+ logger.info(f"Successfully registered {user['role'].value} user")
+ self.test_results.append(TestResult(
+ f"Register {user['role'].value}",
+ True,
+ f"Successfully registered {user['role'].value} user"
+ ))
+ else:
+ error_msg = f"Failed to register {user['role'].value} user: {response.status_code}"
+ if response.text:
+ error_msg += f" - {response.text}"
+ logger.error(error_msg)
+ self.test_results.append(TestResult(
+ f"Register {user['role'].value}",
+ False,
+ error_msg,
+ {"status_code": response.status_code, "response": response.text}
+ ))
+ except Exception as e:
+ error_msg = f"Exception during registration: {str(e)}"
+ logger.error(error_msg)
+ self.test_results.append(TestResult(
+ f"Register {user['role'].value}",
+ False,
+ error_msg
+ ))
+
+ def test_user_authentication(self):
+ """Test login and logout for different roles"""
+ logger.info("Testing User Authentication")
+
+ test_users = [
+ {"username": "test_sysadmin", "password": "Test123!", "role": UserRole.SYSADMIN},
+ {"username": "test_admin", "password": "Test123!", "role": UserRole.ADMIN},
+ {"username": "test_user", "password": "Test123!", "role": UserRole.USER}
+ ]
+
+ for user in test_users:
+ # Test login
+ try:
+ response = self.session.post(
+ f"{API_URL}/api/local/login",
+ data={
+ "username": user["username"],
+ "password": user["password"]
+ },
+ headers={
+ "X-CSRF-Token": "test-csrf-token" # Add CSRF token
+ }
+ )
+
+ if response.status_code == 200:
+ self.test_results.append(TestResult(
+ f"Login {user['role'].value}",
+ True,
+ f"Successfully logged in as {user['role'].value}"
+ ))
+
+ # Test logout
+ logout_response = self.session.post(f"{API_URL}/api/security/local/logout")
+ if logout_response.status_code == 200:
+ self.test_results.append(TestResult(
+ f"Logout {user['role'].value}",
+ True,
+ f"Successfully logged out as {user['role'].value}"
+ ))
+ else:
+ self.test_results.append(TestResult(
+ f"Logout {user['role'].value}",
+ False,
+ f"Failed to logout as {user['role'].value}"
+ ))
+ else:
+ self.test_results.append(TestResult(
+ f"Login {user['role'].value}",
+ False,
+ f"Failed to login as {user['role'].value}"
+ ))
+ except Exception as e:
+ self.test_results.append(TestResult(
+ f"Login {user['role'].value}",
+ False,
+ f"Exception during login: {str(e)}"
+ ))
+
+ def test_files_module(self):
+ """Test CRUD operations for files module"""
+ logger.info("Testing Files Module")
+
+ # Test for each role
+ for role in UserRole:
+ self.current_role = role
+ self._login_as_role(role)
+
+ # Test view files
+ self._test_view_files()
+
+ # Test create file
+ self._test_create_file()
+
+ # Test modify file
+ self._test_modify_file()
+
+ # Test delete file
+ self._test_delete_file()
+
+ self._logout()
+
+ def test_mandates_module(self):
+ """Test CRUD operations for mandates module"""
+ logger.info("Testing Mandates Module")
+
+ for role in UserRole:
+ self.current_role = role
+ self._login_as_role(role)
+
+ # Test view mandates
+ self._test_view_mandates()
+
+ # Test create mandate
+ self._test_create_mandate()
+
+ # Test modify mandate
+ self._test_modify_mandate()
+
+ # Test delete mandate
+ self._test_delete_mandate()
+
+ self._logout()
+
+ def test_prompts_module(self):
+ """Test CRUD operations for prompts module"""
+ logger.info("Testing Prompts Module")
+
+ for role in UserRole:
+ self.current_role = role
+ self._login_as_role(role)
+
+ # Test view prompts
+ self._test_view_prompts()
+
+ # Test create prompt
+ self._test_create_prompt()
+
+ # Test modify prompt
+ self._test_modify_prompt()
+
+ # Test delete prompt
+ self._test_delete_prompt()
+
+ self._logout()
+
+ def test_users_module(self):
+ """Test CRUD operations for users module"""
+ logger.info("Testing Users Module")
+
+ for role in UserRole:
+ self.current_role = role
+ self._login_as_role(role)
+
+ # Test view users
+ self._test_view_users()
+
+ # Test create user
+ self._test_create_user()
+
+ # Test modify user
+ self._test_modify_user()
+
+ # Test delete user
+ self._test_delete_user()
+
+ self._logout()
+
+ def _login_as_role(self, role: UserRole):
+ """Helper method to login as a specific role"""
+ username = f"test_{role.value}"
+ max_retries = 3
+ retry_delay = 12 # 12 seconds delay between retries (to stay under 5 per minute)
+
+ for attempt in range(max_retries):
+ try:
+ # Always wait before attempting login
+ if attempt == 0:
+ logger.info(f"Waiting {retry_delay}s before first login attempt for {role.value}")
+ else:
+ logger.info(f"Rate limit reached, waiting {retry_delay}s before retry {attempt + 1} for {role.value} login")
+ time.sleep(retry_delay)
+
+ response = self.session.post(
+ f"{API_URL}/api/local/login",
+ data={
+ "username": username,
+ "password": "Test123!"
+ },
+ headers={
+ "X-CSRF-Token": "test-csrf-token"
+ }
+ )
+
+ if response.status_code == 200:
+ self.current_user = response.json()
+ logger.info(f"Successfully logged in as {role.value}")
+ return
+ elif response.status_code == 429:
+ logger.info(f"Rate limit reached for {role.value} login: {response.text}")
+ if attempt < max_retries - 1:
+ continue
+ else:
+ logger.error(f"Max retries reached for {role.value} login after rate limits")
+ raise Exception(f"Max retries reached for {role.value} login after rate limits")
+ else:
+ error_msg = f"Failed to login as {role.value}: {response.status_code}"
+ if response.text:
+ error_msg += f" - {response.text}"
+ logger.error(error_msg)
+ raise Exception(error_msg)
+
+ except Exception as e:
+ if attempt == max_retries - 1:
+ logger.error(f"Login error for {role.value} after {max_retries} attempts: {str(e)}")
+ raise
+ continue
+
+ def _logout(self):
+ """Helper method to logout"""
+ self.session.post(f"{API_URL}/api/security/local/logout")
+ self.current_user = None
+
+ def _test_view_files(self):
+ """Test viewing files"""
+ try:
+ response = self.session.get(f"{API_URL}/api/files")
+ if response.status_code == 200:
+ self.test_results.append(TestResult(
+ f"View Files as {self.current_role.value}",
+ True,
+ "Successfully viewed files"
+ ))
+ else:
+ self.test_results.append(TestResult(
+ f"View Files as {self.current_role.value}",
+ False,
+ f"Failed to view files: {response.status_code}"
+ ))
+ except Exception as e:
+ self.test_results.append(TestResult(
+ f"View Files as {self.current_role.value}",
+ False,
+ f"Exception viewing files: {str(e)}"
+ ))
+
+ def _test_create_file(self):
+ """Test creating a file"""
+ try:
+ # Create a test file
+ test_file = {
+ "name": f"test_file_{self.current_role.value}",
+ "content": "Test content",
+ "type": "text/plain"
+ }
+
+ response = self.session.post(
+ f"{API_URL}/api/files",
+ json=test_file
+ )
+
+ if response.status_code == 200:
+ self.test_results.append(TestResult(
+ f"Create File as {self.current_role.value}",
+ True,
+ "Successfully created file"
+ ))
+ else:
+ self.test_results.append(TestResult(
+ f"Create File as {self.current_role.value}",
+ False,
+ f"Failed to create file: {response.status_code}"
+ ))
+ except Exception as e:
+ self.test_results.append(TestResult(
+ f"Create File as {self.current_role.value}",
+ False,
+ f"Exception creating file: {str(e)}"
+ ))
+
+ def _test_modify_file(self):
+ """Test modifying a file"""
+ try:
+ # First get a file to modify
+ files_response = self.session.get(f"{API_URL}/api/files")
+ if files_response.status_code == 200 and files_response.json():
+ file_id = files_response.json()[0]["id"]
+
+ # Modify the file
+ update_data = {
+ "name": f"modified_file_{self.current_role.value}",
+ "content": "Modified content"
+ }
+
+ response = self.session.put(
+ f"{API_URL}/api/files/{file_id}",
+ json=update_data
+ )
+
+ if response.status_code == 200:
+ self.test_results.append(TestResult(
+ f"Modify File as {self.current_role.value}",
+ True,
+ "Successfully modified file"
+ ))
+ else:
+ self.test_results.append(TestResult(
+ f"Modify File as {self.current_role.value}",
+ False,
+ f"Failed to modify file: {response.status_code}"
+ ))
+ else:
+ self.test_results.append(TestResult(
+ f"Modify File as {self.current_role.value}",
+ False,
+ "No files available to modify"
+ ))
+ except Exception as e:
+ self.test_results.append(TestResult(
+ f"Modify File as {self.current_role.value}",
+ False,
+ f"Exception modifying file: {str(e)}"
+ ))
+
+ def _test_delete_file(self):
+ """Test deleting a file"""
+ try:
+ # First get a file to delete
+ files_response = self.session.get(f"{API_URL}/api/files")
+ if files_response.status_code == 200 and files_response.json():
+ file_id = files_response.json()[0]["id"]
+
+ response = self.session.delete(f"{API_URL}/api/files/{file_id}")
+
+ if response.status_code == 200:
+ self.test_results.append(TestResult(
+ f"Delete File as {self.current_role.value}",
+ True,
+ "Successfully deleted file"
+ ))
+ else:
+ self.test_results.append(TestResult(
+ f"Delete File as {self.current_role.value}",
+ False,
+ f"Failed to delete file: {response.status_code}"
+ ))
+ else:
+ self.test_results.append(TestResult(
+ f"Delete File as {self.current_role.value}",
+ False,
+ "No files available to delete"
+ ))
+ except Exception as e:
+ self.test_results.append(TestResult(
+ f"Delete File as {self.current_role.value}",
+ False,
+ f"Exception deleting file: {str(e)}"
+ ))
+
+ def _test_view_mandates(self):
+ """Test viewing mandates"""
+ try:
+ response = self.session.get(f"{API_URL}/api/mandates")
+ if response.status_code == 200:
+ self.test_results.append(TestResult(
+ f"View Mandates as {self.current_role.value}",
+ True,
+ "Successfully viewed mandates"
+ ))
+ else:
+ self.test_results.append(TestResult(
+ f"View Mandates as {self.current_role.value}",
+ False,
+ f"Failed to view mandates: {response.status_code}"
+ ))
+ except Exception as e:
+ self.test_results.append(TestResult(
+ f"View Mandates as {self.current_role.value}",
+ False,
+ f"Exception viewing mandates: {str(e)}"
+ ))
+
+ def _test_create_mandate(self):
+ """Test creating a mandate"""
+ try:
+ test_mandate = {
+ "name": f"test_mandate_{self.current_role.value}",
+ "description": "Test mandate",
+ "enabled": True
+ }
+
+ response = self.session.post(
+ f"{API_URL}/api/mandates",
+ json=test_mandate
+ )
+
+ if response.status_code == 200:
+ self.test_results.append(TestResult(
+ f"Create Mandate as {self.current_role.value}",
+ True,
+ "Successfully created mandate"
+ ))
+ else:
+ self.test_results.append(TestResult(
+ f"Create Mandate as {self.current_role.value}",
+ False,
+ f"Failed to create mandate: {response.status_code}"
+ ))
+ except Exception as e:
+ self.test_results.append(TestResult(
+ f"Create Mandate as {self.current_role.value}",
+ False,
+ f"Exception creating mandate: {str(e)}"
+ ))
+
+ def _test_modify_mandate(self):
+ """Test modifying a mandate"""
+ try:
+ # First get a mandate to modify
+ mandates_response = self.session.get(f"{API_URL}/api/mandates")
+ if mandates_response.status_code == 200 and mandates_response.json():
+ mandate_id = mandates_response.json()[0]["id"]
+
+ update_data = {
+ "name": f"modified_mandate_{self.current_role.value}",
+ "description": "Modified mandate"
+ }
+
+ response = self.session.put(
+ f"{API_URL}/api/mandates/{mandate_id}",
+ json=update_data
+ )
+
+ if response.status_code == 200:
+ self.test_results.append(TestResult(
+ f"Modify Mandate as {self.current_role.value}",
+ True,
+ "Successfully modified mandate"
+ ))
+ else:
+ self.test_results.append(TestResult(
+ f"Modify Mandate as {self.current_role.value}",
+ False,
+ f"Failed to modify mandate: {response.status_code}"
+ ))
+ else:
+ self.test_results.append(TestResult(
+ f"Modify Mandate as {self.current_role.value}",
+ False,
+ "No mandates available to modify"
+ ))
+ except Exception as e:
+ self.test_results.append(TestResult(
+ f"Modify Mandate as {self.current_role.value}",
+ False,
+ f"Exception modifying mandate: {str(e)}"
+ ))
+
+ def _test_delete_mandate(self):
+ """Test deleting a mandate"""
+ try:
+ # First get a mandate to delete
+ mandates_response = self.session.get(f"{API_URL}/api/mandates")
+ if mandates_response.status_code == 200 and mandates_response.json():
+ mandate_id = mandates_response.json()[0]["id"]
+
+ response = self.session.delete(f"{API_URL}/api/mandates/{mandate_id}")
+
+ if response.status_code == 200:
+ self.test_results.append(TestResult(
+ f"Delete Mandate as {self.current_role.value}",
+ True,
+ "Successfully deleted mandate"
+ ))
+ else:
+ self.test_results.append(TestResult(
+ f"Delete Mandate as {self.current_role.value}",
+ False,
+ f"Failed to delete mandate: {response.status_code}"
+ ))
+ else:
+ self.test_results.append(TestResult(
+ f"Delete Mandate as {self.current_role.value}",
+ False,
+ "No mandates available to delete"
+ ))
+ except Exception as e:
+ self.test_results.append(TestResult(
+ f"Delete Mandate as {self.current_role.value}",
+ False,
+ f"Exception deleting mandate: {str(e)}"
+ ))
+
+ def _test_view_prompts(self):
+ """Test viewing prompts"""
+ try:
+ response = self.session.get(f"{API_URL}/api/prompts")
+ if response.status_code == 200:
+ self.test_results.append(TestResult(
+ f"View Prompts as {self.current_role.value}",
+ True,
+ "Successfully viewed prompts"
+ ))
+ else:
+ self.test_results.append(TestResult(
+ f"View Prompts as {self.current_role.value}",
+ False,
+ f"Failed to view prompts: {response.status_code}"
+ ))
+ except Exception as e:
+ self.test_results.append(TestResult(
+ f"View Prompts as {self.current_role.value}",
+ False,
+ f"Exception viewing prompts: {str(e)}"
+ ))
+
+ def _test_create_prompt(self):
+ """Test creating a prompt"""
+ try:
+ test_prompt = {
+ "name": f"test_prompt_{self.current_role.value}",
+ "content": "Test prompt content",
+ "category": "test"
+ }
+
+ response = self.session.post(
+ f"{API_URL}/api/prompts",
+ json=test_prompt
+ )
+
+ if response.status_code == 200:
+ self.test_results.append(TestResult(
+ f"Create Prompt as {self.current_role.value}",
+ True,
+ "Successfully created prompt"
+ ))
+ else:
+ self.test_results.append(TestResult(
+ f"Create Prompt as {self.current_role.value}",
+ False,
+ f"Failed to create prompt: {response.status_code}"
+ ))
+ except Exception as e:
+ self.test_results.append(TestResult(
+ f"Create Prompt as {self.current_role.value}",
+ False,
+ f"Exception creating prompt: {str(e)}"
+ ))
+
+ def _test_modify_prompt(self):
+ """Test modifying a prompt"""
+ try:
+ # First get a prompt to modify
+ prompts_response = self.session.get(f"{API_URL}/api/prompts")
+ if prompts_response.status_code == 200 and prompts_response.json():
+ prompt_id = prompts_response.json()[0]["id"]
+
+ update_data = {
+ "name": f"modified_prompt_{self.current_role.value}",
+ "content": "Modified prompt content"
+ }
+
+ response = self.session.put(
+ f"{API_URL}/api/prompts/{prompt_id}",
+ json=update_data
+ )
+
+ if response.status_code == 200:
+ self.test_results.append(TestResult(
+ f"Modify Prompt as {self.current_role.value}",
+ True,
+ "Successfully modified prompt"
+ ))
+ else:
+ self.test_results.append(TestResult(
+ f"Modify Prompt as {self.current_role.value}",
+ False,
+ f"Failed to modify prompt: {response.status_code}"
+ ))
+ else:
+ self.test_results.append(TestResult(
+ f"Modify Prompt as {self.current_role.value}",
+ False,
+ "No prompts available to modify"
+ ))
+ except Exception as e:
+ self.test_results.append(TestResult(
+ f"Modify Prompt as {self.current_role.value}",
+ False,
+ f"Exception modifying prompt: {str(e)}"
+ ))
+
+ def _test_delete_prompt(self):
+ """Test deleting a prompt"""
+ try:
+ # First get a prompt to delete
+ prompts_response = self.session.get(f"{API_URL}/api/prompts")
+ if prompts_response.status_code == 200 and prompts_response.json():
+ prompt_id = prompts_response.json()[0]["id"]
+
+ response = self.session.delete(f"{API_URL}/api/prompts/{prompt_id}")
+
+ if response.status_code == 200:
+ self.test_results.append(TestResult(
+ f"Delete Prompt as {self.current_role.value}",
+ True,
+ "Successfully deleted prompt"
+ ))
+ else:
+ self.test_results.append(TestResult(
+ f"Delete Prompt as {self.current_role.value}",
+ False,
+ f"Failed to delete prompt: {response.status_code}"
+ ))
+ else:
+ self.test_results.append(TestResult(
+ f"Delete Prompt as {self.current_role.value}",
+ False,
+ "No prompts available to delete"
+ ))
+ except Exception as e:
+ self.test_results.append(TestResult(
+ f"Delete Prompt as {self.current_role.value}",
+ False,
+ f"Exception deleting prompt: {str(e)}"
+ ))
+
+ def _test_view_users(self):
+ """Test viewing users"""
+ try:
+ response = self.session.get(f"{API_URL}/api/users")
+ if response.status_code == 200:
+ self.test_results.append(TestResult(
+ f"View Users as {self.current_role.value}",
+ True,
+ "Successfully viewed users"
+ ))
+ else:
+ self.test_results.append(TestResult(
+ f"View Users as {self.current_role.value}",
+ False,
+ f"Failed to view users: {response.status_code}"
+ ))
+ except Exception as e:
+ self.test_results.append(TestResult(
+ f"View Users as {self.current_role.value}",
+ False,
+ f"Exception viewing users: {str(e)}"
+ ))
+
+ def _test_create_user(self):
+ """Test creating a user"""
+ try:
+ test_user = {
+ "username": f"new_user_{self.current_role.value}",
+ "password": "Test123!",
+ "email": f"new_user_{self.current_role.value}@test.com",
+ "privilege": "user"
+ }
+
+ response = self.session.post(
+ f"{API_URL}/api/users",
+ json=test_user
+ )
+
+ if response.status_code == 200:
+ self.test_results.append(TestResult(
+ f"Create User as {self.current_role.value}",
+ True,
+ "Successfully created user"
+ ))
+ else:
+ self.test_results.append(TestResult(
+ f"Create User as {self.current_role.value}",
+ False,
+ f"Failed to create user: {response.status_code}"
+ ))
+ except Exception as e:
+ self.test_results.append(TestResult(
+ f"Create User as {self.current_role.value}",
+ False,
+ f"Exception creating user: {str(e)}"
+ ))
+
+ def _test_modify_user(self):
+ """Test modifying a user"""
+ try:
+ # First get a user to modify
+ users_response = self.session.get(f"{API_URL}/api/users")
+ if users_response.status_code == 200 and users_response.json():
+ user_id = users_response.json()[0]["id"]
+
+ update_data = {
+ "username": f"modified_user_{self.current_role.value}",
+ "email": f"modified_user_{self.current_role.value}@test.com"
+ }
+
+ response = self.session.put(
+ f"{API_URL}/api/users/{user_id}",
+ json=update_data
+ )
+
+ if response.status_code == 200:
+ self.test_results.append(TestResult(
+ f"Modify User as {self.current_role.value}",
+ True,
+ "Successfully modified user"
+ ))
+ else:
+ self.test_results.append(TestResult(
+ f"Modify User as {self.current_role.value}",
+ False,
+ f"Failed to modify user: {response.status_code}"
+ ))
+ else:
+ self.test_results.append(TestResult(
+ f"Modify User as {self.current_role.value}",
+ False,
+ "No users available to modify"
+ ))
+ except Exception as e:
+ self.test_results.append(TestResult(
+ f"Modify User as {self.current_role.value}",
+ False,
+ f"Exception modifying user: {str(e)}"
+ ))
+
+ def _test_delete_user(self):
+ """Test deleting a user"""
+ try:
+ # First get a user to delete
+ users_response = self.session.get(f"{API_URL}/api/users")
+ if users_response.status_code == 200 and users_response.json():
+ user_id = users_response.json()[0]["id"]
+
+ response = self.session.delete(f"{API_URL}/api/users/{user_id}")
+
+ if response.status_code == 200:
+ self.test_results.append(TestResult(
+ f"Delete User as {self.current_role.value}",
+ True,
+ "Successfully deleted user"
+ ))
+ else:
+ self.test_results.append(TestResult(
+ f"Delete User as {self.current_role.value}",
+ False,
+ f"Failed to delete user: {response.status_code}"
+ ))
+ else:
+ self.test_results.append(TestResult(
+ f"Delete User as {self.current_role.value}",
+ False,
+ "No users available to delete"
+ ))
+ except Exception as e:
+ self.test_results.append(TestResult(
+ f"Delete User as {self.current_role.value}",
+ False,
+ f"Exception deleting user: {str(e)}"
+ ))
+
+ def generate_report(self):
+ """Generate test report"""
+ # Convert TestResult objects to dictionaries
+ serialized_results = [
+ {
+ "test_name": r.test_name,
+ "success": r.success,
+ "message": r.message,
+ "details": r.details
+ }
+ for r in self.test_results
+ ]
+
+ report = {
+ "timestamp": datetime.now().isoformat(),
+ "total_tests": len(self.test_results),
+ "passed_tests": sum(1 for r in self.test_results if r.success),
+ "failed_tests": sum(1 for r in self.test_results if not r.success),
+ "results": serialized_results,
+ "bugs_found": self.bugs_found,
+ "required_adaptations": self.required_adaptations
+ }
+
+ # Save report to file
+ with open('ui_test_report.json', 'w') as f:
+ json.dump(report, f, indent=2)
+
+ # Print summary
+ logger.info(f"""
+Test Report Summary:
+-------------------
+Total Tests: {report['total_tests']}
+Passed: {report['passed_tests']}
+Failed: {report['failed_tests']}
+Bugs Found: {len(report['bugs_found'])}
+Required Adaptations: {len(report['required_adaptations'])}
+ """)
+
+if __name__ == "__main__":
+ test_suite = UITestSuite()
+ test_suite.run_all_tests()
\ No newline at end of file
diff --git a/ui_test_report.json b/ui_test_report.json
new file mode 100644
index 00000000..db1f7444
--- /dev/null
+++ b/ui_test_report.json
@@ -0,0 +1,352 @@
+{
+ "timestamp": "2025-06-02T20:18:49.451773",
+ "total_tests": 57,
+ "passed_tests": 6,
+ "failed_tests": 51,
+ "results": [
+ {
+ "test_name": "Register sysadmin",
+ "success": true,
+ "message": "Successfully registered sysadmin user",
+ "details": null
+ },
+ {
+ "test_name": "Register admin",
+ "success": true,
+ "message": "Successfully registered admin user",
+ "details": null
+ },
+ {
+ "test_name": "Register user",
+ "success": true,
+ "message": "Successfully registered user user",
+ "details": null
+ },
+ {
+ "test_name": "Login sysadmin",
+ "success": true,
+ "message": "Successfully logged in as sysadmin",
+ "details": null
+ },
+ {
+ "test_name": "Logout sysadmin",
+ "success": false,
+ "message": "Failed to logout as sysadmin",
+ "details": null
+ },
+ {
+ "test_name": "Login admin",
+ "success": true,
+ "message": "Successfully logged in as admin",
+ "details": null
+ },
+ {
+ "test_name": "Logout admin",
+ "success": false,
+ "message": "Failed to logout as admin",
+ "details": null
+ },
+ {
+ "test_name": "Login user",
+ "success": true,
+ "message": "Successfully logged in as user",
+ "details": null
+ },
+ {
+ "test_name": "Logout user",
+ "success": false,
+ "message": "Failed to logout as user",
+ "details": null
+ },
+ {
+ "test_name": "View Files as sysadmin",
+ "success": false,
+ "message": "Failed to view files: 405",
+ "details": null
+ },
+ {
+ "test_name": "Create File as sysadmin",
+ "success": false,
+ "message": "Failed to create file: 405",
+ "details": null
+ },
+ {
+ "test_name": "Modify File as sysadmin",
+ "success": false,
+ "message": "No files available to modify",
+ "details": null
+ },
+ {
+ "test_name": "Delete File as sysadmin",
+ "success": false,
+ "message": "No files available to delete",
+ "details": null
+ },
+ {
+ "test_name": "View Files as admin",
+ "success": false,
+ "message": "Failed to view files: 405",
+ "details": null
+ },
+ {
+ "test_name": "Create File as admin",
+ "success": false,
+ "message": "Failed to create file: 405",
+ "details": null
+ },
+ {
+ "test_name": "Modify File as admin",
+ "success": false,
+ "message": "No files available to modify",
+ "details": null
+ },
+ {
+ "test_name": "Delete File as admin",
+ "success": false,
+ "message": "No files available to delete",
+ "details": null
+ },
+ {
+ "test_name": "View Files as user",
+ "success": false,
+ "message": "Failed to view files: 405",
+ "details": null
+ },
+ {
+ "test_name": "Create File as user",
+ "success": false,
+ "message": "Failed to create file: 405",
+ "details": null
+ },
+ {
+ "test_name": "Modify File as user",
+ "success": false,
+ "message": "No files available to modify",
+ "details": null
+ },
+ {
+ "test_name": "Delete File as user",
+ "success": false,
+ "message": "No files available to delete",
+ "details": null
+ },
+ {
+ "test_name": "View Mandates as sysadmin",
+ "success": false,
+ "message": "Failed to view mandates: 405",
+ "details": null
+ },
+ {
+ "test_name": "Create Mandate as sysadmin",
+ "success": false,
+ "message": "Failed to create mandate: 405",
+ "details": null
+ },
+ {
+ "test_name": "Modify Mandate as sysadmin",
+ "success": false,
+ "message": "No mandates available to modify",
+ "details": null
+ },
+ {
+ "test_name": "Delete Mandate as sysadmin",
+ "success": false,
+ "message": "No mandates available to delete",
+ "details": null
+ },
+ {
+ "test_name": "View Mandates as admin",
+ "success": false,
+ "message": "Failed to view mandates: 405",
+ "details": null
+ },
+ {
+ "test_name": "Create Mandate as admin",
+ "success": false,
+ "message": "Failed to create mandate: 405",
+ "details": null
+ },
+ {
+ "test_name": "Modify Mandate as admin",
+ "success": false,
+ "message": "No mandates available to modify",
+ "details": null
+ },
+ {
+ "test_name": "Delete Mandate as admin",
+ "success": false,
+ "message": "No mandates available to delete",
+ "details": null
+ },
+ {
+ "test_name": "View Mandates as user",
+ "success": false,
+ "message": "Failed to view mandates: 405",
+ "details": null
+ },
+ {
+ "test_name": "Create Mandate as user",
+ "success": false,
+ "message": "Failed to create mandate: 405",
+ "details": null
+ },
+ {
+ "test_name": "Modify Mandate as user",
+ "success": false,
+ "message": "No mandates available to modify",
+ "details": null
+ },
+ {
+ "test_name": "Delete Mandate as user",
+ "success": false,
+ "message": "No mandates available to delete",
+ "details": null
+ },
+ {
+ "test_name": "View Prompts as sysadmin",
+ "success": false,
+ "message": "Failed to view prompts: 401",
+ "details": null
+ },
+ {
+ "test_name": "Create Prompt as sysadmin",
+ "success": false,
+ "message": "Failed to create prompt: 401",
+ "details": null
+ },
+ {
+ "test_name": "Modify Prompt as sysadmin",
+ "success": false,
+ "message": "No prompts available to modify",
+ "details": null
+ },
+ {
+ "test_name": "Delete Prompt as sysadmin",
+ "success": false,
+ "message": "No prompts available to delete",
+ "details": null
+ },
+ {
+ "test_name": "View Prompts as admin",
+ "success": false,
+ "message": "Failed to view prompts: 401",
+ "details": null
+ },
+ {
+ "test_name": "Create Prompt as admin",
+ "success": false,
+ "message": "Failed to create prompt: 401",
+ "details": null
+ },
+ {
+ "test_name": "Modify Prompt as admin",
+ "success": false,
+ "message": "No prompts available to modify",
+ "details": null
+ },
+ {
+ "test_name": "Delete Prompt as admin",
+ "success": false,
+ "message": "No prompts available to delete",
+ "details": null
+ },
+ {
+ "test_name": "View Prompts as user",
+ "success": false,
+ "message": "Failed to view prompts: 401",
+ "details": null
+ },
+ {
+ "test_name": "Create Prompt as user",
+ "success": false,
+ "message": "Failed to create prompt: 401",
+ "details": null
+ },
+ {
+ "test_name": "Modify Prompt as user",
+ "success": false,
+ "message": "No prompts available to modify",
+ "details": null
+ },
+ {
+ "test_name": "Delete Prompt as user",
+ "success": false,
+ "message": "No prompts available to delete",
+ "details": null
+ },
+ {
+ "test_name": "View Users as sysadmin",
+ "success": false,
+ "message": "Failed to view users: 405",
+ "details": null
+ },
+ {
+ "test_name": "Create User as sysadmin",
+ "success": false,
+ "message": "Failed to create user: 401",
+ "details": null
+ },
+ {
+ "test_name": "Modify User as sysadmin",
+ "success": false,
+ "message": "No users available to modify",
+ "details": null
+ },
+ {
+ "test_name": "Delete User as sysadmin",
+ "success": false,
+ "message": "No users available to delete",
+ "details": null
+ },
+ {
+ "test_name": "View Users as admin",
+ "success": false,
+ "message": "Failed to view users: 405",
+ "details": null
+ },
+ {
+ "test_name": "Create User as admin",
+ "success": false,
+ "message": "Failed to create user: 401",
+ "details": null
+ },
+ {
+ "test_name": "Modify User as admin",
+ "success": false,
+ "message": "No users available to modify",
+ "details": null
+ },
+ {
+ "test_name": "Delete User as admin",
+ "success": false,
+ "message": "No users available to delete",
+ "details": null
+ },
+ {
+ "test_name": "View Users as user",
+ "success": false,
+ "message": "Failed to view users: 405",
+ "details": null
+ },
+ {
+ "test_name": "Create User as user",
+ "success": false,
+ "message": "Failed to create user: 401",
+ "details": null
+ },
+ {
+ "test_name": "Modify User as user",
+ "success": false,
+ "message": "No users available to modify",
+ "details": null
+ },
+ {
+ "test_name": "Delete User as user",
+ "success": false,
+ "message": "No users available to delete",
+ "details": null
+ }
+ ],
+ "bugs_found": [],
+ "required_adaptations": []
+}
\ No newline at end of file