""" 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 . """ from uuid import UUID from arches.app.utils.betterJSONSerializer import JSONSerializer from tests.base_test import ArchesTestCase from django.db import connection from django.contrib.auth.models import User from django.db.utils import ProgrammingError from django.http import HttpRequest from arches.app.models.tile import Tile, TileValidationError from arches.app.models.resource import Resource from arches.app.models.models import ( CardModel, CardXNodeXWidget, Node, NodeGroup, ResourceXResource, TileModel, Widget, ) # these tests can be run from the command line via # python manage.py test tests.models.tile_model_tests --settings="tests.test_settings" class TileTests(ArchesTestCase): graph_fixtures = [ "rdf_export_document_model", "rdf_export_object_model", "Cardinality Test Model", ] @classmethod def setUpTestData(cls): super().setUpTestData() sql = """ INSERT INTO public.resource_instances(resourceinstanceid, legacyid, graphid, createdtime) VALUES ('40000000-0000-0000-0000-000000000000', '40000000-0000-0000-0000-000000000000', '2f7f8e40-adbc-11e6-ac7f-14109fd34195', '1/1/2000'); INSERT INTO node_groups(nodegroupid, legacygroupid, cardinality) VALUES ('99999999-0000-0000-0000-000000000001', '', 'n'); INSERT INTO node_groups(nodegroupid, legacygroupid, cardinality) VALUES ('32999999-0000-0000-0000-000000000000', '', 'n'); INSERT INTO node_groups(nodegroupid, legacygroupid, cardinality) VALUES ('19999999-0000-0000-0000-000000000000', '', 'n'); INSERT INTO node_groups(nodegroupid, legacygroupid, cardinality) VALUES ('21111111-0000-0000-0000-000000000000', '', 'n'); INSERT INTO node_groups(nodegroupid, legacygroupid, cardinality) VALUES ('41111111-0000-0000-0000-000000000000', '', 'n'); """ with connection.cursor() as cursor: cursor.execute(sql) def test_load_from_python_dict(self): """ Test that we can initialize a Tile object from a Python dictionary """ json = { "tiles": [ { "tiles": [], "resourceinstance_id": "40000000-0000-0000-0000-000000000000", "parenttile_id": "", "nodegroup_id": "19999999-0000-0000-0000-000000000000", "tileid": "", "data": { "20000000-0000-0000-0000-000000000004": { "en": {"value": "TEST 1", "direction": "ltr"}, "es": {"value": "PRUEBA 1", "direction": "ltr"}, }, "20000000-0000-0000-0000-000000000002": { "en": {"value": "TEST 2", "direction": "ltr"} }, "20000000-0000-0000-0000-000000000003": { "en": {"value": "TEST 3", "direction": "ltr"} }, }, }, { "tiles": [], "resourceinstance_id": "40000000-0000-0000-0000-000000000000", "parenttile_id": "", "nodegroup_id": "32999999-0000-0000-0000-000000000000", "tileid": "", "data": { "20000000-0000-0000-0000-000000000004": { "en": {"value": "TEST 4", "direction": "ltr"} }, "20000000-0000-0000-0000-000000000002": { "en": {"value": "TEST 5", "direction": "ltr"} }, }, }, ], "resourceinstance_id": "40000000-0000-0000-0000-000000000000", "parenttile_id": "", "nodegroup_id": "20000000-0000-0000-0000-000000000001", "tileid": "", "data": {}, } t = Tile(json) self.assertEqual(t.resourceinstance_id, "40000000-0000-0000-0000-000000000000") self.assertEqual(t.data, {}) self.assertEqual( t.tiles[0].data["20000000-0000-0000-0000-000000000004"]["en"]["value"], "TEST 1", ) def test_save(self): """ Test that we can save a Tile object back to the database """ login = self.client.login(username="admin", password="admin") json = { "tiles": [ { "tiles": [], "resourceinstance_id": "40000000-0000-0000-0000-000000000000", "parenttile_id": "", "nodegroup_id": "72048cb3-adbc-11e6-9ccf-14109fd34195", "tileid": "", "data": { "72048cb3-adbc-11e6-9ccf-14109fd34195": { "en": {"value": "TEST 1", "direction": "ltr"}, "es": {"value": "PRUEBA 1", "direction": "ltr"}, } }, } ], "resourceinstance_id": "40000000-0000-0000-0000-000000000000", "parenttile_id": "", "nodegroup_id": "7204869c-adbc-11e6-8bec-14109fd34195", "tileid": "", "data": {}, } t = Tile(json) t.save(index=False) tiles = Tile.objects.filter( resourceinstance_id="40000000-0000-0000-0000-000000000000" ) self.assertEqual(tiles.count(), 2) def test_simple_get(self): """ Test that we can get a Tile object """ json = { "resourceinstance_id": "40000000-0000-0000-0000-000000000000", "parenttile_id": "", "nodegroup_id": "72048cb3-adbc-11e6-9ccf-14109fd34195", "tileid": "", "data": { "72048cb3-adbc-11e6-9ccf-14109fd34195": { "en": {"value": "TEST 1", "direction": "ltr"}, "es": {"value": "PRUEBA 1", "direction": "ltr"}, } }, } t = Tile(json) t.save(index=False) t2 = Tile.objects.get(tileid=t.tileid) self.assertEqual(t.tileid, t2.tileid) self.assertEqual( t2.data["72048cb3-adbc-11e6-9ccf-14109fd34195"]["en"]["value"], "TEST 1" ) self.assertEqual( t2.data["72048cb3-adbc-11e6-9ccf-14109fd34195"]["es"]["value"], "PRUEBA 1" ) def test_create_new_authoritative(self): """ Test that a new authoritative tile is created when a user IS a reviwer. """ self.user = User.objects.get(username="admin") json = { "resourceinstance_id": "40000000-0000-0000-0000-000000000000", "parenttile_id": "", "nodegroup_id": "72048cb3-adbc-11e6-9ccf-14109fd34195", "tileid": "", "data": { "72048cb3-adbc-11e6-9ccf-14109fd34195": { "en": {"value": "AUTHORITATIVE", "direction": "ltr"} } }, } authoritative_tile = Tile(json) request = HttpRequest() request.user = self.user authoritative_tile.save(index=False, request=request) self.assertEqual(authoritative_tile.is_provisional(), False) def test_create_new_provisional(self): """ Test that a new provisional tile is created when a user IS NOT a reviwer. """ self.user = User.objects.create_user( username="testuser", password="TestingTesting123!" ) json = { "resourceinstance_id": "40000000-0000-0000-0000-000000000000", "parenttile_id": "", "nodegroup_id": "72048cb3-adbc-11e6-9ccf-14109fd34195", "tileid": "", "data": { "72048cb3-adbc-11e6-9ccf-14109fd34195": { "en": {"value": "PROVISIONAL", "direction": "ltr"} } }, } provisional_tile = Tile(json) request = HttpRequest() request.user = self.user provisional_tile.save(index=False, request=request) self.assertEqual(provisional_tile.is_provisional(), True) def test_save_provisional_from_athoritative(self): """ Test that a provisional edit is created when a user that is not a reviewer edits an athoritative tile """ json = { "tiles": [ { "tiles": [], "resourceinstance_id": "40000000-0000-0000-0000-000000000000", "parenttile_id": "", "nodegroup_id": "72048cb3-adbc-11e6-9ccf-14109fd34195", "tileid": "", "data": { "72048cb3-adbc-11e6-9ccf-14109fd34195": { "en": {"value": "AUTHORITATIVE", "direction": "ltr"} } }, } ], "resourceinstance_id": "40000000-0000-0000-0000-000000000000", "parenttile_id": "", "nodegroup_id": "7204869c-adbc-11e6-8bec-14109fd34195", "tileid": "", "data": {}, } t = Tile(json) t.save(index=False) self.user = User.objects.create_user( username="testuser", password="TestingTesting123!" ) login = self.client.login(username="testuser", password="TestingTesting123!") tiles = Tile.objects.filter( resourceinstance_id="40000000-0000-0000-0000-000000000000" ) provisional_tile = None for tile in tiles: provisional_tile = tile provisional_tile.data["72048cb3-adbc-11e6-9ccf-14109fd34195"] = { "en": {"value": "PROVISIONAL", "direction": "ltr"} } request = HttpRequest() request.user = self.user provisional_tile.save(index=False, request=request) tiles = Tile.objects.filter( resourceinstance_id="40000000-0000-0000-0000-000000000000" ) provisionaledits = provisional_tile.provisionaledits self.assertEqual(tiles.count(), 2) self.assertIn( "72048cb3-adbc-11e6-9ccf-14109fd34195", provisional_tile.data, provisional_tile, ) self.assertEqual( provisional_tile.data["72048cb3-adbc-11e6-9ccf-14109fd34195"]["en"][ "value" ], "AUTHORITATIVE", provisional_tile, ) self.assertEqual(provisionaledits[str(self.user.id)]["action"], "update") self.assertEqual(provisionaledits[str(self.user.id)]["status"], "review") def test_update_sortorder_provisional_tile(self): self.user = User.objects.create_user( username="testuser", password="TestingTesting123!" ) json = { "resourceinstance_id": "40000000-0000-0000-0000-000000000000", "parenttile_id": "", "nodegroup_id": "72048cb3-adbc-11e6-9ccf-14109fd34195", "tileid": "", "data": { "72048cb3-adbc-11e6-9ccf-14109fd34195": { "en": {"value": "PROVISIONAL", "direction": "ltr"} } }, } provisional_tile = Tile(json) request = HttpRequest() request.user = self.user provisional_tile.save(index=False, request=request) self.assertEqual(provisional_tile.sortorder, 0) obj, _ = TileModel.objects.update_or_create( pk=provisional_tile.pk, nodegroup=provisional_tile.nodegroup ) obj.refresh_from_db() # give test opportunity to fail on Django 4.2+ self.assertEqual(obj.sortorder, 1) def test_tile_cardinality(self): """ Tests that the tile is not saved if the cardinality is violated by testin to save a tile with the same values as existing one """ self.user = User.objects.get(username="admin") first_json = { "resourceinstance_id": "40000000-0000-0000-0000-000000000000", "parenttile_id": "", "nodegroup_id": "72048cb3-adbc-11e6-9ccf-14109fd34195", "tileid": "", "data": { "72048cb3-adbc-11e6-9ccf-14109fd34195": { "en": {"value": "AUTHORITATIVE", "direction": "ltr"} } }, } first_tile = Tile(first_json) request = HttpRequest() request.user = self.user first_tile.save(index=False, request=request) second_json = { "resourceinstance_id": "40000000-0000-0000-0000-000000000000", "parenttile_id": "", "nodegroup_id": "72048cb3-adbc-11e6-9ccf-14109fd34195", "tileid": "", "data": { "72048cb3-adbc-11e6-9ccf-14109fd34195": { "en": {"value": "AUTHORITATIVE", "direction": "ltr"} } }, } second_tile = Tile(second_json) with self.assertRaises(ProgrammingError): second_tile.save(index=False, request=request) def test_apply_provisional_edit(self): """ Tests that provisional edit data is properly created """ json = { "resourceinstance_id": "40000000-0000-0000-0000-000000000000", "parenttile_id": "", "nodegroup_id": "72048cb3-adbc-11e6-9ccf-14109fd34195", "tileid": "", "data": { "72048cb3-adbc-11e6-9ccf-14109fd34195": { "en": {"value": "TEST 1", "direction": "ltr"} } }, } user = User.objects.create_user( username="testuser", password="TestingTesting123!" ) provisional_tile = Tile(json) request = HttpRequest() request.user = user provisional_tile.save(index=False, request=request) provisional_tile.apply_provisional_edit(user, {"test": "test"}, "update") provisionaledits = provisional_tile.provisionaledits userid = str(user.id) self.assertEqual(provisionaledits[userid]["action"], "update") self.assertEqual(provisionaledits[userid]["reviewer"], None) self.assertEqual(provisionaledits[userid]["value"], {"test": "test"}) self.assertEqual(provisionaledits[userid]["status"], "review") self.assertEqual(provisionaledits[userid]["reviewtimestamp"], None) def test_user_owns_provisional(self): """ Tests that a user is the owner of a provisional edit """ json = { "resourceinstance_id": "40000000-0000-0000-0000-000000000000", "parenttile_id": "", "nodegroup_id": "72048cb3-adbc-11e6-9ccf-14109fd34195", "tileid": "", "data": { "72048cb3-adbc-11e6-9ccf-14109fd34195": { "en": {"value": "TEST 1", "direction": "ltr"} } }, } user = User.objects.create_user( username="testuser", password="TestingTesting123!" ) provisional_tile = Tile(json) request = HttpRequest() request.user = user provisional_tile.save(index=False, request=request) self.assertEqual(provisional_tile.user_owns_provisional(user), True) def test_tile_deletion(self): """ Tests that a tile is deleted when a user is a reviewer or owner. """ json = { "resourceinstance_id": "40000000-0000-0000-0000-000000000000", "parenttile_id": "", "nodegroup_id": "72048cb3-adbc-11e6-9ccf-14109fd34195", "tileid": "", "data": { "72048cb3-adbc-11e6-9ccf-14109fd34195": { "en": {"value": "TEST 1", "direction": "ltr"} } }, } owner = User.objects.create_user( username="testuser", password="TestingTesting123!" ) reviewer = User.objects.get(username="admin") tile1 = Tile(json) owner_request = HttpRequest() owner_request.user = owner tile1.save(index=False, request=owner_request) tile1.delete(request=owner_request) tile2 = Tile(json) reviewer_request = HttpRequest() reviewer_request.user = reviewer tile2.save(index=False, request=reviewer_request) tile2.delete(request=reviewer_request) self.assertEqual(len(Tile.objects.all()), 0) def test_delete_empty_tile(self): tile = Tile( { "resourceinstance_id": "40000000-0000-0000-0000-000000000000", "parenttile_id": "", "nodegroup_id": "72048cb3-adbc-11e6-9ccf-14109fd34195", "tileid": "", "data": {}, } ) tile.delete() def test_provisional_deletion(self): """ Tests that a tile is NOT deleted if a user does not have the privlages to delete a tile and that the proper provisionaledit is applied. """ json = { "resourceinstance_id": "40000000-0000-0000-0000-000000000000", "parenttile_id": "", "nodegroup_id": "72048cb3-adbc-11e6-9ccf-14109fd34195", "tileid": "", "data": { "72048cb3-adbc-11e6-9ccf-14109fd34195": { "en": {"value": "TEST 1", "direction": "ltr"} } }, } provisional_user = User.objects.create_user( username="testuser", password="TestingTesting123!" ) reviewer = User.objects.get(username="admin") tile = Tile(json) reviewer_request = HttpRequest() reviewer_request.user = reviewer tile.save(index=False, request=reviewer_request) provisional_request = HttpRequest() provisional_request.user = provisional_user tile.delete(request=provisional_request) self.assertEqual(len(Tile.objects.all()), 1) def test_related_resources_managed(self): """ Test that related resource data is managed correctly and that the accompanying table is managed correctly. Test that default ontology and inverse ontology infomation is applied properly. """ json = { "data": { "551a7785-e222-11e8-9baa-a4d18cec433a": None, "6d75ab63-e222-11e8-82a6-a4d18cec433a": None, "a4157df0-e222-11e8-9acb-a4d18cec433a": None, "eb115780-e222-11e8-aaed-a4d18cec433a": [ { "inverseOntologyProperty": "", "ontologyProperty": "", "resourceId": "92b2db6a-d13f-4cc7-aec7-e4caf91b45f8", "resourceXresourceId": "", }, { "inverseOntologyProperty": "http://www.cidoc-crm.org/cidoc-crm/P62i_is_depicted_by", "ontologyProperty": "http://www.cidoc-crm.org/cidoc-crm/P62_depicts", "resourceId": "e72844fc-7bc0-4851-89ca-5bb1c6b3ba22", "resourceXresourceId": "5f418480-534a-4dba-87d9-67eb27f0cc6a", }, ], }, "nodegroup_id": "487154e3-e222-11e8-be46-a4d18cec433a", "parenttile_id": None, "provisionaledits": None, "resourceinstance_id": "654bb228-37e7-4beb-b0f9-b59b61b53577", "sortorder": 0, "tileid": "edbdef07-77fd-4bb6-9fef-641d4a65abce", } main_resource = Resource( pk=json["resourceinstance_id"], graph_id="c35fe0a1-df30-11e8-b280-a4d18cec433a", ) main_resource.save(index=False) related_resource = Resource( pk="e72844fc-7bc0-4851-89ca-5bb1c6b3ba22", graph_id="c35fe0a1-df30-11e8-b280-a4d18cec433a", ) related_resource.save(index=False) related_resource2 = Resource( pk="92b2db6a-d13f-4cc7-aec7-e4caf91b45f8", graph_id="c35fe0a1-df30-11e8-b280-a4d18cec433a", ) related_resource2.save(index=False) t = Tile(json) t.save(index=False) resource_instances = ResourceXResource.objects.filter(tileid=t.tileid) self.assertEqual(2, len(resource_instances)) for ri in resource_instances: ri_dict = JSONSerializer().serializeToPython(ri) if ( str(ri.relationshiptype) == "http://www.cidoc-crm.org/cidoc-crm/P62_depicts" ): expected = { "inverserelationshiptype": "http://www.cidoc-crm.org/cidoc-crm/P62i_is_depicted_by", "nodeid_id": UUID("eb115780-e222-11e8-aaed-a4d18cec433a"), "notes": "", "relationshiptype": "http://www.cidoc-crm.org/cidoc-crm/P62_depicts", "resourceinstancefrom_graphid_id": UUID( "c35fe0a1-df30-11e8-b280-a4d18cec433a" ), "resourceinstanceidfrom_id": UUID( "654bb228-37e7-4beb-b0f9-b59b61b53577" ), "resourceinstanceidto_id": UUID( "e72844fc-7bc0-4851-89ca-5bb1c6b3ba22" ), "resourceinstanceto_graphid_id": UUID( "c35fe0a1-df30-11e8-b280-a4d18cec433a" ), "tileid_id": UUID("edbdef07-77fd-4bb6-9fef-641d4a65abce"), } self.assertTrue( all(item in ri_dict.items() for item in expected.items()) ) else: expected = { "inverserelationshiptype": "http://www.cidoc-crm.org/cidoc-crm/P10i_contains", "nodeid_id": UUID("eb115780-e222-11e8-aaed-a4d18cec433a"), "notes": "", "relationshiptype": "http://www.cidoc-crm.org/cidoc-crm/P10_falls_within", "resourceinstancefrom_graphid_id": UUID( "c35fe0a1-df30-11e8-b280-a4d18cec433a" ), "resourceinstanceidto_id": UUID( "92b2db6a-d13f-4cc7-aec7-e4caf91b45f8" ), "resourceinstanceto_graphid_id": UUID( "c35fe0a1-df30-11e8-b280-a4d18cec433a" ), "tileid_id": UUID("edbdef07-77fd-4bb6-9fef-641d4a65abce"), } self.assertTrue( all(item in ri_dict.items() for item in expected.items()) ) # now test that when we delete a related resource it json = { "data": { "551a7785-e222-11e8-9baa-a4d18cec433a": None, "6d75ab63-e222-11e8-82a6-a4d18cec433a": None, "a4157df0-e222-11e8-9acb-a4d18cec433a": None, "eb115780-e222-11e8-aaed-a4d18cec433a": [ { "inverseOntologyProperty": "", "ontologyProperty": "http://www.cidoc-crm.org/cidoc-crm/P62_depicts", "resourceId": "85b2db6a-d13f-4cc7-aec7-e4caf91b45f7", "resourceXresourceId": "", } ], }, "nodegroup_id": "487154e3-e222-11e8-be46-a4d18cec433a", "parenttile_id": None, "provisionaledits": None, "resourceinstance_id": "654bb228-37e7-4beb-b0f9-b59b61b53577", "sortorder": 0, "tileid": "edbdef07-77fd-4bb6-9fef-641d4a65abce", } related_resource3 = Resource( pk="85b2db6a-d13f-4cc7-aec7-e4caf91b45f7", graph_id="c35fe0a1-df30-11e8-b280-a4d18cec433a", ) related_resource3.save(index=False) t = Tile(json) t.save(index=False) resource_instance = ResourceXResource.objects.get(tileid=t.tileid) ri_dict = JSONSerializer().serializeToPython(resource_instance) expected = { "inverserelationshiptype": "http://www.cidoc-crm.org/cidoc-crm/P10i_contains", "nodeid_id": UUID("eb115780-e222-11e8-aaed-a4d18cec433a"), "notes": "", "relationshiptype": "http://www.cidoc-crm.org/cidoc-crm/P62_depicts", "resourceinstancefrom_graphid_id": UUID( "c35fe0a1-df30-11e8-b280-a4d18cec433a" ), "resourceinstanceidto_id": UUID("85b2db6a-d13f-4cc7-aec7-e4caf91b45f7"), "resourceinstanceto_graphid_id": UUID( "c35fe0a1-df30-11e8-b280-a4d18cec433a" ), "tileid_id": UUID("edbdef07-77fd-4bb6-9fef-641d4a65abce"), } self.assertTrue(all(item in ri_dict.items() for item in expected.items())) # def test_validation(self): # """ # Test that we can get a Tile object # """ # json = { # "tiles": {}, # "resourceinstance_id": "40000000-0000-0000-0000-000000000000", # "parenttile_id": '', # "nodegroup_id": "20000000-0000-0000-0000-000000000001", # "tileid": "", # "data": { # "20000000-0000-0000-0000-000000000004": "TEST 1" # } # } # t = Tile(json) # self.assertTrue(t.validate()['is_valid']) # json['data']['20000000-0000-0000-0000-000000000004'] = '' # t2 = Tile(json) # self.assertFalse(t2.validate()['is_valid']) def test_check_for_missing_nodes(self): # Required file list node. node_group = NodeGroup.objects.get( pk=UUID("41111111-0000-0000-0000-000000000000") ) required_file_list_node = Node( name="Required file list", datatype="file-list", nodegroup=node_group, isrequired=True, istopnode=False, ) required_file_list_node.save() json = { "resourceinstance_id": "40000000-0000-0000-0000-000000000000", "parenttile_id": "", "nodegroup_id": str(node_group.pk), "tileid": "", "data": {required_file_list_node.nodeid: []}, } tile = Tile(json) with self.assertRaisesMessage( TileValidationError, "Required file list" ): # node name tile.check_for_missing_nodes() # Add a widget label, should appear in error msg in lieu of node name card = CardModel.objects.create( nodegroup=node_group, graph_id=UUID("2f7f8e40-adbc-11e6-ac7f-14109fd34195"), ) CardXNodeXWidget.objects.create( card=card, node_id=required_file_list_node.nodeid, widget=Widget.objects.first(), label="Widget name", ) with self.assertRaisesMessage(TileValidationError, "Widget name"): tile.check_for_missing_nodes()