# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo.tests import tagged, TransactionCase from odoo.exceptions import AccessError, ValidationError @tagged('post_install', '-at_install') class TestPlayerRemoval(TransactionCase): at_install = False post_install = True @classmethod def setUpClass(cls, *args, **kwargs): super(TestPlayerRemoval, cls).setUpClass(*args, **kwargs) # Set up minimal email configuration cls.env['ir.config_parameter'].set_param('mail.default.from', 'test@example.com') # Set up test data cls._setup_test_data() @classmethod def _setup_test_data(cls): """Set up test data for player removal tests""" # Create test data cls.team1 = cls.env['sports.team'].create({ 'name': 'Test Team 1', }) cls.team2 = cls.env['sports.team'].create({ 'name': 'Test Team 2', }) # Team creation moved to _setup_test_data() # Ensure we have access to the sports.patient model for our test users patient_model = cls.env['ir.model'].search([('model', '=', 'sports.patient')]) if patient_model: # Create a record rule that allows treatment professionals to read patient records # This is just for testing - in production, this would be handled by proper record rules cls.env['ir.rule'].create({ 'name': 'Test: Allow treatment professionals to read patients', 'model_id': patient_model.id, 'domain_force': '[]', # No domain restrictions for this test 'groups': [(6, 0, [ cls.env.ref('bemade_sports_clinic.group_sports_clinic_treatment_professional').id, ])], 'perm_read': True, 'perm_write': False, 'perm_create': False, 'perm_unlink': False, }) # Create test players cls.player1 = cls.env['sports.patient'].create({ 'first_name': 'Test', 'last_name': 'Player1', 'team_ids': [(6, 0, [cls.team1.id, cls.team2.id])], }) cls.player2 = cls.env['sports.patient'].create({ 'first_name': 'Test', 'last_name': 'Player2', 'team_ids': [(6, 0, [cls.team1.id])], }) # Create test users cls.admin_user = cls.env.ref('base.user_admin') # Create treatment professional user cls.treatment_prof_user = cls.env['res.users'].create({ 'name': 'Treatment Professional', 'login': 'treatment_prof@example.com', 'groups_id': [(6, 0, [ cls.env.ref('bemade_sports_clinic.group_sports_clinic_treatment_professional').id, cls.env.ref('base.group_user').id, ])], }) # Create team staff user cls.team_staff_user = cls.env['res.users'].create({ 'name': 'Team Staff', 'login': 'team_staff@example.com', 'groups_id': [(6, 0, [ cls.env.ref('base.group_user').id, ])], }) # Add team staff to team1 cls.team_staff = cls.env['sports.team.staff'].create({ 'team_id': cls.team1.id, 'partner_id': cls.team_staff_user.partner_id.id, 'role': 'coach', }) # Create regular user with no special permissions cls.regular_user = cls.env['res.users'].create({ 'name': 'Regular User', 'login': 'regular@example.com', 'groups_id': [(6, 0, [cls.env.ref('base.group_user').id])], }) def test_admin_can_remove_player_from_team(self): """Test that admin can remove a player from any team""" self.player1.with_user(self.admin_user).remove_from_team(self.team1.id) self.assertNotIn(self.team1, self.player1.team_ids) # TODO: Test still fails due to mail system access limitations for portal users # def test_treatment_prof_can_remove_player_from_team(self): # """Test that treatment professionals can remove players from teams they are staffed on as therapists""" # # Add treatment professional as therapist to team1 # self.env['sports.team.staff'].create({ # 'team_id': self.team1.id, # 'partner_id': self.treatment_prof_user.partner_id.id, # 'role': 'therapist', # }) # # # Verify initial state # self.assertIn(self.team1, self.player1.team_ids) # # # Perform the removal # result = self.player1.with_user(self.treatment_prof_user).remove_from_team(self.team1.id) # # # Refresh the record to ensure we have the latest data # self.player1.refresh() # # # Verify the player was removed from the team # self.assertNotIn(self.team1, self.player1.team_ids) # # # Verify the response indicates success # self.assertIn('success', result.get('params', {}).get('type', '')) def test_team_staff_cannot_directly_remove_players(self): """Test that team staff cannot directly remove players""" # Verify direct removal is not allowed with self.assertRaises(AccessError): self.player1.with_user(self.team_staff_user).remove_from_team(self.team1.id) # Verify the player is still on the team and no pending removal was set self.assertIn(self.team1, self.player1.team_ids) self.assertFalse(self.player1.pending_removal) def test_team_staff_can_request_removal(self): """Test that team staff can request player removal with proper permissions""" # Create a portal user (coach) coach_user = self.env['res.users'].with_context(no_reset_password=True).create({ 'name': 'Team Coach', 'login': 'coach@example.com', 'groups_id': [(6, 0, [ self.env.ref('base.group_portal').id, self.env.ref('bemade_sports_clinic.group_portal_team_coach').id ])], }) # Add coach to the team as staff coach_staff = self.env['sports.team.staff'].create({ 'team_id': self.team1.id, 'partner_id': coach_user.partner_id.id, 'role': 'coach', }) # Create a head therapist for the team head_therapist = self.env['res.users'].create({ 'name': 'Head Therapist', 'login': 'head.therapist@example.com', 'groups_id': [ (4, self.env.ref('base.group_user').id), (4, self.env.ref('bemade_sports_clinic.group_sports_clinic_treatment_professional').id) ], }) # Add head therapist to the team with head_therapist role therapist_record = self.env['sports.team.staff'].create({ 'team_id': self.team1.id, 'partner_id': head_therapist.partner_id.id, 'role': 'head_therapist', }) # Verify the head therapist was created correctly self.assertEqual(therapist_record.role, 'head_therapist', "Head therapist should be created with head_therapist role") # Add player to the team self.team1.patient_ids = [(4, self.player1.id)] # Ensure the coach has access to the team self.team1.allowed_user_ids = [(4, coach_user.id)] # Switch to coach user context self.player1 = self.player1.with_user(coach_user) # Test the request_removal flow result = self.player1.with_user(coach_user)._request_team_removal( self.team1.id, reason="Test removal request" ) # Verify the pending_removal flag was set self.assertTrue(self.player1.pending_removal) # Verify the success notification self.assertEqual(result['type'], 'ir.actions.client') self.assertEqual(result['tag'], 'display_notification') self.assertEqual(result['params']['title'], 'Removal Request Submitted') self.assertIn('has been submitted and will be processed by an administrator', result['params']['message']) # Now run the cron job to create the activity self.env['sports.patient']._cron_handle_pending_removals() # Verify an activity was created for the head therapist activities = self.env['mail.activity'].search([ ('res_model', '=', 'sports.patient'), ('res_id', '=', self.player1.id), ('summary', 'ilike', 'Player Removal Request') ]) self.assertTrue(activities, "Activity should be created for head therapist by cron job") def test_treatment_prof_cannot_remove_from_other_teams(self): """Test that treatment professionals cannot remove players from teams they are not staffed on""" # Only add treatment professional to team1 as a therapist, not team2 self.env['sports.team.staff'].create({ 'team_id': self.team1.id, 'partner_id': self.treatment_prof_user.partner_id.id, 'role': 'therapist', }) # Expect AccessError when trying to access team they don't have permission for with self.assertRaises(AccessError) as context: self.player1.with_user(self.treatment_prof_user).remove_from_team(self.team2.id) # Verify we got an access denied error (don't check specific message as it might vary) self.assertTrue(str(context.exception), "Should raise AccessError for unauthorized team access") def test_regular_user_cannot_remove_players(self): """Test that regular users cannot remove players from any team""" # Verify regular users get an AccessError when trying to remove players with self.assertRaises(AccessError): self.player1.with_user(self.regular_user).remove_from_team(self.team1.id) # Verify the player is still on the team self.assertIn(self.team1, self.player1.team_ids) def test_remove_nonexistent_team(self): """Test removing a player from a non-existent team""" with self.assertRaises(ValidationError): self.player1.with_user(self.admin_user).remove_from_team(999999) def test_remove_player_not_in_team(self): """Test removing a player from a team they don't belong to""" with self.assertRaises(ValidationError): self.player2.with_user(self.admin_user).remove_from_team(self.team2.id) def test_remove_last_team_archives_player(self): """Test that removing a player from their last team schedules them for archiving""" # Get a fresh copy of the player to avoid cached values player = self.env['sports.patient'].browse(self.player2.id) # Verify initial state self.assertTrue(player.active) self.assertEqual(len(player.team_ids), 1) # Remove from their only team result = player.with_user(self.admin_user).remove_from_team(self.team1.id) # Get a fresh copy of the player after removal player = self.env['sports.patient'].browse(self.player2.id) # Verify player is no longer on the team but still active self.assertEqual(len(player.team_ids), 0, "Player should be removed from all teams") self.assertTrue(player.active, "Player should still be active until the cron runs") # Verify the response indicates the player will be archived self.assertIn('will be archived', result.get('params', {}).get('message', ''), "Response should indicate player will be archived") # Now run the cron manually to test archiving self.env['sports.patient']._cron_archive_players_without_teams() # Get a fresh copy of the player after cron job player = self.env['sports.patient'].browse(self.player2.id) self.assertFalse(player.active, "Player should be archived after the cron runs") def test_remove_with_reason_logs_reason(self): """Test that providing a reason logs it in the chatter""" reason = "Test removal with reason" self.player1.with_user(self.admin_user).remove_from_team( self.team1.id, clear_pending=False, reason=reason ) messages = self.env['mail.message'].search([ ('model', '=', 'sports.patient'), ('res_id', '=', self.player1.id), ('body', 'ilike', reason) ]) self.assertTrue(messages, "Reason should be logged in the chatter") def test_pending_removal_flag_cleared(self): """Test that pending_removal flag is cleared when specified""" # Add player to another team first self.team2.patient_ids = [(4, self.player1.id)] # Set pending_removal flag and clear it during removal self.player1.pending_removal = True self.player1.with_user(self.admin_user).remove_from_team( self.team1.id, clear_pending=True ) # Get a fresh copy to ensure we have the latest data player = self.env['sports.patient'].browse(self.player1.id) self.assertFalse(player.pending_removal, "pending_removal should be cleared when clear_pending=True") def test_pending_removal_flag_not_cleared(self): """Test that pending_removal flag is not cleared when specified""" # Add player to another team first self.team2.patient_ids = [(4, self.player1.id)] # Set pending_removal flag and don't clear it during removal self.player1.pending_removal = True self.player1.with_user(self.admin_user).remove_from_team( self.team1.id, clear_pending=False ) # Get a fresh copy to ensure we have the latest data player = self.env['sports.patient'].browse(self.player1.id) self.assertTrue(player.pending_removal, "pending_removal should remain True when clear_pending=False") def test_remove_player_twice(self): """Test that removing a player twice raises an error""" self.player1.with_user(self.admin_user).remove_from_team(self.team1.id) with self.assertRaises(ValidationError) as context: self.player1.with_user(self.admin_user).remove_from_team(self.team1.id) self.assertIn('not a member', str(context.exception))