feat(tests,users): blocking procedure-suggestion review on test detail, multi-role switcher, Power Automate webhook payload
Aegis CI / lint-and-test (push) Has been cancelled
Snyk Security Scan / Python vulnerabilities (backend) (push) Has been cancelled
Snyk Security Scan / npm vulnerabilities (frontend) (push) Has been cancelled
Snyk Security Scan / Docker image vulnerabilities (backend) (push) Has been cancelled

- A lead opening a test with a pending procedure suggestion awaiting
  their review now gets a blocking popup (approve/reject only) instead
  of discovering it later in a separate queue.
- Users can be granted more than one role via extra_roles; only one is
  ever active at a time (no permission mixing) and a top-bar switcher
  lets the user swap which one, taking effect immediately since role
  is read fresh from the DB on every request.
- The password-setup/reset webhook now posts the agreed Power Automate
  contract ({to, subject, body} with the platform's standard greeting
  and signature template) and supports an admin-configured API key
  sent as an x-api-key header.
This commit is contained in:
kitos
2026-07-20 09:29:36 +02:00
parent 4dea19cae9
commit bbe7f49c86
24 changed files with 708 additions and 49 deletions
+29
View File
@@ -159,6 +159,13 @@ def update_user(db: Session, user_id: uuid.UUID, **fields: object) -> User:
f"Invalid role '{update_data['role']}'. Must be one of: {', '.join(sorted(VALID_ROLES))}"
)
if update_data.get("extra_roles") is not None:
invalid = [r for r in update_data["extra_roles"] if r not in VALID_ROLES]
if invalid:
raise BusinessRuleViolation(
f"Invalid role(s) {', '.join(invalid)}. Must be one of: {', '.join(sorted(VALID_ROLES))}"
)
# Check: "password" in update_data
if "password" in update_data:
# Assign update_data["hashed_password"] = hash_password(str(update_data.pop("password")))
@@ -171,3 +178,25 @@ def update_user(db: Session, user_id: uuid.UUID, **fields: object) -> User:
# Return user
return user
def switch_active_role(db: Session, user: User, new_role: str) -> User:
"""Swap *user*'s currently-active role with one from their extra_roles.
Roles never mix — the user acts under exactly one role at a time, this
just changes which one. Raises BusinessRuleViolation if new_role isn't
one this user has been granted. Does not commit; caller commits.
"""
available = {user.role, *(user.extra_roles or [])}
if new_role not in available:
raise BusinessRuleViolation(f"Role '{new_role}' is not available to this user")
if new_role == user.role:
return user
old_role = user.role
remaining = [r for r in (user.extra_roles or []) if r != new_role]
remaining.append(old_role)
user.role = new_role
user.extra_roles = remaining
return user