bemade-addons/bemade_sports_clinic/tests/test_player_removal.py
Denis Durepos ca57ec8f16 [IMP] bemade_sports_clinic: Refactor portal views and enhance treatment notes
- Refactor portal templates to eliminate intra-module template inheritance
- Integrate emergency contacts section into player injuries template
- Add full internal admin views for treatment notes with chatter support
- Fix ORM warning in test_rights by using proper ORM commands
- Update manifest to reflect new functionality (v18.0.1.9.0)

This refactoring improves template stability by removing XPath errors and
provides treatment professionals with better access to emergency contacts
and treatment notes both in portal and backend interfaces.
2025-07-11 21:47:50 -04:00

332 lines
14 KiB
Python

# -*- 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: Re-enable and fix this test after resolving email configuration issues
# 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(
team_id=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 for review', 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 reason for removal"
self.player1.with_user(self.admin_user).remove_from_team(
self.team1.id,
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))