# -*- coding: utf-8 -*- from odoo.tests import tagged from odoo.tests.common import TransactionCase from hypothesis import given, strategies as st, settings, assume @tagged('post_install', '-at_install', 'helpdesk_rating_five_stars') class TestRatingExport(TransactionCase): """ Property-based tests for rating export functionality Requirements: 4.5 - Requirement 4.5: Export rating data with values in 0-5 range """ def setUp(self): super(TestRatingExport, 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_tickets_with_ratings(self, rating_values): """ Helper method to create multiple tickets with ratings Args: rating_values: List of rating values (0-5) Returns: list: List of rating records """ ratings = [] for i, rating_value in enumerate(rating_values): # Create a ticket ticket = self.HelpdeskTicket.create({ 'name': f'Test Ticket {i} - Rating {rating_value}', 'team_id': self.team.id, }) # Create rating for the ticket rating = self.Rating.create({ 'res_model_id': self.env['ir.model']._get('helpdesk.ticket').id, 'res_model': 'helpdesk.ticket', 'res_id': ticket.id, 'rating': float(rating_value), 'consumed': True, }) ratings.append(rating) return ratings # Feature: helpdesk-rating-five-stars, Property 12: Export contains valid rating values @given(rating_values=st.lists( st.floats(min_value=0.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_export_contains_valid_values(self, rating_values): """ Property 12: Export contains valid rating values For any exported rating data, all Rating_Value entries should be within the 0-5 range. This property verifies that: 1. All exported rating values are in the 0-5 range 2. Export data structure is correct 3. No data corruption occurs during export 4. Export includes all expected fields Validates: Requirements 4.5 """ # Skip if we have no valid ratings assume(len(rating_values) > 0) # Filter out invalid values (between 0 and 1, exclusive) valid_rating_values = [] for val in rating_values: if val == 0.0 or (val >= 1.0 and val <= 5.0): valid_rating_values.append(val) # Skip if no valid values after filtering assume(len(valid_rating_values) > 0) # Create ratings ratings = self._create_tickets_with_ratings(valid_rating_values) rating_ids = [r.id for r in ratings] # Get the rating records rating_records = self.Rating.browse(rating_ids) # Define fields to export (common fields that would be exported) export_fields = ['id', 'rating', 'res_model', 'res_id', 'consumed'] # Use Odoo's export_data method to export the ratings export_result = rating_records.export_data(export_fields) # Verify export was successful self.assertIn('datas', export_result, "Export result should contain 'datas' key") exported_data = export_result['datas'] # Verify we exported the correct number of records self.assertEqual(len(exported_data), len(valid_rating_values), f"Should export {len(valid_rating_values)} records") # Find the index of the 'rating' field in export rating_field_index = export_fields.index('rating') # Verify all exported rating values are in valid range (0-5) for i, row in enumerate(exported_data): exported_rating = float(row[rating_field_index]) # Verify rating is in valid 0-5 range self.assertGreaterEqual(exported_rating, 0.0, f"Exported rating {exported_rating} at row {i} should be >= 0.0") self.assertLessEqual(exported_rating, 5.0, f"Exported rating {exported_rating} at row {i} should be <= 5.0") # Verify rating is either 0 or between 1-5 if exported_rating > 0: self.assertGreaterEqual(exported_rating, 1.0, f"Non-zero exported rating {exported_rating} should be >= 1.0") # Verify exported value matches original value original_value = valid_rating_values[i] self.assertAlmostEqual(exported_rating, original_value, places=2, msg=f"Exported rating {exported_rating} should match original {original_value}") @given( num_ratings=st.integers(min_value=1, max_value=50), include_zero=st.booleans() ) @settings(max_examples=100, deadline=None) def test_property_export_completeness(self, num_ratings, include_zero): """ Property: Export includes all ratings without data loss For any set of ratings, the export should include all records with correct values. Validates: Requirements 4.5 """ assume(num_ratings > 0) # Generate rating values rating_values = [] for i in range(num_ratings): if include_zero and i == 0: rating_values.append(0.0) else: # Generate values between 1-5 rating_values.append(float((i % 5) + 1)) # Create ratings ratings = self._create_tickets_with_ratings(rating_values) rating_ids = [r.id for r in ratings] # Get the rating records rating_records = self.Rating.browse(rating_ids) # Export with multiple fields export_fields = ['id', 'rating', 'res_model', 'res_id', 'consumed', 'feedback'] export_result = rating_records.export_data(export_fields) exported_data = export_result['datas'] # Verify completeness: all records exported self.assertEqual(len(exported_data), num_ratings, f"Should export all {num_ratings} ratings") # Verify all rating values are valid rating_field_index = export_fields.index('rating') for row in exported_data: exported_rating = float(row[rating_field_index]) # Verify in valid range self.assertGreaterEqual(exported_rating, 0.0, f"Exported rating should be >= 0.0") self.assertLessEqual(exported_rating, 5.0, f"Exported rating should be <= 5.0") def test_export_with_zero_ratings(self): """ Test that export correctly handles zero ratings (no rating) Zero ratings should be exported as 0.0 and remain in valid range. Validates: Requirements 4.5 """ # Create ratings with mix of values including zero rating_values = [0.0, 1.0, 3.0, 5.0] ratings = self._create_tickets_with_ratings(rating_values) rating_ids = [r.id for r in ratings] # Export ratings rating_records = self.Rating.browse(rating_ids) export_fields = ['id', 'rating'] export_result = rating_records.export_data(export_fields) exported_data = export_result['datas'] # Verify all exported values are valid rating_field_index = export_fields.index('rating') exported_ratings = [float(row[rating_field_index]) for row in exported_data] # Verify we have the zero rating self.assertIn(0.0, exported_ratings, "Export should include zero rating") # Verify all are in valid range for rating in exported_ratings: self.assertGreaterEqual(rating, 0.0, f"Exported rating {rating} should be >= 0.0") self.assertLessEqual(rating, 5.0, f"Exported rating {rating} should be <= 5.0") def test_export_extreme_values(self): """ Test that export correctly handles extreme values (0, 1, 5) Validates: Requirements 4.5 """ # Create ratings with extreme values rating_values = [0.0, 1.0, 5.0] ratings = self._create_tickets_with_ratings(rating_values) rating_ids = [r.id for r in ratings] # Export ratings rating_records = self.Rating.browse(rating_ids) export_fields = ['id', 'rating'] export_result = rating_records.export_data(export_fields) exported_data = export_result['datas'] # Verify all exported values match expected rating_field_index = export_fields.index('rating') exported_ratings = [float(row[rating_field_index]) for row in exported_data] # Verify we have all extreme values self.assertIn(0.0, exported_ratings, "Export should include 0.0") self.assertIn(1.0, exported_ratings, "Export should include 1.0") self.assertIn(5.0, exported_ratings, "Export should include 5.0") # Verify all are in valid range for rating in exported_ratings: self.assertIn(rating, [0.0, 1.0, 5.0], f"Exported rating {rating} should be one of the extreme values") def test_export_with_all_fields(self): """ Test that export works correctly with all rating fields Validates: Requirements 4.5 """ # Create a rating with all fields populated ticket = self.HelpdeskTicket.create({ 'name': 'Test Ticket for Full Export', 'team_id': self.team.id, }) rating = self.Rating.create({ 'res_model_id': self.env['ir.model']._get('helpdesk.ticket').id, 'res_model': 'helpdesk.ticket', 'res_id': ticket.id, 'rating': 4.0, 'consumed': True, 'feedback': 'Great service!', }) # Export with all common fields export_fields = [ 'id', 'rating', 'res_model', 'res_id', 'consumed', 'feedback', 'rating_stars_filled', 'rating_stars_empty' ] export_result = rating.export_data(export_fields) exported_data = export_result['datas'] # Verify export successful self.assertEqual(len(exported_data), 1, "Should export 1 record") # Verify rating value is valid rating_field_index = export_fields.index('rating') exported_rating = float(exported_data[0][rating_field_index]) self.assertEqual(exported_rating, 4.0, "Exported rating should be 4.0") self.assertGreaterEqual(exported_rating, 0.0, "Exported rating should be >= 0.0") self.assertLessEqual(exported_rating, 5.0, "Exported rating should be <= 5.0") def test_export_large_dataset(self): """ Test that export works correctly with a large dataset Validates: Requirements 4.5 """ # Create a large number of ratings rating_values = [float((i % 5) + 1) for i in range(100)] ratings = self._create_tickets_with_ratings(rating_values) rating_ids = [r.id for r in ratings] # Export ratings rating_records = self.Rating.browse(rating_ids) export_fields = ['id', 'rating'] export_result = rating_records.export_data(export_fields) exported_data = export_result['datas'] # Verify all records exported self.assertEqual(len(exported_data), 100, "Should export all 100 records") # Verify all rating values are valid rating_field_index = export_fields.index('rating') for row in exported_data: exported_rating = float(row[rating_field_index]) self.assertGreaterEqual(exported_rating, 1.0, f"Exported rating {exported_rating} should be >= 1.0") self.assertLessEqual(exported_rating, 5.0, f"Exported rating {exported_rating} should be <= 5.0") def test_export_preserves_precision(self): """ Test that export preserves rating value precision Validates: Requirements 4.5 """ # Create ratings with decimal values rating_values = [1.0, 2.5, 3.7, 4.2, 5.0] ratings = self._create_tickets_with_ratings(rating_values) rating_ids = [r.id for r in ratings] # Export ratings rating_records = self.Rating.browse(rating_ids) export_fields = ['id', 'rating'] export_result = rating_records.export_data(export_fields) exported_data = export_result['datas'] # Verify precision is preserved rating_field_index = export_fields.index('rating') for i, row in enumerate(exported_data): exported_rating = float(row[rating_field_index]) original_rating = rating_values[i] # Verify values match with reasonable precision self.assertAlmostEqual(exported_rating, original_rating, places=1, msg=f"Exported rating should preserve precision: {exported_rating} vs {original_rating}") # Verify in valid range self.assertGreaterEqual(exported_rating, 0.0, f"Exported rating should be >= 0.0") self.assertLessEqual(exported_rating, 5.0, f"Exported rating should be <= 5.0")