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 a11777cc..00000000 Binary files a/static/favicon.ico and /dev/null differ 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