diff --git a/arkindex/documents/tests/test_destroy_elements.py b/arkindex/documents/tests/test_destroy_elements.py index 7e147d67b0e1d3aa80ec6c105cb658bd6911434d..18e0b5b207e20d37a01fd7ba60f6d7162ee70a36 100644 --- a/arkindex/documents/tests/test_destroy_elements.py +++ b/arkindex/documents/tests/test_destroy_elements.py @@ -42,7 +42,13 @@ class TestDestroyElements(FixtureAPITestCase): name='Castle story' ) self.assertEqual(self.corpus.elements.filter(id=castle_story.id).exists(), True) - with self.assertNumQueries(13): + with self.assertExactQueries( + 'element_deletion.sql', + params={ + 'id': str(castle_story.id), + 'corpus_id': str(self.corpus.id), + 'user_id': self.user.id + }, skip=1): response = self.client.delete(reverse('api:element-retrieve', kwargs={'pk': str(castle_story.id)})) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertEqual(self.corpus.elements.filter(id=castle_story.id).exists(), False) diff --git a/arkindex/project/tests/__init__.py b/arkindex/project/tests/__init__.py index 237513bb36dce8e334945563bd228560d4a83826..b42d515b72825d1aa42d945f5e7824e172c809de 100644 --- a/arkindex/project/tests/__init__.py +++ b/arkindex/project/tests/__init__.py @@ -20,10 +20,11 @@ class _AssertExactQueriesContext(CaptureQueriesContext): The implementation is inspired by assertNumQueries's own implementation. """ - def __init__(self, test_case, path, params, connection): + def __init__(self, test_case, path, params, connection, skip=0): self.test_case = test_case self.path = settings.SQL_VALIDATION_DIR / Path(path) self.params = params + self.skip = skip super().__init__(connection) def __exit__(self, exc_type, exc_value, traceback): @@ -34,7 +35,7 @@ class _AssertExactQueriesContext(CaptureQueriesContext): # Django's logged queries are each on a single line without semicolons # so we lint them and assemble them to build a more readable diff for humans. actual_sql = sqlparse.format( - ';'.join(query['sql'] for query in self), + ';'.join(query['sql'] for query in self[self.skip:]), reindent=True, use_space_around_operators=True, indent_width=4, @@ -93,6 +94,7 @@ class FixtureMixin(object): *args, using: str = DEFAULT_DB_ALIAS, params: Union[Iterable, Mapping[str, Any]] = [], + skip: int = 0, **kwargs) -> Optional[_AssertExactQueriesContext]: """ Assert that a function call causes exactly the SQL queries specified in a given file. @@ -118,8 +120,16 @@ class FixtureMixin(object): To make first runs easier, when the expect SQL file does not exist, assertExactQueries will try to write the current SQL queries to it and warn you using an `AssertionError`. Make sure to check this new SQL file as subsequent runs of unit tests will no longer fail. + + In API endpoint tests, some queries do not really need to be tested and are annoying to mock, such as + session queries that use the `django_session` table and are the first query to be made when you call an endpoint + using the test client. You can skip those queries and go straight to the point using `skip`. + This will skip the first query it finds: + + >>> with self.assertExactQueries('my_endpoint.sql', skip=1): + ... self.client.get(reverse('api:my-endpoint')) """ - context = _AssertExactQueriesContext(self, path, params, connections[using]) + context = _AssertExactQueriesContext(self, path, params, connections[using], skip=skip) if func is None: return context diff --git a/arkindex/sql_validation/element_deletion.sql b/arkindex/sql_validation/element_deletion.sql new file mode 100644 index 0000000000000000000000000000000000000000..61c9f93396b684bc814b43f928ca040ec4f1bbaf --- /dev/null +++ b/arkindex/sql_validation/element_deletion.sql @@ -0,0 +1,119 @@ +SELECT "users_user"."id", + "users_user"."password", + "users_user"."last_login", + "users_user"."email", + "users_user"."transkribus_email", + "users_user"."is_active", + "users_user"."is_internal", + "users_user"."is_admin", + "users_user"."verified_email" +FROM "users_user" +WHERE "users_user"."id" = {user_id} +LIMIT 21; + +SELECT "documents_element"."id", + "documents_element"."corpus_id", + "documents_corpus"."created", + "documents_corpus"."updated", + "documents_corpus"."id", + "documents_corpus"."name", + "documents_corpus"."description", + "documents_corpus"."repository_id", + "documents_corpus"."public" +FROM "documents_element" +INNER JOIN "documents_corpus" ON ("documents_element"."corpus_id" = "documents_corpus"."id") +WHERE ("documents_element"."corpus_id" IN + (SELECT DISTINCT U0."id" + FROM "documents_corpus" U0 + LEFT OUTER JOIN "users_corpusright" U1 ON (U0."id" = U1."corpus_id") + WHERE (U0."public" + OR U1."user_id" = {user_id})) + AND "documents_element"."id" = '{id}'::uuid) +LIMIT 21; + +SELECT "users_corpusright"."id", + "users_corpusright"."user_id", + "users_corpusright"."corpus_id", + "users_corpusright"."can_write", + "users_corpusright"."can_admin" +FROM "users_corpusright" +WHERE ("users_corpusright"."corpus_id" = '{corpus_id}'::uuid + AND "users_corpusright"."user_id" = {user_id}) +LIMIT 21; + + +DELETE +FROM documents_transcriptionentity te +WHERE transcription_id IN + (SELECT t.id + FROM documents_transcription t + LEFT JOIN documents_elementpath elementpath USING (element_id) + WHERE t.element_id = '{id}'::uuid + OR elementpath.path && ARRAY['{id}'::uuid] ) ; + + +DELETE +FROM documents_transcription +WHERE element_id = '{id}'::uuid + OR element_id IN + (SELECT element_id + FROM documents_elementpath + WHERE path && ARRAY['{id}'::uuid] ) ; + + +DELETE +FROM documents_classification +WHERE element_id = '{id}'::uuid + OR element_id IN + (SELECT element_id + FROM documents_elementpath + WHERE path && ARRAY['{id}'::uuid] ) ; + + +DELETE +FROM documents_metadata +WHERE element_id = '{id}'::uuid + OR element_id IN + (SELECT element_id + FROM documents_elementpath + WHERE path && ARRAY['{id}'::uuid] ) ; + + +DELETE +FROM dataimport_dataimportelement +WHERE element_id = '{id}'::uuid + OR element_id IN + (SELECT element_id + FROM documents_elementpath + WHERE path && ARRAY['{id}'::uuid] ) ; + + +UPDATE dataimport_dataimport +SET element_id = NULL +WHERE element_id = '{id}'::uuid + OR element_id IN + (SELECT element_id + FROM documents_elementpath + WHERE path && ARRAY['{id}'::uuid] ) ; + + +DELETE +FROM documents_selection selection +WHERE element_id = '{id}'::uuid + OR element_id IN + (SELECT element_id + FROM documents_elementpath + WHERE path && ARRAY['{id}'::uuid] ) ; + +WITH children_ids (id) AS + (DELETE + FROM documents_elementpath + WHERE element_id = '{id}'::uuid + OR path && ARRAY['{id}'::uuid] RETURNING element_id) +DELETE +FROM documents_element element USING children_ids +WHERE element.id = children_ids.id ; + +DELETE +FROM "documents_element" +WHERE "documents_element"."id" = '{id}'::uuid