helpdesk_rating_five_stars/tests/test_average_calculation.py
2025-11-26 10:39:26 +07:00

246 lines
9.0 KiB
Python

# -*- coding: utf-8 -*-
from odoo.tests import tagged
from odoo.tests.common import TransactionCase
from hypothesis import given, strategies as st, settings, assume
import statistics
@tagged('post_install', '-at_install', 'helpdesk_rating_five_stars')
class TestAverageCalculation(TransactionCase):
"""
Property-based tests for rating average calculation
Requirements: 4.2
- Requirement 4.2: Calculate average ratings based on the 0-5 scale
"""
def setUp(self):
super(TestAverageCalculation, self).setUp()
self.Rating = self.env['rating.rating']
self.HelpdeskTeam = self.env['helpdesk.team']
self.HelpdeskTicket = self.env['helpdesk.ticket']
# Create a helpdesk team with rating enabled
self.team = self.HelpdeskTeam.create({
'name': 'Test Support Team',
'use_rating': True,
})
def _create_ticket_with_ratings(self, rating_values):
"""
Helper method to create a ticket with multiple ratings
Args:
rating_values: List of rating values (1-5)
Returns:
tuple: (ticket, list of rating records)
"""
# Create a ticket
ticket = self.HelpdeskTicket.create({
'name': f'Test Ticket for ratings {rating_values}',
'team_id': self.team.id,
})
# Create ratings for the ticket
ratings = []
for rating_value in rating_values:
rating = self.Rating.create({
'res_model': 'helpdesk.ticket',
'res_id': ticket.id,
'rating': float(rating_value),
'consumed': True,
})
ratings.append(rating)
return ticket, ratings
# Feature: helpdesk-rating-five-stars, Property 9: Average calculation uses correct scale
@given(rating_values=st.lists(
st.floats(min_value=1.0, max_value=5.0, allow_nan=False, allow_infinity=False),
min_size=1,
max_size=20
))
@settings(max_examples=100, deadline=None)
def test_property_average_uses_correct_scale(self, rating_values):
"""
Property 9: Average calculation uses correct scale
For any set of ratings, the calculated average should be based on the 0-5 scale.
This property verifies that:
1. All individual ratings are in the 0-5 range
2. The calculated average is in the 0-5 range
3. The average matches the expected mathematical average of the input values
Validates: Requirements 4.2
"""
# Skip if we have no valid ratings
assume(len(rating_values) > 0)
# Create ticket with ratings
ticket, ratings = self._create_ticket_with_ratings(rating_values)
# Verify all individual ratings are in valid range (1-5)
for rating in ratings:
self.assertGreaterEqual(rating.rating, 1.0,
f"Individual rating {rating.rating} should be >= 1.0")
self.assertLessEqual(rating.rating, 5.0,
f"Individual rating {rating.rating} should be <= 5.0")
# Calculate expected average using Python's statistics module
expected_avg = statistics.mean(rating_values)
# Verify expected average is in valid range
self.assertGreaterEqual(expected_avg, 1.0,
f"Expected average {expected_avg} should be >= 1.0")
self.assertLessEqual(expected_avg, 5.0,
f"Expected average {expected_avg} should be <= 5.0")
# Get the average from Odoo's rating system
# Method 1: Use read_group to calculate average
domain = [('res_model', '=', 'helpdesk.ticket'), ('res_id', '=', ticket.id)]
result = self.Rating.read_group(
domain=domain,
fields=['rating:avg'],
groupby=[]
)
if result and result[0].get('rating'):
calculated_avg = result[0]['rating']
# Verify calculated average is in valid range (1-5)
self.assertGreaterEqual(calculated_avg, 1.0,
f"Calculated average {calculated_avg} should be >= 1.0")
self.assertLessEqual(calculated_avg, 5.0,
f"Calculated average {calculated_avg} should be <= 5.0")
# Verify calculated average matches expected average
self.assertAlmostEqual(calculated_avg, expected_avg, places=2,
msg=f"Calculated average {calculated_avg} should match expected {expected_avg}")
def test_average_with_zero_ratings(self):
"""
Test that zero ratings (no rating) are handled correctly in average calculation
Zero ratings should be excluded from average calculations as they represent
"no rating" rather than a rating of 0 stars.
Validates: Requirements 4.2
"""
# Create ticket with mix of real ratings and zero ratings
ticket = self.HelpdeskTicket.create({
'name': 'Test Ticket with zero ratings',
'team_id': self.team.id,
})
# Create some real ratings
self.Rating.create({
'res_model': 'helpdesk.ticket',
'res_id': ticket.id,
'rating': 5.0,
'consumed': True,
})
self.Rating.create({
'res_model': 'helpdesk.ticket',
'res_id': ticket.id,
'rating': 3.0,
'consumed': True,
})
# Create a zero rating (no rating)
self.Rating.create({
'res_model': 'helpdesk.ticket',
'res_id': ticket.id,
'rating': 0.0,
'consumed': False,
})
# Calculate average excluding zero ratings
domain = [
('res_model', '=', 'helpdesk.ticket'),
('res_id', '=', ticket.id),
('rating', '>', 0) # Exclude zero ratings
]
result = self.Rating.read_group(
domain=domain,
fields=['rating:avg'],
groupby=[]
)
if result and result[0].get('rating'):
calculated_avg = result[0]['rating']
expected_avg = (5.0 + 3.0) / 2 # Should be 4.0
# Verify average is calculated correctly without zero ratings
self.assertAlmostEqual(calculated_avg, expected_avg, places=2,
msg=f"Average should exclude zero ratings: {calculated_avg} vs {expected_avg}")
def test_average_single_rating(self):
"""
Test that average calculation works correctly with a single rating
Validates: Requirements 4.2
"""
ticket, ratings = self._create_ticket_with_ratings([4.0])
domain = [('res_model', '=', 'helpdesk.ticket'), ('res_id', '=', ticket.id)]
result = self.Rating.read_group(
domain=domain,
fields=['rating:avg'],
groupby=[]
)
if result and result[0].get('rating'):
calculated_avg = result[0]['rating']
# Average of single rating should equal that rating
self.assertAlmostEqual(calculated_avg, 4.0, places=2,
msg="Average of single rating should equal the rating value")
def test_average_all_same_ratings(self):
"""
Test that average calculation works correctly when all ratings are the same
Validates: Requirements 4.2
"""
ticket, ratings = self._create_ticket_with_ratings([3.0, 3.0, 3.0, 3.0])
domain = [('res_model', '=', 'helpdesk.ticket'), ('res_id', '=', ticket.id)]
result = self.Rating.read_group(
domain=domain,
fields=['rating:avg'],
groupby=[]
)
if result and result[0].get('rating'):
calculated_avg = result[0]['rating']
# Average of identical ratings should equal that rating
self.assertAlmostEqual(calculated_avg, 3.0, places=2,
msg="Average of identical ratings should equal the rating value")
def test_average_extreme_values(self):
"""
Test that average calculation works correctly with extreme values (1 and 5)
Validates: Requirements 4.2
"""
ticket, ratings = self._create_ticket_with_ratings([1.0, 5.0])
domain = [('res_model', '=', 'helpdesk.ticket'), ('res_id', '=', ticket.id)]
result = self.Rating.read_group(
domain=domain,
fields=['rating:avg'],
groupby=[]
)
if result and result[0].get('rating'):
calculated_avg = result[0]['rating']
expected_avg = (1.0 + 5.0) / 2 # Should be 3.0
# Average of extremes should be midpoint
self.assertAlmostEqual(calculated_avg, expected_avg, places=2,
msg=f"Average of 1 and 5 should be 3.0: {calculated_avg}")