From 6c79147cbf6926505b7cf97bf2685cf904009b06 Mon Sep 17 00:00:00 2001 From: Mondo Diaz Date: Wed, 28 Jan 2026 17:24:26 +0000 Subject: [PATCH] Create Global Admins team when admin user is created - Admin user is now automatically added to Global Admins team as owner - Ensures every user belongs to at least one team - Updated unit tests to handle multiple db.add() calls --- backend/app/auth.py | 25 ++++++++++++++++++++++- backend/tests/unit/test_auth.py | 36 ++++++++++++++++++++------------- 2 files changed, 46 insertions(+), 15 deletions(-) diff --git a/backend/app/auth.py b/backend/app/auth.py index 3a5c7a0..746554d 100644 --- a/backend/app/auth.py +++ b/backend/app/auth.py @@ -11,7 +11,7 @@ from typing import Optional from passlib.context import CryptContext from sqlalchemy.orm import Session -from .models import User, Session as UserSession, APIKey +from .models import User, Session as UserSession, APIKey, Team, TeamMembership from .config import get_settings logger = logging.getLogger(__name__) @@ -363,6 +363,8 @@ def create_default_admin(db: Session) -> Optional[User]: The admin password can be set via ORCHARD_ADMIN_PASSWORD environment variable. If not set, defaults to 'changeme123' and requires password change on first login. + + Also creates the "Global Admins" team and adds the admin user to it. """ # Check if any users exist user_count = db.query(User).count() @@ -385,6 +387,27 @@ def create_default_admin(db: Session) -> Optional[User]: must_change_password=must_change, ) + # Create Global Admins team and add admin to it + global_admins_team = Team( + name="Global Admins", + slug="global-admins", + description="System administrators with full access", + created_by="admin", + ) + db.add(global_admins_team) + db.flush() + + membership = TeamMembership( + team_id=global_admins_team.id, + user_id=admin.id, + role="owner", + invited_by="admin", + ) + db.add(membership) + db.commit() + + logger.info("Created Global Admins team and added admin as owner") + if settings.admin_password: logger.info("Created default admin user with configured password") else: diff --git a/backend/tests/unit/test_auth.py b/backend/tests/unit/test_auth.py index cfe4157..773eb82 100644 --- a/backend/tests/unit/test_auth.py +++ b/backend/tests/unit/test_auth.py @@ -10,6 +10,7 @@ class TestCreateDefaultAdmin: def test_create_default_admin_with_env_password(self): """Test that ORCHARD_ADMIN_PASSWORD env var sets admin password.""" from app.auth import create_default_admin, verify_password + from app.models import User # Create mock settings with custom password mock_settings = MagicMock() @@ -19,20 +20,23 @@ class TestCreateDefaultAdmin: mock_db = MagicMock() mock_db.query.return_value.count.return_value = 0 # No existing users - # Track the user that gets created - created_user = None + # Track all objects that get created + created_objects = [] - def capture_user(user): - nonlocal created_user - created_user = user + def capture_object(obj): + created_objects.append(obj) - mock_db.add.side_effect = capture_user + mock_db.add.side_effect = capture_object with patch("app.auth.get_settings", return_value=mock_settings): admin = create_default_admin(mock_db) - # Verify the user was created + # Verify objects were created (user, team, membership) assert mock_db.add.called + assert len(created_objects) >= 1 + + # Find the user object + created_user = next((obj for obj in created_objects if isinstance(obj, User)), None) assert created_user is not None assert created_user.username == "admin" assert created_user.is_admin is True @@ -44,6 +48,7 @@ class TestCreateDefaultAdmin: def test_create_default_admin_with_default_password(self): """Test that default password 'changeme123' is used when env var not set.""" from app.auth import create_default_admin, verify_password + from app.models import User # Create mock settings with empty password (default) mock_settings = MagicMock() @@ -53,20 +58,23 @@ class TestCreateDefaultAdmin: mock_db = MagicMock() mock_db.query.return_value.count.return_value = 0 # No existing users - # Track the user that gets created - created_user = None + # Track all objects that get created + created_objects = [] - def capture_user(user): - nonlocal created_user - created_user = user + def capture_object(obj): + created_objects.append(obj) - mock_db.add.side_effect = capture_user + mock_db.add.side_effect = capture_object with patch("app.auth.get_settings", return_value=mock_settings): admin = create_default_admin(mock_db) - # Verify the user was created + # Verify objects were created assert mock_db.add.called + assert len(created_objects) >= 1 + + # Find the user object + created_user = next((obj for obj in created_objects if isinstance(obj, User)), None) assert created_user is not None assert created_user.username == "admin" assert created_user.is_admin is True