diff --git a/ngen/models/taxonomy.py b/ngen/models/taxonomy.py index 1fe43215..a0435357 100644 --- a/ngen/models/taxonomy.py +++ b/ngen/models/taxonomy.py @@ -71,6 +71,13 @@ def slugify(self): def get_ancestors_reports(self, flat=True): reports = self.get_ancestors_related(lambda obj: obj.reports.all()) return [report for report_list in reports for report in report_list] + #nuevo cambio# + def get_matching_report(self, lang): + rep = self.reports.filter(lang=lang) + if rep.exists(): + return rep[:1] + else: + return [r for r in self.get_ancestors_reports() if r.lang == lang][:1] class Meta: db_table = "taxonomy" diff --git a/ngen/templatetags/ngen_tags.py b/ngen/templatetags/ngen_tags.py index 13ef83d4..9ef126b8 100644 --- a/ngen/templatetags/ngen_tags.py +++ b/ngen/templatetags/ngen_tags.py @@ -51,15 +51,10 @@ def encode_static(path, encoding="base64", file_type="image"): def get_encoded_logo(): return encode_static(settings.LOGO_WIDE_PATH) - + #nuevo cambio @register.simple_tag def get_matching_report(taxonomy, lang): - rep = taxonomy.reports.filter(lang=lang) - if rep.exists(): - return rep[:1] - else: - return [r for r in taxonomy.get_ancestors_reports() if r.lang == lang][:1] - + return taxonomy.get_matching_report(lang) def get_file_data(file_path): with open(file_path, "rb") as f: diff --git a/ngen/tests/models/test_email_message.py b/ngen/tests/models/test_email_message.py index 55a42d17..b2738aff 100644 --- a/ngen/tests/models/test_email_message.py +++ b/ngen/tests/models/test_email_message.py @@ -2,10 +2,11 @@ Django Unit Tests for EmailMessage model """ -from django.test import TestCase -from django.utils import timezone +from django.test import TestCase # type: ignore +from django.utils import timezone # type: ignore from ngen.models import EmailMessage +from ngen.models.taxonomy import Report, Taxonomy class EmailMessageTest(TestCase): @@ -18,7 +19,6 @@ def setUpTestData(cls): """ EmailMessage model test setup """ - cls.root_email_message = EmailMessage.objects.create( root_message_id="<172654248025.81.10116784141945641235@cert.unlp.edu.ar>", parent_message_id=None, @@ -69,10 +69,7 @@ def setUpTestData(cls): subject="Test Another Subject", senders=[{"name": "CERT User", "email": "test@cert.unlp.edu.ar"}], recipients=[ - { - "name": "Another Victim Name", - "email": "another_victim@university.com", - } + {"name": "Another Victim Name", "email": "another_victim@university.com"} ], date=timezone.now(), body="some body", @@ -80,14 +77,26 @@ def setUpTestData(cls): send_attempt_failed=False, ) + # Setup for taxonomies and reports + cls.abuelo = Taxonomy.objects.create(name="Abuelo", type="incident") + cls.padre = Taxonomy.objects.create(name="Padre", type="incident", parent=cls.abuelo) + cls.hijo = Taxonomy.objects.create(name="Hijo", type="incident", parent=cls.padre) + + cls.reporte_abuelo = Report.objects.create( + taxonomy=cls.abuelo, lang="es", problem="Reporte abuelo" + ) + cls.reporte_padre = Report.objects.create( + taxonomy=cls.padre, lang="en", problem="Parent report" + ) + def test_email_message_creation(self): """ This will test EmailMessage creation """ - self.assertTrue(isinstance(self.root_email_message, EmailMessage)) - self.assertTrue(isinstance(self.email_message_2, EmailMessage)) - self.assertTrue(isinstance(self.email_message_3, EmailMessage)) - self.assertTrue(isinstance(self.email_message_4, EmailMessage)) + self.assertIsInstance(self.root_email_message, EmailMessage) + self.assertIsInstance(self.email_message_2, EmailMessage) + self.assertIsInstance(self.email_message_3, EmailMessage) + self.assertIsInstance(self.email_message_4, EmailMessage) def test_get_message_thread_by(self): """ @@ -102,7 +111,98 @@ def test_get_message_thread_by(self): "<172654248025.81.10116784141945641235@cert.unlp.edu.ar>" ) - self.assertQuerysetEqual(email_messages, expected_messages) + self.assertQuerysetEqual(email_messages, expected_messages, transform=lambda x: x) non_existent = EmailMessage.get_message_thread_by("non-existent") self.assertEqual(non_existent.count(), 0) + + def test_uses_parent_report_if_available(self): + """ + Verifies that if the parent has a report in the requested language, it is used. + """ + report = self.hijo.get_matching_report(lang="en") + self.assertEqual(report[0], self.reporte_padre) + + def test_does_not_use_parent_report_if_different_language(self): + """ + Verifies that the parent's report is not used if it is in a different language. + """ + report = self.hijo.get_matching_report(lang="es") + self.assertEqual(report[0], self.reporte_abuelo) + + def test_returns_empty_if_no_reports_in_language(self): + """ + Verifies that if no one in the hierarchy has a report in the requested language, it returns empty. + """ + report = self.hijo.get_matching_report(lang="fr") + self.assertEqual(report, []) + + def test_uses_own_report_if_available(self): + """ + Verifies that if the node itself has a report in the language, it is used instead of the parent's. + """ + own_report = Report.objects.create( + taxonomy=self.hijo, lang="es", problem="Soy yo" + ) + report = self.hijo.get_matching_report(lang="es") + self.assertEqual(report[0], own_report) + + def test_get_message_thread_by_ignores_invalid_messages(self): + """ + Verifies that messages with None as root_message_id are not returned in threads. + """ + bad_message = EmailMessage.objects.create( + root_message_id="testgetmessagethread@test.com", + parent_message_id=None, + message_id="", + references=[], + subject="Broken", + senders=[], + recipients=[], + date=timezone.now(), + body="bad", + sent=False, + send_attempt_failed=True, + ) + result = EmailMessage.get_message_thread_by("non-existent-id") + self.assertNotIn(bad_message, result) + + def test_email_message_references_chain(self): + """ + Verifies that the references field contains the expected message chain. + """ + expected_refs = [ + "<172654248025.81.10116784141945641235@cert.unlp.edu.ar>", + "<172654293755.81.289851525536098366@cert.unlp.edu.ar>", + ] + self.assertEqual(self.email_message_3.references, expected_refs) + + def test_get_message_thread_by_returns_empty_for_unknown_id(self): + """ + Ensures that querying a thread with a non-existent message ID returns an empty queryset. + """ + result = EmailMessage.get_message_thread_by("non-existent-id") + self.assertEqual(result.count(), 0) + + def test_email_message_sent_flags(self): + """ + Tests the sent and send_attempt_failed flags are correctly set on creation. + """ + self.assertTrue(self.root_email_message.sent) + self.assertFalse(self.root_email_message.send_attempt_failed) + + def test_message_thread_ordering(self): + """ + Ensures that messages returned in a thread are in the expected order. + """ + messages = EmailMessage.get_message_thread_by( + self.root_email_message.message_id + ) + self.assertEqual( + [m.message_id for m in messages], + [ + self.root_email_message.message_id, + self.email_message_2.message_id, + self.email_message_3.message_id, + ], + ) diff --git a/ngen/tests/models/test_events.py b/ngen/tests/models/test_events.py index ef3111ae..ef5749c6 100644 --- a/ngen/tests/models/test_events.py +++ b/ngen/tests/models/test_events.py @@ -4,7 +4,7 @@ import uuid -from django.test import TestCase +from django.test import TestCase # type: ignore from ngen.models import ( Event, diff --git a/ngen/tests/models/test_taxonomy.py b/ngen/tests/models/test_taxonomy.py index 5578af80..7848666b 100644 --- a/ngen/tests/models/test_taxonomy.py +++ b/ngen/tests/models/test_taxonomy.py @@ -1,15 +1,15 @@ -from django.core.exceptions import ValidationError -from django.test import TestCase +from django.core.exceptions import ValidationError # type: ignore +from django.test import TestCase # type: ignore from ngen.models import Taxonomy, TaxonomyGroup +from ngen.models.taxonomy import Report class TaxonomyTestCase(TestCase): - @classmethod def setUpTestData(cls): """ - Create instances of Taxonomy + Create instances of Taxonomy for testing. """ cls.parent = Taxonomy.objects.create(type="vulnerability", name="Parent") cls.child1 = Taxonomy.objects.create( @@ -56,159 +56,145 @@ def setUpTestData(cls): parent=cls.p2_child1, alias_of=cls.aNode_child2, ) + cls.setUpReports() + @classmethod + def setUpReports(cls): + """ + Create example Taxonomy and Reports for testing. + """ + cls.taxonomy = Taxonomy.objects.create( + name="Test Taxonomy", type="incident" + ) + Report.objects.create( + taxonomy=cls.taxonomy, lang="en", problem="Test problem" + ) + Report.objects.create( + taxonomy=cls.taxonomy, lang="es", problem="Problema de prueba" + ) - # self.aNode = Taxonomy.objects.create(type='vulnerability' name= 'Node') + def test_get_matching_report_in_english(self): + """ + Test: Retrieve matching report in English. + """ + report = self.taxonomy.get_matching_report(lang="en") + self.assertEqual(report[0].problem, "Test problem") - # def test_duplicated_taxonomy(self): - # """ - # Test that taxonomies are not duplicated. This test does not work because slugs unique key constraint is preventing - # a taxonomy with same name and different slug to exist. - # """ - # with self.assertRaises(UniqueViolation): - # # Create taxonomies with the same name and different slugs - # taxonomy1 = Taxonomy.objects.create(type='vulnerability', name='Ejemplo', slug='parent-test-1') - # taxonomy2 = Taxonomy.objects.create(type='vulnerability', name='Ejemplo', slug='parent-test-2') + def test_get_matching_report_in_spanish(self): + """ + Test: Retrieve matching report in Spanish. + """ + report = self.taxonomy.get_matching_report(lang="es") + self.assertEqual(report[0].problem, "Problema de prueba") - # def test_unique_slug_creation(self): - # """ - # Test: Slugs are created correctly and are unique. Can't create two slugs with the same name. - # """ - # with self.assertRaises(UniqueViolation): - # # Create taxonomies with the same slugs - # Slug1 = Taxonomy.objects.create(type='vulnerability', name="Test Name", slug="slug1") - # Slug2 = Taxonomy.objects.create(type='incident', name="Test Name2", slug="slug1") + def test_get_matching_report_not_found(self): + """ + Test: No matching report for the requested language. + """ + report = self.taxonomy.get_matching_report(lang="fr") + self.assertEqual(len(report), 0) def test_node_deletion(self): """ - Test: Simple deletion + Test: Simple deletion of a node. """ - self.aNode.delete() # Delete aNode - deleted_node = Taxonomy.objects.filter( - id=self.aNode.id - ).first() # Check for deleted node ID in the DB + self.aNode.delete() + deleted_node = Taxonomy.objects.filter(id=self.aNode.id).first() self.assertIsNone(deleted_node) def test_parent_deletion(self): """ - Test: If a root parent is deleted, all children must NOT have a parent + Test: If a root parent is deleted, all children must NOT have a parent. """ - children_queryset = ( - self.aRootNode.get_children() - ) # Getting children from Root before deletion + children = list(self.aRootNode.get_children()) + self.aRootNode.delete() - # Convert the queryset to a list or other data structure - children = list(children_queryset) - - self.aRootNode.delete() # Delete Root - - # Children should not have any parents for child in children: - child.refresh_from_db() # Refresh the child object from the database + child.refresh_from_db() self.assertIsNone(child.parent) def test_middle_node_deletion(self): """ - Test: If a middle node is deleted, parent and children should be connected + Test: If a middle node is deleted, parent and children should be connected. """ - # Saving parent_node = self.aNode.get_parent() - children_queryset = self.aNode.get_children() - - # Convert the queryset to list - children = list(children_queryset) + children = list(self.aNode.get_children()) self.assertIsNotNone(parent_node) - # Save the children data before deletion - children_before_deletion = children + self.aNode.delete() - self.aNode.delete() # Delete aNode - - # Refresh parent node from the database parent_node.refresh_from_db() - - # Verify that each child's parent is now the original parent of the middle node - for child_node in children_before_deletion: + for child_node in children: child_node.refresh_from_db() self.assertEqual(child_node.parent, parent_node) - # Verify that each child node is in the list of children of the original parent node - parent_node.refresh_from_db() - for child_node in children_before_deletion: - self.assertIn(child_node, parent_node.get_children()) - def test_taxonomy_cycles(self): """ - Test: if the taxonomy tree has cycles + Test: Ensure the taxonomy tree does not allow cycles. """ self.parent.parent = self.child2 - self.assertRaises( - Exception, self.parent.save - ) # ToDo: Espero excepcion especifica para cuando se produce un ciclo + with self.assertRaises(ValidationError): + self.parent.save() def test_taxonomy_root(self): """ - Test: Root node + Test: Verify root node has no parent. """ self.assertIsNone(self.parent.get_parent()) def test_taxonomy_is_alias(self): """ - Test: Alias node + Test: Verify alias nodes. """ self.assertTrue(self.aNode_child3.is_alias) self.assertFalse(self.aNode_child1.is_alias) def test_taxonomy_is_internal(self): """ - Test: Alias node + Test: Verify internal nodes. """ self.assertTrue(self.aNode_child3.is_internal) self.assertFalse(self.p2_child1.is_internal) def test_taxonomy_internal_creation_parent_and_alias(self): """ - Test: Alias parent + Test: Alias parent creation validation. """ - self.assertRaises( - ValidationError, - Taxonomy.objects.create, - type="vulnerability", - name="Alias Parent", - alias_of=self.aNode_child1, - parent=self.parent, - ) - - def test_taxonomy_external_creation_parent_and_alias(self): - """ - Test: Alias parent - """ - self.assertIsInstance( + with self.assertRaises(ValidationError): Taxonomy.objects.create( type="vulnerability", name="Alias Parent", alias_of=self.aNode_child1, - parent=None, - group=self.taxonomy_group2, - ), - Taxonomy, - ) + parent=self.parent, + ) - def test_taxonomy_alias_creation_parent_alias(self): + def test_taxonomy_external_creation_parent_and_alias(self): """ - Test: Alias parent + Test: External alias creation with a group. """ - self.assertRaises( - ValidationError, - Taxonomy.objects.create, + taxonomy = Taxonomy.objects.create( type="vulnerability", name="Alias Parent", - alias_of=self.p2_child1_child1, + alias_of=self.aNode_child1, + parent=None, + group=self.taxonomy_group2, ) + self.assertIsInstance(taxonomy, Taxonomy) + + def test_taxonomy_alias_creation_parent_alias(self): + """ + Test: Alias creation validation. + """ + with self.assertRaises(ValidationError): + Taxonomy.objects.create( + type="vulnerability", + name="Alias Parent", + alias_of=self.p2_child1_child1, + ) def test_taxonomy_alias_update_alias_is_alias(self): """ - Test: Alias parent + Test: Alias update validation. """ with self.assertRaises(ValidationError): self.aNode_child1.alias_of = self.aNode_child1 @@ -216,8 +202,48 @@ def test_taxonomy_alias_update_alias_is_alias(self): def test_taxonomy_alias_update_parent_is_alias(self): """ - Test: Alias parent + Test: Parent alias update validation. """ with self.assertRaises(ValidationError): self.aNode_child4.parent = self.aNode_child3 self.aNode_child4.save() + + def test_create_taxonomy(self): + """ + Test: Verify taxonomy creation. + """ + self.assertEqual(self.parent.name, "Parent") + self.assertEqual(self.child1.parent, self.parent) + + def test_is_internal_property(self): + """ + Test: Verify is_internal property. + """ + self.assertEqual(self.child1.is_internal, self.child1.group is None) + self.assertEqual(self.aNode_child3.is_internal, self.child1.group is None) + self.assertEqual(not self.parent2.is_internal, self.parent2.group is not None) + + def test_is_alias_property(self): + """ + Test: Verify is_alias property. + """ + self.assertTrue(self.aNode_child3.is_alias) + + def test_alias_of_itself_raises_error(self): + """ + Test: Taxonomy cannot be an alias of itself. + """ + with self.assertRaises(ValidationError): + alias = Taxonomy(name="Invalid Alias", alias_of=self.parent) + alias.full_clean() + + def test_alias_of_alias_raises_error(self): + """ + Test: Taxonomy cannot be an alias of another alias. + """ + with self.assertRaises(ValidationError): + alias = Taxonomy(name="Alias", alias_of=self.aNode_child3) + alias.full_clean() + + + \ No newline at end of file