# Part of Odoo. See LICENSE file for full copyright and licensing details. import logging from odoo.addons.website.tests.common import HttpCaseWithWebsiteUser from odoo.exceptions import ValidationError from odoo.fields import Command from odoo.tests import HttpCase, tagged from odoo.addons.http_routing.tests.common import MockRequest from odoo.addons.website_sale.controllers.main import WebsiteSale _logger = logging.getLogger(__name__) ATTACHMENT_DATA = [ b"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAEElEQVR4nGKqf3geEAAA//8EGgIyYKYzzgAAAABJRU5ErkJggg==", b"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAEElEQVR4nGKqvvQEEAAA//8EBQI0GMlQsAAAAABJRU5ErkJggg==", b"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAEElEQVR4nGKKLakBBAAA//8ChwFQsvFlAwAAAABJRU5ErkJggg==", b"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAEElEQVR4nGJqkdoACAAA//8CfAFRzSyOUAAAAABJRU5ErkJggg==", b"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAEElEQVR4nGLSfxgICAAA//8CrAFkoLBhpQAAAABJRU5ErkJggg==", ] ATTACHMENT_COUNT = 5 @tagged('post_install', '-at_install') class TestProductPictureController(HttpCase): @classmethod def setUpClass(cls): super().setUpClass() cls.website = cls.env['website'].browse(1) cls.WebsiteSaleController = WebsiteSale() cls.product = cls.env['product.product'].create({ 'name': 'Storage Test Box', 'standard_price': 70.0, 'list_price': 79.0, 'website_published': True, }) cls.attachments = cls.env['ir.attachment'].create([ { 'datas': ATTACHMENT_DATA[i], 'name': f'image0{i}.gif', 'public': True } for i in range(ATTACHMENT_COUNT)]) def _create_product_images(self): with MockRequest(self.product.env, website=self.website): self.WebsiteSaleController.add_product_media( [{'id': attachment.id} for attachment in self.attachments], 'image', self.product.id, self.product.product_tmpl_id.id, ) def _get_product_image_data(self): return [ hasattr(image, 'video_url') and image.video_url or image.image_1920 for image in self.product._get_images() ] def test_bulk_image_upload(self): # Turns attachments to product_images self._create_product_images() # Check if the media now exists on the product : for i, image in enumerate(self.product.product_template_image_ids): # Check if all names are now in the product self.assertIn(image.name, self.attachments.mapped('name')) # Check if image datas are the same self.assertEqual(image.image_1920, ATTACHMENT_DATA[i]) # Check if exactly ATTACHMENT_COUNT images were saved (no dupes/misses?) self.assertEqual(ATTACHMENT_COUNT, len(self.product.product_template_image_ids)) def test_image_clear(self): # First create some images self._create_product_images() self.assertEqual(ATTACHMENT_COUNT, len(self.product.product_template_image_ids)) # Remove all images # (Exception raised if error) with MockRequest(self.product.env, website=self.website): self.WebsiteSaleController.clear_product_images( self.product.id, self.product.product_tmpl_id.id, ) # According to the product, there are no variants images. self.assertEqual(0, len(self.product.product_template_image_ids)) def test_extra_images_with_new_variant(self): # Test that adding images for a variant that is not yet created works product_attribute = self.env['product.attribute'].create({ "name": "Test attribute", "create_variant": "dynamic", }) product_attribute_values = self.env['product.attribute.value'].create([ { "name" : "Test Dynamic 1", "attribute_id": product_attribute.id, "sequence": 1, }, { "name" : "Test Dynamic 2", "attribute_id": product_attribute.id, "sequence": 2, } ]) product_template = self.env['product.template'].create({ "name": "test product", "website_published": True, }) product_template_attribute_line = self.env['product.template.attribute.line'].create({ "attribute_id": product_attribute.id, "product_tmpl_id": product_template.id, "value_ids": product_attribute_values, }) self.assertEqual(0, len(product_template.product_variant_ids)) with MockRequest(product_template.env, website=self.website): self.WebsiteSaleController.add_product_media( [{'id': self.attachments[0].id}], 'image', False, product_template.id, [product_template_attribute_line.product_template_value_ids[0].id], ) self.assertEqual(1, len(product_template.product_variant_ids)) def test_resequence_image_first(self): self._create_product_images() with MockRequest(self.product.env, website=self.website): images = self.product._get_images() i1, i2, i3, i4, i5, i6 = self._get_product_image_data() self.WebsiteSaleController.resequence_product_image( images[2]._name, images[2].id, 'first', ) # Trigger the reordering of product.image records based on their sequence. self.env['product.image'].invalidate_model() self.assertListEqual(self._get_product_image_data(), [i3, i1, i2, i4, i5, i6]) self.assertEqual(self.product.image_1920, i3) def test_resequence_image_left(self): self._create_product_images() with MockRequest(self.product.env, website=self.website): images = self.product._get_images() i1, i2, i3, i4, i5, i6 = self._get_product_image_data() self.WebsiteSaleController.resequence_product_image( images[2]._name, images[2].id, 'left', ) self.env['product.image'].invalidate_model() self.assertListEqual(self._get_product_image_data(), [i1, i3, i2, i4, i5, i6]) def test_resequence_image_right(self): self._create_product_images() with MockRequest(self.product.env, website=self.website): images = self.product._get_images() i1, i2, i3, i4, i5, i6 = self._get_product_image_data() self.WebsiteSaleController.resequence_product_image( images[2]._name, images[2].id, 'right', ) self.env['product.image'].invalidate_model() self.assertListEqual(self._get_product_image_data(), [i1, i2, i4, i3, i5, i6]) def test_resequence_image_last(self): self._create_product_images() with MockRequest(self.product.env, website=self.website): images = self.product._get_images() i1, i2, i3, i4, i5, i6 = self._get_product_image_data() self.WebsiteSaleController.resequence_product_image( images[2]._name, images[2].id, 'last', ) self.env['product.image'].invalidate_model() self.assertListEqual(self._get_product_image_data(), [i1, i2, i4, i5, i6, i3]) def test_resequence_image_first_to_last(self): """ Moving an image from first to last position is an edge case in the code. """ self._create_product_images() with MockRequest(self.product.env, website=self.website): images = self.product._get_images() i1, i2, i3, i4, i5, i6 = self._get_product_image_data() self.WebsiteSaleController.resequence_product_image( images[0]._name, images[0].id, 'last', ) self.env['product.image'].invalidate_model() self.assertListEqual(self._get_product_image_data(), [i2, i3, i4, i5, i6, i1]) self.assertEqual(self.product.image_1920, i2) def test_resequence_video_left(self): self._create_product_images() with MockRequest(self.product.env, website=self.website): images = self.product._get_images() images[2].video_url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ" i1, i2, i3, i4, i5, i6 = self._get_product_image_data() self.WebsiteSaleController.resequence_product_image( images[2]._name, images[2].id, 'left', ) self.env['product.image'].invalidate_model() self.assertListEqual(self._get_product_image_data(), [i1, i3, i2, i4, i5, i6]) def test_resequence_video_first(self): """ A video can't be resequenced to first position. """ self._create_product_images() with MockRequest(self.product.env, website=self.website): images = self.product._get_images() images[2].video_url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ" i1, i2, i3, i4, i5, i6 = self._get_product_image_data() with self.assertRaises(ValidationError): self.WebsiteSaleController.resequence_product_image( images[2]._name, images[2].id, 'first', ) self.env['product.image'].invalidate_model() self.assertListEqual(self._get_product_image_data(), [i1, i2, i3, i4, i5, i6]) def test_resequence_video_replace_first(self): """ A video can't replace an image that was resequenced away from first position. """ self._create_product_images() with MockRequest(self.product.env, website=self.website): images = self.product._get_images() images[1].video_url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ" i1, i2, i3, i4, i5, i6 = self._get_product_image_data() with self.assertRaises(ValidationError): self.WebsiteSaleController.resequence_product_image( images[0]._name, images[0].id, 'right', ) self.env['product.image'].invalidate_model() self.assertListEqual(self._get_product_image_data(), [i1, i2, i3, i4, i5, i6]) @tagged('post_install', '-at_install') class TestWebsiteSaleEditor(HttpCaseWithWebsiteUser): @classmethod def setUpClass(cls): super().setUpClass() cls.user_website_user.group_ids += cls.env.ref('sales_team.group_sale_manager') cls.user_website_user.group_ids += cls.env.ref('product.group_product_manager') def test_category_page_and_products_snippet(self): category = self.env['product.public.category'].create({ 'name': 'Test Category', }) self.env['product.public.category'].create({ 'parent_id': category.id, 'name': 'Test Category - Child', }) self.env['product.template'].create({ 'name': 'Test Product', 'website_published': True, 'public_categ_ids': [ Command.link(category.id) ] }) self.env['product.template'].create({ 'name': 'Test Product Outside Category', 'website_published': True, }) self.start_tour(self.env['website'].get_client_action_url('/shop'), 'category_page_and_products_snippet_edition', login="admin") self.start_tour('/shop', 'category_page_and_products_snippet_use', login=None) def test_website_sale_restricted_editor_ui(self): self.env['product.template'].create({ 'name': 'Test Product', 'website_sequence': 0, 'website_published': True, }) self.start_tour(self.env['website'].get_client_action_url('/shop'), 'website_sale_restricted_editor_ui', login="website_user") @tagged('post_install', '-at_install') class TestProductVideoUpload(HttpCase): @classmethod def setUpClass(cls): super().setUpClass() cls.website = cls.env['website'].browse(1) cls.WebsiteSaleController = WebsiteSale() cls.product = cls.env['product.product'].create({ 'name': 'Test Video Product', 'standard_price': 100.0, 'list_price': 120.0, 'website_published': True, }) cls.video_data = { 'src': 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', # A placeholder video URL 'name': 'Test Video', } def _upload_video(self): with MockRequest(self.product.env, website=self.website): self.WebsiteSaleController.add_product_media( [{'src': self.video_data['src'], 'name': self.video_data['name']}], 'video', self.product.id, self.product.product_tmpl_id.id, ) def test_video_upload(self): # Upload a video to the product self._upload_video() # Retrieve the product's media data video_url = self.product.product_template_image_ids[0].video_url image_1920 = self.product.product_template_image_ids[0].image_1920 # Check that the video URL and thumbnail are correctly saved self.assertEqual(video_url, self.video_data['src']) self.assertIsNotNone(image_1920) # Ensure a thumbnail was generated # Verify that the video was added as part of the media self.assertEqual(len(self.product.product_template_image_ids), 1) def test_video_upload_invalid(self): # Try to upload invalid video data (e.g., empty src) with MockRequest(self.product.env, website=self.website): with self.assertRaises(ValidationError): self.WebsiteSaleController.add_product_media( [{'src': '', 'name': 'Invalid Video'}], 'video', self.product.id, self.product.product_tmpl_id.id, )