#!/usr/bin/env python3 """ End-to-End (E2E) Tests for Team Manager Tests complete workflows including: - Full lifecycle: init → assign → list → status → delete - Phase gate transitions - Error scenarios - Multi-phase workflow - RBAC testing - Batch operations """ import sys import os import unittest import tempfile import json import shutil from pathlib import Path # Add parent directory to path sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from scripts.mocks import ( MockTeamManager, MockLogger, MockFileSystem, create_admin_context, create_team_lead_context, create_viewer_context, ) class TestE2EFullWorkflow(unittest.TestCase): """E2E: Complete project lifecycle workflow.""" def setUp(self): """Set up test fixtures.""" self.admin_ctx = create_admin_context("admin-user") self.logger = MockLogger("test") self.fs = MockFileSystem() self.manager = MockTeamManager( "e2e-workflow-test", user_context=self.admin_ctx, logger=self.logger, fs=self.fs ) def tearDown(self): """Clean up.""" self.manager.reset() def test_complete_project_lifecycle(self): """ E2E-001: Full workflow: init → assign → start → complete → export → delete """ # Step 1: Initialize project print("\n[E2E-001] Step 1: Initialize project") result = self.manager.initialize_project() self.assertTrue(result) self.assertEqual(len(self.manager.teams), 12) self.assertTrue(self.logger.has_event("project_initialized")) # Step 2: Assign roles to Team 1 print("[E2E-001] Step 2: Assign roles to Team 1") team1 = self.manager.teams[1] for i, role in enumerate(team1.roles): result = self.manager.assign_role(1, role.name, f"Person {i+1}") self.assertTrue(result) assigned_count = sum(1 for r in team1.roles if r.assigned_to) self.assertEqual(assigned_count, 4) # Step 3: Start Team 1 print("[E2E-001] Step 3: Start Team 1") result = self.manager.start_team(1) self.assertTrue(result) self.assertEqual(team1.status, "active") self.assertIsNotNone(team1.started_at) # Step 4: Complete Team 1 print("[E2E-001] Step 4: Complete Team 1") result = self.manager.complete_team(1) self.assertTrue(result) self.assertEqual(team1.status, "completed") self.assertIsNotNone(team1.completed_at) # Step 5: Check phase status print("[E2E-001] Step 5: Check Phase 1 status") phase_status = self.manager.get_phase_status("Phase 1: Strategy, Governance & Planning") self.assertEqual(phase_status["completed"], 1) self.assertGreater(phase_status["progress_pct"], 0) # Step 6: List all teams print("[E2E-001] Step 6: List teams") team_list = self.manager.list_teams() self.assertEqual(len(team_list), 12) # Step 7: Delete the project print("[E2E-001] Step 7: Delete project") result = self.manager.delete_project(confirmed=True) self.assertTrue(result["success"]) print("[E2E-001] ✓ Complete lifecycle test passed") class TestE2EPhaseGates(unittest.TestCase): """E2E: Phase gate transitions.""" def setUp(self): self.admin_ctx = create_admin_context() self.manager = MockTeamManager( "e2e-phase-test", user_context=self.admin_ctx, logger=MockLogger("test"), fs=MockFileSystem() ) self.manager.initialize_project() def test_phase1_completion(self): """ E2E-002: Complete all Phase 1 teams and verify progress """ print("\n[E2E-002] Testing Phase 1 completion") # Get Phase 1 teams phase1_teams = [t for t in self.manager.teams.values() if t.phase == "Phase 1: Strategy, Governance & Planning"] self.assertEqual(len(phase1_teams), 3) # Teams 1, 2, 3 # Initially 0% complete status = self.manager.get_phase_status("Phase 1: Strategy, Governance & Planning") self.assertEqual(status["progress_pct"], 0.0) # Complete all Phase 1 teams for team in phase1_teams: self.manager.start_team(team.id) self.manager.complete_team(team.id) # Now 100% complete status = self.manager.get_phase_status("Phase 1: Strategy, Governance & Planning") self.assertEqual(status["progress_pct"], 100.0) self.assertEqual(status["completed"], 3) print("[E2E-002] ✓ Phase 1 completion test passed") def test_phase_progression(self): """ E2E-003: Test progression through multiple phases """ print("\n[E2E-003] Testing phase progression") phases = [ "Phase 1: Strategy, Governance & Planning", "Phase 2: Platform & Foundation", "Phase 3: The Build Squads", "Phase 4: Validation & Hardening", "Phase 5: Delivery & Sustainment", ] for phase in phases: phase_teams = [t for t in self.manager.teams.values() if t.phase == phase] for team in phase_teams: self.manager.start_team(team.id) self.manager.complete_team(team.id) status = self.manager.get_phase_status(phase) self.assertEqual(status["progress_pct"], 100.0) print(f" ✓ {phase}: 100%") print("[E2E-003] ✓ Phase progression test passed") class TestE2EErrorScenarios(unittest.TestCase): """E2E: Error scenarios.""" def setUp(self): self.admin_ctx = create_admin_context() self.manager = MockTeamManager( "e2e-error-test", user_context=self.admin_ctx, logger=MockLogger("test"), fs=MockFileSystem() ) self.manager.initialize_project() def test_assign_to_invalid_team(self): """ E2E-004: Assign to invalid team returns False """ print("\n[E2E-004] Testing invalid team assignment") result = self.manager.assign_role(99, "Some Role", "John Doe") self.assertFalse(result) print("[E2E-004] ✓ Invalid team assignment handled correctly") def test_assign_invalid_role(self): """ E2E-005: Assign invalid role returns False """ print("\n[E2E-005] Testing invalid role assignment") result = self.manager.assign_role(1, "Nonexistent Role", "John Doe") self.assertFalse(result) print("[E2E-005] ✓ Invalid role assignment handled correctly") def test_delete_without_confirmation(self): """ E2E-006: Delete without confirmation requires confirmation """ print("\n[E2E-006] Testing delete without confirmation") result = self.manager.delete_team(1, confirmed=False) self.assertFalse(result["success"]) self.assertTrue(result["requires_confirmation"]) print("[E2E-006] ✓ Delete confirmation required correctly") def test_delete_nonexistent_project(self): """ E2E-007: Delete non-existent project fails gracefully """ print("\n[E2E-007] Testing delete of non-existent project") empty_manager = MockTeamManager( "nonexistent-project", user_context=self.admin_ctx, logger=MockLogger("test"), fs=MockFileSystem() ) result = empty_manager.delete_project(confirmed=True) self.assertFalse(result["success"]) print("[E2E-007] ✓ Non-existent project deletion handled correctly") def test_unauthorized_access(self): """ E2E-008: Unauthorized access raises PermissionDenied """ print("\n[E2E-008] Testing unauthorized access") from scripts.team_manager import PermissionDenied viewer_ctx = create_viewer_context() manager = MockTeamManager( "e2e-auth-test", user_context=viewer_ctx, logger=MockLogger("test"), fs=MockFileSystem() ) with self.assertRaises(PermissionDenied): manager.initialize_project() print("[E2E-008] ✓ Unauthorized access handled correctly") class TestE2ERBAC(unittest.TestCase): """E2E: Role-based access control.""" def setUp(self): self.fs = MockFileSystem() def test_admin_can_modify_any_team(self): """ E2E-009: Admin can modify any team """ print("\n[E2E-009] Testing admin permissions") admin_ctx = create_admin_context() manager = MockTeamManager("rbac-test", user_context=admin_ctx, logger=MockLogger("test"), fs=self.fs) manager.initialize_project() # Admin should be able to modify any team for team_id in range(1, 13): result = manager.assign_role(team_id, manager.teams[team_id].roles[0].name, f"Admin Assignee {team_id}") self.assertTrue(result, f"Admin should be able to modify team {team_id}") print("[E2E-009] ✓ Admin permissions verified") def test_team_lead_can_only_modify_their_team(self): """ E2E-010: Team lead can only modify their assigned team """ print("\n[E2E-010] Testing team lead permissions") admin_ctx = create_admin_context() manager = MockTeamManager("rbac-test", user_context=admin_ctx, logger=MockLogger("test"), fs=self.fs) manager.initialize_project() # Create team lead context for team 5 team_lead_ctx = create_team_lead_context(team_id=5) lead_manager = MockTeamManager( "rbac-test", user_context=team_lead_ctx, logger=MockLogger("test"), fs=self.fs ) load_result = lead_manager.load() self.assertTrue(load_result, "Failed to load project for team lead") self.assertIn(5, lead_manager.teams, "Team 5 should exist after loading") from scripts.team_manager import PermissionDenied # Should succeed for team 5 result = lead_manager.assign_role(5, lead_manager.teams[5].roles[0].name, "Lead Assignee") self.assertTrue(result) print("[E2E-010] ✓ Team lead permissions verified") def test_viewer_cannot_modify(self): """ E2E-011: Viewer cannot modify anything """ print("\n[E2E-011] Testing viewer restrictions") viewer_ctx = create_viewer_context() manager = MockTeamManager( "rbac-test", user_context=viewer_ctx, logger=MockLogger("test"), fs=MockFileSystem() ) from scripts.team_manager import PermissionDenied with self.assertRaises(PermissionDenied): manager.initialize_project() print("[E2E-011] ✓ Viewer restrictions verified") class TestE2EBatchOperations(unittest.TestCase): """E2E: Batch operations.""" def setUp(self): self.admin_ctx = create_admin_context() self.fs = MockFileSystem() self.manager = MockTeamManager( "e2e-batch-test", user_context=self.admin_ctx, logger=MockLogger("test"), fs=self.fs ) self.manager.initialize_project() self.temp_dir = tempfile.mkdtemp() def tearDown(self): """Clean up temp files.""" import shutil shutil.rmtree(self.temp_dir, ignore_errors=True) def test_csv_export_import_roundtrip(self): """ E2E-012: CSV export/import roundtrip preserves data """ print("\n[E2E-012] Testing CSV roundtrip") # First assign some roles self.manager.assign_role(1, "Business Relationship Manager", "Alice") self.manager.assign_role(1, "Lead Product Manager", "Bob") # Export to CSV using real file system csv_path = Path(self.temp_dir) / "test_export.csv" result = self.manager.export_csv_file(csv_path) self.assertTrue(result["success"]) # Verify CSV content exists on real file system self.assertTrue(csv_path.exists()) # Verify content with open(csv_path, 'r') as f: content = f.read() self.assertIn("Alice", content) self.assertIn("Business Relationship Manager", content) print("[E2E-012] ✓ CSV roundtrip test passed") def test_json_export_import_roundtrip(self): """ E2E-013: JSON export/import roundtrip preserves data """ print("\n[E2E-013] Testing JSON roundtrip") # Assign and complete Team 1 self.manager.assign_role(1, "Business Relationship Manager", "Alice") self.manager.start_team(1) self.manager.complete_team(1) # Export to JSON using real file system json_path = Path(self.temp_dir) / "test_export.json" result = self.manager.export_json_file(json_path) self.assertTrue(result["success"]) self.assertEqual(result["team_count"], 12) # Verify JSON file exists self.assertTrue(json_path.exists()) # Verify JSON content with open(json_path, 'r') as f: data = json.load(f) self.assertEqual(data["project_name"], "e2e-batch-test") self.assertEqual(len(data["teams"]), 12) print("[E2E-013] ✓ JSON roundtrip test passed") class TestE2EAgentMapping(unittest.TestCase): """E2E: Agent type to team mapping.""" def setUp(self): self.admin_ctx = create_admin_context() self.manager = MockTeamManager( "e2e-agent-test", user_context=self.admin_ctx, logger=MockLogger("test"), fs=MockFileSystem() ) self.manager.initialize_project() def test_agent_type_mapping(self): """ E2E-014: Agent types map to correct teams """ print("\n[E2E-014] Testing agent type mappings") test_cases = [ ("planner", 2, "Enterprise Architecture"), ("coder", 7, "Core Feature Squad"), ("reviewer", 10, "Quality Engineering (SDET)"), ("security", 9, "Cybersecurity (AppSec)"), ("tester", 10, "Quality Engineering (SDET)"), ("ops", 11, "Site Reliability Engineering (SRE)"), ] for agent_type, expected_id, expected_name in test_cases: team = self.manager.get_agent_team(agent_type) self.assertIsNotNone(team, f"Agent type '{agent_type}' should map to a team") self.assertEqual(team.id, expected_id) self.assertEqual(team.name, expected_name) print(f" ✓ '{agent_type}' → Team {expected_id}: {expected_name}") print("[E2E-014] ✓ Agent type mapping verified") def test_invalid_agent_type(self): """ E2E-015: Invalid agent type returns None """ print("\n[E2E-015] Testing invalid agent type") team = self.manager.get_agent_team("invalid_agent_type") self.assertIsNone(team) print("[E2E-015] ✓ Invalid agent type handled correctly") class TestE2ETeamValidation(unittest.TestCase): """E2E: Team size validation.""" def setUp(self): self.admin_ctx = create_admin_context() self.manager = MockTeamManager( "e2e-validation-test", user_context=self.admin_ctx, logger=MockLogger("test"), fs=MockFileSystem() ) self.manager.initialize_project() def test_empty_team_validation_fails(self): """ E2E-016: Empty team fails size validation """ print("\n[E2E-016] Testing empty team validation") results = self.manager.validate_team_size(1) self.assertFalse(results["valid"]) self.assertEqual(len(results["violations"]), 1) self.assertEqual(results["violations"][0]["issue"], "undersized") print("[E2E-016] ✓ Empty team validation works") def test_properly_sized_team_passes(self): """ E2E-017: Properly staffed team passes validation """ print("\n[E2E-017] Testing properly sized team") team = self.manager.teams[1] for i, role in enumerate(team.roles[:4]): # Assign 4 people self.manager.assign_role(1, role.name, f"Person {i+1}") results = self.manager.validate_team_size(1) self.assertTrue(results["valid"]) self.assertEqual(len(results["violations"]), 0) print("[E2E-017] ✓ Properly sized team passes validation") class TestE2EPersistence(unittest.TestCase): """E2E: Save/load persistence.""" def setUp(self): self.admin_ctx = create_admin_context() self.fs = MockFileSystem() def test_save_and_load(self): """ E2E-018: Save and load preserves all data """ print("\n[E2E-018] Testing save/load persistence") # Create and populate manager manager1 = MockTeamManager( "persistence-test", user_context=self.admin_ctx, logger=MockLogger("test"), fs=self.fs ) manager1.initialize_project() manager1.assign_role(1, "Business Relationship Manager", "Test User") manager1.start_team(1) manager1.save() # Load into new manager manager2 = MockTeamManager( "persistence-test", user_context=self.admin_ctx, logger=MockLogger("test2"), fs=self.fs ) result = manager2.load() self.assertTrue(result) # Verify data preserved self.assertEqual(manager2.teams[1].status, "active") role = next(r for r in manager2.teams[1].roles if r.name == "Business Relationship Manager") self.assertEqual(role.assigned_to, "Test User") print("[E2E-018] ✓ Save/load persistence verified") def run_e2e_tests(): """Run all E2E tests and return results.""" print("=" * 70) print("Running E2E Tests for Team Manager") print("=" * 70) loader = unittest.TestLoader() suite = unittest.TestSuite() # Add all test classes suite.addTests(loader.loadTestsFromTestCase(TestE2EFullWorkflow)) suite.addTests(loader.loadTestsFromTestCase(TestE2EPhaseGates)) suite.addTests(loader.loadTestsFromTestCase(TestE2EErrorScenarios)) suite.addTests(loader.loadTestsFromTestCase(TestE2ERBAC)) suite.addTests(loader.loadTestsFromTestCase(TestE2EBatchOperations)) suite.addTests(loader.loadTestsFromTestCase(TestE2EAgentMapping)) suite.addTests(loader.loadTestsFromTestCase(TestE2ETeamValidation)) suite.addTests(loader.loadTestsFromTestCase(TestE2EPersistence)) runner = unittest.TextTestRunner(verbosity=2) result = runner.run(suite) print("\n" + "=" * 70) print(f"Tests run: {result.testsRun}") print(f"Failures: {len(result.failures)}") print(f"Errors: {len(result.errors)}") print(f"Skipped: {len(result.skipped)}") print("=" * 70) return result.wasSuccessful() if __name__ == "__main__": success = run_e2e_tests() sys.exit(0 if success else 1)