""" ARCHES - a program developed to inventory and manage immovable cultural heritage. Copyright (C) 2013 J. Paul Getty Trust and World Monuments Fund This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . """ import os import json from http import HTTPStatus from tests import test_settings from arches.app.models.system_settings import settings from tests.base_test import ArchesTestCase from django.contrib.auth import get_user_model from django.urls import reverse from arches.app.models.graph import Graph from arches.app.models.models import Node, NodeGroup, GraphModel, CardModel, Edge from arches.app.utils.betterJSONSerializer import JSONSerializer, JSONDeserializer # these tests can be run from the command line via # python manage.py test tests.views.graph_manager_tests --settings="tests.test_settings" class GraphManagerViewTests(ArchesTestCase): @classmethod def setUpTestData(cls): super().setUpTestData() cls.loadOntology() cls.NODE_NODETYPE_GRAPHID = "22000000-0000-0000-0000-000000000001" if not Graph.objects.filter(graphid=cls.NODE_NODETYPE_GRAPHID).exists(): # Node Branch graph_dict = { "author": "Arches", "color": None, "deploymentdate": None, "deploymentfile": None, "description": "Represents a single node in a graph", "graphid": "22000000-0000-0000-0000-000000000000", "iconclass": "fa fa-circle", "isresource": False, "name": "Node", "ontology_id": "e6e8db47-2ccf-11e6-927e-b8f6b115d7dd", "subtitle": "Represents a single node in a graph.", "version": "v1", } GraphModel.objects.create(**graph_dict).save() node_dict = { "config": None, "datatype": "semantic", "description": "Represents a single node in a graph", "graph_id": "22000000-0000-0000-0000-000000000000", "isrequired": False, "issearchable": True, "istopnode": True, "name": "Node", "nodegroup_id": None, "nodeid": "20000000-0000-0000-0000-100000000000", "ontologyclass": "http://www.cidoc-crm.org/cidoc-crm/E1_CRM_Entity", } Node.objects.create(**node_dict).save() # Node/Node Type Branch graph_dict = { "author": "Arches", "color": None, "deploymentdate": None, "deploymentfile": None, "description": "Represents a node and node type pairing", "graphid": "22000000-0000-0000-0000-000000000001", "iconclass": "fa fa-angle-double-down", "isresource": False, "name": "Node/Node Type", "ontology_id": "e6e8db47-2ccf-11e6-927e-b8f6b115d7dd", "subtitle": "Represents a node and node type pairing", "version": "v1", } GraphModel.objects.create(**graph_dict).save() nodegroup_dict = { "cardinality": "n", "legacygroupid": "", "nodegroupid": "20000000-0000-0000-0000-100000000001", "parentnodegroup_id": None, } NodeGroup.objects.create(**nodegroup_dict).save() card_dict = { "active": True, "cardid": "bf9ea150-3eaa-11e8-8b2b-c3a348661f61", "description": "Represents a node and node type pairing", "graph_id": "22000000-0000-0000-0000-000000000001", "helpenabled": False, "helptext": None, "helptitle": None, "instructions": "", "name": "Node/Node Type", "nodegroup_id": "20000000-0000-0000-0000-100000000001", "sortorder": None, "visible": True, } CardModel.objects.create(**card_dict).save() nodes = [ { "config": None, "datatype": "string", "description": "", "graph_id": "22000000-0000-0000-0000-000000000001", "isrequired": False, "issearchable": True, "istopnode": True, "name": "Node", "nodegroup_id": "20000000-0000-0000-0000-100000000001", "nodeid": "20000000-0000-0000-0000-100000000001", "ontologyclass": "http://www.cidoc-crm.org/cidoc-crm/E1_CRM_Entity", }, { "config": {"rdmCollection": None}, "datatype": "concept", "description": "", "graph_id": "22000000-0000-0000-0000-000000000001", "isrequired": False, "issearchable": True, "istopnode": False, "name": "Node Type", "nodegroup_id": "20000000-0000-0000-0000-100000000001", "nodeid": "20000000-0000-0000-0000-100000000002", "ontologyclass": "http://www.cidoc-crm.org/cidoc-crm/E55_Type", }, ] for node in nodes: Node.objects.create(**node).save() edges_dict = { "description": None, "domainnode_id": "20000000-0000-0000-0000-100000000001", "edgeid": "22200000-0000-0000-0000-000000000001", "graph_id": "22000000-0000-0000-0000-000000000001", "name": None, "ontologyproperty": "http://www.cidoc-crm.org/cidoc-crm/P2_has_type", "rangenode_id": "20000000-0000-0000-0000-100000000002", } Edge.objects.create(**edges_dict).save() graph = Graph.new() graph.name = "TEST GRAPH" graph.subtitle = "ARCHES TEST GRAPH" graph.author = "Arches" graph.description = "ARCHES TEST GRAPH" graph.ontology_id = "e6e8db47-2ccf-11e6-927e-b8f6b115d7dd" graph.version = "v1.0.0" graph.iconclass = "fa fa-building" graph.nodegroups = [] graph.root.ontologyclass = "http://www.cidoc-crm.org/cidoc-crm/E1_CRM_Entity" graph.save() graph.root.name = "ROOT NODE" graph.root.description = "Test Root Node" graph.root.datatype = "semantic" graph.root.save() graph = Graph.objects.get(graphid=graph.pk) cls.appended_branch_1 = graph.append_branch( "http://www.ics.forth.gr/isl/CRMdig/L54_is_same-as", graphid=cls.NODE_NODETYPE_GRAPHID, ) cls.appended_branch_2 = graph.append_branch( "http://www.ics.forth.gr/isl/CRMdig/L54_is_same-as", graphid=cls.NODE_NODETYPE_GRAPHID, ) graph.save() cls.ROOT_ID = graph.root.nodeid cls.GRAPH_ID = str(graph.pk) cls.NODE_COUNT = 5 cls.graph = graph def test_graph_manager(self): """ Test the graph manager view """ self.client.login(username="admin", password="admin") url = reverse("graph", kwargs={"graphid": ""}) response = self.client.get(url) graphs = json.loads(response.context["graphs"]) self.assertEqual( len(graphs), GraphModel.objects.all() .exclude(graphid=settings.SYSTEM_SETTINGS_RESOURCE_MODEL_ID) .count(), ) url = reverse("graph_designer", kwargs={"graphid": self.GRAPH_ID}) response = self.client.get(url) graph = json.loads(response.context["graph_json"]) node_count = len(graph["nodes"]) self.assertEqual(node_count, self.NODE_COUNT) edge_count = len(graph["edges"]) self.assertEqual(edge_count, self.NODE_COUNT - 1) def test_graph_settings(self): """ Test the graph settings view """ self.client.login(username="admin", password="admin") url = reverse("graph_settings", kwargs={"graphid": self.GRAPH_ID}) response = self.client.get(url) self.assertEqual(response.status_code, 200) graph = json.loads(response.content) graph["name"] = "new graph name" graph["root"] = {"datatype": "semantic", "config": None} graph["nodegroups"] = [] post_data = {"graph": graph, "relatable_resource_ids": [str(self.ROOT_ID)]} post_data = JSONSerializer().serialize(post_data) content_type = "application/x-www-form-urlencoded" response = self.client.post(url, post_data, content_type) response_json = json.loads(response.content) self.assertTrue(response_json["success"]) self.assertEqual(response_json["graph"]["name"], "new graph name") self.assertTrue(str(self.ROOT_ID) in response_json["relatable_resource_ids"]) def test_node_update(self): """ Test updating a node (HERITAGE_RESOURCE_PLACE) via node view """ self.client.login(username="admin", password="admin") url = reverse("update_node", kwargs={"graphid": self.GRAPH_ID}) node = Node.objects.get(nodeid=str(self.appended_branch_1.root.pk)) node.name = "new node name" nodegroup, created = NodeGroup.objects.get_or_create( pk=str(self.appended_branch_1.root.pk) ) node.nodegroup = nodegroup post_data = JSONSerializer().serializeToPython(node) post_data["parentproperty"] = ( "http://www.ics.forth.gr/isl/CRMdig/L54_is_same-as" ) content_type = "application/x-www-form-urlencoded" response = self.client.post( url, JSONSerializer().serialize(post_data), content_type ) response_json = json.loads(response.content) node_count = 0 for node in response_json["nodes"]: if node["nodeid"] == str(self.appended_branch_1.root.pk): self.assertEqual(node["name"], "new node name") if node["nodegroup_id"] == str(self.appended_branch_1.root.pk): node_count = node_count + 1 self.assertEqual(node_count, 2) node_ = Node.objects.get(nodeid=str(self.appended_branch_1.root.pk)) self.assertEqual(node_.name, "new node name") self.assertTrue(node_.is_collector) def test_node_delete(self): """ Test delete a node (HERITAGE_RESOURCE_PLACE) via node view """ self.client.login(username="admin", password="admin") node = Node.objects.get(nodeid=str(self.appended_branch_1.root.pk)) url = reverse("delete_node", kwargs={"graphid": self.GRAPH_ID}) post_data = JSONSerializer().serialize({"nodeid": node.nodeid}) response = self.client.delete(url, post_data) self.assertEqual(response.status_code, 200) graph = Graph.objects.get(graphid=self.GRAPH_ID).serialize() self.assertEqual(len(graph["nodes"]), 3) self.assertEqual(len(graph["edges"]), 2) def test_update_node_malicious_config_key(self): self.client.login(username="admin", password="admin") url = reverse("update_node", kwargs={"graphid": self.GRAPH_ID}) node = Node.objects.get(nodeid=self.appended_branch_1.root.pk) nodegroup, _created = NodeGroup.objects.get_or_create( pk=self.appended_branch_1.root.pk ) node.nodegroup = nodegroup node.config = { "placeholder": {"en": "Enter text"}, "i18n_properties": ["placeholder"], "malicious'": None, } data = JSONSerializer().serializeToPython(node) data["parentproperty"] = "http://www.ics.forth.gr/isl/CRMdig/L54_is_same-as" with self.assertLogs("django.request", level="WARNING"): response = self.client.post(url, data, content_type="application/json") self.assertContains( response, "aliases cannot contain", # TODO: should become BAD_REQUEST eventually status_code=HTTPStatus.INTERNAL_SERVER_ERROR, ) def test_graph_clone_on_unpublished_graph(self): """ Test clone a graph (HERITAGE_RESOURCE) via view """ self.client.login(username="admin", password="admin") url = reverse("clone_graph", kwargs={"graphid": self.GRAPH_ID}) post_data = {} content_type = "application/x-www-form-urlencoded" response = self.client.post(url, post_data, content_type) response_json = json.loads(response.content) self.assertEqual(len(response_json["nodes"]), self.NODE_COUNT) cloned_graph = Graph.objects.get(pk=response_json["graphid"]) original_graph_node_ids = [str(node.pk) for node in self.graph.nodes.values()] cloned_graph_node_ids = [str(node.pk) for node in cloned_graph.nodes.values()] self.assertFalse(set(original_graph_node_ids) & set(cloned_graph_node_ids)) def test_graph_clone_on_published_graph(self): """ Test clone a graph (HERITAGE_RESOURCE) via view """ self.client.login(username="admin", password="admin") user_id = self.client.session["_auth_user_id"] logged_in_user = get_user_model().objects.get(pk=user_id) self.graph.publish(user=logged_in_user) url = reverse("clone_graph", kwargs={"graphid": self.GRAPH_ID}) post_data = {} content_type = "application/x-www-form-urlencoded" response = self.client.post(url, post_data, content_type) response_json = json.loads(response.content) self.assertEqual(len(response_json["nodes"]), self.NODE_COUNT) cloned_graph = Graph.objects.get(pk=response_json["graphid"]) original_graph_node_ids = [str(node.pk) for node in self.graph.nodes.values()] cloned_graph_node_ids = [str(node.pk) for node in cloned_graph.nodes.values()] self.assertFalse(set(original_graph_node_ids) & set(cloned_graph_node_ids)) def test_new_graph(self): """ Test creating a new graph via the view """ self.client.login(username="admin", password="admin") url = reverse("new_graph") post_data = JSONSerializer().serialize({"isresource": False}) content_type = "application/x-www-form-urlencoded" response = self.client.post(url, post_data, content_type) response_json = json.loads(response.content) self.assertEqual(len(response_json["nodes"]), 1) self.assertFalse(response_json["isresource"]) def test_delete_graph(self): """ test the graph delete method """ self.client.login(username="admin", password="admin") url = reverse("delete_graph", kwargs={"graphid": self.GRAPH_ID}) response = self.client.delete(url) node_count = Node.objects.filter(graph_id=self.GRAPH_ID).count() edge_count = Edge.objects.filter(graph_id=self.GRAPH_ID).count() self.assertEqual(node_count, 0) self.assertEqual(edge_count, 0) def test_branch_export_on_unpublished_graph(self): self.client.login(username="admin", password="admin") url = reverse("export_branch", kwargs={"graphid": self.GRAPH_ID}) node = [value for value in self.graph.nodes.values()][1] post_data = JSONSerializer().serialize(node) content_type = "application/x-www-form-urlencoded" response = self.client.post(url, post_data, content_type) exported_branch = Graph.objects.get(pk=response.json()["graphid"]) original_graph_node_ids = [str(node.pk) for node in self.graph.nodes.values()] export_branch_node_ids = [ str(node.pk) for node in exported_branch.nodes.values() ] self.assertFalse(set(original_graph_node_ids) & set(export_branch_node_ids)) def test_branch_export_on_published_graph(self): self.client.login(username="admin", password="admin") user_id = self.client.session["_auth_user_id"] logged_in_user = get_user_model().objects.get(pk=user_id) self.graph.publish(user=logged_in_user) url = reverse("export_branch", kwargs={"graphid": self.GRAPH_ID}) node = [value for value in self.graph.nodes.values()][1] post_data = JSONSerializer().serialize(node) content_type = "application/x-www-form-urlencoded" response = self.client.post(url, post_data, content_type) exported_branch = Graph.objects.get(pk=response.json()["graphid"]) original_graph_node_ids = [str(node.pk) for node in self.graph.nodes.values()] export_branch_node_ids = [ str(node.pk) for node in exported_branch.nodes.values() ] self.assertFalse(set(original_graph_node_ids) & set(export_branch_node_ids)) def test_graph_export(self): """ test graph export method """ self.client.login(username="admin", password="admin") url = reverse("export_graph", kwargs={"graphid": self.GRAPH_ID}) response = self.client.get(url) graph_json = json.loads(response._container[0]) node_count = len(graph_json["graph"][0]["nodes"]) self.assertTrue(response._container[0]) self.assertEqual(node_count, self.NODE_COUNT) self.assertEqual(response.headers["content-type"], "json/plain") def test_graph_import(self): """ test graph import method """ self.client.login(username="admin", password="admin") url = reverse("import_graph") with open( os.path.join( list(test_settings.RESOURCE_GRAPH_LOCATIONS)[0], "Cardinality Test Model.json", ) ) as f: response = self.client.post(url, {"importedGraph": f}) self.assertIsNotNone(response.content) # Note: If you change the imported_json array to make this test work you should also change the expected # response in the import_graph method in arches.app.media.js.views.graph.js imported_json = JSONDeserializer().deserialize(response.content) self.assertEqual(imported_json[0], []) self.assertEqual(imported_json[1]["graphs_saved"], 1) self.assertEqual(imported_json[1]["name"], "Cardinality Test Model")