246 lines
9.0 KiB
Python
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}")
|