2025-07-11 21:47:50 -04:00
|
|
|
from odoo.tests import HttpCase, tagged
|
|
|
|
|
from odoo import Command, fields
|
|
|
|
|
import json
|
|
|
|
|
from freezegun import freeze_time
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@tagged("-at_install", "post_install")
|
|
|
|
|
class TestPortalIntegration(HttpCase):
|
|
|
|
|
"""Integration tests for the sports clinic portal features"""
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def setUpClass(cls):
|
|
|
|
|
super().setUpClass()
|
|
|
|
|
|
|
|
|
|
# Create organization and team
|
|
|
|
|
cls.organization = cls.env['sports.organization'].create({
|
|
|
|
|
'name': 'Test Organization',
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
cls.team = cls.env['sports.team'].create({
|
|
|
|
|
'name': 'Test Integration Team',
|
|
|
|
|
'organization_id': cls.organization.id,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
# Create some patients/players
|
|
|
|
|
cls.patient1 = cls.env['sports.patient'].create({
|
|
|
|
|
'first_name': 'John',
|
|
|
|
|
'last_name': 'Player',
|
|
|
|
|
'birthdate': '2005-01-01',
|
|
|
|
|
'team_ids': [(4, cls.team.id)],
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
cls.patient2 = cls.env['sports.patient'].create({
|
|
|
|
|
'first_name': 'Jane',
|
|
|
|
|
'last_name': 'Athlete',
|
|
|
|
|
'birthdate': '2006-02-02',
|
|
|
|
|
'team_ids': [(4, cls.team.id)],
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
# Create an active injury for patient1
|
|
|
|
|
cls.existing_injury = cls.env['sports.patient.injury'].create({
|
|
|
|
|
'patient_id': cls.patient1.id,
|
|
|
|
|
'team_id': cls.team.id,
|
|
|
|
|
'diagnosis': 'Existing Sprained Ankle',
|
|
|
|
|
'stage': 'active',
|
|
|
|
|
'injury_date': fields.Date.today(),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
# Create users with different roles
|
|
|
|
|
# 1. Therapist (treatment professional)
|
|
|
|
|
cls.therapist_partner = cls.env['res.partner'].create({
|
|
|
|
|
'name': 'Integration Therapist',
|
|
|
|
|
'email': 'integration.therapist@example.com',
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
cls.therapist_user = cls.env['res.users'].with_context(no_reset_password=True).create({
|
|
|
|
|
'partner_id': cls.therapist_partner.id,
|
|
|
|
|
'login': 'integration.therapist@example.com',
|
|
|
|
|
'password': 'therapist123',
|
|
|
|
|
'name': cls.therapist_partner.name,
|
|
|
|
|
'groups_id': [
|
|
|
|
|
Command.link(cls.env.ref('base.group_portal').id),
|
|
|
|
|
Command.link(cls.env.ref('bemade_sports_clinic.group_portal_treatment_professional').id),
|
|
|
|
|
]
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
# 2. Coach
|
|
|
|
|
cls.coach_partner = cls.env['res.partner'].create({
|
|
|
|
|
'name': 'Integration Coach',
|
|
|
|
|
'email': 'integration.coach@example.com',
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
cls.coach_user = cls.env['res.users'].with_context(no_reset_password=True).create({
|
|
|
|
|
'partner_id': cls.coach_partner.id,
|
|
|
|
|
'login': 'integration.coach@example.com',
|
|
|
|
|
'password': 'coach123',
|
|
|
|
|
'name': cls.coach_partner.name,
|
|
|
|
|
'groups_id': [
|
|
|
|
|
Command.link(cls.env.ref('base.group_portal').id),
|
|
|
|
|
Command.link(cls.env.ref('bemade_sports_clinic.group_portal_team_coach').id),
|
|
|
|
|
]
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
# Create team staff entries
|
|
|
|
|
cls.env['sports.team.staff'].create({
|
|
|
|
|
'team_id': cls.team.id,
|
|
|
|
|
'partner_id': cls.therapist_partner.id,
|
2025-07-16 11:37:50 -04:00
|
|
|
'role': 'therapist',
|
2025-07-11 21:47:50 -04:00
|
|
|
'user_id': cls.therapist_user.id,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
cls.env['sports.team.staff'].create({
|
|
|
|
|
'team_id': cls.team.id,
|
|
|
|
|
'partner_id': cls.coach_partner.id,
|
|
|
|
|
'role': 'coach',
|
|
|
|
|
'user_id': cls.coach_user.id,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
def test_01_therapist_portal_access(self):
|
|
|
|
|
"""Test that therapists can access the portal and see all relevant information"""
|
|
|
|
|
# Login as therapist
|
|
|
|
|
self.authenticate('integration.therapist@example.com', 'therapist123')
|
|
|
|
|
|
|
|
|
|
# 1. Check access to teams page
|
|
|
|
|
teams_response = self.url_open('/my/teams')
|
|
|
|
|
self.assertEqual(teams_response.status_code, 200)
|
|
|
|
|
self.assertIn(self.team.name, teams_response.text)
|
|
|
|
|
|
|
|
|
|
# 2. Check access to team details page
|
|
|
|
|
team_details_response = self.url_open(f'/my/team?team_id={self.team.id}')
|
|
|
|
|
self.assertEqual(team_details_response.status_code, 200)
|
|
|
|
|
self.assertIn(self.patient1.name, team_details_response.text)
|
|
|
|
|
self.assertIn(self.patient2.name, team_details_response.text)
|
|
|
|
|
|
|
|
|
|
# 3. Check access to all players page
|
|
|
|
|
players_response = self.url_open('/my/players')
|
|
|
|
|
self.assertEqual(players_response.status_code, 200)
|
|
|
|
|
self.assertIn(self.patient1.name, players_response.text)
|
|
|
|
|
self.assertIn(self.patient2.name, players_response.text)
|
|
|
|
|
|
|
|
|
|
# 4. Check access to player detail page
|
|
|
|
|
player_response = self.url_open(f'/my/player?player_id={self.patient1.id}')
|
|
|
|
|
self.assertEqual(player_response.status_code, 200)
|
|
|
|
|
self.assertIn(self.existing_injury.diagnosis, player_response.text)
|
|
|
|
|
|
|
|
|
|
# 5. Check that therapist sees internal notes field
|
|
|
|
|
self.assertIn('internal_notes', player_response.text)
|
|
|
|
|
|
|
|
|
|
# 6. Check injury form access with parental consent field
|
|
|
|
|
injury_form_response = self.url_open(f'/my/patient/injury/new?patient_id={self.patient1.id}')
|
|
|
|
|
self.assertEqual(injury_form_response.status_code, 200)
|
|
|
|
|
self.assertIn('Consent for Disclosure to Parent', injury_form_response.text)
|
|
|
|
|
|
|
|
|
|
def test_02_coach_portal_access(self):
|
|
|
|
|
"""Test that coaches can access the portal but with limited information"""
|
|
|
|
|
# Login as coach
|
|
|
|
|
self.authenticate('integration.coach@example.com', 'coach123')
|
|
|
|
|
|
|
|
|
|
# 1. Check access to teams page
|
|
|
|
|
teams_response = self.url_open('/my/teams')
|
|
|
|
|
self.assertEqual(teams_response.status_code, 200)
|
|
|
|
|
self.assertIn(self.team.name, teams_response.text)
|
|
|
|
|
|
|
|
|
|
# 2. Check access to team details page
|
|
|
|
|
team_details_response = self.url_open(f'/my/team?team_id={self.team.id}')
|
|
|
|
|
self.assertEqual(team_details_response.status_code, 200)
|
|
|
|
|
self.assertIn(self.patient1.name, team_details_response.text)
|
|
|
|
|
self.assertIn(self.patient2.name, team_details_response.text)
|
|
|
|
|
|
|
|
|
|
# 3. Check access to all players page
|
|
|
|
|
players_response = self.url_open('/my/players')
|
|
|
|
|
self.assertEqual(players_response.status_code, 200)
|
|
|
|
|
self.assertIn(self.patient1.name, players_response.text)
|
|
|
|
|
self.assertIn(self.patient2.name, players_response.text)
|
|
|
|
|
|
|
|
|
|
# 4. Check access to player detail page
|
|
|
|
|
player_response = self.url_open(f'/my/player?player_id={self.patient1.id}')
|
|
|
|
|
self.assertEqual(player_response.status_code, 200)
|
|
|
|
|
self.assertIn(self.existing_injury.diagnosis, player_response.text)
|
|
|
|
|
|
|
|
|
|
# 5. Check that coach does not see internal notes field
|
|
|
|
|
html_content = player_response.text
|
|
|
|
|
# This is a partial check - we look for a display:none or similar in the HTML
|
|
|
|
|
# The actual implementation might hide it completely or with CSS
|
|
|
|
|
self.assertNotIn('Internal Notes:</strong>', html_content)
|
|
|
|
|
|
|
|
|
|
# 6. Check injury form access without parental consent field
|
|
|
|
|
injury_form_response = self.url_open(f'/my/patient/injury/new?patient_id={self.patient1.id}')
|
|
|
|
|
self.assertEqual(injury_form_response.status_code, 200)
|
|
|
|
|
self.assertNotIn('<select class="form-control" id="parental_consent"', injury_form_response.text)
|
|
|
|
|
|
|
|
|
|
def test_03_injury_reporting_through_portal(self):
|
|
|
|
|
"""Test that injuries can be reported through the portal by both roles"""
|
|
|
|
|
# A. Test injury reporting by coach
|
|
|
|
|
self.authenticate('integration.coach@example.com', 'coach123')
|
|
|
|
|
|
|
|
|
|
# Submit injury creation form
|
|
|
|
|
coach_injury_data = {
|
|
|
|
|
'csrf_token': self.csrf_token(),
|
|
|
|
|
'patient_id': self.patient2.id,
|
|
|
|
|
'team_id': self.team.id,
|
|
|
|
|
'injury_date': '2025-07-10',
|
|
|
|
|
'diagnosis': 'Coach Reported Knee Pain',
|
|
|
|
|
'external_notes': 'External note from coach test',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
coach_response = self.url_open(
|
|
|
|
|
'/my/patient/injury/create',
|
|
|
|
|
data=coach_injury_data,
|
|
|
|
|
timeout=30,
|
|
|
|
|
)
|
|
|
|
|
self.assertEqual(coach_response.status_code, 200)
|
|
|
|
|
|
|
|
|
|
# Verify that injury was created with correct values
|
|
|
|
|
coach_injury = self.env['sports.patient.injury'].search([
|
|
|
|
|
('patient_id', '=', self.patient2.id),
|
|
|
|
|
('diagnosis', '=', 'Coach Reported Knee Pain'),
|
|
|
|
|
], limit=1)
|
|
|
|
|
|
|
|
|
|
self.assertTrue(coach_injury, "Coach should be able to create an injury")
|
|
|
|
|
self.assertEqual(coach_injury.stage, 'unverified', "Coach-created injury should be unverified")
|
|
|
|
|
self.assertEqual(coach_injury.parental_consent, 'no', "Coach-created injury should default parental consent to 'no'")
|
|
|
|
|
|
|
|
|
|
# B. Test injury reporting by therapist
|
|
|
|
|
self.authenticate('integration.therapist@example.com', 'therapist123')
|
|
|
|
|
|
|
|
|
|
# Submit injury creation form
|
|
|
|
|
therapist_injury_data = {
|
|
|
|
|
'csrf_token': self.csrf_token(),
|
|
|
|
|
'patient_id': self.patient1.id,
|
|
|
|
|
'team_id': self.team.id,
|
|
|
|
|
'injury_date': '2025-07-10',
|
|
|
|
|
'diagnosis': 'Therapist Reported Wrist Injury',
|
|
|
|
|
'external_notes': 'External note from therapist test',
|
|
|
|
|
'internal_notes': 'Internal note from therapist test',
|
|
|
|
|
'parental_consent': 'yes',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
therapist_response = self.url_open(
|
|
|
|
|
'/my/patient/injury/create',
|
|
|
|
|
data=therapist_injury_data,
|
|
|
|
|
timeout=30,
|
|
|
|
|
)
|
|
|
|
|
self.assertEqual(therapist_response.status_code, 200)
|
|
|
|
|
|
|
|
|
|
# Verify that injury was created with correct values
|
|
|
|
|
therapist_injury = self.env['sports.patient.injury'].search([
|
|
|
|
|
('patient_id', '=', self.patient1.id),
|
|
|
|
|
('diagnosis', '=', 'Therapist Reported Wrist Injury'),
|
|
|
|
|
], limit=1)
|
|
|
|
|
|
|
|
|
|
self.assertTrue(therapist_injury, "Therapist should be able to create an injury")
|
|
|
|
|
self.assertEqual(therapist_injury.stage, 'active', "Therapist-created injury should be active")
|
|
|
|
|
self.assertEqual(therapist_injury.parental_consent, 'yes', "Therapist should be able to set parental consent")
|
|
|
|
|
self.assertEqual(therapist_injury.internal_notes, 'Internal note from therapist test', "Internal notes should be saved")
|
|
|
|
|
|
|
|
|
|
def test_04_injury_verification_workflow(self):
|
|
|
|
|
"""Test that coaches create unverified injuries and therapists can verify them"""
|
|
|
|
|
# Create an unverified injury as coach
|
|
|
|
|
self.authenticate('integration.coach@example.com', 'coach123')
|
|
|
|
|
|
|
|
|
|
# Submit injury creation form
|
|
|
|
|
unverified_injury_data = {
|
|
|
|
|
'csrf_token': self.csrf_token(),
|
|
|
|
|
'patient_id': self.patient2.id,
|
|
|
|
|
'team_id': self.team.id,
|
|
|
|
|
'injury_date': '2025-07-10',
|
|
|
|
|
'diagnosis': 'Unverified Foot Injury',
|
|
|
|
|
'external_notes': 'Needs verification',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.url_open(
|
|
|
|
|
'/my/patient/injury/create',
|
|
|
|
|
data=unverified_injury_data,
|
|
|
|
|
timeout=30,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Find the created injury
|
|
|
|
|
unverified_injury = self.env['sports.patient.injury'].search([
|
|
|
|
|
('patient_id', '=', self.patient2.id),
|
|
|
|
|
('diagnosis', '=', 'Unverified Foot Injury'),
|
|
|
|
|
], limit=1)
|
|
|
|
|
|
|
|
|
|
self.assertTrue(unverified_injury, "Injury should be created")
|
|
|
|
|
self.assertEqual(unverified_injury.stage, 'unverified', "Injury should be unverified")
|
|
|
|
|
|
|
|
|
|
# Now login as therapist and verify the injury
|
|
|
|
|
self.authenticate('integration.therapist@example.com', 'therapist123')
|
|
|
|
|
|
|
|
|
|
# Access the player page to see the unverified injury
|
|
|
|
|
player_response = self.url_open(f'/my/player?player_id={self.patient2.id}')
|
|
|
|
|
self.assertEqual(player_response.status_code, 200)
|
|
|
|
|
self.assertIn('Unverified Foot Injury', player_response.text)
|
|
|
|
|
|
|
|
|
|
# Verify the injury
|
|
|
|
|
verify_data = {
|
|
|
|
|
'csrf_token': self.csrf_token(),
|
|
|
|
|
'injury_id': unverified_injury.id,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.url_open(
|
|
|
|
|
'/my/injury/verify',
|
|
|
|
|
data=verify_data,
|
|
|
|
|
timeout=30,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Refresh the injury record and check that it's now active
|
|
|
|
|
unverified_injury.invalidate_cache()
|
|
|
|
|
self.assertEqual(unverified_injury.stage, 'active', "Injury should be verified and active now")
|
|
|
|
|
|
|
|
|
|
def test_05_player_status_updates(self):
|
|
|
|
|
"""Test that player status is correctly updated based on injury status"""
|
|
|
|
|
# First verify that patient1 is injured (since they have an active injury)
|
|
|
|
|
self.patient1.invalidate_cache()
|
|
|
|
|
self.assertTrue(self.patient1.is_injured, "Patient with active injury should be marked as injured")
|
|
|
|
|
|
|
|
|
|
# Now resolve the injury and check that status is updated
|
|
|
|
|
with freeze_time('2025-07-11'):
|
|
|
|
|
self.existing_injury.write({
|
|
|
|
|
'stage': 'resolved',
|
|
|
|
|
'resolution_date': fields.Date.today(),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
# Refresh the patient record
|
|
|
|
|
self.patient1.invalidate_cache()
|
|
|
|
|
self.assertFalse(self.patient1.is_injured, "Patient with resolved injury should not be marked as injured")
|
|
|
|
|
|
|
|
|
|
# Create a new injury and check that status is updated again
|
|
|
|
|
new_injury = self.env['sports.patient.injury'].create({
|
|
|
|
|
'patient_id': self.patient1.id,
|
|
|
|
|
'team_id': self.team.id,
|
|
|
|
|
'diagnosis': 'New Test Injury',
|
|
|
|
|
'stage': 'active',
|
|
|
|
|
'injury_date': fields.Date.today(),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
# Refresh the patient record
|
|
|
|
|
self.patient1.invalidate_cache()
|
|
|
|
|
self.assertTrue(self.patient1.is_injured, "Patient with new active injury should be marked as injured again")
|