""" Integration tests for Teams API endpoints. """ import pytest @pytest.mark.integration class TestTeamsCRUD: """Tests for team creation, listing, updating, and deletion.""" def test_create_team(self, integration_client, unique_test_id): """Test creating a new team.""" team_name = f"Test Team {unique_test_id}" team_slug = f"test-team-{unique_test_id}" response = integration_client.post( "/api/v1/teams", json={ "name": team_name, "slug": team_slug, "description": "A test team", }, ) assert response.status_code == 201, f"Failed to create team: {response.text}" data = response.json() assert data["name"] == team_name assert data["slug"] == team_slug assert data["description"] == "A test team" assert data["user_role"] == "owner" assert data["member_count"] == 1 assert data["project_count"] == 0 # Cleanup integration_client.delete(f"/api/v1/teams/{team_slug}") def test_create_team_duplicate_slug(self, integration_client, unique_test_id): """Test that duplicate team slugs are rejected.""" team_slug = f"dup-team-{unique_test_id}" # Create first team response = integration_client.post( "/api/v1/teams", json={"name": "First Team", "slug": team_slug}, ) assert response.status_code == 201 # Try to create second team with same slug response = integration_client.post( "/api/v1/teams", json={"name": "Second Team", "slug": team_slug}, ) assert response.status_code == 400 assert "already exists" in response.json()["detail"].lower() # Cleanup integration_client.delete(f"/api/v1/teams/{team_slug}") def test_create_team_invalid_slug(self, integration_client): """Test that invalid team slugs are rejected.""" invalid_slugs = [ "UPPERCASE", "with spaces", "-starts-with-hyphen", "ends-with-hyphen-", "has--double--hyphen", ] for invalid_slug in invalid_slugs: response = integration_client.post( "/api/v1/teams", json={"name": "Test", "slug": invalid_slug}, ) assert response.status_code == 422, f"Slug '{invalid_slug}' should be invalid" def test_list_teams(self, integration_client, unique_test_id): """Test listing teams the user belongs to.""" # Create a team team_slug = f"list-team-{unique_test_id}" integration_client.post( "/api/v1/teams", json={"name": "List Test Team", "slug": team_slug}, ) # List teams response = integration_client.get("/api/v1/teams") assert response.status_code == 200 data = response.json() assert "items" in data assert "pagination" in data # Find our team team = next((t for t in data["items"] if t["slug"] == team_slug), None) assert team is not None assert team["name"] == "List Test Team" # Cleanup integration_client.delete(f"/api/v1/teams/{team_slug}") def test_get_team(self, integration_client, unique_test_id): """Test getting team details.""" team_slug = f"get-team-{unique_test_id}" integration_client.post( "/api/v1/teams", json={"name": "Get Test Team", "slug": team_slug, "description": "Test"}, ) response = integration_client.get(f"/api/v1/teams/{team_slug}") assert response.status_code == 200 data = response.json() assert data["slug"] == team_slug assert data["name"] == "Get Test Team" assert data["user_role"] == "owner" # Cleanup integration_client.delete(f"/api/v1/teams/{team_slug}") def test_get_nonexistent_team(self, integration_client): """Test getting a team that doesn't exist.""" response = integration_client.get("/api/v1/teams/nonexistent-team-12345") assert response.status_code == 404 def test_update_team(self, integration_client, unique_test_id): """Test updating team details.""" team_slug = f"update-team-{unique_test_id}" integration_client.post( "/api/v1/teams", json={"name": "Original Name", "slug": team_slug}, ) response = integration_client.put( f"/api/v1/teams/{team_slug}", json={"name": "Updated Name", "description": "New description"}, ) assert response.status_code == 200 data = response.json() assert data["name"] == "Updated Name" assert data["description"] == "New description" assert data["slug"] == team_slug # Slug should not change # Cleanup integration_client.delete(f"/api/v1/teams/{team_slug}") def test_delete_team(self, integration_client, unique_test_id): """Test deleting a team.""" team_slug = f"delete-team-{unique_test_id}" integration_client.post( "/api/v1/teams", json={"name": "Delete Test Team", "slug": team_slug}, ) response = integration_client.delete(f"/api/v1/teams/{team_slug}") assert response.status_code == 204 # Verify team is gone response = integration_client.get(f"/api/v1/teams/{team_slug}") assert response.status_code == 404 @pytest.mark.integration class TestTeamMembers: """Tests for team membership management.""" @pytest.fixture def test_team(self, integration_client, unique_test_id): """Create a test team for member tests.""" team_slug = f"member-team-{unique_test_id}" response = integration_client.post( "/api/v1/teams", json={"name": "Member Test Team", "slug": team_slug}, ) assert response.status_code == 201 yield team_slug # Cleanup try: integration_client.delete(f"/api/v1/teams/{team_slug}") except Exception: pass def test_list_members(self, integration_client, test_team): """Test listing team members.""" response = integration_client.get(f"/api/v1/teams/{test_team}/members") assert response.status_code == 200 members = response.json() assert len(members) == 1 assert members[0]["role"] == "owner" def test_owner_is_first_member(self, integration_client, test_team): """Test that the team creator is automatically the owner.""" response = integration_client.get(f"/api/v1/teams/{test_team}/members") members = response.json() assert len(members) >= 1 owner = next((m for m in members if m["role"] == "owner"), None) assert owner is not None @pytest.mark.integration class TestTeamProjects: """Tests for team project management.""" @pytest.fixture def test_team(self, integration_client, unique_test_id): """Create a test team for project tests.""" team_slug = f"proj-team-{unique_test_id}" response = integration_client.post( "/api/v1/teams", json={"name": "Project Test Team", "slug": team_slug}, ) assert response.status_code == 201 data = response.json() yield {"slug": team_slug, "id": data["id"]} # Cleanup try: integration_client.delete(f"/api/v1/teams/{team_slug}") except Exception: pass def test_list_team_projects_empty(self, integration_client, test_team): """Test listing projects in an empty team.""" response = integration_client.get(f"/api/v1/teams/{test_team['slug']}/projects") assert response.status_code == 200 data = response.json() assert data["items"] == [] assert data["pagination"]["total"] == 0 def test_create_project_in_team(self, integration_client, test_team, unique_test_id): """Test creating a project within a team.""" project_name = f"team-project-{unique_test_id}" response = integration_client.post( "/api/v1/projects", json={ "name": project_name, "description": "A team project", "team_id": test_team["id"], }, ) assert response.status_code == 200, f"Failed to create project: {response.text}" data = response.json() assert data["team_id"] == test_team["id"] assert data["team_slug"] == test_team["slug"] # Verify project appears in team projects list response = integration_client.get(f"/api/v1/teams/{test_team['slug']}/projects") assert response.status_code == 200 projects = response.json()["items"] assert any(p["name"] == project_name for p in projects) # Cleanup integration_client.delete(f"/api/v1/projects/{project_name}") def test_project_team_info_in_response(self, integration_client, test_team, unique_test_id): """Test that project responses include team info.""" project_name = f"team-info-project-{unique_test_id}" # Create project in team integration_client.post( "/api/v1/projects", json={"name": project_name, "team_id": test_team["id"]}, ) # Get project and verify team info response = integration_client.get(f"/api/v1/projects/{project_name}") assert response.status_code == 200 data = response.json() assert data["team_id"] == test_team["id"] assert data["team_slug"] == test_team["slug"] assert data["team_name"] == "Project Test Team" # Cleanup integration_client.delete(f"/api/v1/projects/{project_name}") @pytest.mark.integration class TestTeamAuthorization: """Tests for team-based authorization.""" def test_cannot_delete_team_with_projects(self, integration_client, unique_test_id): """Test that teams with projects cannot be deleted.""" team_slug = f"nodelete-team-{unique_test_id}" project_name = f"nodelete-project-{unique_test_id}" # Create team response = integration_client.post( "/api/v1/teams", json={"name": "No Delete Team", "slug": team_slug}, ) team_id = response.json()["id"] # Create project in team integration_client.post( "/api/v1/projects", json={"name": project_name, "team_id": team_id}, ) # Try to delete team - should fail response = integration_client.delete(f"/api/v1/teams/{team_slug}") assert response.status_code == 400 assert "project" in response.json()["detail"].lower() # Cleanup - delete project first, then team integration_client.delete(f"/api/v1/projects/{project_name}") integration_client.delete(f"/api/v1/teams/{team_slug}")