from django.test import TestCase from django.urls import reverse from django.core.exceptions import ValidationError from apps.products.models import ( Article, ArticleSection, MainProduct, SubProduct, SubProductVersion, ) class MainProductModelTest(TestCase): def setUp(self): self.product = MainProduct.objects.create( name="Test Product", short_description="Short description", description="Full description of the test product.", ) def test_str_representation(self): self.assertEqual(str(self.product), "Test Product") def test_slug_auto_generated_on_create(self): self.assertEqual(self.product.slug, "test-product") def test_slug_not_overwritten_on_update(self): self.product.name = "Changed Name" self.product.save() self.assertEqual(self.product.slug, "test-product") def test_default_order_is_zero(self): self.assertEqual(self.product.order, 0) def test_default_is_active_is_true(self): self.assertTrue(self.product.is_active) def test_get_absolute_url(self): url = self.product.get_absolute_url() self.assertEqual(url, reverse("products:main_product_detail", kwargs={"main_slug": "test-product"})) def test_slug_uniqueness(self): from django.db import IntegrityError with self.assertRaises(IntegrityError): MainProduct.objects.create( name="Test Product", slug="test-product", short_description="Another", description="Another", ) def test_timestamps_set_on_create(self): self.assertIsNotNone(self.product.created_at) self.assertIsNotNone(self.product.updated_at) class SubProductModelTest(TestCase): def setUp(self): self.main_product = MainProduct.objects.create( name="Main Product", short_description="Short", description="Description", ) self.sub_product = SubProduct.objects.create( main_product=self.main_product, name="Sub Product", short_description="Sub short", description="Sub description.", ) def test_str_representation(self): self.assertIn("Main Product", str(self.sub_product)) self.assertIn("Sub Product", str(self.sub_product)) def test_slug_auto_generated(self): self.assertEqual(self.sub_product.slug, "sub-product") def test_get_absolute_url(self): url = self.sub_product.get_absolute_url() expected = reverse( "products:sub_product_detail", kwargs={"main_slug": "main-product", "sub_slug": "sub-product"}, ) self.assertEqual(url, expected) def test_cascade_delete_with_main_product(self): main_pk = self.main_product.pk sub_pk = self.sub_product.pk self.main_product.delete() self.assertFalse(SubProduct.objects.filter(pk=sub_pk).exists()) self.assertFalse(MainProduct.objects.filter(pk=main_pk).exists()) class ArticleModelTest(TestCase): def setUp(self): self.main_product = MainProduct.objects.create( name="Main", short_description="Short", description="Desc", ) self.sub_product = SubProduct.objects.create( main_product=self.main_product, name="Sub", short_description="Short", description="Desc", ) self.article = Article.objects.create( sub_product=self.sub_product, title="Test Article", description="Article body.", ) def test_str_representation(self): self.assertEqual(str(self.article), "Test Article") def test_cascade_delete_with_sub_product(self): article_pk = self.article.pk self.sub_product.delete() self.assertFalse(Article.objects.filter(pk=article_pk).exists()) def test_main_product_article(self): article = Article.objects.create( main_product=self.main_product, title="Product Article", description="Body.", ) self.assertEqual(article.main_product, self.main_product) self.assertIsNone(article.sub_product_id) def test_cascade_delete_with_main_product(self): article = Article.objects.create( main_product=self.main_product, title="Product Article", description="Body.", ) article_pk = article.pk self.main_product.delete() self.assertFalse(Article.objects.filter(pk=article_pk).exists()) def test_requires_exactly_one_parent(self): article = Article( main_product=self.main_product, sub_product=self.sub_product, title="Invalid", description="Body.", ) with self.assertRaises(ValidationError): article.full_clean() def test_requires_a_parent(self): article = Article(title="Orphan", description="Body.") with self.assertRaises(ValidationError): article.full_clean() class ReleaseFileStorageTest(TestCase): def test_save_overwrites_existing_release_file(self): from django.core.files.uploadedfile import SimpleUploadedFile from apps.products.models import MainProduct, SubProductVersion main = MainProduct.objects.create( name="Storage Test", short_description="s", description="d", ) version = SubProductVersion.objects.create( main_product=main, version="1.0", windows_download_file=SimpleUploadedFile("setup.exe", b"v1"), is_active=True, ) version.windows_download_file.save( "setup.exe", SimpleUploadedFile("setup.exe", b"v2"), save=True, ) version.refresh_from_db() with version.windows_download_file.open("rb") as handle: self.assertEqual(handle.read(), b"v2") class SubProductVersionModelTest(TestCase): def setUp(self): self.main_product = MainProduct.objects.create( name="Main", short_description="Short", description="Desc", ) self.sub_product = SubProduct.objects.create( main_product=self.main_product, name="Sub", short_description="Short", description="Desc", ) def test_main_product_version(self): version = SubProductVersion.objects.create( main_product=self.main_product, version="1.0.0", ) self.assertEqual(version.main_product, self.main_product) self.assertIsNone(version.sub_product_id) def test_requires_exactly_one_parent(self): version = SubProductVersion( main_product=self.main_product, sub_product=self.sub_product, version="1.0.0", ) with self.assertRaises(ValidationError): version.full_clean() def test_display_channel_label_uses_custom_text(self): from apps.products.models import RELEASE_CHANNEL_BETA version = SubProductVersion.objects.create( main_product=self.main_product, version="2.0", release_channel=RELEASE_CHANNEL_BETA, channel_label="Preview build", ) self.assertEqual(version.display_channel_label, "Preview build") self.assertEqual(version.display_channel_css_modifier, "custom") def test_display_channel_label_uses_preset(self): from apps.products.models import RELEASE_CHANNEL_PREVIOUS version = SubProductVersion.objects.create( sub_product=self.sub_product, version="1.0", release_channel=RELEASE_CHANNEL_PREVIOUS, ) self.assertEqual(version.display_channel_label, "Previous") self.assertTrue(version.is_previous_channel) class ArticleSectionModelTest(TestCase): def setUp(self): main = MainProduct.objects.create(name="M", short_description="s", description="d") sub = SubProduct.objects.create(main_product=main, name="S", short_description="s", description="d") self.article = Article.objects.create(sub_product=sub, title="Article", description="Desc") self.section = ArticleSection.objects.create( article=self.article, title="Link", value="https://example.com", ) def test_str_representation(self): self.assertIn("Article", str(self.section)) self.assertIn("Link", str(self.section)) def test_cascade_delete_with_article(self): section_pk = self.section.pk self.article.delete() self.assertFalse(ArticleSection.objects.filter(pk=section_pk).exists())