def test_complex_propagation_count(self): """Test multiple object ACL propagation. This test is meant to catch invalid ACL entries for propagation that can not happen. Example for this is propagation control -> relationships -> document. In that propagation rule we should only propagate control acl entries to relationships to documents. But what can happen is; when a control has multiple relationships, some to objects and only one to document, all of those relationships would get an ACL entry even though in some cases that is a propagation dead end. Setup for this test is: Objects: control regulation objective program audit assessment, assessment_2 Relationships: control - regulation control - objective objective - regulations program - control, regulation, objective, audit audit - assessment, assessment_2, audit - control-snapshot, regulation-snapshot, objective-snapshot control_snapshot - regulation_snapshot control_snapshot - objective_snapshot objective_snapshot - regulations_snapshot document - regulation, objective, control evidence - assessment """ with factories.single_commit(): person = factories.PersonFactory() control = factories.ControlFactory() regulation = factories.RegulationFactory() objective = factories.ObjectiveFactory() normal_objects = [control, regulation, objective] for obj1, obj2 in itertools.combinations(normal_objects, 2): factories.RelationshipFactory(source=obj1, destination=obj2) assessment = factories.AssessmentFactory() assessment_2 = factories.AssessmentFactory(audit=assessment.audit) factories.RelationshipFactory( source=assessment, destination=assessment.audit, ) factories.RelationshipFactory( source=assessment_2, destination=assessment.audit, ) factories.RelationshipFactory( source=assessment.audit, destination=assessment.audit.program, ) for obj in normal_objects: factories.RelationshipFactory( source=assessment.audit.program, destination=obj, ) snapshots = self._create_snapshots(assessment.audit, normal_objects) for snapshot in snapshots: factories.RelationshipFactory( source=assessment.audit, destination=snapshot, ) for obj1, obj2 in itertools.combinations(snapshots, 2): factories.RelationshipFactory(source=obj1, destination=obj2) for obj in normal_objects: document = factories.DocumentFactory() factories.RelationshipFactory(source=obj, destination=document) evidence = factories.EvidenceUrlFactory() factories.RelationshipFactory(source=evidence, destination=assessment) acl_entry = factories.AccessControlListFactory( person=person, ac_role=self.roles["Control"]["Admin"], object=control, ) propagation._propagate([acl_entry.id]) self.assertEqual( all_models.AccessControlList.query.count(), 3, # 1 for control, 1 for relationship to document and 1 for document. ) acl_entry = factories.AccessControlListFactory( person=person, ac_role=self.roles["Program"]["Program Editors"], object=assessment.audit.program, ) propagation._propagate([acl_entry.id]) self.assertEqual( all_models.AccessControlList.query.count(), 3 + 1 + 2 + 4 + 6 + 2 + 6 + 6 # 3 previous entries for control # 1 original program ACL entry # 2 for audit (relationship + audit propagation) # 4 for assessments (2 assessments and 2 relationships for them) # 6 for snapshots (3 snapshots with relationships) # 2 assessment document with relationships # 6 for normal objects # 6 for normal object documents )
def test_changing_assessment_urls_triggers_notifications(self, _): """Test that changing Assessment URLs results in change notification. Adding (removing) a URL to (from) Assessment should be detected and considered an Assessment change. """ self.import_file("assessment_with_templates.csv") asmts = {asmt.slug: asmt for asmt in Assessment.query} self.client.get("/_notifications/send_daily_digest") self.assertEqual(self._get_notifications().count(), 0) asmt = Assessment.query.get(asmts["A 5"].id) # add a URL, there should be no notifications because the Assessment # has not been started yet url = factories.DocumentFactory(link="www.foo.com") response, relationship = self.objgen.generate_relationship(url, asmt) self.assertEqual(response.status_code, 201) change_notifs = self._get_notifications(notif_type="assessment_updated") self.assertEqual(change_notifs.count(), 0) asmt = Assessment.query.get(asmts["A 5"].id) # move Assessment to to "In Progress" state and clear notifications self.api_helper.modify_object( asmt, {"status": Assessment.PROGRESS_STATE}) self.client.get("/_notifications/send_daily_digest") self.assertEqual(self._get_notifications().count(), 0) asmt = Assessment.query.get(asmts["A 5"].id) # add another URL, change notification should be created url2 = factories.DocumentFactory(link="www.bar.com") response, _ = self.objgen.generate_relationship(url2, asmt) self.assertEqual(response.status_code, 201) change_notifs = self._get_notifications(notif_type="assessment_updated") self.assertEqual(change_notifs.count(), 1) # clear notifications, delete a URL, test for change notification self.client.get("/_notifications/send_daily_digest") self.assertEqual(change_notifs.count(), 0) asmt = Assessment.query.get(asmts["A 5"].id) self.api_helper.delete(relationship) change_notifs = self._get_notifications(notif_type="assessment_updated") self.assertEqual(change_notifs.count(), 1) # changing URLs if completed should result in "reopened" notification asmt = Assessment.query.get(asmts["A 5"].id) self.api_helper.modify_object( asmt, {"status": Assessment.FINAL_STATE}) self.client.get("/_notifications/send_daily_digest") self.assertEqual(self._get_notifications().count(), 0) asmt = Assessment.query.get(asmts["A 5"].id) url = factories.DocumentFactory(link="www.abc.com") response, relationship = self.objgen.generate_relationship(url, asmt) self.assertEqual(response.status_code, 201) reopened_notifs = self._get_notifications(notif_type="assessment_reopened") self.assertEqual(reopened_notifs.count(), 1)
def test_complex_propagation_count(self): """Test multiple object ACL propagation. This test is meant to catch invalid ACL entries for propagation that can not happen. Example for this is propagation control -> relationships -> document. In that propagation rule we should only propagate control acl entries to relationships to documents. But what can happen is; when a control has multiple relationships, some to objects and only one to document, all of those relationships would get an ACL entry even though in some cases that is a propagation dead end. Setup for this test is: Objects: control regulation objective program audit assessment, assessment_2 Relationships: control - regulation control - objective objective - regulations program - control, regulation, objective, audit audit - assessment, assessment_2, audit - control-snapshot, regulation-snapshot, objective-snapshot control_snapshot - regulation_snapshot control_snapshot - objective_snapshot objective_snapshot - regulations_snapshot document - regulation, objective, control evidence - assessment """ # pylint: disable=too-many-locals with factories.single_commit(): control = factories.ControlFactory() regulation = factories.RegulationFactory() objective = factories.ObjectiveFactory() normal_objects = [control, regulation, objective] for obj1, obj2 in itertools.combinations(normal_objects, 2): if control in (obj1, obj2): with mock.patch( 'ggrc.models.relationship.is_external_app_user', return_value=True): factories.RelationshipFactory(source=obj1, destination=obj2, is_external=True) else: factories.RelationshipFactory(source=obj1, destination=obj2) assessment = factories.AssessmentFactory() assessment_2 = factories.AssessmentFactory(audit=assessment.audit) factories.RelationshipFactory( source=assessment, destination=assessment.audit, ) factories.RelationshipFactory( source=assessment_2, destination=assessment.audit, ) factories.RelationshipFactory( source=assessment.audit, destination=assessment.audit.program, ) for obj in normal_objects: factories.RelationshipFactory( source=assessment.audit.program, destination=obj, ) snapshots = self._create_snapshots(assessment.audit, normal_objects) for snapshot in snapshots: factories.RelationshipFactory( source=assessment.audit, destination=snapshot, ) for obj1, obj2 in itertools.combinations(snapshots, 2): factories.RelationshipFactory(source=obj1, destination=obj2) for obj in normal_objects: document = factories.DocumentFactory() factories.RelationshipFactory(source=obj, destination=document) evidence = factories.EvidenceUrlFactory() factories.RelationshipFactory(source=evidence, destination=assessment) acl_entry = control._access_control_list[0] propagation._propagate([acl_entry.id], self.user_id) self.assertEqual( all_models.AccessControlList.query.filter( all_models.AccessControlList.parent_id.isnot(None)).count(), 2, # 1 for relationship to document and 1 for document. ) acl_entry = next( acl for acl in assessment.audit.program._access_control_list if acl.ac_role.name == "Program Editors") propagation._propagate([acl_entry.id], self.user_id) self.assertEqual( all_models.AccessControlList.query.filter( all_models.AccessControlList.parent_id.isnot(None)).count(), 2 + 2 + 4 + 6 + 2 + 6 + 6 # 2 previous entries for control # 2 for audit (relationship + audit propagation) # 4 for assessments (2 assessments and 2 relationships for them) # 6 for snapshots (3 snapshots with relationships) # 2 assessment document with relationships # 6 for normal objects # 6 for normal object documents )
def test_changing_assessment_attachment_triggers_notifications(self, _): """Test that changing Assessment attachment results in change notification. Adding (removing) an attachment to (from) Assessment should be detected and considered an Assessment change. """ self.import_file("assessment_with_templates.csv") asmts = {asmt.slug: asmt for asmt in Assessment.query} self.client.get("/_notifications/send_daily_digest") self.assertEqual(self._get_notifications().count(), 0) asmt = Assessment.query.get(asmts["A 5"].id) # add an attachment, there should be no notifications because the # Assessment has not been started yet pdf_doc = factories.DocumentFactory(title="foo.pdf") data = { "document": { "type": pdf_doc.type, "id": pdf_doc.id }, "documentable": { "type": asmt.type, "id": asmt.id } } response, obj_doc = self.objgen.generate_object(ObjectDocument, data=data) self.assertEqual(response.status_code, 201) change_notifs = self._get_notifications( notif_type="assessment_updated") self.assertEqual(change_notifs.count(), 0) asmt = Assessment.query.get(asmts["A 5"].id) # move Assessment to to "In Progress" state and clear notifications self.api_helper.modify_object(asmt, {"status": Assessment.PROGRESS_STATE}) self.client.get("/_notifications/send_daily_digest") self.assertEqual(self._get_notifications().count(), 0) asmt = Assessment.query.get(asmts["A 5"].id) # attach another document, change notification should be created image = factories.DocumentFactory(link="foobar.png") data = { "document": { "type": image.type, "id": image.id }, "documentable": { "type": asmt.type, "id": asmt.id } } response, _ = self.objgen.generate_object(ObjectDocument, data=data) self.assertEqual(response.status_code, 201) change_notifs = self._get_notifications( notif_type="assessment_updated") self.assertEqual(change_notifs.count(), 1) # clear notifications, remove an attachment, test for change notification self.client.get("/_notifications/send_daily_digest") self.assertEqual(change_notifs.count(), 0) asmt = Assessment.query.get(asmts["A 5"].id) self.api_helper.delete(obj_doc) change_notifs = self._get_notifications( notif_type="assessment_updated") self.assertEqual(change_notifs.count(), 1) # changing attachments completed should result in "reopened" notification asmt = Assessment.query.get(asmts["A 5"].id) self.api_helper.modify_object(asmt, {"status": Assessment.FINAL_STATE}) self.client.get("/_notifications/send_daily_digest") self.assertEqual(self._get_notifications().count(), 0) asmt = Assessment.query.get(asmts["A 5"].id) file_foo = factories.DocumentFactory(link="foo.txt") data = { "document": { "type": file_foo.type, "id": file_foo.id }, "documentable": { "type": asmt.type, "id": asmt.id } } response, _ = self.objgen.generate_object(ObjectDocument, data=data) self.assertEqual(response.status_code, 201) reopened_notifs = self._get_notifications( notif_type="assessment_reopened") self.assertEqual(reopened_notifs.count(), 1)
def test_multiply_actions(self): """Test multiply actions""" assessment = factories.AssessmentFactory() doc_map = factories.DocumentFactory(link="google1.com") doc_del = factories.DocumentFactory(link="google2.com") factories.RelationshipFactory(source=assessment, destination=doc_del) ca_def = factories.CustomAttributeDefinitionFactory( title="def1", definition_type="assessment", definition_id=assessment.id, attribute_type="Dropdown", multi_choice_options="no,yes", multi_choice_mandatory="0,3" ) ca_val = factories.CustomAttributeValueFactory( custom_attribute=ca_def, attributable=assessment, attribute_value="no" ) response = self.api.put(assessment, { "custom_attribute_values": [ { "id": ca_val.id, "custom_attribute_id": ca_def.id, "attribute_value": "yes", "type": "CustomAttributeValue", }], "actions": {"add_related": [ { "id": None, "type": "Document", "document_type": "EVIDENCE", "title": "evidence1", "link": "google3.com", }, { "id": doc_map.id, "type": "Document", }, { "id": None, "type": "Comment", "description": "comment1", "custom_attribute_definition_id": ca_def.id, } ], "remove_related": [ { "id": doc_del.id, "type": "Document", }]}}) self.assert200(response) preconditions_failed = response.json["assessment"]["preconditions_failed"] self.assertIs(preconditions_failed, True) assessment_by_url = self.simple_query( "Assessment", expression=["evidence url", "~", "google1.com"] ) self.assertEqual(len(assessment_by_url), 1) assessment_by_url = self.simple_query( "Assessment", expression=["evidence url", "~", "google2.com"] ) self.assertFalse(assessment_by_url) assessment_by_evidence = self.simple_query( "Assessment", expression=["evidence file", "~", "google3.com"] ) self.assertEqual(len(assessment_by_evidence), 1) assessment_by_comment = self.simple_query( "Assessment", expression=["comment", "~", "comment1"] ) self.assertEqual(len(assessment_by_comment), 1)