diff --git a/arkindex/documents/api/elements.py b/arkindex/documents/api/elements.py index 8178827464fc736e98e046cb0fe1e7b5cb24ec8e..4c95fdea0bb91c09e222956edb7f6e36b8787ac8 100644 --- a/arkindex/documents/api/elements.py +++ b/arkindex/documents/api/elements.py @@ -1303,17 +1303,16 @@ class ElementNeighbors(ACLMixin, ListAPIView): queryset = Element.objects.none() def get_queryset(self): - element = get_object_or_404( # Include the attributes required for ACL checks and the API response - Element.objects.select_related("corpus", "type").only("id", "name", "type__slug", "corpus__public"), + Element + .objects + .filter(corpus__in=Corpus.objects.readable(self.request.user)) + .select_related("corpus", "type") + .only("id", "name", "type__slug", "corpus__public"), id=self.kwargs["pk"] ) - # Check access permission - if not self.has_access(element.corpus, Role.Guest.value): - raise PermissionDenied(detail="You do not have a read access to this element.") - return Element.objects.get_neighbors(element) diff --git a/arkindex/documents/fixtures/data.json b/arkindex/documents/fixtures/data.json index 20d3e109a80e64313b5c5424d140a0e90c7460ac..038367ce6f36e56fd72e6dcc0e364cd73700248a 100644 --- a/arkindex/documents/fixtures/data.json +++ b/arkindex/documents/fixtures/data.json @@ -1,7 +1,7 @@ [ { "model": "process.process", - "pk": "245bc206-350c-43d5-8db3-d98645c4eaa9", + "pk": "111cabac-6f43-4ff3-b6a3-77f79eb7e7fb", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", @@ -13,7 +13,7 @@ "activity_state": "disabled", "started": null, "finished": null, - "farm": "c19116b6-866f-4bb7-b447-3544629a8151", + "farm": "ae9b2873-eca1-4e11-a3e6-9d825ea0882f", "element": null, "folder_type": null, "element_type": null, @@ -32,19 +32,19 @@ }, { "model": "process.process", - "pk": "64b06ffd-a8c4-4ba2-bbb5-c99eb99121a0", + "pk": "1c47429f-a493-4555-bfad-bf8e85509237", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "name": "Process fixture", - "creator": 2, - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "mode": "workers", + "name": null, + "creator": 1, + "corpus": null, + "mode": "local", "revision": null, "activity_state": "disabled", "started": null, "finished": null, - "farm": "c19116b6-866f-4bb7-b447-3544629a8151", + "farm": null, "element": null, "folder_type": null, "element_type": null, @@ -63,7 +63,7 @@ }, { "model": "process.process", - "pk": "98e30036-bd74-4480-82c0-98c3199a4503", + "pk": "285f00ac-5f20-4aad-aee3-e7331f83114e", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", @@ -94,19 +94,19 @@ }, { "model": "process.process", - "pk": "e395a111-ab5a-4c93-8901-b1f8bafde684", + "pk": "715a2c6b-a3ef-44be-8928-b63ef762c04d", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "name": null, - "creator": 1, - "corpus": null, - "mode": "local", + "name": "Process fixture", + "creator": 2, + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "mode": "workers", "revision": null, "activity_state": "disabled", "started": null, "finished": null, - "farm": null, + "farm": "ae9b2873-eca1-4e11-a3e6-9d825ea0882f", "element": null, "folder_type": null, "element_type": null, @@ -125,182 +125,184 @@ }, { "model": "process.repository", - "pk": "6a07d21c-cc43-4c2b-8c72-5105a0399055", + "pk": "7fa115ef-b142-4379-a48c-7611fc3c6e64", "fields": { "url": "http://my_repo.fake/workers/worker" } }, { "model": "process.repository", - "pk": "b44445fb-1586-4673-b61c-c8057ccc9878", + "pk": "bd6b6e2b-614f-4cb7-a2c1-681302a0eb35", "fields": { "url": "http://gitlab/repo" } }, { "model": "process.revision", - "pk": "5f17c014-45e6-416e-9a6e-86a5de0f79c5", + "pk": "5826626e-6233-45a1-beb4-437158a12311", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "repo": "b44445fb-1586-4673-b61c-c8057ccc9878", - "hash": "42", - "message": "Salve", - "author": "Some user" + "repo": "7fa115ef-b142-4379-a48c-7611fc3c6e64", + "hash": "1337", + "message": "My w0rk3r", + "author": "Test user" } }, { "model": "process.revision", - "pk": "88be293e-5e58-4b60-8438-3feaf38c0f12", + "pk": "b3dfded2-ec79-4d8f-a444-2606fa9c09bb", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "repo": "6a07d21c-cc43-4c2b-8c72-5105a0399055", - "hash": "1337", - "message": "My w0rk3r", - "author": "Test user" + "repo": "bd6b6e2b-614f-4cb7-a2c1-681302a0eb35", + "hash": "42", + "message": "Salve", + "author": "Some user" } }, { "model": "process.worker", - "pk": "19c68046-9fde-4aa6-804d-d3888205722a", + "pk": "2031fda4-eb0f-4a0e-a356-2173edd71ec2", "fields": { "name": "Generic worker with a Model", "slug": "generic", - "type": "189c3b32-1172-4a79-b9a2-3cb14f7f52e3", + "type": "692ec457-fb42-4b36-9e92-cbb8786e62c0", "description": "", - "repository": "6a07d21c-cc43-4c2b-8c72-5105a0399055", + "repository": "7fa115ef-b142-4379-a48c-7611fc3c6e64", "public": false, "archived": null } }, { "model": "process.worker", - "pk": "1ab3c191-f306-4c6b-89b5-5d41a50a3d22", + "pk": "7c4d8def-5cfa-4c74-9ccb-ff0e50728381", "fields": { - "name": "Document layout analyser", - "slug": "dla", - "type": "c804b4ef-b2ed-4b82-90c6-fb020e3542d8", + "name": "Custom worker", + "slug": "custom", + "type": "aa8bb452-390e-4898-b5d9-3d50799517d3", "description": "", - "repository": "6a07d21c-cc43-4c2b-8c72-5105a0399055", + "repository": null, "public": false, "archived": null } }, { "model": "process.worker", - "pk": "5a12340d-c2b9-4d28-800b-6563fb9f6bec", + "pk": "8c4476f3-5d3d-4e4f-bcbd-0bb3787b751a", "fields": { - "name": "Recognizer", - "slug": "reco", - "type": "189c3b32-1172-4a79-b9a2-3cb14f7f52e3", + "name": "Document layout analyser", + "slug": "dla", + "type": "552c4367-459c-4388-b92b-dcf438c88843", "description": "", - "repository": "6a07d21c-cc43-4c2b-8c72-5105a0399055", + "repository": "7fa115ef-b142-4379-a48c-7611fc3c6e64", "public": false, "archived": null } }, { "model": "process.worker", - "pk": "62666365-d37d-4e30-91ad-723405dbdc8e", + "pk": "9195247f-d052-4ec6-8828-b2dbd94d5626", "fields": { - "name": "Worker requiring a GPU", - "slug": "worker-gpu", - "type": "429e7774-99cd-4672-9438-7d576a69a1a4", + "name": "File import", + "slug": "file_import", + "type": "f0d162ed-62a7-47c3-ac7d-8a8a8a733c1e", "description": "", - "repository": "6a07d21c-cc43-4c2b-8c72-5105a0399055", + "repository": "7fa115ef-b142-4379-a48c-7611fc3c6e64", "public": false, "archived": null } }, { "model": "process.worker", - "pk": "bf76d80a-ca52-47e2-8670-2d60c1a675b0", + "pk": "bd6b30f8-4756-47b0-8380-c455caa80802", "fields": { - "name": "Custom worker", - "slug": "custom", - "type": "2b2c3dae-98be-42c3-927b-28440f8df5d0", + "name": "Recognizer", + "slug": "reco", + "type": "692ec457-fb42-4b36-9e92-cbb8786e62c0", "description": "", - "repository": null, + "repository": "7fa115ef-b142-4379-a48c-7611fc3c6e64", "public": false, "archived": null } }, { "model": "process.worker", - "pk": "e61fde1d-e5bb-4188-aefa-1ceb04fa34a7", + "pk": "f0f67f2c-3f02-4d22-b430-092b232eea5a", "fields": { - "name": "File import", - "slug": "file_import", - "type": "2164e147-3109-4a68-93d7-d68341dedfb2", + "name": "Worker requiring a GPU", + "slug": "worker-gpu", + "type": "0fc130c1-011c-4352-9859-3053f8525611", "description": "", - "repository": "6a07d21c-cc43-4c2b-8c72-5105a0399055", + "repository": "7fa115ef-b142-4379-a48c-7611fc3c6e64", "public": false, "archived": null } }, { "model": "process.workertype", - "pk": "189c3b32-1172-4a79-b9a2-3cb14f7f52e3", + "pk": "0fc130c1-011c-4352-9859-3053f8525611", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "slug": "recognizer", - "display_name": "Recognizer" + "slug": "worker", + "display_name": "Worker requiring a GPU" } }, { "model": "process.workertype", - "pk": "2164e147-3109-4a68-93d7-d68341dedfb2", + "pk": "552c4367-459c-4388-b92b-dcf438c88843", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "slug": "import", - "display_name": "Import" + "slug": "dla", + "display_name": "Document Layout Analysis" } }, { "model": "process.workertype", - "pk": "2b2c3dae-98be-42c3-927b-28440f8df5d0", + "pk": "692ec457-fb42-4b36-9e92-cbb8786e62c0", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "slug": "custom", - "display_name": "Custom" + "slug": "recognizer", + "display_name": "Recognizer" } }, { "model": "process.workertype", - "pk": "429e7774-99cd-4672-9438-7d576a69a1a4", + "pk": "aa8bb452-390e-4898-b5d9-3d50799517d3", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "slug": "worker", - "display_name": "Worker requiring a GPU" + "slug": "custom", + "display_name": "Custom" } }, { "model": "process.workertype", - "pk": "c804b4ef-b2ed-4b82-90c6-fb020e3542d8", + "pk": "f0d162ed-62a7-47c3-ac7d-8a8a8a733c1e", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "slug": "dla", - "display_name": "Document Layout Analysis" + "slug": "import", + "display_name": "Import" } }, { "model": "process.workerversion", - "pk": "1e0dee90-0973-4017-b23d-4d972d7cfc09", + "pk": "39b56fa8-5244-4385-a5fc-12c3d3a8c1eb", "fields": { - "worker": "e61fde1d-e5bb-4188-aefa-1ceb04fa34a7", - "revision": "88be293e-5e58-4b60-8438-3feaf38c0f12", + "worker": "8c4476f3-5d3d-4e4f-bcbd-0bb3787b751a", + "revision": "5826626e-6233-45a1-beb4-437158a12311", "version": null, - "configuration": {}, + "configuration": { + "test": 42 + }, "state": "available", "gpu_usage": "disabled", "model_usage": "disabled", - "docker_image": "9a1b1d5d-ec3d-4a21-89da-0e930292467d", + "docker_image": "00515030-0750-46ea-9aa5-62c8bcd0252f", "docker_image_iid": null, "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z" @@ -308,18 +310,16 @@ }, { "model": "process.workerversion", - "pk": "5ce2dcd8-f35e-41d1-bd73-b40c9661db77", + "pk": "58751d57-3620-4f19-b7c7-4d579e471d28", "fields": { - "worker": "bf76d80a-ca52-47e2-8670-2d60c1a675b0", - "revision": null, - "version": 1, - "configuration": { - "custom": "value" - }, - "state": "created", + "worker": "9195247f-d052-4ec6-8828-b2dbd94d5626", + "revision": "5826626e-6233-45a1-beb4-437158a12311", + "version": null, + "configuration": {}, + "state": "available", "gpu_usage": "disabled", "model_usage": "disabled", - "docker_image": null, + "docker_image": "00515030-0750-46ea-9aa5-62c8bcd0252f", "docker_image_iid": null, "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z" @@ -327,18 +327,18 @@ }, { "model": "process.workerversion", - "pk": "70b6b804-b6f9-4efb-9d79-b8258c336eb7", + "pk": "a4b91c83-650e-4881-a2ac-82d9598f776d", "fields": { - "worker": "5a12340d-c2b9-4d28-800b-6563fb9f6bec", - "revision": "88be293e-5e58-4b60-8438-3feaf38c0f12", + "worker": "2031fda4-eb0f-4a0e-a356-2173edd71ec2", + "revision": "5826626e-6233-45a1-beb4-437158a12311", "version": null, "configuration": { "test": 42 }, "state": "available", "gpu_usage": "disabled", - "model_usage": "disabled", - "docker_image": "9a1b1d5d-ec3d-4a21-89da-0e930292467d", + "model_usage": "required", + "docker_image": "00515030-0750-46ea-9aa5-62c8bcd0252f", "docker_image_iid": null, "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z" @@ -346,10 +346,10 @@ }, { "model": "process.workerversion", - "pk": "79d83370-d164-4052-bb29-18f475916ea4", + "pk": "c82e0c63-ef75-45c7-b97f-f67927b0453e", "fields": { - "worker": "62666365-d37d-4e30-91ad-723405dbdc8e", - "revision": "88be293e-5e58-4b60-8438-3feaf38c0f12", + "worker": "f0f67f2c-3f02-4d22-b430-092b232eea5a", + "revision": "5826626e-6233-45a1-beb4-437158a12311", "version": null, "configuration": { "test": 42 @@ -357,7 +357,7 @@ "state": "available", "gpu_usage": "required", "model_usage": "disabled", - "docker_image": "9a1b1d5d-ec3d-4a21-89da-0e930292467d", + "docker_image": "00515030-0750-46ea-9aa5-62c8bcd0252f", "docker_image_iid": null, "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z" @@ -365,18 +365,18 @@ }, { "model": "process.workerversion", - "pk": "9e43ea6a-3f53-4a54-9a66-8f1699570f59", + "pk": "d120d2c7-165a-421c-8cb3-ea67452d063d", "fields": { - "worker": "19c68046-9fde-4aa6-804d-d3888205722a", - "revision": "88be293e-5e58-4b60-8438-3feaf38c0f12", - "version": null, + "worker": "7c4d8def-5cfa-4c74-9ccb-ff0e50728381", + "revision": null, + "version": 1, "configuration": { - "test": 42 + "custom": "value" }, - "state": "available", + "state": "created", "gpu_usage": "disabled", - "model_usage": "required", - "docker_image": "9a1b1d5d-ec3d-4a21-89da-0e930292467d", + "model_usage": "disabled", + "docker_image": null, "docker_image_iid": null, "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z" @@ -384,10 +384,10 @@ }, { "model": "process.workerversion", - "pk": "b0db756e-b291-4ffe-99c7-cd347741d7db", + "pk": "f50622da-5cdc-406d-9c3d-2416da0100a0", "fields": { - "worker": "1ab3c191-f306-4c6b-89b5-5d41a50a3d22", - "revision": "88be293e-5e58-4b60-8438-3feaf38c0f12", + "worker": "bd6b30f8-4756-47b0-8380-c455caa80802", + "revision": "5826626e-6233-45a1-beb4-437158a12311", "version": null, "configuration": { "test": 42 @@ -395,7 +395,7 @@ "state": "available", "gpu_usage": "disabled", "model_usage": "disabled", - "docker_image": "9a1b1d5d-ec3d-4a21-89da-0e930292467d", + "docker_image": "00515030-0750-46ea-9aa5-62c8bcd0252f", "docker_image_iid": null, "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z" @@ -403,14 +403,14 @@ }, { "model": "process.workerrun", - "pk": "4b931fc3-4c2b-44ea-8743-4907915554bb", + "pk": "296d16dc-b085-4192-9ba0-7690f3ecb1f9", "fields": { - "process": "64b06ffd-a8c4-4ba2-bbb5-c99eb99121a0", - "version": "b0db756e-b291-4ffe-99c7-cd347741d7db", + "process": "1c47429f-a493-4555-bfad-bf8e85509237", + "version": "d120d2c7-165a-421c-8cb3-ea67452d063d", "model_version": null, "parents": "[]", "configuration": null, - "summary": "Worker Document layout analyser @ b0db75", + "summary": "Worker Custom worker @ version 1", "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", "has_results": false @@ -418,14 +418,14 @@ }, { "model": "process.workerrun", - "pk": "4d26aa77-969b-4734-9260-a52b63d6a1d6", + "pk": "29e4ecc4-be14-4b57-85bc-7342fa461d89", "fields": { - "process": "64b06ffd-a8c4-4ba2-bbb5-c99eb99121a0", - "version": "70b6b804-b6f9-4efb-9d79-b8258c336eb7", + "process": "285f00ac-5f20-4aad-aee3-e7331f83114e", + "version": "d120d2c7-165a-421c-8cb3-ea67452d063d", "model_version": null, - "parents": "[\"4b931fc3-4c2b-44ea-8743-4907915554bb\"]", + "parents": "[]", "configuration": null, - "summary": "Worker Recognizer @ 70b6b8", + "summary": "Worker Custom worker @ version 1", "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", "has_results": false @@ -433,14 +433,14 @@ }, { "model": "process.workerrun", - "pk": "83959955-af5d-459f-8e33-47b39e256852", + "pk": "2b67063a-0b6e-4e60-b6c7-4c9fb120a52f", "fields": { - "process": "e395a111-ab5a-4c93-8901-b1f8bafde684", - "version": "5ce2dcd8-f35e-41d1-bd73-b40c9661db77", + "process": "715a2c6b-a3ef-44be-8928-b63ef762c04d", + "version": "39b56fa8-5244-4385-a5fc-12c3d3a8c1eb", "model_version": null, "parents": "[]", "configuration": null, - "summary": "Worker Custom worker @ version 1", + "summary": "Worker Document layout analyser @ 39b56f", "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", "has_results": false @@ -448,14 +448,14 @@ }, { "model": "process.workerrun", - "pk": "86240072-39ba-4597-a7a5-9078f3134657", + "pk": "8a661430-dac6-4b27-89c3-e597842bf538", "fields": { - "process": "98e30036-bd74-4480-82c0-98c3199a4503", - "version": "5ce2dcd8-f35e-41d1-bd73-b40c9661db77", + "process": "715a2c6b-a3ef-44be-8928-b63ef762c04d", + "version": "f50622da-5cdc-406d-9c3d-2416da0100a0", "model_version": null, - "parents": "[]", + "parents": "[\"2b67063a-0b6e-4e60-b6c7-4c9fb120a52f\"]", "configuration": null, - "summary": "Worker Custom worker @ version 1", + "summary": "Worker Recognizer @ f50622", "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", "has_results": false @@ -463,7 +463,7 @@ }, { "model": "documents.corpus", - "pk": "fdbe4b5c-9475-4310-8877-eb07344e294a", + "pk": "a00441c1-d58f-4a66-9108-b9c0985d14d0", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", @@ -476,11 +476,11 @@ }, { "model": "documents.elementtype", - "pk": "3bbbd33a-b906-4526-9e0b-0643ade1b080", + "pk": "099df260-b6a6-4559-b091-f2f57a78640f", "fields": { - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "slug": "word", - "display_name": "Word", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "slug": "page", + "display_name": "Page", "folder": false, "indexable": false, "color": "28b62c" @@ -488,23 +488,23 @@ }, { "model": "documents.elementtype", - "pk": "4200ad41-d032-476a-acb4-943e6481ee6d", + "pk": "3b0543d9-e822-42aa-90fc-c45af23524e1", "fields": { - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "slug": "volume", - "display_name": "Volume", - "folder": true, + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "slug": "text_line", + "display_name": "Line", + "folder": false, "indexable": false, "color": "28b62c" } }, { "model": "documents.elementtype", - "pk": "634e6aee-87a1-4845-a63e-83e7226aa557", + "pk": "6e334f43-b407-4b4d-aa2a-553da2dddf65", "fields": { - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "slug": "text_line", - "display_name": "Line", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "slug": "word", + "display_name": "Word", "folder": false, "indexable": false, "color": "28b62c" @@ -512,11 +512,11 @@ }, { "model": "documents.elementtype", - "pk": "7afe34f9-ad57-4ce7-872a-4d9aa31b6d0b", + "pk": "71cb6579-1b09-4bc9-9278-48f648fb6af6", "fields": { - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "slug": "page", - "display_name": "Page", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "slug": "act", + "display_name": "Act", "folder": false, "indexable": false, "color": "28b62c" @@ -524,9 +524,9 @@ }, { "model": "documents.elementtype", - "pk": "bc871e02-a23f-4799-a4d7-bb81813f41f5", + "pk": "eb4f8afb-4749-49a6-b3ba-d72be2d50384", "fields": { - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", "slug": "surface", "display_name": "Surface", "folder": false, @@ -536,291 +536,291 @@ }, { "model": "documents.elementtype", - "pk": "eda3da9f-1a6b-4f7a-91d1-f28e4e74f76a", + "pk": "f9fa0ac3-c3a5-4916-b7a1-18de2de95918", "fields": { - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "slug": "act", - "display_name": "Act", - "folder": false, + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "slug": "volume", + "display_name": "Volume", + "folder": true, "indexable": false, "color": "28b62c" } }, { "model": "documents.elementpath", - "pk": "04489822-c85b-4ac8-8d59-f7e1ac914295", + "pk": "0e601c72-0bfc-4784-a514-7522515b0cc0", "fields": { - "element": "6c6a774e-88c3-40c5-9913-d7ec2a3d5125", - "path": "[\"3bcfeab1-e4d4-4f63-8f60-8fc5fc551a64\"]", + "element": "ef4e20a9-7991-4d62-a2e8-bf565c0c6b33", + "path": "[\"5e14a51b-092b-450c-9239-56bc02ff880f\", \"46ee25df-c8c1-46f8-af4c-d62d0c8f4600\"]", "ordering": 0 } }, { "model": "documents.elementpath", - "pk": "0a801b84-7243-4ee0-857a-446a6af3ae97", + "pk": "0f814347-76db-4bcf-9876-584a7294a444", "fields": { - "element": "3bcfeab1-e4d4-4f63-8f60-8fc5fc551a64", - "path": "[]", - "ordering": 0 + "element": "51af1d15-e242-49e4-a797-7697c5162059", + "path": "[\"5e14a51b-092b-450c-9239-56bc02ff880f\"]", + "ordering": 2 } }, { "model": "documents.elementpath", - "pk": "0aad9073-0d49-492e-8455-4bd99e492e89", + "pk": "2c714775-0cd2-4375-b481-67dd7c6e313f", "fields": { - "element": "469f4606-f5ea-406d-a0bd-6d48050c39ed", - "path": "[\"3bcfeab1-e4d4-4f63-8f60-8fc5fc551a64\", \"4d2f59fe-9808-44b1-b3b5-017490ae4064\"]", + "element": "fb316906-9851-4851-a3d6-2a82a2b0f066", + "path": "[\"67ca764b-de6a-41a8-885c-ce2ce422e391\"]", "ordering": 0 } }, { "model": "documents.elementpath", - "pk": "2a801688-b438-4234-a1de-4b06553f1d55", + "pk": "2cead33e-466f-4d4c-9c83-05fc3b614382", "fields": { - "element": "d3dc3f63-9894-434b-9715-7bd906b63e63", - "path": "[\"3bcfeab1-e4d4-4f63-8f60-8fc5fc551a64\", \"6c6a774e-88c3-40c5-9913-d7ec2a3d5125\"]", - "ordering": 0 + "element": "9b7b7b7a-0b5e-42ed-8cbc-75caf378d45e", + "path": "[\"5e14a51b-092b-450c-9239-56bc02ff880f\", \"be338e62-cfcd-4faa-9b23-9da3900313f9\"]", + "ordering": 2 } }, { "model": "documents.elementpath", - "pk": "394d7eca-1ccc-47a4-ae44-8dd33c9ceb17", + "pk": "2fd6a3e6-b36c-48b3-aea1-1f77ae76add7", "fields": { - "element": "60313262-3273-4c39-a8ee-cedc733f6990", - "path": "[\"7a007e29-b509-4c4e-8321-78464df6f03a\"]", - "ordering": 0 + "element": "d2fd3f5e-30b4-4f80-ad81-62d6cea02e2e", + "path": "[\"5e14a51b-092b-450c-9239-56bc02ff880f\", \"51af1d15-e242-49e4-a797-7697c5162059\"]", + "ordering": 1 } }, { "model": "documents.elementpath", - "pk": "3d71eb28-24c5-4028-a72b-a3c170c117eb", + "pk": "46f31ff0-4569-4824-945f-68972a42221a", "fields": { - "element": "17a65610-023b-46c7-93a6-453335626d17", - "path": "[\"3bcfeab1-e4d4-4f63-8f60-8fc5fc551a64\", \"6c6a774e-88c3-40c5-9913-d7ec2a3d5125\"]", - "ordering": 3 + "element": "369a2f84-b6fc-48f4-8a8f-f5261674329c", + "path": "[\"5e14a51b-092b-450c-9239-56bc02ff880f\", \"2cdfa4f2-a66c-46c0-8038-8e5ac7e7d36e\"]", + "ordering": 0 } }, { "model": "documents.elementpath", - "pk": "4cd70fa4-1e6e-452a-8212-4be6138a6b34", + "pk": "55fe1f7d-f148-4465-9a37-5e4a52f466da", "fields": { - "element": "c16df32e-03b5-47e4-868f-98d57eac1f15", - "path": "[\"7a007e29-b509-4c4e-8321-78464df6f03a\"]", + "element": "9a43676e-46bd-4653-bdcd-73ded20c39ad", + "path": "[\"5e14a51b-092b-450c-9239-56bc02ff880f\", \"83d06bd7-3efc-4574-b833-62be2d1d94ad\"]", "ordering": 1 } }, { "model": "documents.elementpath", - "pk": "4e407215-0ed2-4ff2-98ff-a22d6b23349a", + "pk": "5e98433f-20f0-4236-a9d0-c773a0713103", "fields": { - "element": "a8c108a2-a6b7-4401-9d64-65cf263531a7", - "path": "[\"3bcfeab1-e4d4-4f63-8f60-8fc5fc551a64\", \"6c6a774e-88c3-40c5-9913-d7ec2a3d5125\"]", - "ordering": 1 + "element": "ca255c31-ab61-4a34-b8a4-320f901a2cc9", + "path": "[\"5e14a51b-092b-450c-9239-56bc02ff880f\", \"be338e62-cfcd-4faa-9b23-9da3900313f9\"]", + "ordering": 3 } }, { "model": "documents.elementpath", - "pk": "56655c48-69b4-4ea0-a724-b10d1a47e231", + "pk": "653029f5-2c55-4b38-9f6c-8170e81cfb0d", "fields": { - "element": "b8c4d1e3-aa9d-425a-b70f-c8ff16ae147c", - "path": "[\"7a007e29-b509-4c4e-8321-78464df6f03a\"]", - "ordering": 2 + "element": "2cdfa4f2-a66c-46c0-8038-8e5ac7e7d36e", + "path": "[\"5e14a51b-092b-450c-9239-56bc02ff880f\"]", + "ordering": 3 } }, { "model": "documents.elementpath", - "pk": "5c064e36-d3b5-4a8b-8f2d-97da74e84a70", + "pk": "74874a72-894b-43b6-9626-c323c0467fe6", "fields": { - "element": "86f68e65-f23b-4d01-a9ca-c40eed9cda32", - "path": "[\"3bcfeab1-e4d4-4f63-8f60-8fc5fc551a64\"]", - "ordering": 6 + "element": "ef9367bc-5de0-4c3f-aebf-39086e11efaa", + "path": "[\"5e14a51b-092b-450c-9239-56bc02ff880f\", \"2adf861a-a36a-4127-bf21-583ff95bc08c\"]", + "ordering": 0 } }, { "model": "documents.elementpath", - "pk": "617f3ea6-17f8-4514-9f87-4c8bdd747e3f", + "pk": "7c2e8330-98c3-48ae-b974-f1974eaabacd", "fields": { - "element": "4d2f59fe-9808-44b1-b3b5-017490ae4064", - "path": "[\"3bcfeab1-e4d4-4f63-8f60-8fc5fc551a64\"]", - "ordering": 2 + "element": "be338e62-cfcd-4faa-9b23-9da3900313f9", + "path": "[\"5e14a51b-092b-450c-9239-56bc02ff880f\"]", + "ordering": 0 } }, { "model": "documents.elementpath", - "pk": "6cc0a005-dbff-4b53-b452-b6d4d8ec8eb3", + "pk": "890f1f05-6c4e-417c-9459-2c603c1ba5aa", "fields": { - "element": "09ee64d6-3d8d-4d74-a1cd-ec34c8c2cd38", - "path": "[\"3bcfeab1-e4d4-4f63-8f60-8fc5fc551a64\", \"664f19d6-472d-49cc-bd87-26c19db7d2d3\"]", - "ordering": 0 + "element": "7bf57bb3-1836-4b17-a3e0-a8981dae790b", + "path": "[\"5e14a51b-092b-450c-9239-56bc02ff880f\"]", + "ordering": 7 } }, { "model": "documents.elementpath", - "pk": "8adb5d49-8651-4239-b98d-425a70ff39cf", + "pk": "89f2d847-69e2-4cdc-831d-ef595472c7f4", "fields": { - "element": "6a41ddf7-41e4-40ad-8234-d773b74058bc", - "path": "[\"3bcfeab1-e4d4-4f63-8f60-8fc5fc551a64\"]", - "ordering": 1 + "element": "e1355423-3976-43ca-842c-2e146ef7e79a", + "path": "[\"5e14a51b-092b-450c-9239-56bc02ff880f\", \"7bf57bb3-1836-4b17-a3e0-a8981dae790b\"]", + "ordering": 0 } }, { "model": "documents.elementpath", - "pk": "a2d6b2bd-677e-4f72-9a9d-64957b854a90", + "pk": "93b0d14a-9a35-4bcc-b6be-2051a8951a10", "fields": { - "element": "18ada82e-36c6-47e4-bcff-bee5aa63889b", - "path": "[\"3bcfeab1-e4d4-4f63-8f60-8fc5fc551a64\", \"4d2f59fe-9808-44b1-b3b5-017490ae4064\"]", - "ordering": 2 + "element": "b9320e75-eed0-431e-9902-f339b7e42702", + "path": "[\"5e14a51b-092b-450c-9239-56bc02ff880f\", \"3e73d3d0-a713-4d2f-a1e8-ec268a0e7df1\"]", + "ordering": 0 } }, { "model": "documents.elementpath", - "pk": "a3d290d2-c6a1-4c80-8baf-37d42497e61e", + "pk": "a350fa3b-5988-4ab5-9294-168200e2136b", "fields": { - "element": "d227268a-2c2e-40ff-b22c-ab6e52eb5bba", - "path": "[\"3bcfeab1-e4d4-4f63-8f60-8fc5fc551a64\"]", - "ordering": 3 + "element": "3e73d3d0-a713-4d2f-a1e8-ec268a0e7df1", + "path": "[\"5e14a51b-092b-450c-9239-56bc02ff880f\"]", + "ordering": 6 } }, { "model": "documents.elementpath", - "pk": "a735c855-bd4b-4ece-8345-eac1dc791ff9", + "pk": "a8d8ad5f-4e11-4cd1-ba7c-d30070c02b82", "fields": { - "element": "ba888ed5-8c85-4f49-8f87-499219187ade", - "path": "[\"3bcfeab1-e4d4-4f63-8f60-8fc5fc551a64\"]", + "element": "2adf861a-a36a-4127-bf21-583ff95bc08c", + "path": "[\"5e14a51b-092b-450c-9239-56bc02ff880f\"]", "ordering": 5 } }, { "model": "documents.elementpath", - "pk": "a8012a46-1e99-40a4-ad86-d2b2040eb026", + "pk": "aefdb812-83a8-4c75-abc3-ca4cbc6bdd9f", "fields": { - "element": "4c054d5e-f30c-4f70-a368-8e6f78f48ba1", - "path": "[\"3bcfeab1-e4d4-4f63-8f60-8fc5fc551a64\", \"d227268a-2c2e-40ff-b22c-ab6e52eb5bba\"]", - "ordering": 0 + "element": "46ee25df-c8c1-46f8-af4c-d62d0c8f4600", + "path": "[\"5e14a51b-092b-450c-9239-56bc02ff880f\"]", + "ordering": 1 } }, { "model": "documents.elementpath", - "pk": "a8b4a820-4de5-4164-88e9-66b3fd875938", + "pk": "b07d77e6-c1a7-46c8-83ec-131852006367", "fields": { - "element": "34cdee1d-40e3-4499-b04b-93f6d2bfb802", - "path": "[\"3bcfeab1-e4d4-4f63-8f60-8fc5fc551a64\", \"6c6a774e-88c3-40c5-9913-d7ec2a3d5125\"]", + "element": "2bd1a3ce-6073-4387-8a71-a5d0134ce894", + "path": "[\"67ca764b-de6a-41a8-885c-ce2ce422e391\"]", "ordering": 2 } }, { "model": "documents.elementpath", - "pk": "ab00de7e-015c-4f76-a3c0-645cb879a024", + "pk": "b5b18993-cf3b-4204-800b-e7bc0b317fe6", "fields": { - "element": "2c0f28b1-b8e9-4f08-9014-75ce7950ee6f", - "path": "[\"3bcfeab1-e4d4-4f63-8f60-8fc5fc551a64\", \"6a41ddf7-41e4-40ad-8234-d773b74058bc\"]", - "ordering": 1 + "element": "83d06bd7-3efc-4574-b833-62be2d1d94ad", + "path": "[\"5e14a51b-092b-450c-9239-56bc02ff880f\"]", + "ordering": 4 } }, { "model": "documents.elementpath", - "pk": "b100fd6e-d7ed-4481-8604-f07e9d554637", + "pk": "c2852eb9-9be8-49ce-9882-3dc1492a066a", "fields": { - "element": "3603393d-4831-4dbf-9672-3fbe1077e03a", - "path": "[\"3bcfeab1-e4d4-4f63-8f60-8fc5fc551a64\", \"4d2f59fe-9808-44b1-b3b5-017490ae4064\"]", + "element": "36345a26-69b9-4cf9-9682-5eb62d1726cc", + "path": "[\"5e14a51b-092b-450c-9239-56bc02ff880f\", \"46ee25df-c8c1-46f8-af4c-d62d0c8f4600\"]", "ordering": 1 } }, { "model": "documents.elementpath", - "pk": "b7098cdc-dcba-4efc-8213-e92af55fa6f9", + "pk": "c9f3a671-5b6e-4930-8e0a-61e202de4b12", "fields": { - "element": "3f3bb3ee-ec7c-436c-9dde-f8aa0e1a69d7", - "path": "[\"3bcfeab1-e4d4-4f63-8f60-8fc5fc551a64\"]", - "ordering": 7 + "element": "b7e07d82-0d10-4060-843e-a838b766ec3d", + "path": "[\"5e14a51b-092b-450c-9239-56bc02ff880f\", \"51af1d15-e242-49e4-a797-7697c5162059\"]", + "ordering": 0 } }, { "model": "documents.elementpath", - "pk": "b992817b-343e-4a5b-bf6d-42cb334696d1", + "pk": "cf09a2a7-9630-437f-9183-4c1e1dc9909b", "fields": { - "element": "b0a0984b-dcf2-46da-8d6e-933020a46011", - "path": "[\"3bcfeab1-e4d4-4f63-8f60-8fc5fc551a64\", \"6a41ddf7-41e4-40ad-8234-d773b74058bc\"]", + "element": "ae27310b-dc31-413b-b551-2b74c81147ab", + "path": "[\"5e14a51b-092b-450c-9239-56bc02ff880f\", \"46ee25df-c8c1-46f8-af4c-d62d0c8f4600\"]", "ordering": 2 } }, { "model": "documents.elementpath", - "pk": "c35f1e16-f92d-4589-afc4-6c3115fa36d7", + "pk": "d1ae93db-ba88-4ab9-abbb-c61d2b6e87a3", "fields": { - "element": "9fd626f8-a5eb-4ae3-9f4f-f0ea4216712e", - "path": "[\"3bcfeab1-e4d4-4f63-8f60-8fc5fc551a64\", \"664f19d6-472d-49cc-bd87-26c19db7d2d3\"]", - "ordering": 1 + "element": "bfea27aa-fb3a-4298-a71f-f131cd2e6a13", + "path": "[\"5e14a51b-092b-450c-9239-56bc02ff880f\", \"be338e62-cfcd-4faa-9b23-9da3900313f9\"]", + "ordering": 0 } }, { "model": "documents.elementpath", - "pk": "d0586c21-e60a-41d8-9a17-b71eeb75bc68", + "pk": "d4688c02-4cd6-4d53-b0df-89a4197c8b35", "fields": { - "element": "e2f8eee0-cf55-404c-86e6-6950ab88faac", - "path": "[\"3bcfeab1-e4d4-4f63-8f60-8fc5fc551a64\", \"3f3bb3ee-ec7c-436c-9dde-f8aa0e1a69d7\"]", - "ordering": 0 + "element": "21250ac5-76b6-4c39-8ff2-60e77188501e", + "path": "[\"5e14a51b-092b-450c-9239-56bc02ff880f\", \"51af1d15-e242-49e4-a797-7697c5162059\"]", + "ordering": 2 } }, { "model": "documents.elementpath", - "pk": "da4fe1fb-7ad7-4b38-973f-c9a4ca8d402f", + "pk": "d86f1d72-da60-4696-b25c-d42c2338dee6", "fields": { - "element": "3189dc31-fa75-4c60-b1d0-7a15720e35ca", - "path": "[\"3bcfeab1-e4d4-4f63-8f60-8fc5fc551a64\", \"86f68e65-f23b-4d01-a9ca-c40eed9cda32\"]", + "element": "67ca764b-de6a-41a8-885c-ce2ce422e391", + "path": "[]", "ordering": 0 } }, { "model": "documents.elementpath", - "pk": "e2c73d2f-8881-4b4e-909f-7244abd4c92c", + "pk": "da260b1c-ea75-41b0-8702-198c05b8b2ec", "fields": { - "element": "664f19d6-472d-49cc-bd87-26c19db7d2d3", - "path": "[\"3bcfeab1-e4d4-4f63-8f60-8fc5fc551a64\"]", - "ordering": 4 + "element": "5e14a51b-092b-450c-9239-56bc02ff880f", + "path": "[]", + "ordering": 0 } }, { "model": "documents.elementpath", - "pk": "e6c19e34-49a7-4dc2-aa37-8d96f2a7b43e", + "pk": "f234f8df-58fe-46b1-8a1b-f4601babb42e", "fields": { - "element": "7a007e29-b509-4c4e-8321-78464df6f03a", - "path": "[]", - "ordering": 0 + "element": "9523eff1-25a3-423f-8660-fa3b0ab51c93", + "path": "[\"5e14a51b-092b-450c-9239-56bc02ff880f\", \"be338e62-cfcd-4faa-9b23-9da3900313f9\"]", + "ordering": 1 } }, { "model": "documents.elementpath", - "pk": "e9d64de1-c0c6-44a1-bb33-38c87a144282", + "pk": "f35e8c1a-c09a-463e-bfe7-5aef6eea762e", "fields": { - "element": "d5daac1b-5f57-4d3c-b120-54adbcc82d7d", - "path": "[\"3bcfeab1-e4d4-4f63-8f60-8fc5fc551a64\", \"ba888ed5-8c85-4f49-8f87-499219187ade\"]", + "element": "e6c910b3-5dda-4db8-8ee0-2342e5f5d6ad", + "path": "[\"5e14a51b-092b-450c-9239-56bc02ff880f\", \"83d06bd7-3efc-4574-b833-62be2d1d94ad\"]", "ordering": 0 } }, { "model": "documents.elementpath", - "pk": "ed3283f8-0af1-467e-8a52-77f9d434af78", + "pk": "f415dff7-e248-4f3b-8b69-426b411a4dc6", "fields": { - "element": "92101c2b-885f-42e2-a0cb-13a8a04c0ea8", - "path": "[\"3bcfeab1-e4d4-4f63-8f60-8fc5fc551a64\", \"6a41ddf7-41e4-40ad-8234-d773b74058bc\"]", - "ordering": 0 + "element": "5d300cf4-3dae-43f1-9c91-26ed0c89064c", + "path": "[\"67ca764b-de6a-41a8-885c-ce2ce422e391\"]", + "ordering": 1 } }, { "model": "documents.element", - "pk": "09ee64d6-3d8d-4d74-a1cd-ec34c8c2cd38", + "pk": "21250ac5-76b6-4c39-8ff2-60e77188501e", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "bc871e02-a23f-4799-a4d7-bb81813f41f5", - "name": "Surface B", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "6e334f43-b407-4b4d-aa2a-553da2dddf65", + "name": "DATUM", "creator": null, "worker_version": null, "worker_run": null, - "image": "d3547a48-79f6-4c1f-8f16-c0be1a1da9fc", - "polygon": "LINEARRING (600 600, 600 1000, 1000 1000, 1000 600, 600 600)", + "image": "d484743f-e61a-4151-9c9c-cbc86031fe3e", + "polygon": "LINEARRING (700 700, 700 800, 800 800, 800 700, 700 700)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -828,18 +828,18 @@ }, { "model": "documents.element", - "pk": "17a65610-023b-46c7-93a6-453335626d17", + "pk": "2adf861a-a36a-4127-bf21-583ff95bc08c", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "634e6aee-87a1-4845-a63e-83e7226aa557", - "name": "Text line", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "71cb6579-1b09-4bc9-9278-48f648fb6af6", + "name": "Act 3", "creator": null, "worker_version": null, "worker_run": null, - "image": "d3547a48-79f6-4c1f-8f16-c0be1a1da9fc", - "polygon": "LINEARRING (400 400, 400 500, 500 500, 500 400, 400 400)", + "image": null, + "polygon": null, "rotation_angle": 0, "mirrored": false, "confidence": null @@ -847,18 +847,18 @@ }, { "model": "documents.element", - "pk": "18ada82e-36c6-47e4-bcff-bee5aa63889b", + "pk": "2bd1a3ce-6073-4387-8a71-a5d0134ce894", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "3bbbd33a-b906-4526-9e0b-0643ade1b080", - "name": "DATUM", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "099df260-b6a6-4559-b091-f2f57a78640f", + "name": "Volume 2, page 2r", "creator": null, "worker_version": null, "worker_run": null, - "image": "d4db6162-7d7b-4839-a84f-756e74fef150", - "polygon": "LINEARRING (700 700, 700 800, 800 800, 800 700, 700 700)", + "image": "21636e84-7d76-4261-b09f-1dc3a16cf2db", + "polygon": "LINEARRING (0 0, 0 1000, 1000 1000, 1000 0, 0 0)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -866,18 +866,18 @@ }, { "model": "documents.element", - "pk": "2c0f28b1-b8e9-4f08-9014-75ce7950ee6f", + "pk": "2cdfa4f2-a66c-46c0-8038-8e5ac7e7d36e", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "3bbbd33a-b906-4526-9e0b-0643ade1b080", - "name": "ROY", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "71cb6579-1b09-4bc9-9278-48f648fb6af6", + "name": "Act 1", "creator": null, "worker_version": null, "worker_run": null, - "image": "47f164d3-d26f-41e8-9df1-b538645d60b5", - "polygon": "LINEARRING (400 400, 400 500, 500 500, 500 400, 400 400)", + "image": null, + "polygon": null, "rotation_angle": 0, "mirrored": false, "confidence": null @@ -885,18 +885,18 @@ }, { "model": "documents.element", - "pk": "3189dc31-fa75-4c60-b1d0-7a15720e35ca", + "pk": "36345a26-69b9-4cf9-9682-5eb62d1726cc", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "bc871e02-a23f-4799-a4d7-bb81813f41f5", - "name": "Surface E", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "6e334f43-b407-4b4d-aa2a-553da2dddf65", + "name": "ROY", "creator": null, "worker_version": null, "worker_run": null, - "image": "d4db6162-7d7b-4839-a84f-756e74fef150", - "polygon": "LINEARRING (300 300, 300 600, 600 600, 600 300, 300 300)", + "image": "56695e99-e1dc-4a94-b19f-061017b046e2", + "polygon": "LINEARRING (400 400, 400 500, 500 500, 500 400, 400 400)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -904,18 +904,18 @@ }, { "model": "documents.element", - "pk": "34cdee1d-40e3-4499-b04b-93f6d2bfb802", + "pk": "369a2f84-b6fc-48f4-8a8f-f5261674329c", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "3bbbd33a-b906-4526-9e0b-0643ade1b080", - "name": "DATUM", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "eb4f8afb-4749-49a6-b3ba-d72be2d50384", + "name": "Surface A", "creator": null, "worker_version": null, "worker_run": null, - "image": "d3547a48-79f6-4c1f-8f16-c0be1a1da9fc", - "polygon": "LINEARRING (700 700, 700 800, 800 800, 800 700, 700 700)", + "image": "b7bc85ce-6999-4dd6-bb4e-5442de55c57e", + "polygon": "LINEARRING (0 0, 0 600, 600 600, 600 0, 0 0)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -923,18 +923,18 @@ }, { "model": "documents.element", - "pk": "3603393d-4831-4dbf-9672-3fbe1077e03a", + "pk": "3e73d3d0-a713-4d2f-a1e8-ec268a0e7df1", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "3bbbd33a-b906-4526-9e0b-0643ade1b080", - "name": "ROY", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "71cb6579-1b09-4bc9-9278-48f648fb6af6", + "name": "Act 4", "creator": null, "worker_version": null, "worker_run": null, - "image": "d4db6162-7d7b-4839-a84f-756e74fef150", - "polygon": "LINEARRING (400 400, 400 500, 500 500, 500 400, 400 400)", + "image": null, + "polygon": null, "rotation_angle": 0, "mirrored": false, "confidence": null @@ -942,18 +942,18 @@ }, { "model": "documents.element", - "pk": "3bcfeab1-e4d4-4f63-8f60-8fc5fc551a64", + "pk": "46ee25df-c8c1-46f8-af4c-d62d0c8f4600", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "4200ad41-d032-476a-acb4-943e6481ee6d", - "name": "Volume 1", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "099df260-b6a6-4559-b091-f2f57a78640f", + "name": "Volume 1, page 1v", "creator": null, "worker_version": null, "worker_run": null, - "image": null, - "polygon": null, + "image": "56695e99-e1dc-4a94-b19f-061017b046e2", + "polygon": "LINEARRING (0 0, 0 1000, 1000 1000, 1000 0, 0 0)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -961,18 +961,18 @@ }, { "model": "documents.element", - "pk": "3f3bb3ee-ec7c-436c-9dde-f8aa0e1a69d7", + "pk": "51af1d15-e242-49e4-a797-7697c5162059", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "eda3da9f-1a6b-4f7a-91d1-f28e4e74f76a", - "name": "Act 5", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "099df260-b6a6-4559-b091-f2f57a78640f", + "name": "Volume 1, page 2r", "creator": null, "worker_version": null, "worker_run": null, - "image": null, - "polygon": null, + "image": "d484743f-e61a-4151-9c9c-cbc86031fe3e", + "polygon": "LINEARRING (0 0, 0 1000, 1000 1000, 1000 0, 0 0)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -980,18 +980,18 @@ }, { "model": "documents.element", - "pk": "469f4606-f5ea-406d-a0bd-6d48050c39ed", + "pk": "5d300cf4-3dae-43f1-9c91-26ed0c89064c", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "3bbbd33a-b906-4526-9e0b-0643ade1b080", - "name": "PARIS", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "099df260-b6a6-4559-b091-f2f57a78640f", + "name": "Volume 2, page 1v", "creator": null, "worker_version": null, "worker_run": null, - "image": "d4db6162-7d7b-4839-a84f-756e74fef150", - "polygon": "LINEARRING (100 100, 100 200, 200 200, 200 100, 100 100)", + "image": "888b07cf-6e17-4d1e-9b01-1186121b1cfb", + "polygon": "LINEARRING (0 0, 0 1000, 1000 1000, 1000 0, 0 0)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -999,18 +999,18 @@ }, { "model": "documents.element", - "pk": "4c054d5e-f30c-4f70-a368-8e6f78f48ba1", + "pk": "5e14a51b-092b-450c-9239-56bc02ff880f", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "bc871e02-a23f-4799-a4d7-bb81813f41f5", - "name": "Surface A", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "f9fa0ac3-c3a5-4916-b7a1-18de2de95918", + "name": "Volume 1", "creator": null, "worker_version": null, "worker_run": null, - "image": "d3547a48-79f6-4c1f-8f16-c0be1a1da9fc", - "polygon": "LINEARRING (0 0, 0 600, 600 600, 600 0, 0 0)", + "image": null, + "polygon": null, "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1018,18 +1018,18 @@ }, { "model": "documents.element", - "pk": "4d2f59fe-9808-44b1-b3b5-017490ae4064", + "pk": "67ca764b-de6a-41a8-885c-ce2ce422e391", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "7afe34f9-ad57-4ce7-872a-4d9aa31b6d0b", - "name": "Volume 1, page 2r", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "f9fa0ac3-c3a5-4916-b7a1-18de2de95918", + "name": "Volume 2", "creator": null, "worker_version": null, "worker_run": null, - "image": "d4db6162-7d7b-4839-a84f-756e74fef150", - "polygon": "LINEARRING (0 0, 0 1000, 1000 1000, 1000 0, 0 0)", + "image": null, + "polygon": null, "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1037,18 +1037,18 @@ }, { "model": "documents.element", - "pk": "60313262-3273-4c39-a8ee-cedc733f6990", + "pk": "7bf57bb3-1836-4b17-a3e0-a8981dae790b", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "7afe34f9-ad57-4ce7-872a-4d9aa31b6d0b", - "name": "Volume 2, page 1r", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "71cb6579-1b09-4bc9-9278-48f648fb6af6", + "name": "Act 5", "creator": null, "worker_version": null, "worker_run": null, - "image": "2552f45d-6880-4a4c-b9c2-1091598a52b4", - "polygon": "LINEARRING (0 0, 0 1000, 1000 1000, 1000 0, 0 0)", + "image": null, + "polygon": null, "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1056,12 +1056,12 @@ }, { "model": "documents.element", - "pk": "664f19d6-472d-49cc-bd87-26c19db7d2d3", + "pk": "83d06bd7-3efc-4574-b833-62be2d1d94ad", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "eda3da9f-1a6b-4f7a-91d1-f28e4e74f76a", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "71cb6579-1b09-4bc9-9278-48f648fb6af6", "name": "Act 2", "creator": null, "worker_version": null, @@ -1075,18 +1075,18 @@ }, { "model": "documents.element", - "pk": "6a41ddf7-41e4-40ad-8234-d773b74058bc", + "pk": "9523eff1-25a3-423f-8660-fa3b0ab51c93", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "7afe34f9-ad57-4ce7-872a-4d9aa31b6d0b", - "name": "Volume 1, page 1v", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "6e334f43-b407-4b4d-aa2a-553da2dddf65", + "name": "ROY", "creator": null, "worker_version": null, "worker_run": null, - "image": "47f164d3-d26f-41e8-9df1-b538645d60b5", - "polygon": "LINEARRING (0 0, 0 1000, 1000 1000, 1000 0, 0 0)", + "image": "b7bc85ce-6999-4dd6-bb4e-5442de55c57e", + "polygon": "LINEARRING (400 400, 400 500, 500 500, 500 400, 400 400)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1094,17 +1094,17 @@ }, { "model": "documents.element", - "pk": "6c6a774e-88c3-40c5-9913-d7ec2a3d5125", + "pk": "9a43676e-46bd-4653-bdcd-73ded20c39ad", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "7afe34f9-ad57-4ce7-872a-4d9aa31b6d0b", - "name": "Volume 1, page 1r", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "eb4f8afb-4749-49a6-b3ba-d72be2d50384", + "name": "Surface C", "creator": null, "worker_version": null, "worker_run": null, - "image": "d3547a48-79f6-4c1f-8f16-c0be1a1da9fc", + "image": "56695e99-e1dc-4a94-b19f-061017b046e2", "polygon": "LINEARRING (0 0, 0 1000, 1000 1000, 1000 0, 0 0)", "rotation_angle": 0, "mirrored": false, @@ -1113,18 +1113,18 @@ }, { "model": "documents.element", - "pk": "7a007e29-b509-4c4e-8321-78464df6f03a", + "pk": "9b7b7b7a-0b5e-42ed-8cbc-75caf378d45e", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "4200ad41-d032-476a-acb4-943e6481ee6d", - "name": "Volume 2", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "6e334f43-b407-4b4d-aa2a-553da2dddf65", + "name": "DATUM", "creator": null, "worker_version": null, "worker_run": null, - "image": null, - "polygon": null, + "image": "b7bc85ce-6999-4dd6-bb4e-5442de55c57e", + "polygon": "LINEARRING (700 700, 700 800, 800 800, 800 700, 700 700)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1132,18 +1132,18 @@ }, { "model": "documents.element", - "pk": "86f68e65-f23b-4d01-a9ca-c40eed9cda32", + "pk": "ae27310b-dc31-413b-b551-2b74c81147ab", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "eda3da9f-1a6b-4f7a-91d1-f28e4e74f76a", - "name": "Act 4", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "6e334f43-b407-4b4d-aa2a-553da2dddf65", + "name": "DATUM", "creator": null, "worker_version": null, "worker_run": null, - "image": null, - "polygon": null, + "image": "56695e99-e1dc-4a94-b19f-061017b046e2", + "polygon": "LINEARRING (700 700, 700 800, 800 800, 800 700, 700 700)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1151,17 +1151,17 @@ }, { "model": "documents.element", - "pk": "92101c2b-885f-42e2-a0cb-13a8a04c0ea8", + "pk": "b7e07d82-0d10-4060-843e-a838b766ec3d", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "3bbbd33a-b906-4526-9e0b-0643ade1b080", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "6e334f43-b407-4b4d-aa2a-553da2dddf65", "name": "PARIS", "creator": null, "worker_version": null, "worker_run": null, - "image": "47f164d3-d26f-41e8-9df1-b538645d60b5", + "image": "d484743f-e61a-4151-9c9c-cbc86031fe3e", "polygon": "LINEARRING (100 100, 100 200, 200 200, 200 100, 100 100)", "rotation_angle": 0, "mirrored": false, @@ -1170,18 +1170,18 @@ }, { "model": "documents.element", - "pk": "9fd626f8-a5eb-4ae3-9f4f-f0ea4216712e", + "pk": "b9320e75-eed0-431e-9902-f339b7e42702", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "bc871e02-a23f-4799-a4d7-bb81813f41f5", - "name": "Surface C", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "eb4f8afb-4749-49a6-b3ba-d72be2d50384", + "name": "Surface E", "creator": null, "worker_version": null, "worker_run": null, - "image": "47f164d3-d26f-41e8-9df1-b538645d60b5", - "polygon": "LINEARRING (0 0, 0 1000, 1000 1000, 1000 0, 0 0)", + "image": "d484743f-e61a-4151-9c9c-cbc86031fe3e", + "polygon": "LINEARRING (300 300, 300 600, 600 600, 600 300, 300 300)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1189,18 +1189,18 @@ }, { "model": "documents.element", - "pk": "a8c108a2-a6b7-4401-9d64-65cf263531a7", + "pk": "be338e62-cfcd-4faa-9b23-9da3900313f9", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "3bbbd33a-b906-4526-9e0b-0643ade1b080", - "name": "ROY", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "099df260-b6a6-4559-b091-f2f57a78640f", + "name": "Volume 1, page 1r", "creator": null, "worker_version": null, "worker_run": null, - "image": "d3547a48-79f6-4c1f-8f16-c0be1a1da9fc", - "polygon": "LINEARRING (400 400, 400 500, 500 500, 500 400, 400 400)", + "image": "b7bc85ce-6999-4dd6-bb4e-5442de55c57e", + "polygon": "LINEARRING (0 0, 0 1000, 1000 1000, 1000 0, 0 0)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1208,18 +1208,18 @@ }, { "model": "documents.element", - "pk": "b0a0984b-dcf2-46da-8d6e-933020a46011", + "pk": "bfea27aa-fb3a-4298-a71f-f131cd2e6a13", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "3bbbd33a-b906-4526-9e0b-0643ade1b080", - "name": "DATUM", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "6e334f43-b407-4b4d-aa2a-553da2dddf65", + "name": "PARIS", "creator": null, "worker_version": null, "worker_run": null, - "image": "47f164d3-d26f-41e8-9df1-b538645d60b5", - "polygon": "LINEARRING (700 700, 700 800, 800 800, 800 700, 700 700)", + "image": "b7bc85ce-6999-4dd6-bb4e-5442de55c57e", + "polygon": "LINEARRING (100 100, 100 200, 200 200, 200 100, 100 100)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1227,18 +1227,18 @@ }, { "model": "documents.element", - "pk": "b8c4d1e3-aa9d-425a-b70f-c8ff16ae147c", + "pk": "ca255c31-ab61-4a34-b8a4-320f901a2cc9", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "7afe34f9-ad57-4ce7-872a-4d9aa31b6d0b", - "name": "Volume 2, page 2r", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "3b0543d9-e822-42aa-90fc-c45af23524e1", + "name": "Text line", "creator": null, "worker_version": null, "worker_run": null, - "image": "4b93eabe-b42d-4b88-8f31-a63d0258000a", - "polygon": "LINEARRING (0 0, 0 1000, 1000 1000, 1000 0, 0 0)", + "image": "b7bc85ce-6999-4dd6-bb4e-5442de55c57e", + "polygon": "LINEARRING (400 400, 400 500, 500 500, 500 400, 400 400)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1246,18 +1246,18 @@ }, { "model": "documents.element", - "pk": "ba888ed5-8c85-4f49-8f87-499219187ade", + "pk": "d2fd3f5e-30b4-4f80-ad81-62d6cea02e2e", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "eda3da9f-1a6b-4f7a-91d1-f28e4e74f76a", - "name": "Act 3", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "6e334f43-b407-4b4d-aa2a-553da2dddf65", + "name": "ROY", "creator": null, "worker_version": null, "worker_run": null, - "image": null, - "polygon": null, + "image": "d484743f-e61a-4151-9c9c-cbc86031fe3e", + "polygon": "LINEARRING (400 400, 400 500, 500 500, 500 400, 400 400)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1265,18 +1265,18 @@ }, { "model": "documents.element", - "pk": "c16df32e-03b5-47e4-868f-98d57eac1f15", + "pk": "e1355423-3976-43ca-842c-2e146ef7e79a", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "7afe34f9-ad57-4ce7-872a-4d9aa31b6d0b", - "name": "Volume 2, page 1v", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "eb4f8afb-4749-49a6-b3ba-d72be2d50384", + "name": "Surface F", "creator": null, "worker_version": null, "worker_run": null, - "image": "5de01fda-de62-4d26-b8bf-5c3f4678de3b", - "polygon": "LINEARRING (0 0, 0 1000, 1000 1000, 1000 0, 0 0)", + "image": "d484743f-e61a-4151-9c9c-cbc86031fe3e", + "polygon": "LINEARRING (600 600, 600 1000, 1000 1000, 1000 600, 600 600)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1284,18 +1284,18 @@ }, { "model": "documents.element", - "pk": "d227268a-2c2e-40ff-b22c-ab6e52eb5bba", + "pk": "e6c910b3-5dda-4db8-8ee0-2342e5f5d6ad", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "eda3da9f-1a6b-4f7a-91d1-f28e4e74f76a", - "name": "Act 1", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "eb4f8afb-4749-49a6-b3ba-d72be2d50384", + "name": "Surface B", "creator": null, "worker_version": null, "worker_run": null, - "image": null, - "polygon": null, + "image": "b7bc85ce-6999-4dd6-bb4e-5442de55c57e", + "polygon": "LINEARRING (600 600, 600 1000, 1000 1000, 1000 600, 600 600)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1303,17 +1303,17 @@ }, { "model": "documents.element", - "pk": "d3dc3f63-9894-434b-9715-7bd906b63e63", + "pk": "ef4e20a9-7991-4d62-a2e8-bf565c0c6b33", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "3bbbd33a-b906-4526-9e0b-0643ade1b080", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "6e334f43-b407-4b4d-aa2a-553da2dddf65", "name": "PARIS", "creator": null, "worker_version": null, "worker_run": null, - "image": "d3547a48-79f6-4c1f-8f16-c0be1a1da9fc", + "image": "56695e99-e1dc-4a94-b19f-061017b046e2", "polygon": "LINEARRING (100 100, 100 200, 200 200, 200 100, 100 100)", "rotation_angle": 0, "mirrored": false, @@ -1322,17 +1322,17 @@ }, { "model": "documents.element", - "pk": "d5daac1b-5f57-4d3c-b120-54adbcc82d7d", + "pk": "ef9367bc-5de0-4c3f-aebf-39086e11efaa", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "bc871e02-a23f-4799-a4d7-bb81813f41f5", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "eb4f8afb-4749-49a6-b3ba-d72be2d50384", "name": "Surface D", "creator": null, "worker_version": null, "worker_run": null, - "image": "d4db6162-7d7b-4839-a84f-756e74fef150", + "image": "d484743f-e61a-4151-9c9c-cbc86031fe3e", "polygon": "LINEARRING (0 0, 0 300, 300 300, 300 0, 0 0)", "rotation_angle": 0, "mirrored": false, @@ -1341,18 +1341,18 @@ }, { "model": "documents.element", - "pk": "e2f8eee0-cf55-404c-86e6-6950ab88faac", + "pk": "fb316906-9851-4851-a3d6-2a82a2b0f066", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "bc871e02-a23f-4799-a4d7-bb81813f41f5", - "name": "Surface F", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "099df260-b6a6-4559-b091-f2f57a78640f", + "name": "Volume 2, page 1r", "creator": null, "worker_version": null, "worker_run": null, - "image": "d4db6162-7d7b-4839-a84f-756e74fef150", - "polygon": "LINEARRING (600 600, 600 1000, 1000 1000, 1000 600, 600 600)", + "image": "29e9787c-7af0-4964-8b76-e8f5de3e0675", + "polygon": "LINEARRING (0 0, 0 1000, 1000 1000, 1000 0, 0 0)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1360,79 +1360,79 @@ }, { "model": "documents.entitytype", - "pk": "4e1138f1-be4c-4205-834b-b2795e2bd1a3", + "pk": "141135ea-c4a4-42eb-ac1b-03f8fe7a6cb0", "fields": { "name": "date", "color": "ff0000", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a" + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0" } }, { "model": "documents.entitytype", - "pk": "564768cc-38be-45be-9e61-8dcade951c00", + "pk": "7bb9ed54-338e-4ca6-ba64-0801785ff671", "fields": { - "name": "location", + "name": "organization", "color": "ff0000", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a" + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0" } }, { "model": "documents.entitytype", - "pk": "a773549d-4adf-4dde-a595-3b2318f5cc0d", + "pk": "7d637285-d95b-4b7c-9368-cce209a69879", "fields": { "name": "person", "color": "ff0000", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a" + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0" } }, { "model": "documents.entitytype", - "pk": "e19eb34b-55eb-4a8f-8019-18eea8e62fba", + "pk": "a4aa202a-5087-4f86-a3d6-8742175ba9f2", "fields": { - "name": "number", + "name": "location", "color": "ff0000", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a" + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0" } }, { "model": "documents.entitytype", - "pk": "e96f8757-37d7-4457-9f2a-8e870cdadae5", + "pk": "ff3f7dc1-b950-4f9a-9cd8-27d8aa1b8c8e", "fields": { - "name": "organization", + "name": "number", "color": "ff0000", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a" + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0" } }, { "model": "documents.transcription", - "pk": "28e2570a-4022-4718-8827-5be712a53d4b", + "pk": "2906dc29-9d4e-4aab-bbbd-a62b0e015bbc", "fields": { - "element": "3603393d-4831-4dbf-9672-3fbe1077e03a", - "worker_version": "70b6b804-b6f9-4efb-9d79-b8258c336eb7", + "element": "21250ac5-76b6-4c39-8ff2-60e77188501e", + "worker_version": "f50622da-5cdc-406d-9c3d-2416da0100a0", "worker_run": null, - "text": "ROY", + "text": "DATUM", "orientation": "horizontal-lr", "confidence": 1.0 } }, { "model": "documents.transcription", - "pk": "626eaeaf-ae24-4bc0-b1f6-78ad744a3645", + "pk": "3cf06526-6690-4c64-9b3e-6a8865e3e177", "fields": { - "element": "6c6a774e-88c3-40c5-9913-d7ec2a3d5125", - "worker_version": "70b6b804-b6f9-4efb-9d79-b8258c336eb7", + "element": "b7e07d82-0d10-4060-843e-a838b766ec3d", + "worker_version": "f50622da-5cdc-406d-9c3d-2416da0100a0", "worker_run": null, - "text": "Lorem ipsum dolor sit amet", + "text": "PARIS", "orientation": "horizontal-lr", "confidence": 1.0 } }, { "model": "documents.transcription", - "pk": "62b7b9e6-0869-444c-aa8b-054fc124f6d9", + "pk": "46c00ebe-6d6e-47e3-ad0a-6b95561dca34", "fields": { - "element": "a8c108a2-a6b7-4401-9d64-65cf263531a7", - "worker_version": "70b6b804-b6f9-4efb-9d79-b8258c336eb7", + "element": "d2fd3f5e-30b4-4f80-ad81-62d6cea02e2e", + "worker_version": "f50622da-5cdc-406d-9c3d-2416da0100a0", "worker_run": null, "text": "ROY", "orientation": "horizontal-lr", @@ -1441,123 +1441,123 @@ }, { "model": "documents.transcription", - "pk": "6f477447-1b65-44f6-a912-6ff6b24165a2", + "pk": "54079611-98bb-4791-9ffa-fe0d9f287a5a", "fields": { - "element": "2c0f28b1-b8e9-4f08-9014-75ce7950ee6f", - "worker_version": "70b6b804-b6f9-4efb-9d79-b8258c336eb7", + "element": "bfea27aa-fb3a-4298-a71f-f131cd2e6a13", + "worker_version": "f50622da-5cdc-406d-9c3d-2416da0100a0", "worker_run": null, - "text": "ROY", + "text": "PARIS", "orientation": "horizontal-lr", "confidence": 1.0 } }, { "model": "documents.transcription", - "pk": "797bde96-8b76-4a9b-a4b9-b3378fcfdcdf", + "pk": "62426d09-a1e8-49d4-991e-4a0f71077abf", "fields": { - "element": "18ada82e-36c6-47e4-bcff-bee5aa63889b", - "worker_version": "70b6b804-b6f9-4efb-9d79-b8258c336eb7", + "element": "ef4e20a9-7991-4d62-a2e8-bf565c0c6b33", + "worker_version": "f50622da-5cdc-406d-9c3d-2416da0100a0", "worker_run": null, - "text": "DATUM", + "text": "PARIS", "orientation": "horizontal-lr", "confidence": 1.0 } }, { "model": "documents.transcription", - "pk": "7b0f48b5-0580-47ef-9927-5e5fbdb749ff", + "pk": "7dadbfd7-356a-43da-9f08-c8e32439a8f2", "fields": { - "element": "469f4606-f5ea-406d-a0bd-6d48050c39ed", - "worker_version": "70b6b804-b6f9-4efb-9d79-b8258c336eb7", + "element": "9b7b7b7a-0b5e-42ed-8cbc-75caf378d45e", + "worker_version": "f50622da-5cdc-406d-9c3d-2416da0100a0", "worker_run": null, - "text": "PARIS", + "text": "DATUM", "orientation": "horizontal-lr", "confidence": 1.0 } }, { "model": "documents.transcription", - "pk": "a94dabcd-9e37-4cbc-a45a-6e9a5a76b01a", + "pk": "8ce611e8-6064-4d74-935c-775d317ce6ce", "fields": { - "element": "d3dc3f63-9894-434b-9715-7bd906b63e63", - "worker_version": "70b6b804-b6f9-4efb-9d79-b8258c336eb7", + "element": "be338e62-cfcd-4faa-9b23-9da3900313f9", + "worker_version": "f50622da-5cdc-406d-9c3d-2416da0100a0", "worker_run": null, - "text": "PARIS", + "text": "Lorem ipsum dolor sit amet", "orientation": "horizontal-lr", "confidence": 1.0 } }, { "model": "documents.transcription", - "pk": "e74d7029-86e6-41ad-beed-12f4b215fb57", + "pk": "98936cc4-a0c0-45ba-9a71-b83e721ef393", "fields": { - "element": "34cdee1d-40e3-4499-b04b-93f6d2bfb802", - "worker_version": "70b6b804-b6f9-4efb-9d79-b8258c336eb7", + "element": "36345a26-69b9-4cf9-9682-5eb62d1726cc", + "worker_version": "f50622da-5cdc-406d-9c3d-2416da0100a0", "worker_run": null, - "text": "DATUM", + "text": "ROY", "orientation": "horizontal-lr", "confidence": 1.0 } }, { "model": "documents.transcription", - "pk": "eae51f9a-eb40-4d27-946f-274231062d30", + "pk": "b466e3de-f7db-4566-8110-16c14c60ae7d", "fields": { - "element": "92101c2b-885f-42e2-a0cb-13a8a04c0ea8", - "worker_version": "70b6b804-b6f9-4efb-9d79-b8258c336eb7", + "element": "ae27310b-dc31-413b-b551-2b74c81147ab", + "worker_version": "f50622da-5cdc-406d-9c3d-2416da0100a0", "worker_run": null, - "text": "PARIS", + "text": "DATUM", "orientation": "horizontal-lr", "confidence": 1.0 } }, { "model": "documents.transcription", - "pk": "fb8d4786-a627-4049-9433-20755e7f3ca5", + "pk": "ee8fc9fb-f6fa-435a-b1fd-41aa8bbf54cf", "fields": { - "element": "b0a0984b-dcf2-46da-8d6e-933020a46011", - "worker_version": "70b6b804-b6f9-4efb-9d79-b8258c336eb7", + "element": "9523eff1-25a3-423f-8660-fa3b0ab51c93", + "worker_version": "f50622da-5cdc-406d-9c3d-2416da0100a0", "worker_run": null, - "text": "DATUM", + "text": "ROY", "orientation": "horizontal-lr", "confidence": 1.0 } }, { "model": "documents.allowedmetadata", - "pk": "7c15d98e-10f2-4c0b-809f-b65242d5e74b", + "pk": "0b3e7a6f-dbc5-4eea-8eda-8f4ed32d92cd", "fields": { - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "date", - "name": "date" + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "location", + "name": "location" } }, { "model": "documents.allowedmetadata", - "pk": "819968c2-2433-4597-ac8b-852ed3397a9a", + "pk": "43f8eda2-3dc7-49ab-bf85-9ea2792e5d90", "fields": { - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "location", - "name": "location" + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "text", + "name": "folio" } }, { "model": "documents.allowedmetadata", - "pk": "c079f5d7-f039-43a5-93c6-9019e8203e6b", + "pk": "f9047ccd-8459-4a0a-a919-e2cc146b79e8", "fields": { - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "type": "text", - "name": "folio" + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "type": "date", + "name": "date" } }, { "model": "documents.metadata", - "pk": "03221610-3351-472d-9f7e-b23d7cbc7b92", + "pk": "2c4f50d5-e172-451d-b502-97e7ea7a78d6", "fields": { - "element": "6a41ddf7-41e4-40ad-8234-d773b74058bc", + "element": "2bd1a3ce-6073-4387-8a71-a5d0134ce894", "name": "folio", "type": "text", - "value": "1v", + "value": "2r", "entity": null, "worker_version": null, "worker_run": null @@ -1565,12 +1565,12 @@ }, { "model": "documents.metadata", - "pk": "18459ddf-615f-47cc-8de6-11ab62446632", + "pk": "2d8b3817-7ba9-4d21-aad6-6eea3e2ccfd7", "fields": { - "element": "664f19d6-472d-49cc-bd87-26c19db7d2d3", - "name": "number", + "element": "be338e62-cfcd-4faa-9b23-9da3900313f9", + "name": "folio", "type": "text", - "value": "2", + "value": "1r", "entity": null, "worker_version": null, "worker_run": null @@ -1578,12 +1578,12 @@ }, { "model": "documents.metadata", - "pk": "34ec1306-1d88-4fc4-8049-d157252c5d51", + "pk": "4ad9804a-9356-4dde-a9e4-aee88acfeab3", "fields": { - "element": "6c6a774e-88c3-40c5-9913-d7ec2a3d5125", - "name": "folio", + "element": "7bf57bb3-1836-4b17-a3e0-a8981dae790b", + "name": "number", "type": "text", - "value": "1r", + "value": "5", "entity": null, "worker_version": null, "worker_run": null @@ -1591,12 +1591,12 @@ }, { "model": "documents.metadata", - "pk": "5712f834-778d-450b-ba46-86262c4a13f8", + "pk": "538685ab-a382-4488-aeee-e1892f8ef347", "fields": { - "element": "ba888ed5-8c85-4f49-8f87-499219187ade", - "name": "number", + "element": "46ee25df-c8c1-46f8-af4c-d62d0c8f4600", + "name": "folio", "type": "text", - "value": "3", + "value": "1v", "entity": null, "worker_version": null, "worker_run": null @@ -1604,9 +1604,9 @@ }, { "model": "documents.metadata", - "pk": "7d8d0dc1-fcf6-4e47-9b03-25d5baaac590", + "pk": "6f9f136a-d2f9-4399-9ea6-b2f63e52c985", "fields": { - "element": "86f68e65-f23b-4d01-a9ca-c40eed9cda32", + "element": "3e73d3d0-a713-4d2f-a1e8-ec268a0e7df1", "name": "number", "type": "text", "value": "4", @@ -1617,12 +1617,12 @@ }, { "model": "documents.metadata", - "pk": "820d9d2f-c6d0-40df-9bce-cb9062f17b0b", + "pk": "8794103d-35cb-41c2-b963-9197adf8e187", "fields": { - "element": "60313262-3273-4c39-a8ee-cedc733f6990", + "element": "5d300cf4-3dae-43f1-9c91-26ed0c89064c", "name": "folio", "type": "text", - "value": "1r", + "value": "1v", "entity": null, "worker_version": null, "worker_run": null @@ -1630,12 +1630,12 @@ }, { "model": "documents.metadata", - "pk": "93e2a919-9320-47ce-9b0f-de178dab40bc", + "pk": "9d9f745e-3e24-40bd-8d84-f3b21f5a21d0", "fields": { - "element": "3f3bb3ee-ec7c-436c-9dde-f8aa0e1a69d7", + "element": "2cdfa4f2-a66c-46c0-8038-8e5ac7e7d36e", "name": "number", "type": "text", - "value": "5", + "value": "1", "entity": null, "worker_version": null, "worker_run": null @@ -1643,12 +1643,12 @@ }, { "model": "documents.metadata", - "pk": "9fc32596-bd62-4f8d-894d-abfeefc6157a", + "pk": "a7430848-c83e-4646-94c4-dfc026bb0ae0", "fields": { - "element": "4d2f59fe-9808-44b1-b3b5-017490ae4064", - "name": "folio", + "element": "83d06bd7-3efc-4574-b833-62be2d1d94ad", + "name": "number", "type": "text", - "value": "2r", + "value": "2", "entity": null, "worker_version": null, "worker_run": null @@ -1656,12 +1656,12 @@ }, { "model": "documents.metadata", - "pk": "ba9916fa-52e6-4203-8db4-662f9083c70c", + "pk": "dedbe859-d13a-4d2c-86d1-3e48c44518be", "fields": { - "element": "c16df32e-03b5-47e4-868f-98d57eac1f15", + "element": "fb316906-9851-4851-a3d6-2a82a2b0f066", "name": "folio", "type": "text", - "value": "1v", + "value": "1r", "entity": null, "worker_version": null, "worker_run": null @@ -1669,12 +1669,12 @@ }, { "model": "documents.metadata", - "pk": "bf7933a6-1646-40d0-be66-1306bc564481", + "pk": "e10c025b-c119-4857-8bd3-43393137b8a2", "fields": { - "element": "b8c4d1e3-aa9d-425a-b70f-c8ff16ae147c", - "name": "folio", + "element": "2adf861a-a36a-4127-bf21-583ff95bc08c", + "name": "number", "type": "text", - "value": "2r", + "value": "3", "entity": null, "worker_version": null, "worker_run": null @@ -1682,12 +1682,12 @@ }, { "model": "documents.metadata", - "pk": "cffe6d09-810c-4365-8107-af240d60097e", + "pk": "f4558644-4883-45fb-9b4c-7921ad287f64", "fields": { - "element": "d227268a-2c2e-40ff-b22c-ab6e52eb5bba", - "name": "number", + "element": "51af1d15-e242-49e4-a797-7697c5162059", + "name": "folio", "type": "text", - "value": "1", + "value": "2r", "entity": null, "worker_version": null, "worker_run": null @@ -1710,12 +1710,12 @@ }, { "model": "images.image", - "pk": "2552f45d-6880-4a4c-b9c2-1091598a52b4", + "pk": "21636e84-7d76-4261-b09f-1dc3a16cf2db", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", "server": 1, - "path": "img4", + "path": "img6", "width": 1000, "height": 1000, "hash": null, @@ -1724,12 +1724,12 @@ }, { "model": "images.image", - "pk": "47f164d3-d26f-41e8-9df1-b538645d60b5", + "pk": "29e9787c-7af0-4964-8b76-e8f5de3e0675", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", "server": 1, - "path": "img2", + "path": "img4", "width": 1000, "height": 1000, "hash": null, @@ -1738,12 +1738,12 @@ }, { "model": "images.image", - "pk": "4b93eabe-b42d-4b88-8f31-a63d0258000a", + "pk": "56695e99-e1dc-4a94-b19f-061017b046e2", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", "server": 1, - "path": "img6", + "path": "img2", "width": 1000, "height": 1000, "hash": null, @@ -1752,7 +1752,7 @@ }, { "model": "images.image", - "pk": "5de01fda-de62-4d26-b8bf-5c3f4678de3b", + "pk": "888b07cf-6e17-4d1e-9b01-1186121b1cfb", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", @@ -1766,7 +1766,7 @@ }, { "model": "images.image", - "pk": "d3547a48-79f6-4c1f-8f16-c0be1a1da9fc", + "pk": "b7bc85ce-6999-4dd6-bb4e-5442de55c57e", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", @@ -1780,7 +1780,7 @@ }, { "model": "images.image", - "pk": "d4db6162-7d7b-4839-a84f-756e74fef150", + "pk": "d484743f-e61a-4151-9c9c-cbc86031fe3e", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", @@ -1794,56 +1794,56 @@ }, { "model": "users.right", - "pk": "066494da-9d28-44a8-ad04-0616cde80af0", + "pk": "27023448-93c5-4164-aed7-753f7c1d8764", "fields": { - "user": 2, + "user": 4, "group": null, - "content_type": 20, - "content_id": "fdbe4b5c-9475-4310-8877-eb07344e294a", - "level": 100 + "content_type": 35, + "content_id": "bac5ff73-6f02-49bc-a063-00ff64494d39", + "level": 10 } }, { "model": "users.right", - "pk": "58ce1f20-32a1-4507-8edd-28fff7a1a77f", + "pk": "a2967ea5-e1de-462e-882e-9faaf600b53e", "fields": { - "user": 2, + "user": 3, "group": null, - "content_type": 12, - "content_id": "c19116b6-866f-4bb7-b447-3544629a8151", - "level": 10 + "content_type": 35, + "content_id": "bac5ff73-6f02-49bc-a063-00ff64494d39", + "level": 50 } }, { "model": "users.right", - "pk": "9f94b10b-ffd6-4a87-885d-af494d41c850", + "pk": "a72c8509-4829-4790-a165-55d9cb3e96d9", "fields": { - "user": 3, + "user": 2, "group": null, - "content_type": 35, - "content_id": "c104ee5f-be60-4cdd-a738-10bf24601682", - "level": 50 + "content_type": 20, + "content_id": "a00441c1-d58f-4a66-9108-b9c0985d14d0", + "level": 100 } }, { "model": "users.right", - "pk": "b04e5fb5-dbca-4255-aae1-4c100c510c9d", + "pk": "aa443485-f321-4fcd-9bdf-6b8547e6982a", "fields": { "user": 2, "group": null, "content_type": 35, - "content_id": "c104ee5f-be60-4cdd-a738-10bf24601682", + "content_id": "bac5ff73-6f02-49bc-a063-00ff64494d39", "level": 100 } }, { "model": "users.right", - "pk": "da8297fb-5484-4511-8d29-0e8d6cbccc49", + "pk": "b4b624b8-128b-472c-99a3-79c7b86fd7cb", "fields": { - "user": 4, + "user": 2, "group": null, - "content_type": 35, - "content_id": "c104ee5f-be60-4cdd-a738-10bf24601682", + "content_type": 12, + "content_id": "ae9b2873-eca1-4e11-a3e6-9d825ea0882f", "level": 10 } }, @@ -1851,7 +1851,7 @@ "model": "users.user", "pk": 1, "fields": { - "password": "pbkdf2_sha256$390000$IL4asfhs96nzQKK9md5Axl$uXyWwT/if+W9OsJ+OeHkPZWm4xlQ7D1ep2NyjxSNOLs=", + "password": "pbkdf2_sha256$390000$yo6qgdx09Wc9jqWv5Ua46o$noUOYhVgP2jQOJKDegPh/hgA8K/IE4sSqGFGJZ11jeA=", "last_login": null, "email": "root@root.fr", "display_name": "Admin", @@ -1866,7 +1866,7 @@ "model": "users.user", "pk": 2, "fields": { - "password": "pbkdf2_sha256$390000$RvCTxXTmAXRgZ29LJrofXG$vnWF7puXQBP+G8wcclsOrd2ZpHbiq7jC/kX7F31tSQQ=", + "password": "pbkdf2_sha256$390000$XjsB2WBbuUlUxXMFFHNP0S$HcqLCn8egh9bJc3NNa1YNsFKdqDQFrjZVbfce75Gd8s=", "last_login": null, "email": "user@user.fr", "display_name": "Test user", @@ -1909,7 +1909,7 @@ }, { "model": "users.group", - "pk": "c104ee5f-be60-4cdd-a738-10bf24601682", + "pk": "bac5ff73-6f02-49bc-a063-00ff64494d39", "fields": { "name": "User group", "public": false, @@ -3943,15 +3943,15 @@ }, { "model": "ponos.farm", - "pk": "c19116b6-866f-4bb7-b447-3544629a8151", + "pk": "ae9b2873-eca1-4e11-a3e6-9d825ea0882f", "fields": { "name": "Wheat farm", - "seed": "03f762b3c505f0406a411b3fb3bbe4785c0e7763a05b515e7208b99ee93e955c" + "seed": "f05f8519260e328318528860a15595dd7f6401880018661243c57071c161c740" } }, { "model": "ponos.task", - "pk": "4ca6c03f-9fee-4d44-8b6d-a3cfa5ae1ffb", + "pk": "e844ca04-2226-4425-8347-ddc8eb1c29ee", "fields": { "run": 0, "depth": 0, @@ -3967,22 +3967,22 @@ "agent": null, "requires_gpu": false, "gpu": null, - "process": "245bc206-350c-43d5-8db3-d98645c4eaa9", + "process": "111cabac-6f43-4ff3-b6a3-77f79eb7e7fb", "worker_run": null, "container": null, "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", "expiry": "2100-12-31T23:59:59.999Z", "extra_files": "{}", - "token": "xm8g6R1OTqmlJVsQwA9aYZfHNbR2Q0fWmB1BRSy4plw=", + "token": "AkuV/NInTwyu6jFjlOy1Hk38kQEZrUYJj7E6LhCfbz4=", "parents": [] } }, { "model": "ponos.artifact", - "pk": "9a1b1d5d-ec3d-4a21-89da-0e930292467d", + "pk": "00515030-0750-46ea-9aa5-62c8bcd0252f", "fields": { - "task": "4ca6c03f-9fee-4d44-8b6d-a3cfa5ae1ffb", + "task": "e844ca04-2226-4425-8347-ddc8eb1c29ee", "path": "/path/to/docker_build", "size": 42000, "content_type": "application/octet-stream", @@ -3992,30 +3992,30 @@ }, { "model": "training.dataset", - "pk": "3c74b8de-7c42-4f2a-921f-a5ef957b80da", + "pk": "13da2dd3-d8cf-43c4-9f61-b2c7d4562d69", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", "creator": 2, "task": null, - "name": "Second Dataset", - "description": "dataset number two", + "name": "First Dataset", + "description": "dataset number one", "state": "open", "sets": "[\"training\", \"test\", \"validation\"]" } }, { "model": "training.dataset", - "pk": "87c1ca55-bb92-4b6d-90af-a2f50ae49df9", + "pk": "925b6d6a-1121-42d9-8946-ceada2921c66", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "fdbe4b5c-9475-4310-8877-eb07344e294a", + "corpus": "a00441c1-d58f-4a66-9108-b9c0985d14d0", "creator": 2, "task": null, - "name": "First Dataset", - "description": "dataset number one", + "name": "Second Dataset", + "description": "dataset number two", "state": "open", "sets": "[\"training\", \"test\", \"validation\"]" } diff --git a/arkindex/documents/tests/test_allowed_metadata.py b/arkindex/documents/tests/test_allowed_metadata.py index b7c62412b114dfb23309b401164f3e66fddde8dc..00468e091ac75ee01643211caf7a8c01386fe888 100644 --- a/arkindex/documents/tests/test_allowed_metadata.py +++ b/arkindex/documents/tests/test_allowed_metadata.py @@ -1,3 +1,5 @@ +from unittest.mock import patch + from django.urls import reverse from rest_framework import status @@ -51,7 +53,8 @@ class TestAllowedMetaData(FixtureAPITestCase): list(expected_meta[3:]) ) - def test_list_private_corpus(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_list_private_corpus(self, has_access_mock): with self.assertNumQueries(1): response = self.client.get( reverse("api:corpus-allowed-metadata", kwargs={"pk": str(self.private_corpus.id)}), @@ -77,7 +80,7 @@ class TestAllowedMetaData(FixtureAPITestCase): def test_create(self): self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.post( reverse("api:corpus-allowed-metadata", kwargs={"pk": str(self.corpus.id)}), data={"name": "flan", "type": "text"} @@ -93,7 +96,7 @@ class TestAllowedMetaData(FixtureAPITestCase): def test_create_unique(self): self.client.force_login(self.user) self.corpus.allowed_metadatas.create(name="flan", type=MetaType.Text) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:corpus-allowed-metadata", kwargs={"pk": str(self.corpus.id)}), data={"name": "flan", "type": "text"} @@ -122,21 +125,21 @@ class TestAllowedMetaData(FixtureAPITestCase): ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_create_requires_corpus_admin(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_create_requires_corpus_admin(self, has_access_mock): self.client.force_login(self.user) - for role in [Role.Guest, Role.Contributor]: - with self.subTest(role=role): - self.corpus.memberships.filter(user=self.user).update(level=role.value) - with self.assertNumQueries(5): - response = self.client.post( - reverse("api:corpus-allowed-metadata", kwargs={"pk": str(self.corpus.id)}), - data={"name": "flan", "type": "text"} - ) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_create_private_corpus(self): + self.corpus.memberships.filter(user=self.user).update(level=Role.Contributor.value) + with self.assertNumQueries(3): + response = self.client.post( + reverse("api:corpus-allowed-metadata", kwargs={"pk": str(self.corpus.id)}), + data={"name": "flan", "type": "text"} + ) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_create_private_corpus(self, has_access_mock): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:corpus-allowed-metadata", kwargs={"pk": str(self.private_corpus.id)}), data={"name": "flan", "type": "text"} @@ -145,7 +148,7 @@ class TestAllowedMetaData(FixtureAPITestCase): def test_create_invalid(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:corpus-allowed-metadata", kwargs={"pk": str(self.corpus.id)}), data={"name": "flan", "type": "pouet"} @@ -175,9 +178,10 @@ class TestAllowedMetaData(FixtureAPITestCase): response = self.client.get(reverse("api:allowed-metadata-edit", kwargs={"corpus": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "pk": self.test_meta.id})) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_retrieve_private_corpus(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_retrieve_private_corpus(self, has_access_mock): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.get(reverse("api:allowed-metadata-edit", kwargs={"corpus": self.private_corpus.id, "pk": self.test_meta.id})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) @@ -228,9 +232,10 @@ class TestAllowedMetaData(FixtureAPITestCase): ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_update_private_corpus(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_update_private_corpus(self, has_access_mock): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.put( reverse("api:allowed-metadata-edit", kwargs={"corpus": self.private_corpus.id, "pk": self.test_meta.id}), {"type": "url", "name": "newName"}, @@ -239,7 +244,7 @@ class TestAllowedMetaData(FixtureAPITestCase): def test_update_invalid_type(self): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.put( reverse("api:allowed-metadata-edit", kwargs={"corpus": self.corpus.id, "pk": self.test_meta.id}), {"type": "invalidType", "name": "newName"}, @@ -251,7 +256,7 @@ class TestAllowedMetaData(FixtureAPITestCase): def test_update_missing_argument(self): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.put( reverse("api:allowed-metadata-edit", kwargs={"corpus": self.corpus.id, "pk": self.test_meta.id}), {"name": "newName"}, @@ -263,7 +268,7 @@ class TestAllowedMetaData(FixtureAPITestCase): def test_update_duplicate(self): self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.put( reverse("api:allowed-metadata-edit", kwargs={"corpus": self.corpus.id, "pk": self.test_meta.id}), {"type": self.allowed_meta[1].type.value, "name": self.allowed_meta[1].name}, @@ -273,21 +278,20 @@ class TestAllowedMetaData(FixtureAPITestCase): "detail": ["An AllowedMetaData with this type and name already exists in this corpus."] }) - def test_update_requires_corpus_admin(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_update_requires_corpus_admin(self, has_access_mock): self.client.force_login(self.user) - for role in [Role.Guest, Role.Contributor]: - with self.subTest(role=role): - self.corpus.memberships.filter(user=self.user).update(level=role.value) - with self.assertNumQueries(5): - response = self.client.put( - reverse("api:allowed-metadata-edit", kwargs={"corpus": self.corpus.id, "pk": self.test_meta.id}), - {"type": self.allowed_meta[2].type.value, "name": self.allowed_meta[2].name}, - ) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.corpus.memberships.filter(user=self.user).update(level=Role.Contributor.value) + with self.assertNumQueries(3): + response = self.client.put( + reverse("api:allowed-metadata-edit", kwargs={"corpus": self.corpus.id, "pk": self.test_meta.id}), + {"type": self.allowed_meta[2].type.value, "name": self.allowed_meta[2].name}, + ) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) def test_partial_update(self): self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.patch( reverse("api:allowed-metadata-edit", kwargs={"corpus": self.corpus.id, "pk": self.test_meta.id}), {"name": "newName"}, @@ -317,9 +321,10 @@ class TestAllowedMetaData(FixtureAPITestCase): {"type": "reference"}) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_partial_update_private_corpus(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_partial_update_private_corpus(self, has_access_mock): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.patch( reverse("api:allowed-metadata-edit", kwargs={"corpus": self.private_corpus.id, "pk": self.test_meta.id}), {"type": "reference"}) @@ -327,7 +332,7 @@ class TestAllowedMetaData(FixtureAPITestCase): def test_partial_update_invalid_type(self): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.patch( reverse("api:allowed-metadata-edit", kwargs={"corpus": self.corpus.id, "pk": self.test_meta.id}), {"type": "invalidType"}, @@ -339,7 +344,7 @@ class TestAllowedMetaData(FixtureAPITestCase): def test_partial_update_duplicate(self): self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.patch( reverse("api:allowed-metadata-edit", kwargs={"corpus": self.corpus.id, "pk": self.test_meta.id}), {"type": self.allowed_meta[2].type.value}, @@ -349,21 +354,20 @@ class TestAllowedMetaData(FixtureAPITestCase): "detail": ["An AllowedMetaData with this type and name already exists in this corpus."] }) - def test_partial_update_requires_corpus_admin(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_partial_update_requires_corpus_admin(self, has_access_mock): self.client.force_login(self.user) - for role in [Role.Guest, Role.Contributor]: - with self.subTest(role=role): - self.corpus.memberships.filter(user=self.user).update(level=role.value) - with self.assertNumQueries(5): - response = self.client.patch( - reverse("api:allowed-metadata-edit", kwargs={"corpus": self.corpus.id, "pk": self.test_meta.id}), - {"type": self.allowed_meta[2].type.value}, - ) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.corpus.memberships.filter(user=self.user).update(level=Role.Contributor.value) + with self.assertNumQueries(3): + response = self.client.patch( + reverse("api:allowed-metadata-edit", kwargs={"corpus": self.corpus.id, "pk": self.test_meta.id}), + {"type": self.allowed_meta[2].type.value}, + ) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) def test_destroy(self): self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.delete(reverse("api:allowed-metadata-edit", kwargs={"corpus": self.corpus.id, "pk": self.test_meta.id})) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) @@ -372,7 +376,7 @@ class TestAllowedMetaData(FixtureAPITestCase): def test_destroy_not_found(self): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.delete(reverse("api:allowed-metadata-edit", kwargs={"corpus": self.corpus.id, "pk": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"})) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) @@ -395,17 +399,17 @@ class TestAllowedMetaData(FixtureAPITestCase): response = self.client.delete(reverse("api:allowed-metadata-edit", kwargs={"corpus": self.corpus.id, "pk": self.test_meta.id})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_destroy_private_corpus(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_destroy_private_corpus(self, has_access_mock): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.delete(reverse("api:allowed-metadata-edit", kwargs={"corpus": self.private_corpus.id, "pk": self.test_meta.id})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_destroy_requires_corpus_admin(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_destroy_requires_corpus_admin(self, has_access_mock): self.client.force_login(self.user) - for role in [Role.Guest, Role.Contributor]: - with self.subTest(role=role): - self.corpus.memberships.filter(user=self.user).update(level=role.value) - with self.assertNumQueries(5): - response = self.client.delete(reverse("api:allowed-metadata-edit", kwargs={"corpus": self.corpus.id, "pk": self.test_meta.id})) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.corpus.memberships.filter(user=self.user).update(level=Role.Contributor.value) + with self.assertNumQueries(3): + response = self.client.delete(reverse("api:allowed-metadata-edit", kwargs={"corpus": self.corpus.id, "pk": self.test_meta.id})) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) diff --git a/arkindex/documents/tests/test_bulk_classification.py b/arkindex/documents/tests/test_bulk_classification.py index d722c54e41a8cc713c466a6d7a1c6655640a2315..ce5ab5e7d739c82a33ee9bbbcff77d2f338af1c7 100644 --- a/arkindex/documents/tests/test_bulk_classification.py +++ b/arkindex/documents/tests/test_bulk_classification.py @@ -1,3 +1,5 @@ +from unittest.mock import patch + from django.urls import reverse from rest_framework import status @@ -24,15 +26,17 @@ class TestBulkClassification(FixtureAPITestCase): response = self.client.post(reverse("api:classification-bulk"), format="json") self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_wrong_acl(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights") + def test_wrong_acl(self, filter_rights_mock): """ The user must have access to the parent element """ + filter_rights_mock.return_value = Corpus.objects.none() self.client.force_login(self.user) private_page = self.private_corpus.elements.create( type=self.private_corpus.types.create(slug="page"), ) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:classification-bulk"), format="json", @@ -61,7 +65,7 @@ class TestBulkClassification(FixtureAPITestCase): Classifications must be linked to a worker run """ self.client.force_login(self.user) - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.post( reverse("api:classification-bulk"), format="json", @@ -89,7 +93,7 @@ class TestBulkClassification(FixtureAPITestCase): def test_worker_run_not_found(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post( reverse("api:classification-bulk"), format="json", @@ -119,7 +123,7 @@ class TestBulkClassification(FixtureAPITestCase): self.dog_class.delete() self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.post( reverse("api:classification-bulk"), format="json", @@ -145,7 +149,7 @@ class TestBulkClassification(FixtureAPITestCase): Test the bulk classification API deletes previous classifications with the same worker run """ self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(7): response = self.client.post( reverse("api:classification-bulk"), format="json", @@ -205,7 +209,7 @@ class TestBulkClassification(FixtureAPITestCase): Test the bulk classification API prevents creating classifications with duplicate ML classes """ self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.post( reverse("api:classification-bulk"), format="json", @@ -297,7 +301,7 @@ class TestBulkClassification(FixtureAPITestCase): other_worker_run = process2.worker_runs.create(version=self.worker_run.version, parents=[]) self.worker_run.process.run() task = self.worker_run.process.tasks.first() - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.post( reverse("api:classification-bulk"), format="json", @@ -325,7 +329,7 @@ class TestBulkClassification(FixtureAPITestCase): A regular user can create classifications with a WorkerRun of their own local process """ self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(7): response = self.client.post( reverse("api:classification-bulk"), format="json", @@ -396,7 +400,7 @@ class TestBulkClassification(FixtureAPITestCase): self.worker_run.process.run() task = self.worker_run.process.tasks.first() - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.post( reverse("api:classification-bulk"), format="json", @@ -445,7 +449,7 @@ class TestBulkClassification(FixtureAPITestCase): self.assertNotEqual(self.worker_run.process_id, local_worker_run.process_id) - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.post( reverse("api:classification-bulk"), format="json", diff --git a/arkindex/documents/tests/test_bulk_element_transcriptions.py b/arkindex/documents/tests/test_bulk_element_transcriptions.py index 8075296695abe24a16f360f933c6e6a0a73516b9..3ef2499efb3e35ee188e6aa9f817a88f88560afe 100644 --- a/arkindex/documents/tests/test_bulk_element_transcriptions.py +++ b/arkindex/documents/tests/test_bulk_element_transcriptions.py @@ -1,4 +1,5 @@ import uuid +from unittest.mock import patch from django.db.models import Count from django.test import override_settings @@ -13,9 +14,6 @@ from arkindex.project.tests import FixtureAPITestCase class TestBulkElementTranscriptions(FixtureAPITestCase): - """ - Tests for text element creation view - """ @classmethod def setUpTestData(cls): @@ -90,7 +88,7 @@ class TestBulkElementTranscriptions(FixtureAPITestCase): } for poly, text, confidence, orientation in transcriptions] } self.client.force_login(self.user) - with self.assertNumQueries(15): + with self.assertNumQueries(13): response = self.client.post( reverse("api:element-transcriptions-bulk", kwargs={"pk": self.page.id}), format="json", @@ -150,7 +148,7 @@ class TestBulkElementTranscriptions(FixtureAPITestCase): } for poly, text, confidence, orientation, element_confidence in transcriptions] } self.client.force_login(self.user) - with self.assertNumQueries(15): + with self.assertNumQueries(13): response = self.client.post( reverse("api:element-transcriptions-bulk", kwargs={"pk": self.page.id}), format="json", @@ -211,7 +209,7 @@ class TestBulkElementTranscriptions(FixtureAPITestCase): self.assertEqual(created_elts.count(), 1) self.client.force_login(self.user) - with self.assertNumQueries(15): + with self.assertNumQueries(13): response = self.client.post( reverse("api:element-transcriptions-bulk", kwargs={"pk": self.page.id}), format="json", @@ -253,7 +251,7 @@ class TestBulkElementTranscriptions(FixtureAPITestCase): } self.client.force_login(self.user) - with self.assertNumQueries(15): + with self.assertNumQueries(13): response = self.client.post( reverse("api:element-transcriptions-bulk", kwargs={"pk": self.page.id}), format="json", @@ -302,7 +300,7 @@ class TestBulkElementTranscriptions(FixtureAPITestCase): } self.client.force_login(self.user) - with self.assertNumQueries(13): + with self.assertNumQueries(11): response = self.client.post( reverse("api:element-transcriptions-bulk", kwargs={"pk": self.huge_page.id}), format="json", @@ -334,12 +332,13 @@ class TestBulkElementTranscriptions(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), {"detail": "You do not have permission to perform this action."}) - def test_bulk_transcriptions_private(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_bulk_transcriptions_private(self, filter_rights_mock): """ Accessing a non writeable element triggers a 404_NOT_FOUND """ self.client.force_login(self.user) - with self.assertNumQueries(5): + with self.assertNumQueries(2): response = self.client.post( reverse("api:element-transcriptions-bulk", kwargs={"pk": self.private_page.id}), format="json", @@ -372,7 +371,7 @@ class TestBulkElementTranscriptions(FixtureAPITestCase): def test_bulk_transcriptions_version_xor_run(self): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.post( reverse("api:element-transcriptions-bulk", kwargs={"pk": self.page.id}), format="json", @@ -469,7 +468,7 @@ class TestBulkElementTranscriptions(FixtureAPITestCase): } self.client.force_login(self.user) - with self.assertNumQueries(15): + with self.assertNumQueries(13): response = self.client.post( reverse("api:element-transcriptions-bulk", kwargs={"pk": self.page.id}), format="json", @@ -536,7 +535,7 @@ class TestBulkElementTranscriptions(FixtureAPITestCase): } for poly, text, confidence in transcriptions] } self.client.force_login(self.user) - with self.assertNumQueries(15): + with self.assertNumQueries(13): response = self.client.post( reverse("api:element-transcriptions-bulk", kwargs={"pk": top_level.id}), format="json", @@ -584,7 +583,7 @@ class TestBulkElementTranscriptions(FixtureAPITestCase): } for poly, text, confidence in transcriptions] } self.client.force_login(self.user) - with self.assertNumQueries(15): + with self.assertNumQueries(13): response = self.client.post( reverse("api:element-transcriptions-bulk", kwargs={"pk": self.page.id}), format="json", @@ -616,7 +615,7 @@ class TestBulkElementTranscriptions(FixtureAPITestCase): } for poly, text, confidence, orientation in transcriptions] } self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.post( reverse("api:element-transcriptions-bulk", kwargs={"pk": self.page.id}), format="json", @@ -647,7 +646,7 @@ class TestBulkElementTranscriptions(FixtureAPITestCase): } for poly, text, confidence in transcriptions] } self.client.force_login(self.user) - with self.assertNumQueries(15): + with self.assertNumQueries(13): response = self.client.post( reverse("api:element-transcriptions-bulk", kwargs={"pk": self.rotated_page.id}), format="json", @@ -684,7 +683,7 @@ class TestBulkElementTranscriptions(FixtureAPITestCase): } for poly, text, confidence in transcriptions] } self.client.force_login(self.user) - with self.assertNumQueries(15): + with self.assertNumQueries(13): response = self.client.post( reverse("api:element-transcriptions-bulk", kwargs={"pk": self.mirrored_page.id}), format="json", @@ -721,7 +720,7 @@ class TestBulkElementTranscriptions(FixtureAPITestCase): } for poly, text, confidence in transcriptions] } self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.post( reverse("api:element-transcriptions-bulk", kwargs={"pk": self.page.id}), format="json", @@ -799,7 +798,7 @@ class TestBulkElementTranscriptions(FixtureAPITestCase): self.worker_run.process.run() task = self.worker_run.process.tasks.first() - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post( reverse("api:element-transcriptions-bulk", kwargs={"pk": self.page.id}), format="json", @@ -832,7 +831,7 @@ class TestBulkElementTranscriptions(FixtureAPITestCase): self.worker_run.process.run() task = self.worker_run.process.tasks.first() - with self.assertNumQueries(14): + with self.assertNumQueries(12): response = self.client.post( reverse("api:element-transcriptions-bulk", kwargs={"pk": self.page.id}), format="json", @@ -872,7 +871,7 @@ class TestBulkElementTranscriptions(FixtureAPITestCase): self.worker_run.process.run() task = self.worker_run.process.tasks.first() - with self.assertNumQueries(14): + with self.assertNumQueries(12): response = self.client.post( reverse("api:element-transcriptions-bulk", kwargs={"pk": self.page.id}), format="json", diff --git a/arkindex/documents/tests/test_bulk_elements.py b/arkindex/documents/tests/test_bulk_elements.py index 2548632c65e6ba17971c84396867c9bd3355efb6..21360cb42866b9e4b77c88071e245743c98af012 100644 --- a/arkindex/documents/tests/test_bulk_elements.py +++ b/arkindex/documents/tests/test_bulk_elements.py @@ -1,10 +1,11 @@ +from unittest.mock import patch from uuid import uuid4 from django.contrib.gis.geos import LineString from django.urls import reverse from rest_framework import status -from arkindex.documents.models import Element, ElementPath +from arkindex.documents.models import Corpus, Element, ElementPath from arkindex.process.models import ProcessMode, WorkerRun, WorkerVersion from arkindex.project.tests import FixtureAPITestCase from arkindex.users.models import Role @@ -87,7 +88,8 @@ class TestBulkElements(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), {"detail": "You do not have permission to perform this action."}) - def test_bulk_create_writable_corpus(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_bulk_create_writable_corpus(self, filter_rights_mock): self.corpus.memberships.filter(user=self.user).update(level=Role.Guest.value) self.client.force_login(self.user) response = self.client.post( @@ -114,7 +116,7 @@ class TestBulkElements(FixtureAPITestCase): def test_bulk_create_requires_elements(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post( reverse("api:elements-bulk-create", kwargs={"pk": str(self.element.id)}), data={ @@ -128,7 +130,7 @@ class TestBulkElements(FixtureAPITestCase): def test_bulk_create_missing_types(self): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.post( reverse("api:elements-bulk-create", kwargs={"pk": str(self.element.id)}), data={ @@ -158,7 +160,7 @@ class TestBulkElements(FixtureAPITestCase): element_path1, element_path2 = self.element.paths.order_by("path__0") self.client.force_login(self.user) - with self.assertNumQueries(13): + with self.assertNumQueries(11): response = self.client.post( reverse("api:elements-bulk-create", kwargs={"pk": str(self.element.id)}), data=self.payload, @@ -203,7 +205,7 @@ class TestBulkElements(FixtureAPITestCase): # Try to create a sub element on that zone self.client.force_login(self.user) - with self.assertNumQueries(13): + with self.assertNumQueries(11): response = self.client.post( reverse("api:elements-bulk-create", kwargs={"pk": str(element.id)}), data={ @@ -234,7 +236,7 @@ class TestBulkElements(FixtureAPITestCase): def test_bulk_create_negative_polygon(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post( reverse("api:elements-bulk-create", kwargs={"pk": str(self.element.id)}), { @@ -337,7 +339,7 @@ class TestBulkElements(FixtureAPITestCase): } ] } - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.post( reverse("api:elements-bulk-create", kwargs={"pk": str(self.element.id)}), data=payload, @@ -354,7 +356,7 @@ class TestBulkElements(FixtureAPITestCase): def test_bulk_create_unknown_worker_run(self): self.client.force_login(self.user) random_uuid = str(uuid4()) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post( reverse("api:elements-bulk-create", kwargs={"pk": str(self.element.id)}), { @@ -379,7 +381,7 @@ class TestBulkElements(FixtureAPITestCase): Worker run attribute is required, worker version attribute is forbidden """ self.client.force_login(self.user) - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.post( reverse("api:elements-bulk-create", kwargs={"pk": str(self.element.id)}), data={ @@ -448,7 +450,7 @@ class TestBulkElements(FixtureAPITestCase): task = self.worker_run.process.tasks.first() payload = {**self.payload, "worker_run_id": str(other_worker_run.id)} - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.post( reverse("api:elements-bulk-create", kwargs={"pk": str(self.element.id)}), data=payload, @@ -465,7 +467,7 @@ class TestBulkElements(FixtureAPITestCase): A regular user can create elements with a WorkerRun of their own local process """ self.client.force_login(self.user) - with self.assertNumQueries(13): + with self.assertNumQueries(11): response = self.client.post( reverse("api:elements-bulk-create", kwargs={"pk": str(self.element.id)}), data=self.payload, @@ -514,7 +516,7 @@ class TestBulkElements(FixtureAPITestCase): task = self.worker_run.process.tasks.first() payload = {**self.payload, "worker_run_id": str(self.worker_run.id)} - with self.assertNumQueries(12): + with self.assertNumQueries(10): response = self.client.post( reverse("api:elements-bulk-create", kwargs={"pk": str(self.element.id)}), data=payload, @@ -561,7 +563,7 @@ class TestBulkElements(FixtureAPITestCase): payload = {**self.payload, "worker_run_id": str(self.local_worker_run.id)} - with self.assertNumQueries(12): + with self.assertNumQueries(10): response = self.client.post( reverse("api:elements-bulk-create", kwargs={"pk": str(self.element.id)}), data=payload, diff --git a/arkindex/documents/tests/test_bulk_transcription_entities.py b/arkindex/documents/tests/test_bulk_transcription_entities.py index 6a49250e42c322687ac81016df36951c635238e5..fc6b747da335aabf97e8f0c20826808aa1a9c626 100644 --- a/arkindex/documents/tests/test_bulk_transcription_entities.py +++ b/arkindex/documents/tests/test_bulk_transcription_entities.py @@ -1,4 +1,5 @@ import uuid +from unittest.mock import patch from django.urls import reverse from rest_framework import status @@ -39,10 +40,11 @@ class TestBulkTranscriptionEntities(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(response.json(), {"detail": "You do not have permission to perform this action."}) - def test_requires_contributor(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_requires_contributor(self, has_access_mock): self.user.rights.update(level=Role.Guest.value) self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post(reverse("api:transcription-entities-bulk", kwargs={"pk": self.transcription.id})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(response.json(), { @@ -96,7 +98,7 @@ class TestBulkTranscriptionEntities(FixtureAPITestCase): worker_version=self.local_worker_run.version, ) self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.post( reverse("api:transcription-entities-bulk", kwargs={"pk": str(self.transcription.id)}), data={ @@ -120,7 +122,7 @@ class TestBulkTranscriptionEntities(FixtureAPITestCase): def test_entity_fields_validation(self): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:transcription-entities-bulk", kwargs={"pk": str(self.transcription.id)}), data={ @@ -150,7 +152,7 @@ class TestBulkTranscriptionEntities(FixtureAPITestCase): "length": 5, "confidence": 0.05, } - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.post( reverse("api:transcription-entities-bulk", kwargs={"pk": str(self.transcription.id)}), data={ @@ -242,7 +244,7 @@ class TestBulkTranscriptionEntities(FixtureAPITestCase): self.worker_run.process.run() task = self.worker_run.process.tasks.first() - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:transcription-entities-bulk", kwargs={"pk": str(self.transcription.id)}), data={ @@ -279,7 +281,7 @@ class TestBulkTranscriptionEntities(FixtureAPITestCase): ) self.assertEqual(self.transcription.transcription_entities.count(), 0) - with self.assertNumQueries(13): + with self.assertNumQueries(10): response = self.client.post( reverse("api:transcription-entities-bulk", kwargs={"pk": str(self.transcription.id)}), data={ @@ -361,7 +363,7 @@ class TestBulkTranscriptionEntities(FixtureAPITestCase): self.worker_run.process.run() task = self.worker_run.process.tasks.first() - with self.assertNumQueries(12): + with self.assertNumQueries(9): response = self.client.post( reverse("api:transcription-entities-bulk", kwargs={"pk": str(self.transcription.id)}), data={ @@ -396,7 +398,7 @@ class TestBulkTranscriptionEntities(FixtureAPITestCase): self.worker_run.process.run() task = self.worker_run.process.tasks.first() - with self.assertNumQueries(12): + with self.assertNumQueries(9): response = self.client.post( reverse("api:transcription-entities-bulk", kwargs={"pk": str(self.transcription.id)}), data={ diff --git a/arkindex/documents/tests/test_bulk_transcriptions.py b/arkindex/documents/tests/test_bulk_transcriptions.py index ece36ea4cdf64a9d5fceb2ca90052e76badcce42..8c84c55a09a11824ea657ed942224a4baf6aa356 100644 --- a/arkindex/documents/tests/test_bulk_transcriptions.py +++ b/arkindex/documents/tests/test_bulk_transcriptions.py @@ -1,7 +1,9 @@ +from unittest.mock import patch + from django.urls import reverse from rest_framework import status -from arkindex.documents.models import TextOrientation +from arkindex.documents.models import Corpus, TextOrientation from arkindex.process.models import ProcessMode, WorkerRun, WorkerVersion from arkindex.project.tests import FixtureAPITestCase @@ -30,12 +32,13 @@ class TestBulkTranscriptions(FixtureAPITestCase): response = self.client.post(reverse("api:transcription-bulk")) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_bulk_transcriptions_not_found(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_bulk_transcriptions_not_found(self, filter_rights_mock): self.client.force_login(self.user) self.user.rights.all().delete() forbidden_element = self.corpus.elements.get(name="Volume 1, page 1r") - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post(reverse("api:transcription-bulk"), { "worker_run_id": str(self.local_worker_run.id), "transcriptions": [ @@ -121,7 +124,7 @@ class TestBulkTranscriptions(FixtureAPITestCase): test_element = self.corpus.elements.get(name="Volume 2, page 1r") self.assertFalse(test_element.transcriptions.exists()) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.post(reverse("api:transcription-bulk"), { "worker_run_id": str(self.local_worker_run.id), "transcriptions": [ @@ -323,7 +326,7 @@ class TestBulkTranscriptions(FixtureAPITestCase): self.assertFalse(element1.transcriptions.exists()) self.assertFalse(element2.transcriptions.exists()) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.post(reverse("api:transcription-bulk"), { "worker_run_id": str(self.local_worker_run.id), "transcriptions": [ @@ -413,7 +416,7 @@ class TestBulkTranscriptions(FixtureAPITestCase): element = self.corpus.elements.get(name="Volume 2, page 1r") self.assertFalse(element.transcriptions.exists()) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post( reverse("api:transcription-bulk"), format="json", @@ -465,7 +468,7 @@ class TestBulkTranscriptions(FixtureAPITestCase): self.worker_run.process.run() task = self.worker_run.process.tasks.first() - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post( reverse("api:transcription-bulk"), format="json", diff --git a/arkindex/documents/tests/test_classes.py b/arkindex/documents/tests/test_classes.py index e0ff7d2c025e72914275e173614aa1d4090290f8..efe439164b65fe9fe88eef608f5896e2d6998ec6 100644 --- a/arkindex/documents/tests/test_classes.py +++ b/arkindex/documents/tests/test_classes.py @@ -1,3 +1,4 @@ +from unittest.mock import patch from django.test import override_settings from django.urls import reverse @@ -163,7 +164,8 @@ class TestClasses(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.json(), {"search": ["There cannot be more than 3 unique search terms."]}) - def test_corpus_classes_corpus_rights(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_corpus_classes_corpus_rights(self, has_access_mock): self.client.force_login(self.user) private_corpus = Corpus.objects.create(name="private") response = self.client.post(reverse("api:corpus-classes", kwargs={"pk": private_corpus.pk}), {}) @@ -233,7 +235,8 @@ class TestClasses(FixtureAPITestCase): response = self.client.put(reverse("api:ml-class-retrieve", kwargs={"corpus": self.corpus.id, "mlclass": self.text.id}), {"name": "new name"}) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_update_requires_contributor(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_update_requires_contributor(self, has_access_mock): self.user.rights.update(level=Role.Guest.value) self.client.force_login(self.user) response = self.client.put(reverse("api:ml-class-retrieve", kwargs={"corpus": self.corpus.id, "mlclass": self.text.id}), {"name": "new name"}) @@ -262,7 +265,8 @@ class TestClasses(FixtureAPITestCase): response = self.client.patch(reverse("api:ml-class-retrieve", kwargs={"corpus": self.corpus.id, "mlclass": self.text.id}), {"name": "new name"}) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_partial_update_requires_contributor(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_partial_update_requires_contributor(self, has_access_mock): self.user.rights.update(level=Role.Guest.value) self.client.force_login(self.user) response = self.client.patch(reverse("api:ml-class-retrieve", kwargs={"corpus": self.corpus.id, "mlclass": self.text.id}), {"name": "new name"}) @@ -295,7 +299,8 @@ class TestClasses(FixtureAPITestCase): response = self.client.delete(reverse("api:ml-class-retrieve", kwargs={"corpus": self.corpus.id, "mlclass": self.text.id})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_destroy_requires_contributor(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_destroy_requires_contributor(self, has_access_mock): self.user.rights.update(level=Role.Guest.value) self.client.force_login(self.user) response = self.client.delete(reverse("api:ml-class-retrieve", kwargs={"corpus": self.corpus.id, "mlclass": self.text.id})) diff --git a/arkindex/documents/tests/test_classification.py b/arkindex/documents/tests/test_classification.py index 4270e08a162d2772075c2234102f9faf0257229f..04a2e5c434a2b6ededee676ff1d375ff08348b3f 100644 --- a/arkindex/documents/tests/test_classification.py +++ b/arkindex/documents/tests/test_classification.py @@ -1,4 +1,5 @@ import uuid +from unittest.mock import patch from django.test import override_settings from django.urls import reverse @@ -31,7 +32,7 @@ class TestClassifications(FixtureAPITestCase): Creating a manual classification set auto fields correctly """ self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.post(reverse("api:classification-create"), { "element": str(self.element.id), "ml_class": str(self.text.id), @@ -62,7 +63,7 @@ class TestClassifications(FixtureAPITestCase): A manual classification may be created specifying the version as null """ self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.post( reverse("api:classification-create"), data={ @@ -84,7 +85,7 @@ class TestClassifications(FixtureAPITestCase): reverse("api:classification-create"), {"element": str(self.element.id), "ml_class": str(self.text.id)} ) - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.post(*request) self.assertEqual(response.status_code, status.HTTP_201_CREATED) @@ -124,7 +125,8 @@ class TestClassifications(FixtureAPITestCase): "non_field_errors": ["A classification from this worker run already exists for this element and this class."] }) - def test_create_writable_corpus(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_create_writable_corpus(self, filter_rights_mock): """ Require the element and ML class to be in a writable corpus """ @@ -133,7 +135,7 @@ class TestClassifications(FixtureAPITestCase): self.element.corpus = self.private_corpus self.element.save() - with self.assertNumQueries(6): + with self.assertNumQueries(2): response = self.client.post(reverse("api:classification-create"), { "element": str(self.element.id), "ml_class": str(ml_class.id), @@ -153,7 +155,7 @@ class TestClassifications(FixtureAPITestCase): self.private_corpus.memberships.create(user=self.user, level=Role.Contributor.value) ml_class = self.private_corpus.ml_classes.create(name="Heatmor") - with self.assertNumQueries(6): + with self.assertNumQueries(5): response = self.client.post(reverse("api:classification-create"), { "element": str(self.element.id), "ml_class": str(ml_class.id), @@ -170,7 +172,7 @@ class TestClassifications(FixtureAPITestCase): """ self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.post(reverse("api:classification-create"), { "element": str(self.element.id), "ml_class": str(self.text.id), @@ -206,7 +208,7 @@ class TestClassifications(FixtureAPITestCase): def test_create_worker_version(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post(reverse("api:classification-create"), { "element": str(self.element.id), "ml_class": str(self.text.id), @@ -224,7 +226,7 @@ class TestClassifications(FixtureAPITestCase): A regular user can create a classification with a WorkerRun of their own local process """ self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(7): response = self.client.post(reverse("api:classification-create"), { "element": str(self.element.id), "ml_class": str(self.text.id), @@ -261,7 +263,7 @@ class TestClassifications(FixtureAPITestCase): self.worker_run.process.run() task = self.worker_run.process.tasks.first() - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.post( reverse("api:classification-create"), { @@ -302,7 +304,7 @@ class TestClassifications(FixtureAPITestCase): self.worker_run.process.run() task = self.worker_run.process.tasks.first() - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.post( reverse("api:classification-create"), { @@ -389,7 +391,7 @@ class TestClassifications(FixtureAPITestCase): self.worker_run.process.run() task = self.worker_run.process.tasks.first() - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post( reverse("api:classification-create"), { @@ -409,7 +411,7 @@ class TestClassifications(FixtureAPITestCase): def test_create_worker_version_xor_worker_run(self): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.post(reverse("api:classification-create"), { "element": str(self.element.id), "ml_class": str(self.text.id), @@ -424,7 +426,7 @@ class TestClassifications(FixtureAPITestCase): def test_create_worker_run_not_found(self): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.post(reverse("api:classification-create"), { "element": str(self.element.id), "ml_class": str(self.text.id), @@ -441,7 +443,7 @@ class TestClassifications(FixtureAPITestCase): Ensure CreateClassification accepts a confidence of 0 """ self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(7): response = self.client.post(reverse("api:classification-create"), { "element": str(self.element.id), "ml_class": str(self.text.id), @@ -477,7 +479,7 @@ class TestClassifications(FixtureAPITestCase): self.assertEqual(self.element.classifications.count(), 1) # Create a manual classification with the same class - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.post(reverse("api:classification-create"), { "element": str(self.element.id), "ml_class": str(self.text.id), @@ -516,7 +518,7 @@ class TestClassifications(FixtureAPITestCase): ) self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.put(reverse("api:classification-validate", kwargs={"pk": classification.id})) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -567,7 +569,7 @@ class TestClassifications(FixtureAPITestCase): confidence=.1, ) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.put(reverse("api:classification-reject", kwargs={"pk": classification.id})) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -595,7 +597,7 @@ class TestClassifications(FixtureAPITestCase): self.client.force_login(self.user) classification = self.element.classifications.create(ml_class=self.text, confidence=.42) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.put(reverse("api:classification-reject", kwargs={"pk": classification.id})) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) @@ -632,7 +634,7 @@ class TestClassifications(FixtureAPITestCase): ) # First try to reject - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.put(reverse("api:classification-reject", kwargs={"pk": classification.id})) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -686,7 +688,8 @@ class TestClassifications(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), {"non_field_errors": ["Selection is not available on this instance."]}) - def test_create_selection_private_corpus(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_create_selection_private_corpus(self, has_access_mock): self.client.force_login(self.user) with self.assertNumQueries(5): response = self.client.post( @@ -695,11 +698,12 @@ class TestClassifications(FixtureAPITestCase): ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - def test_create_selection_writable_corpus(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_create_selection_writable_corpus(self, has_access_mock): self.corpus.memberships.filter(user=self.user).update(level=Role.Guest.value) self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.post( reverse("api:classification-selection"), data={"corpus_id": self.corpus.id, "ml_class": self.text.id, "mode": "create"} diff --git a/arkindex/documents/tests/test_corpus.py b/arkindex/documents/tests/test_corpus.py index 4aeade12e3a58a9e11da5a6ffd2d85b1c9471624..884d60ccdb0399479e47bccad8277e7c6f9b1a10 100644 --- a/arkindex/documents/tests/test_corpus.py +++ b/arkindex/documents/tests/test_corpus.py @@ -1,4 +1,5 @@ from datetime import datetime, timezone +from unittest import expectedFailure from unittest.mock import call, patch from uuid import uuid4 @@ -73,6 +74,7 @@ class TestCorpus(FixtureAPITestCase): mock_now.return_value = FAKE_NOW cls.corpus_hidden = Corpus.objects.create(name="C Hidden") + @expectedFailure def test_anon(self): # An anonymous user has only access to public with self.assertNumQueries(4): @@ -104,10 +106,11 @@ class TestCorpus(FixtureAPITestCase): ] ) + @expectedFailure def test_user(self): # A normal user has access to public + its private self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(6): response = self.client.get(reverse("api:corpus")) self.assertEqual(response.status_code, status.HTTP_200_OK) data = response.json() @@ -222,6 +225,7 @@ class TestCorpus(FixtureAPITestCase): self.assertEqual(len(data), 13) self.assertSetEqual({corpus["top_level_type"] for corpus in data}, {None, "top_level"}) + @expectedFailure def test_mixin(self): vol1 = Element.objects.get(name="Volume 1") vol2 = Element.objects.get(name="Volume 2") @@ -293,7 +297,6 @@ class TestCorpus(FixtureAPITestCase): }) self.assertEqual(response.status_code, status.HTTP_201_CREATED) corpus = Corpus.objects.get(name="New Corpus", description="Some description", public=False) - self.assertNotIn(self.corpus_private, Corpus.objects.admin(self.user)) # Assert defaults types are set on the new corpus self.assertCountEqual( @@ -342,7 +345,7 @@ class TestCorpus(FixtureAPITestCase): "description": self.corpus_public.description, "public": True, "indexable": False, - "rights": ["read"], + "rights": ["read", "write", "admin"], "created": DB_CREATED, "authorized_users": 1, "top_level_type": None, @@ -350,7 +353,7 @@ class TestCorpus(FixtureAPITestCase): def test_retrieve(self): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.get(reverse("api:corpus-retrieve", kwargs={"pk": self.corpus_private.id})) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertDictEqual(response.json(), { @@ -359,7 +362,7 @@ class TestCorpus(FixtureAPITestCase): "description": self.corpus_private.description, "public": False, "indexable": False, - "rights": ["read", "write"], + "rights": ["read", "write", "admin"], "types": [], "created": DB_CREATED, "authorized_users": 2, @@ -371,15 +374,11 @@ class TestCorpus(FixtureAPITestCase): response = self.client.get(reverse("api:corpus-retrieve", kwargs={"pk": uuid4()})) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_retrieve_requires_login(self): - response = self.client.get(reverse("api:corpus-retrieve", kwargs={"pk": self.corpus_private.id})) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_retrieve_public_ignores_verified(self): self.user.verified_email = False self.user.save() self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.get(reverse("api:corpus-retrieve", kwargs={"pk": self.corpus_public.id})) self.assertEqual(response.status_code, status.HTTP_200_OK) data = response.json() @@ -399,6 +398,7 @@ class TestCorpus(FixtureAPITestCase): "top_level_type": None, }) + @expectedFailure def test_retrieve_private_requires_guest(self): self.user.rights.all().delete() self.client.force_login(self.user) @@ -482,6 +482,7 @@ class TestCorpus(FixtureAPITestCase): }) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + @expectedFailure def test_partial_update_requires_admin_right_on_corpus(self): for role in [Role.Guest, Role.Contributor]: with self.subTest(role=role): @@ -591,6 +592,7 @@ class TestCorpus(FixtureAPITestCase): }) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + @expectedFailure def test_update_requires_admin_right_on_corpus(self): for role in [Role.Guest, Role.Contributor]: with self.subTest(role=role): @@ -647,6 +649,7 @@ class TestCorpus(FixtureAPITestCase): response = self.client.delete(reverse("api:corpus-retrieve", kwargs={"pk": self.corpus_private.id})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + @expectedFailure def test_delete_requires_admin_right(self): for role in [Role.Guest, Role.Contributor]: with self.subTest(role=role): @@ -655,6 +658,7 @@ class TestCorpus(FixtureAPITestCase): response = self.client.delete(reverse("api:corpus-retrieve", kwargs={"pk": self.corpus_private.id})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + @expectedFailure def test_delete_no_right_not_found(self): self.user.rights.all().delete() self.client.force_login(self.user) @@ -706,6 +710,7 @@ class TestCorpus(FixtureAPITestCase): response = self.client.delete(reverse("api:corpus-delete-selection", kwargs={"pk": self.corpus_private.id})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + @expectedFailure def test_delete_selected_elements_requires_admin_right(self): self.assertNotIn(self.corpus_private, Corpus.objects.admin(self.user)) self.client.force_login(self.user) diff --git a/arkindex/documents/tests/test_corpus_authorized_users.py b/arkindex/documents/tests/test_corpus_authorized_users.py index 2d913b9fca7aaee6b15e9c8f557020612bd657dc..8779b27b483573595ff5337c880afb740a10e584 100644 --- a/arkindex/documents/tests/test_corpus_authorized_users.py +++ b/arkindex/documents/tests/test_corpus_authorized_users.py @@ -1,3 +1,5 @@ +from unittest import expectedFailure + from django.urls import reverse from rest_framework import status @@ -68,6 +70,7 @@ class TestCorpusAuthorizedUsers(FixtureAPITestCase): ] ) + @expectedFailure def test_user_one(self): self.client.force_login(self.user_one) response = self.client.get(reverse("api:corpus")) @@ -103,6 +106,7 @@ class TestCorpusAuthorizedUsers(FixtureAPITestCase): ] ) + @expectedFailure def test_user_two(self): self.client.force_login(self.user_two) response = self.client.get(reverse("api:corpus")) @@ -133,6 +137,7 @@ class TestCorpusAuthorizedUsers(FixtureAPITestCase): ] ) + @expectedFailure def test_user_three(self): self.client.force_login(self.user_three) response = self.client.get(reverse("api:corpus")) diff --git a/arkindex/documents/tests/test_create_elements.py b/arkindex/documents/tests/test_create_elements.py index 006f9c31794e84be61197c4a8a600f8fd1a22102..b39fa8626123b79336754ccbcf48845159f6a5d4 100644 --- a/arkindex/documents/tests/test_create_elements.py +++ b/arkindex/documents/tests/test_create_elements.py @@ -80,7 +80,7 @@ class TestCreateElements(FixtureAPITestCase): # Create a Volume self.client.force_login(self.user) request = self.make_create_request("my new volume") - with self.assertNumQueries(10): + with self.assertNumQueries(7): response = self.client.post(**request) self.assertEqual(response.status_code, status.HTTP_201_CREATED) data = response.json() @@ -125,7 +125,7 @@ class TestCreateElements(FixtureAPITestCase): name="The castle of my dreams", image=str(self.image.id), ) - with self.assertNumQueries(15): + with self.assertNumQueries(12): response = self.client.post(**request) self.assertEqual(response.status_code, status.HTTP_201_CREATED) data = response.json() @@ -180,7 +180,7 @@ class TestCreateElements(FixtureAPITestCase): name="The castle of my dreams", polygon=polygon, ) - with self.assertNumQueries(15): + with self.assertNumQueries(12): response = self.client.post(**request) self.assertEqual(response.status_code, status.HTTP_201_CREATED) data = response.json() @@ -200,7 +200,7 @@ class TestCreateElements(FixtureAPITestCase): name="Castle story", elt_type="act" ) - with self.assertNumQueries(14): + with self.assertNumQueries(11): response = self.client.post(**request) self.assertEqual(response.status_code, status.HTTP_201_CREATED) act = Element.objects.get(id=response.json()["id"]) @@ -216,7 +216,7 @@ class TestCreateElements(FixtureAPITestCase): name="The castle of my dreams again", polygon=[[10, 10], [10, 40], [40, 40], [40, 10], [10, 10]], ) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post(**request) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), { @@ -232,7 +232,7 @@ class TestCreateElements(FixtureAPITestCase): image=str(self.image.id), polygon=[[0, 0], [10, 10], [40, 40], [-10, 10], [0, 0]] ) - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.post(**request) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), { @@ -250,7 +250,7 @@ class TestCreateElements(FixtureAPITestCase): image=str(self.image.id), polygon=polygon ) - with self.assertNumQueries(15): + with self.assertNumQueries(12): response = self.client.post(**request) self.assertEqual(response.status_code, status.HTTP_201_CREATED) data = response.json() @@ -300,7 +300,7 @@ class TestCreateElements(FixtureAPITestCase): image=str(self.huge_image.id), polygon=polygon ) - with self.assertNumQueries(15): + with self.assertNumQueries(12): response = self.client.post(**request) self.assertEqual(response.status_code, status.HTTP_201_CREATED, response.json()) self.assertListEqual(response.json()["zone"]["polygon"], polygon) @@ -315,7 +315,7 @@ class TestCreateElements(FixtureAPITestCase): corpus=str(new_corpus.id), elt_type="act" ) - with self.assertNumQueries(6): + with self.assertNumQueries(5): response = self.client.post(**request) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), { @@ -336,7 +336,7 @@ class TestCreateElements(FixtureAPITestCase): elt_type="volume", name="something", ) - with self.assertNumQueries(9): + with self.assertNumQueries(7): response = self.client.post(**request) self.assertEqual(response.status_code, status.HTTP_201_CREATED) new_vol = new_corpus.elements.get() @@ -351,7 +351,7 @@ class TestCreateElements(FixtureAPITestCase): elt_type="kartoffelsalad", name="something", ) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post(**request) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), { @@ -367,7 +367,7 @@ class TestCreateElements(FixtureAPITestCase): ) self.image.status = S3FileStatus.Error self.image.save() - with self.assertNumQueries(15): + with self.assertNumQueries(12): response = self.client.post(**request) self.assertEqual(response.status_code, status.HTTP_201_CREATED) element = self.corpus.elements.get(id=response.json()["id"]) @@ -384,7 +384,7 @@ class TestCreateElements(FixtureAPITestCase): self.image.height = 0 self.image.status = S3FileStatus.Unchecked self.image.save() - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.post(**request) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), {"image": ["This image does not have valid dimensions."]}) @@ -395,7 +395,7 @@ class TestCreateElements(FixtureAPITestCase): name="slim output !", ) request["path"] += "?slim_output=true" - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.post(**request) self.assertEqual(response.status_code, status.HTTP_201_CREATED) element = self.corpus.elements.get(id=response.json()["id"]) @@ -412,7 +412,7 @@ class TestCreateElements(FixtureAPITestCase): image=str(self.image.id), ) request["path"] += "?slim_output=true" - with self.assertNumQueries(13): + with self.assertNumQueries(11): response = self.client.post(**request) self.assertEqual(response.status_code, status.HTTP_201_CREATED) element = self.corpus.elements.get(id=response.json()["id"]) @@ -449,7 +449,7 @@ class TestCreateElements(FixtureAPITestCase): if mirrored is not None: request["data"]["mirrored"] = mirrored - with self.assertNumQueries(15): + with self.assertNumQueries(12): response = self.client.post(**request) self.assertEqual(response.status_code, status.HTTP_201_CREATED) @@ -476,7 +476,7 @@ class TestCreateElements(FixtureAPITestCase): rotation_angle=rotation_angle, ) - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.post(**request) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) @@ -497,7 +497,7 @@ class TestCreateElements(FixtureAPITestCase): image=str(self.image.id), polygon=polygon ) - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.post(**request) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), { @@ -514,7 +514,7 @@ class TestCreateElements(FixtureAPITestCase): confidence=0.42, ) - with self.assertNumQueries(15): + with self.assertNumQueries(12): response = self.client.post(**request) self.assertEqual(response.status_code, status.HTTP_201_CREATED) @@ -541,7 +541,7 @@ class TestCreateElements(FixtureAPITestCase): confidence=confidence, ) - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.post(**request) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) @@ -555,7 +555,7 @@ class TestCreateElements(FixtureAPITestCase): """ self.client.force_login(self.local_worker_run.process.creator) - with self.assertNumQueries(11): + with self.assertNumQueries(8): response = self.client.post( reverse("api:elements-create"), { @@ -582,7 +582,7 @@ class TestCreateElements(FixtureAPITestCase): self.worker_run.process.run() task = self.worker_run.process.tasks.first() - with self.assertNumQueries(10): + with self.assertNumQueries(7): response = self.client.post( reverse("api:elements-create"), { @@ -617,7 +617,7 @@ class TestCreateElements(FixtureAPITestCase): self.worker_run.process.run() task = self.worker_run.process.tasks.first() - with self.assertNumQueries(10): + with self.assertNumQueries(7): response = self.client.post( reverse("api:elements-create"), { @@ -699,7 +699,7 @@ class TestCreateElements(FixtureAPITestCase): self.worker_run.process.run() task = self.worker_run.process.tasks.first() - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post( reverse("api:elements-create"), { @@ -724,7 +724,7 @@ class TestCreateElements(FixtureAPITestCase): elt_type="act", worker_run_id=random_uuid, ) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.post(**request) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) @@ -738,7 +738,7 @@ class TestCreateElements(FixtureAPITestCase): worker_version=str(self.worker_version.id), worker_run_id=str(self.local_worker_run.id), ) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.post(**request) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) @@ -755,7 +755,7 @@ class TestCreateElements(FixtureAPITestCase): worker_version=str(self.worker_version.id), ) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post(**request) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) diff --git a/arkindex/documents/tests/test_create_parent_selection.py b/arkindex/documents/tests/test_create_parent_selection.py index 64e562bb4f2ab604aa3b3d16286d99c8a113a7d4..f60e35e3e775ee56f1697b8cf07f28981987403f 100644 --- a/arkindex/documents/tests/test_create_parent_selection.py +++ b/arkindex/documents/tests/test_create_parent_selection.py @@ -41,7 +41,7 @@ class TestMoveSelection(FixtureAPITestCase): @override_settings(ARKINDEX_FEATURES={"selection": False}) def test_disabled(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post(reverse("api:create-parent-selection"), { "corpus_id": str(self.corpus.id), "parent_id": str(self.parent.id), @@ -52,14 +52,15 @@ class TestMoveSelection(FixtureAPITestCase): ) @override_settings(ARKINDEX_FEATURES={"selection": True}) - def test_wrong_acl(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_wrong_acl(self, filter_rights_mock): private_corpus = Corpus.objects.create(name="private", public=False) private_element = private_corpus.elements.create( type=private_corpus.types.create(slug="folder"), ) self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(2): response = self.client.post(reverse("api:create-parent-selection"), { "corpus_id": str(private_corpus.id), "parent_id": str(private_element.id), @@ -77,7 +78,7 @@ class TestMoveSelection(FixtureAPITestCase): def test_wrong_parent(self): self.client.force_login(self.user) self.user.selected_elements.add(self.page) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post(reverse("api:create-parent-selection"), { "corpus_id": str(self.corpus.id), "parent_id": "12341234-1234-1234-1234-123412341234" @@ -92,7 +93,7 @@ class TestMoveSelection(FixtureAPITestCase): def test_same_parent(self): self.client.force_login(self.user) self.user.selected_elements.add(self.page) - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.post(reverse("api:create-parent-selection"), { "corpus_id": str(self.corpus.id), "parent_id": str(self.page.id), @@ -110,7 +111,7 @@ class TestMoveSelection(FixtureAPITestCase): parent = corpus2.elements.create(type=corpus2.types.create(slug="folder")) self.client.force_login(self.user) self.user.selected_elements.add(self.page) - with self.assertNumQueries(6): + with self.assertNumQueries(5): response = self.client.post(reverse("api:create-parent-selection"), { "corpus_id": str(self.corpus.id), "parent_id": str(parent.id), @@ -126,7 +127,7 @@ class TestMoveSelection(FixtureAPITestCase): parent = self.corpus.elements.get(name="Volume 1") self.client.force_login(self.user) self.user.selected_elements.add(self.page) - with self.assertNumQueries(9): + with self.assertNumQueries(7): response = self.client.post(reverse("api:create-parent-selection"), { "corpus_id": str(self.corpus.id), "parent_id": str(parent.id), @@ -143,7 +144,7 @@ class TestMoveSelection(FixtureAPITestCase): parent_id = self.page.id self.client.force_login(self.user) self.user.selected_elements.add(target) - with self.assertNumQueries(10): + with self.assertNumQueries(8): response = self.client.post(reverse("api:create-parent-selection"), { "corpus_id": str(self.corpus.id), "parent_id": str(parent_id), @@ -161,7 +162,7 @@ class TestMoveSelection(FixtureAPITestCase): self.user.selected_elements.add(self.page) another_page = self.corpus.elements.get(name="Volume 1, page 1v") self.user.selected_elements.add(another_page) - with self.assertNumQueries(10): + with self.assertNumQueries(8): response = self.client.post(reverse("api:create-parent-selection"), { "corpus_id": str(self.corpus.id), "parent_id": str(self.parent.id), diff --git a/arkindex/documents/tests/test_create_transcriptions.py b/arkindex/documents/tests/test_create_transcriptions.py index f110bfeca987087394df3492eced1b0aa52f5e60..d29c2f7f98b72f6056ecad5af5e9724cc603bfbf 100644 --- a/arkindex/documents/tests/test_create_transcriptions.py +++ b/arkindex/documents/tests/test_create_transcriptions.py @@ -1,3 +1,4 @@ +from unittest.mock import patch from uuid import uuid4 from django.test import override_settings @@ -41,9 +42,10 @@ class TestTranscriptionCreate(FixtureAPITestCase): "detail": "Authentication credentials were not provided." }) - def test_write_right(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_write_right(self, has_access_mock): self.client.force_login(self.private_read_user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:transcription-create", kwargs={"pk": self.private_page.id}), format="json", @@ -54,9 +56,10 @@ class TestTranscriptionCreate(FixtureAPITestCase): "detail": "A write access to the element's corpus is required." }) - def test_no_read_right(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_no_read_right(self, filter_rights_mock): self.client.force_login(self.user) - with self.assertNumQueries(5): + with self.assertNumQueries(2): response = self.client.post( reverse("api:transcription-create", kwargs={"pk": self.private_page.id}), format="json", @@ -66,7 +69,7 @@ class TestTranscriptionCreate(FixtureAPITestCase): def test_no_element(self): self.client.force_login(self.user) - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.post( reverse("api:transcription-create", kwargs={"pk": uuid4()}), format="json", @@ -76,7 +79,7 @@ class TestTranscriptionCreate(FixtureAPITestCase): def test_manual(self): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:transcription-create", kwargs={"pk": self.line.id}), format="json", @@ -104,7 +107,7 @@ class TestTranscriptionCreate(FixtureAPITestCase): """ self.client.force_login(self.user) ts = self.line.transcriptions.create(text="GLOUBIBOULGA") - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:transcription-create", kwargs={"pk": self.line.id}), format="json", @@ -123,7 +126,7 @@ class TestTranscriptionCreate(FixtureAPITestCase): Check that a transcription is created with the specified orientation """ self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:transcription-create", kwargs={"pk": self.line.id}), format="json", @@ -147,7 +150,7 @@ class TestTranscriptionCreate(FixtureAPITestCase): Specifying an invalid text-orientation causes an error """ self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:transcription-create", kwargs={"pk": self.line.id}), format="json", @@ -158,7 +161,7 @@ class TestTranscriptionCreate(FixtureAPITestCase): @override_settings(ARKINDEX_FEATURES={"search": False}) def test_no_search(self): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:transcription-create", kwargs={"pk": self.line.id}), format="json", @@ -168,7 +171,7 @@ class TestTranscriptionCreate(FixtureAPITestCase): def test_worker_version(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:transcription-create", kwargs={"pk": self.line.id}), format="json", @@ -189,7 +192,7 @@ class TestTranscriptionCreate(FixtureAPITestCase): """ self.client.force_login(self.user) - with self.assertNumQueries(10): + with self.assertNumQueries(7): response = self.client.post( reverse("api:transcription-create", kwargs={"pk": self.line.id}), format="json", @@ -227,7 +230,7 @@ class TestTranscriptionCreate(FixtureAPITestCase): self.worker_run.process.run() task = self.worker_run.process.tasks.first() - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.post( reverse("api:transcription-create", kwargs={"pk": self.line.id}), format="json", @@ -263,7 +266,7 @@ class TestTranscriptionCreate(FixtureAPITestCase): self.worker_run.process.run() task = self.worker_run.process.tasks.first() - with self.assertNumQueries(10): + with self.assertNumQueries(7): response = self.client.post( reverse("api:transcription-create", kwargs={"pk": self.line.id}), format="json", @@ -349,7 +352,7 @@ class TestTranscriptionCreate(FixtureAPITestCase): self.worker_run.process.run() task = self.worker_run.process.tasks.first() - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:transcription-create", kwargs={"pk": self.line.id}), format="json", @@ -368,7 +371,7 @@ class TestTranscriptionCreate(FixtureAPITestCase): def test_worker_run_not_found(self): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:transcription-create", kwargs={"pk": self.line.id}), format="json", @@ -386,7 +389,7 @@ class TestTranscriptionCreate(FixtureAPITestCase): def test_worker_run_required_confidence(self): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:transcription-create", kwargs={"pk": self.line.id}), format="json", @@ -402,7 +405,7 @@ class TestTranscriptionCreate(FixtureAPITestCase): def test_worker_version_xor_worker_run(self): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:transcription-create", kwargs={"pk": self.line.id}), format="json", @@ -424,7 +427,7 @@ class TestTranscriptionCreate(FixtureAPITestCase): null_zone_page = self.corpus.elements.create(type=self.page.type) self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:transcription-create", kwargs={"pk": null_zone_page.id}), format="json", @@ -450,7 +453,7 @@ class TestTranscriptionCreate(FixtureAPITestCase): """ self.client.force_login(self.user) - with self.assertNumQueries(10): + with self.assertNumQueries(7): response = self.client.post( reverse("api:transcription-create", kwargs={"pk": self.line.id}), format="json", diff --git a/arkindex/documents/tests/test_destroy_elements.py b/arkindex/documents/tests/test_destroy_elements.py index f9403f3745bb9a0e4da3b229db4d105876b27e22..da6312ac19d0fcd0863ce87752baa5bd7dcf95ef 100644 --- a/arkindex/documents/tests/test_destroy_elements.py +++ b/arkindex/documents/tests/test_destroy_elements.py @@ -30,14 +30,15 @@ class TestDestroyElements(FixtureAPITestCase): response = self.client.delete(reverse("api:element-retrieve", kwargs={"pk": str(self.vol.id)})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_element_destroy_acl(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_element_destroy_acl(self, has_access_mock): self.private_corpus.memberships.create(user=self.user, level=Role.Contributor.value) self.client.force_login(self.user) castle_story = self.private_corpus.elements.create( type=self.volume_type, name="Castle story" ) - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.delete(reverse("api:element-retrieve", kwargs={"pk": str(castle_story.id)})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual( @@ -53,7 +54,7 @@ class TestDestroyElements(FixtureAPITestCase): name="Castle story" ) self.assertTrue(self.corpus.elements.filter(id=castle_story.id).exists()) - with self.assertNumQueries(7): + with self.assertNumQueries(3): response = self.client.delete(reverse("api:element-retrieve", kwargs={"pk": str(castle_story.id)})) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) @@ -80,7 +81,7 @@ class TestDestroyElements(FixtureAPITestCase): with self.subTest(delete_children=delete_children): delay_mock.reset_mock() - with self.assertNumQueries(7): + with self.assertNumQueries(3): response = self.client.delete( reverse("api:element-retrieve", kwargs={"pk": str(castle_story.id)}) + f"?delete_children={delete_children}", @@ -109,7 +110,7 @@ class TestDestroyElements(FixtureAPITestCase): name="Castle story", creator=self.user, ) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.delete(reverse("api:element-retrieve", kwargs={"pk": str(castle_story.id)})) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) @@ -123,7 +124,8 @@ class TestDestroyElements(FixtureAPITestCase): "description": "Element deletion", }) - def test_element_destroy_creator_acl(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_element_destroy_creator_acl(self, has_access_mock): """ An element's creator cannot delete without write access """ @@ -134,7 +136,7 @@ class TestDestroyElements(FixtureAPITestCase): name="Castle story", creator=self.user, ) - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.delete(reverse("api:element-retrieve", kwargs={"pk": str(castle_story.id)})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual( @@ -148,7 +150,7 @@ class TestDestroyElements(FixtureAPITestCase): """ Dataset.objects.get(name="First Dataset").dataset_elements.create(element=self.vol, set="test") self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.delete(reverse("api:element-retrieve", kwargs={"pk": str(self.vol.id)})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), {"detail": "You cannot delete an element that is part of a dataset."}) @@ -159,7 +161,7 @@ class TestDestroyElements(FixtureAPITestCase): We can now delete a non-empty element """ self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(3): response = self.client.delete(reverse("api:element-retrieve", kwargs={"pk": str(self.vol.id)})) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) @@ -316,11 +318,12 @@ class TestDestroyElements(FixtureAPITestCase): self.assertFalse(delay_mock.called) @patch("arkindex.project.triggers.documents_tasks.element_trash.delay") - def test_destroy_corpus_elements_requires_admin(self, delay_mock): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_destroy_corpus_elements_requires_admin(self, has_access_mock, delay_mock): self.corpus.memberships.update(level=Role.Contributor.value) self.client.force_login(self.user) - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.delete(reverse("api:corpus-elements", kwargs={"corpus": self.corpus.id})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), {"detail": "You do not have admin access to this corpus."}) @@ -332,7 +335,7 @@ class TestDestroyElements(FixtureAPITestCase): self.client.force_login(self.user) self.assertFalse(self.corpus.elements.filter(name="blablablabla").exists()) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.delete( reverse("api:corpus-elements", kwargs={"corpus": self.corpus.id}), QUERY_STRING="name=blablablabla", @@ -344,7 +347,7 @@ class TestDestroyElements(FixtureAPITestCase): @patch("arkindex.project.triggers.documents_tasks.element_trash.delay") def test_destroy_corpus_elements(self, delay_mock): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.delete(reverse("api:corpus-elements", kwargs={"corpus": self.corpus.id})) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) @@ -361,7 +364,7 @@ class TestDestroyElements(FixtureAPITestCase): @patch("arkindex.project.triggers.documents_tasks.element_trash.delay") def test_destroy_corpus_elements_delete_children(self, delay_mock): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.delete( reverse("api:corpus-elements", kwargs={"corpus": self.corpus.id}), QUERY_STRING="delete_children=false" @@ -388,11 +391,12 @@ class TestDestroyElements(FixtureAPITestCase): self.assertFalse(delay_mock.called) @patch("arkindex.project.triggers.documents_tasks.element_trash.delay") - def test_destroy_element_children_requires_admin(self, delay_mock): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_destroy_element_children_requires_admin(self, has_access_mock, delay_mock): self.corpus.memberships.filter(user=self.user).update(level=Role.Contributor.value) self.client.force_login(self.user) - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.delete(reverse("api:elements-children", kwargs={"pk": self.vol.id})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), {"detail": "You do not have admin access to this corpus."}) @@ -404,7 +408,7 @@ class TestDestroyElements(FixtureAPITestCase): self.client.force_login(self.user) element = self.corpus.elements.create(type=self.volume_type, name="Lonely element") - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.delete(reverse("api:elements-children", kwargs={"pk": element.id})) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) @@ -413,7 +417,7 @@ class TestDestroyElements(FixtureAPITestCase): @patch("arkindex.project.triggers.documents_tasks.element_trash.delay") def test_destroy_element_children(self, delay_mock): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.delete(reverse("api:elements-children", kwargs={"pk": self.vol.id})) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) @@ -434,7 +438,7 @@ class TestDestroyElements(FixtureAPITestCase): @patch("arkindex.project.triggers.documents_tasks.element_trash.delay") def test_destroy_element_children_delete_children(self, delay_mock): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.delete( reverse("api:elements-children", kwargs={"pk": self.vol.id}), QUERY_STRING="delete_children=false" @@ -465,11 +469,12 @@ class TestDestroyElements(FixtureAPITestCase): self.assertFalse(delay_mock.called) @patch("arkindex.project.triggers.documents_tasks.element_trash.delay") - def test_destroy_element_parents_requires_writable(self, delay_mock): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_destroy_element_parents_requires_writable(self, has_access_mock, delay_mock): self.corpus.memberships.filter(user=self.user).update(level=Role.Contributor.value) self.client.force_login(self.user) - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.delete(reverse("api:elements-parents", kwargs={"pk": self.surface.id})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), {"detail": "You do not have admin access to this corpus."}) @@ -481,7 +486,7 @@ class TestDestroyElements(FixtureAPITestCase): self.client.force_login(self.user) element = self.corpus.elements.create(type=self.volume_type, name="Lonely element") - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.delete(reverse("api:elements-parents", kwargs={"pk": element.id})) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) @@ -490,7 +495,7 @@ class TestDestroyElements(FixtureAPITestCase): @patch("arkindex.project.triggers.documents_tasks.element_trash.delay") def test_destroy_element_parents(self, delay_mock): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.delete(reverse("api:elements-parents", kwargs={"pk": self.surface.id})) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) @@ -511,7 +516,7 @@ class TestDestroyElements(FixtureAPITestCase): @patch("arkindex.project.triggers.documents_tasks.element_trash.delay") def test_destroy_element_parents_delete_parents(self, delay_mock): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.delete( reverse("api:elements-parents", kwargs={"pk": self.surface.id}), QUERY_STRING="delete_children=false" diff --git a/arkindex/documents/tests/test_destroy_worker_results.py b/arkindex/documents/tests/test_destroy_worker_results.py index 1f590c4c14dc6f98d23786fa685758920d611cb7..fbea2d6d51a63b7004ae98f21c011e684424f4d0 100644 --- a/arkindex/documents/tests/test_destroy_worker_results.py +++ b/arkindex/documents/tests/test_destroy_worker_results.py @@ -42,9 +42,10 @@ class TestDestroyWorkerResults(FixtureAPITestCase): response = self.client.delete(reverse("api:worker-delete-results", kwargs={"corpus": str(self.corpus.id)})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_wrong_acl(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_wrong_acl(self, has_access_mock): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.delete(reverse("api:worker-delete-results", kwargs={"corpus": str(self.private_corpus.id)})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual( @@ -65,7 +66,7 @@ class TestDestroyWorkerResults(FixtureAPITestCase): @patch("arkindex.project.triggers.documents_tasks.worker_results_delete.delay") def test_no_filter(self, delay_mock): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.delete(reverse("api:worker-delete-results", kwargs={"corpus": str(self.corpus.id)})) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) @@ -83,7 +84,7 @@ class TestDestroyWorkerResults(FixtureAPITestCase): @patch("arkindex.project.triggers.documents_tasks.worker_results_delete.delay") def test_filter_version(self, delay_mock): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.delete( reverse("api:worker-delete-results", kwargs={"corpus": str(self.corpus.id)}) + f"?worker_version_id={self.version.id}", @@ -104,7 +105,7 @@ class TestDestroyWorkerResults(FixtureAPITestCase): @patch("arkindex.project.triggers.documents_tasks.worker_results_delete.delay") def test_filter_element(self, delay_mock): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.delete( reverse("api:worker-delete-results", kwargs={"corpus": str(self.corpus.id)}) + f"?element_id={self.page.id}" @@ -125,7 +126,7 @@ class TestDestroyWorkerResults(FixtureAPITestCase): @patch("arkindex.project.triggers.documents_tasks.worker_results_delete.delay") def test_filter_unset_configuration(self, delay_mock): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.delete( reverse("api:worker-delete-results", kwargs={"corpus": str(self.corpus.id)}) + "?configuration_id=false", @@ -146,7 +147,7 @@ class TestDestroyWorkerResults(FixtureAPITestCase): @patch("arkindex.project.triggers.documents_tasks.worker_results_delete.delay") def test_filter_model_version(self, delay_mock): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.delete( reverse("api:worker-delete-results", kwargs={"corpus": str(self.corpus.id)}) + f"?model_version_id={self.model_version.id}", @@ -167,7 +168,7 @@ class TestDestroyWorkerResults(FixtureAPITestCase): @patch("arkindex.project.triggers.documents_tasks.worker_results_delete.delay") def test_filter_element_worker_version_model_version_configuration(self, delay_mock): self.client.force_login(self.user) - with self.assertNumQueries(10): + with self.assertNumQueries(7): response = self.client.delete( reverse("api:worker-delete-results", kwargs={"corpus": str(self.corpus.id)}) + ( @@ -197,7 +198,7 @@ class TestDestroyWorkerResults(FixtureAPITestCase): def test_invalid_version_id(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.delete( reverse("api:worker-delete-results", kwargs={"corpus": str(self.corpus.id)}) + "?worker_version_id=lol" @@ -210,7 +211,7 @@ class TestDestroyWorkerResults(FixtureAPITestCase): def test_wrong_version_id(self): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.delete( reverse("api:worker-delete-results", kwargs={"corpus": str(self.corpus.id)}) + "?worker_version_id=12341234-1234-1234-1234-123412341234" @@ -223,7 +224,7 @@ class TestDestroyWorkerResults(FixtureAPITestCase): def test_invalid_element_id(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.delete( reverse("api:worker-delete-results", kwargs={"corpus": str(self.corpus.id)}) + "?element_id=lol" @@ -236,7 +237,7 @@ class TestDestroyWorkerResults(FixtureAPITestCase): def test_wrong_element_id(self): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.delete( reverse("api:worker-delete-results", kwargs={"corpus": str(self.corpus.id)}) + "?element_id=12341234-1234-1234-1234-123412341234" @@ -249,7 +250,7 @@ class TestDestroyWorkerResults(FixtureAPITestCase): def test_invalid_model_version_id(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.delete( reverse("api:worker-delete-results", kwargs={"corpus": str(self.corpus.id)}) + "?model_version_id=lol" @@ -262,7 +263,7 @@ class TestDestroyWorkerResults(FixtureAPITestCase): def test_wrong_model_version_id(self): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.delete( reverse("api:worker-delete-results", kwargs={"corpus": str(self.corpus.id)}) + "?model_version_id=12341234-1234-1234-1234-123412341234" @@ -275,7 +276,7 @@ class TestDestroyWorkerResults(FixtureAPITestCase): def test_invalid_configuration_id(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.delete( reverse("api:worker-delete-results", kwargs={"corpus": str(self.corpus.id)}) + "?configuration_id=true" @@ -288,7 +289,7 @@ class TestDestroyWorkerResults(FixtureAPITestCase): def test_wrong_configuration_id(self): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.delete( reverse("api:worker-delete-results", kwargs={"corpus": str(self.corpus.id)}) + "?configuration_id=12341234-1234-1234-1234-123412341234" @@ -302,7 +303,7 @@ class TestDestroyWorkerResults(FixtureAPITestCase): @override_settings(ARKINDEX_FEATURES={"selection": False}) def test_selection_feature_flag(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.delete( reverse("api:worker-delete-results", kwargs={"corpus": str(self.corpus.id)}) + "?use_selection=true" @@ -316,7 +317,7 @@ class TestDestroyWorkerResults(FixtureAPITestCase): @override_settings(ARKINDEX_FEATURES={"selection": True}) def test_selection_no_element_id(self): self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.delete( reverse("api:worker-delete-results", kwargs={"corpus": str(self.corpus.id)}) + f"?use_selection=true&element_id={self.page.id}" @@ -334,7 +335,7 @@ class TestDestroyWorkerResults(FixtureAPITestCase): def test_selection_empty(self): self.client.force_login(self.user) self.assertFalse(self.user.selected_elements.exists()) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.delete( reverse("api:worker-delete-results", kwargs={"corpus": str(self.corpus.id)}) + "?use_selection=true" @@ -367,7 +368,7 @@ class TestDestroyWorkerResults(FixtureAPITestCase): self.user.selected_elements.add(self.page) self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.delete( reverse("api:worker-delete-results", kwargs={"corpus": str(self.corpus.id)}) + "?use_selection=true" @@ -390,7 +391,7 @@ class TestDestroyWorkerResults(FixtureAPITestCase): self.user.selected_elements.add(self.page) self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.delete( reverse("api:worker-delete-results", kwargs={"corpus": str(self.corpus.id)}) + f"?use_selection=true&worker_version_id={self.version.id}" diff --git a/arkindex/documents/tests/test_edit_transcriptions.py b/arkindex/documents/tests/test_edit_transcriptions.py index a3341ab3defaa75d5fda48ba0ede4ebf94ccbd5c..c756ea5ddc2100297dbcee1b32fffbea87c2d021 100644 --- a/arkindex/documents/tests/test_edit_transcriptions.py +++ b/arkindex/documents/tests/test_edit_transcriptions.py @@ -1,3 +1,4 @@ +from unittest.mock import patch from uuid import uuid4 from django.urls import reverse @@ -80,7 +81,8 @@ class TestEditTranscription(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), {"detail": "Authentication credentials were not provided."}) - def test_get_private(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_get_private(self, filter_rights_mock): user = User.objects.create_user("f@gh.ij", "b") user.verified_email = True user.save() @@ -183,7 +185,8 @@ class TestEditTranscription(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.json(), {"orientation": ["Value is not of type TextOrientation"]}) - def test_patch_write_right(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_patch_write_right(self, has_access_mock): """ A write right is required to patch a manual transcription """ @@ -198,7 +201,8 @@ class TestEditTranscription(FixtureAPITestCase): "detail": "A write access to transcription element corpus is required." }) - def test_patch_admin_right(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_patch_admin_right(self, has_access_mock): """ Updating a transcription produced by a ML worker is forbidden """ @@ -281,7 +285,8 @@ class TestEditTranscription(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.json(), {"orientation": ["Value is not of type TextOrientation"]}) - def test_put_write_right(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_put_write_right(self, has_access_mock): """ A write right is required to put a manual transcription """ @@ -296,7 +301,8 @@ class TestEditTranscription(FixtureAPITestCase): "detail": "A write access to transcription element corpus is required." }) - def test_put_admin_right(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_put_admin_right(self, has_access_mock): """ Updating a transcription produced by a ML worker is forbidden """ @@ -346,7 +352,8 @@ class TestEditTranscription(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertFalse(Transcription.objects.filter(id=self.manual_transcription.id).exists()) - def test_delete_manual_write_right(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_delete_manual_write_right(self, has_access_mock): """ Deleting a transcription without a write right is forbidden """ diff --git a/arkindex/documents/tests/test_element_paths_api.py b/arkindex/documents/tests/test_element_paths_api.py index 12667ba439ab7e386c258b12b8f28ba3180ca2bd..f71d1298e392a981604640bd7992651106c434a3 100644 --- a/arkindex/documents/tests/test_element_paths_api.py +++ b/arkindex/documents/tests/test_element_paths_api.py @@ -1,3 +1,5 @@ +from unittest.mock import patch + from django.urls import reverse from rest_framework import status @@ -41,7 +43,7 @@ class TestElementsAPI(FixtureAPITestCase): self.client.force_login(self.user) # Link desk to its parent room - with self.assertNumQueries(13): + with self.assertNumQueries(11): response = self.client.post(reverse("api:element-parent", kwargs=self.default_kwargs)) self.assertEqual(response.status_code, status.HTTP_201_CREATED) @@ -65,10 +67,13 @@ class TestElementsAPI(FixtureAPITestCase): response = self.client.post(reverse("api:element-parent", kwargs=self.default_kwargs)) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_create_acl(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights") + def test_create_acl(self, filter_rights_mock): self.client.force_login(self.user) self.desk.corpus = self.private_corpus self.desk.save() + filter_rights_mock.return_value = Corpus.objects.filter(id=self.corpus.id) + response = self.client.post(reverse("api:element-parent", kwargs=self.default_kwargs)) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual( @@ -131,10 +136,13 @@ class TestElementsAPI(FixtureAPITestCase): response = self.client.delete(reverse("api:element-parent", kwargs=self.default_kwargs)) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_delete_acl(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights") + def test_delete_acl(self, filter_rights_mock): self.client.force_login(self.user) self.desk.corpus = self.private_corpus self.desk.save() + filter_rights_mock.return_value = Corpus.objects.filter(id=self.corpus.id) + response = self.client.delete(reverse("api:element-parent", kwargs=self.default_kwargs)) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual( diff --git a/arkindex/documents/tests/test_element_type.py b/arkindex/documents/tests/test_element_type.py index bed5b21d49ee7df46aa573dcacb33db113dd76ae..37d2882b3c70a74f5e32a8a567f426f538dc6e67 100644 --- a/arkindex/documents/tests/test_element_type.py +++ b/arkindex/documents/tests/test_element_type.py @@ -1,3 +1,5 @@ +from unittest.mock import call, patch + from django.urls import reverse from rest_framework import status @@ -23,7 +25,8 @@ class TestElementType(FixtureAPITestCase): }, format="json") self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_create_no_access(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_create_requires_admin(self, filter_rights_mock): self.client.force_login(self.user) response = self.client.post(reverse("api:element-type-create"), { "corpus": self.private_corpus.id, @@ -32,15 +35,8 @@ class TestElementType(FixtureAPITestCase): }, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - def test_create_requires_admin(self): - self.private_corpus.memberships.create(user=self.user, level=Role.Contributor.value) - self.client.force_login(self.user) - response = self.client.post(reverse("api:element-type-create"), { - "corpus": self.private_corpus.id, - "slug": "New_element_type", - "display_name": "New element type", - }, format="json") - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(filter_rights_mock.call_count, 1) + self.assertEqual(filter_rights_mock.call_args, call(self.user, Corpus, Role.Admin.value)) def test_create_wrong_slug(self): self.client.force_login(self.superuser) @@ -153,16 +149,8 @@ class TestElementType(FixtureAPITestCase): }, format="json") self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_partial_update_no_access(self): - private_element_type = ElementType.objects.create(corpus=self.private_corpus, slug="element", display_name="Element") - self.client.force_login(self.user) - response = self.client.patch(reverse("api:element-type", kwargs={"pk": private_element_type.id}), { - "slug": "New_element_type", - "display_name": "New element type", - }, format="json") - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - - def test_partial_update_requires_admin(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_partial_update_requires_admin(self, filter_rights_mock): private_element_type = ElementType.objects.create(corpus=self.private_corpus, slug="element", display_name="Element") self.private_corpus.memberships.create(user=self.user, level=Role.Contributor.value) self.client.force_login(self.user) @@ -172,6 +160,9 @@ class TestElementType(FixtureAPITestCase): }, format="json") self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertEqual(filter_rights_mock.call_count, 1) + self.assertEqual(filter_rights_mock.call_args, call(self.user, Corpus, Role.Admin.value)) + def test_partial_update_wrong_slug(self): element_type = ElementType.objects.create(corpus=self.corpus, slug="new_type", display_name="Element") self.client.force_login(self.superuser) @@ -254,16 +245,8 @@ class TestElementType(FixtureAPITestCase): }, format="json") self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_update_no_access(self): - private_element_type = ElementType.objects.create(corpus=self.private_corpus, slug="element", display_name="Element") - self.client.force_login(self.user) - response = self.client.put(reverse("api:element-type", kwargs={"pk": private_element_type.id}), { - "slug": "New_element_type", - "display_name": "New element type", - }, format="json") - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - - def test_update_requires_admin(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_update_requires_admin(self, filter_rights_mock): private_element_type = ElementType.objects.create(corpus=self.private_corpus, slug="element", display_name="Element") self.private_corpus.memberships.create(user=self.user, level=Role.Contributor.value) self.client.force_login(self.user) @@ -273,6 +256,9 @@ class TestElementType(FixtureAPITestCase): }, format="json") self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertEqual(filter_rights_mock.call_count, 1) + self.assertEqual(filter_rights_mock.call_args, call(self.user, Corpus, Role.Admin.value)) + def test_update_wrong_slug(self): element_type = ElementType.objects.create(corpus=self.corpus, slug="new_type", display_name="Element") self.client.force_login(self.superuser) @@ -341,13 +327,17 @@ class TestElementType(FixtureAPITestCase): response = self.client.delete(reverse("api:element-type", kwargs={"pk": self.element_type.id})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_delete_requires_admin(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_delete_requires_admin(self, filter_rights_mock): private_element_type = ElementType.objects.create(corpus=self.private_corpus, slug="element", display_name="Element") self.private_corpus.memberships.create(user=self.user, level=Role.Contributor.value) self.client.force_login(self.user) response = self.client.delete(reverse("api:element-type", kwargs={"pk": private_element_type.id})) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertEqual(filter_rights_mock.call_count, 1) + self.assertEqual(filter_rights_mock.call_args, call(self.user, Corpus, Role.Admin.value)) + def test_delete(self): self.client.force_login(self.superuser) with self.assertNumQueries(11): diff --git a/arkindex/documents/tests/test_entities_api.py b/arkindex/documents/tests/test_entities_api.py index 39c4dde2fd0c13e7fc9121688ed83babf5ee390f..28dcd2b9f6d6e064da369713368005dd7cbd2937 100644 --- a/arkindex/documents/tests/test_entities_api.py +++ b/arkindex/documents/tests/test_entities_api.py @@ -1,5 +1,7 @@ import uuid +from unittest.mock import call, patch +from django.contrib.auth.models import AnonymousUser from django.contrib.gis.geos import LinearRing from django.urls import reverse from rest_framework import status @@ -16,6 +18,7 @@ from arkindex.documents.models import ( ) from arkindex.process.models import ProcessMode, WorkerRun, WorkerVersion from arkindex.project.tests import FixtureAPITestCase +from arkindex.users.models import Role class TestEntitiesAPI(FixtureAPITestCase): @@ -98,7 +101,8 @@ class TestEntitiesAPI(FixtureAPITestCase): def test_get_entity(self): with self.assertNumQueries(3): response = self.client.get(reverse("api:entity-details", kwargs={"pk": str(self.entity.id)})) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, status.HTTP_200_OK) + data = response.json() self.assertEqual(data["id"], str(self.entity.id)) self.assertEqual(data["type"]["id"], str(self.entity.type.id)) @@ -108,21 +112,25 @@ class TestEntitiesAPI(FixtureAPITestCase): def test_get_entity_elements(self): with self.assertNumQueries(6): response = self.client.get(reverse("api:entity-elements", kwargs={"pk": str(self.entity.id)})) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, status.HTTP_200_OK) + data = response.json() results = data["results"] self.assertEqual(len(results), 1) self.assertEqual(results[0]["id"], str(self.element.id)) self.assertEqual(results[0]["name"], self.element.name) - def test_get_entity_elements_corpus_acl(self): - self.client.force_login(self.user) - self.element.corpus = self.private_corpus - self.element.save() - with self.assertNumQueries(5): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_get_entity_elements_corpus_acl(self, filter_rights_mock): + with self.assertNumQueries(0): response = self.client.get(reverse("api:entity-elements", kwargs={"pk": str(self.entity.id)})) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertListEqual(response.json().get("results"), []) + self.assertListEqual(filter_rights_mock.call_args_list, [ + call(AnonymousUser(), Corpus, Role.Guest.value), + call(AnonymousUser(), Corpus, Role.Guest.value), + ]) def test_get_entity_elements_from_transcription(self): elt = self.corpus.elements.create( @@ -133,9 +141,11 @@ class TestEntitiesAPI(FixtureAPITestCase): ) elt_tr = elt.transcriptions.create(worker_version=self.worker_version_1, text="goodbye") TranscriptionEntity.objects.create(transcription=elt_tr, entity=self.entity, offset=42, length=7) + with self.assertNumQueries(6): response = self.client.get(reverse("api:entity-elements", kwargs={"pk": str(self.entity.id)})) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, status.HTTP_200_OK) + data = response.json() results = data["results"] self.assertEqual(len(results), 2) @@ -200,7 +210,7 @@ class TestEntitiesAPI(FixtureAPITestCase): "child_type_id": self.location_type.id } self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(7): response = self.client.post(reverse("api:corpus-roles", kwargs={"pk": str(self.corpus.id)}), data=data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) data = response.json() @@ -219,7 +229,7 @@ class TestEntitiesAPI(FixtureAPITestCase): "child_type_id": self.role.child_type.id, } self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.post(reverse("api:corpus-roles", kwargs={"pk": str(self.corpus.id)}), data=data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) data = response.json() @@ -254,27 +264,6 @@ class TestEntitiesAPI(FixtureAPITestCase): response = self.client.post(reverse("api:corpus-roles", kwargs={"pk": str(self.corpus.id)}), data=data) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_create_role_no_corpus_rights(self): - self.client.force_login(self.user) - - private_corpus = Corpus.objects.create(name="private") - org_type = EntityType.objects.create(name="organization", corpus=private_corpus) - location_type = EntityType.objects.create(name="location", corpus=private_corpus) - data = { - "parent_name": "other parent", - "child_name": "other child", - "parent_type_id": org_type.id, - "child_type_id": location_type.id - } - with self.assertNumQueries(7): - response = self.client.post(reverse("api:corpus-roles", kwargs={"pk": str(private_corpus.id)}), data=data) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - data = response.json() - self.assertEqual(data, { - "corpus": ["You do not have write access to this corpus"], - "id": [str(private_corpus.id)] - }) - def test_create_role_type_not_in_corpus(self): private_corpus = Corpus.objects.create(name="private") ext_type_1 = EntityType.objects.create(name="goose", corpus=private_corpus) @@ -286,7 +275,7 @@ class TestEntitiesAPI(FixtureAPITestCase): "parent_name": "nick", "child_name": "bradley" } - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.post(reverse("api:corpus-roles", kwargs={"pk": str(self.corpus.id)}), data=data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), { @@ -305,7 +294,7 @@ class TestEntitiesAPI(FixtureAPITestCase): }, } self.client.force_login(self.user) - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.post(reverse("api:entity-create"), data=data, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), {"type_id": ["“location†is not a valid UUID."]}) @@ -321,7 +310,7 @@ class TestEntitiesAPI(FixtureAPITestCase): }, } self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post(reverse("api:entity-create"), data=data, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), {"type_id": ['Invalid pk "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" - object does not exist.']}) @@ -338,7 +327,7 @@ class TestEntitiesAPI(FixtureAPITestCase): }, } self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post(reverse("api:entity-create"), data=data, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), {"type_id": [f"EntityType {str(extraneous_type.id)} does not exist in corpus Unit Tests."]}) @@ -353,7 +342,7 @@ class TestEntitiesAPI(FixtureAPITestCase): }, } self.client.force_login(self.user) - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.post(reverse("api:entity-create"), data=data, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), {"type_id": ["This field is required."]}) @@ -387,7 +376,7 @@ class TestEntitiesAPI(FixtureAPITestCase): }, } self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post(reverse("api:entity-create"), data=data, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.json(), { @@ -406,7 +395,7 @@ class TestEntitiesAPI(FixtureAPITestCase): }, } self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.post(reverse("api:entity-create"), data=data, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), {"worker_run_id": ['Invalid pk "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" - object does not exist.']}) @@ -416,7 +405,7 @@ class TestEntitiesAPI(FixtureAPITestCase): A user can create an entity without a worker run """ self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(7): response = self.client.post( reverse("api:entity-create"), format="json", @@ -502,7 +491,7 @@ class TestEntitiesAPI(FixtureAPITestCase): self.worker_run_1.process.run() task = self.worker_run_1.process.tasks.first() - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post( reverse("api:entity-create"), format="json", @@ -536,7 +525,7 @@ class TestEntitiesAPI(FixtureAPITestCase): }, } self.client.force_login(self.user) - with self.assertNumQueries(10): + with self.assertNumQueries(8): response = self.client.post(reverse("api:entity-create"), data=data, format="json") self.assertEqual(response.status_code, status.HTTP_201_CREATED) @@ -588,7 +577,7 @@ class TestEntitiesAPI(FixtureAPITestCase): self.worker_run_1.process.run() task = self.worker_run_1.process.tasks.first() - with self.assertNumQueries(9): + with self.assertNumQueries(7): response = self.client.post( reverse("api:entity-create"), format="json", @@ -623,7 +612,7 @@ class TestEntitiesAPI(FixtureAPITestCase): self.worker_run_1.process.run() task = self.worker_run_1.process.tasks.first() - with self.assertNumQueries(9): + with self.assertNumQueries(7): response = self.client.post( reverse("api:entity-create"), format="json", @@ -660,7 +649,7 @@ class TestEntitiesAPI(FixtureAPITestCase): "role": str(self.role.id) } self.client.force_login(self.user) - with self.assertNumQueries(16): + with self.assertNumQueries(14): response = self.client.post(reverse("api:entity-link-create"), data=data, format="json") self.assertEqual(response.status_code, status.HTTP_201_CREATED) link = EntityLink.objects.get(id=response.json()["id"]) @@ -697,13 +686,13 @@ class TestEntitiesAPI(FixtureAPITestCase): "role": str(self.role.id) } self.client.force_login(self.user) - with self.assertNumQueries(11): + with self.assertNumQueries(9): response = self.client.post(reverse("api:entity-link-create"), data=data, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test_create_transcription_entity(self): self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.post( reverse("api:transcription-entity-create", kwargs={"pk": str(self.transcription.id)}), data=self.tr_entities_sample, @@ -730,7 +719,7 @@ class TestEntitiesAPI(FixtureAPITestCase): def test_create_transcription_entity_with_confidence(self): self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.post( reverse("api:transcription-entity-create", kwargs={"pk": str(self.transcription.id)}), data=self.tr_entities_confidence_sample, @@ -767,7 +756,7 @@ class TestEntitiesAPI(FixtureAPITestCase): def test_create_transcription_entity_worker_version(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post( reverse("api:transcription-entity-create", kwargs={"pk": str(self.transcription.id)}), data={ @@ -788,7 +777,7 @@ class TestEntitiesAPI(FixtureAPITestCase): A regular user can create classifications with a WorkerRun of their own local process """ self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(7): response = self.client.post( reverse("api:transcription-entity-create", kwargs={"pk": str(self.transcription.id)}), data={ @@ -813,7 +802,7 @@ class TestEntitiesAPI(FixtureAPITestCase): def test_create_transcription_entity_forbidden_version(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post( reverse("api:transcription-entity-create", kwargs={"pk": str(self.transcription.id)}), data={ @@ -831,7 +820,7 @@ class TestEntitiesAPI(FixtureAPITestCase): def test_create_transcription_entity_bad_worker_run(self): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.post( reverse("api:transcription-entity-create", kwargs={"pk": str(self.transcription.id)}), data={ @@ -847,25 +836,33 @@ class TestEntitiesAPI(FixtureAPITestCase): "worker_run_id": ['Invalid pk "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" - object does not exist.'], }) - def test_create_transcription_entity_wrong_acl(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights") + def test_create_transcription_entity_wrong_acl(self, filter_rights_mock): self.client.force_login(self.user) self.element.corpus = self.private_corpus self.element.save() - with self.assertNumQueries(7): + filter_rights_mock.return_value = Corpus.objects.exclude(id=self.private_corpus.id) + + with self.assertNumQueries(5): response = self.client.post( reverse("api:transcription-entity-create", kwargs={"pk": str(self.transcription.id)}), data=self.tr_entities_sample, format="json" ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertDictEqual( response.json(), {"transcription": ["invalid UUID or Corpus write-access is forbidden"]} ) + self.assertListEqual(filter_rights_mock.call_args_list, [ + call(self.user, Corpus, Role.Contributor.value), + call(self.user, Corpus, Role.Contributor.value), + ]) def test_create_transcription_entity_wrong_length(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post( reverse("api:transcription-entity-create", kwargs={"pk": str(self.transcription.id)}), data={ @@ -883,7 +880,7 @@ class TestEntitiesAPI(FixtureAPITestCase): def test_create_transcription_entity_wrong_high_confidence(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post( reverse("api:transcription-entity-create", kwargs={"pk": str(self.transcription.id)}), data={ @@ -902,7 +899,7 @@ class TestEntitiesAPI(FixtureAPITestCase): def test_create_transcription_entity_wrong_low_confidence(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post( reverse("api:transcription-entity-create", kwargs={"pk": str(self.transcription.id)}), data={ @@ -919,7 +916,8 @@ class TestEntitiesAPI(FixtureAPITestCase): {"confidence": ["Ensure this value is greater than or equal to 0."]} ) - def test_create_transcription_entity_different_corpus(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights") + def test_create_transcription_entity_different_corpus(self, filter_rights_mock): self.client.force_login(self.user) person_type = EntityType.objects.create(name="person", corpus=self.private_corpus) ent = Entity.objects.create( @@ -929,17 +927,24 @@ class TestEntitiesAPI(FixtureAPITestCase): worker_version=self.worker_version_1, ) self.tr_entities_sample.update({"entity": ent.id}) - with self.assertNumQueries(6): + filter_rights_mock.return_value = Corpus.objects.exclude(id=self.private_corpus.id) + + with self.assertNumQueries(4): response = self.client.post( reverse("api:transcription-entity-create", kwargs={"pk": str(self.transcription.id)}), data=self.tr_entities_sample, format="json" ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertDictEqual( response.json(), {"entity": [f'Invalid pk "{ent.id}" - object does not exist.']} ) + self.assertListEqual(filter_rights_mock.call_args_list, [ + call(self.user, Corpus, Role.Contributor.value), + call(self.user, Corpus, Role.Contributor.value), + ]) def test_create_transcription_entity_duplicate(self): self.client.force_login(self.user) @@ -949,7 +954,7 @@ class TestEntitiesAPI(FixtureAPITestCase): offset=4, length=len(self.entity.name) ) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.post( reverse("api:transcription-entity-create", kwargs={"pk": str(self.transcription.id)}), data=self.tr_entities_sample, @@ -974,7 +979,7 @@ class TestEntitiesAPI(FixtureAPITestCase): ) self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.post( reverse("api:transcription-entity-create", kwargs={"pk": str(self.transcription.id)}), data={ @@ -1051,7 +1056,7 @@ class TestEntitiesAPI(FixtureAPITestCase): self.worker_run_1.process.run() task = self.worker_run_1.process.tasks.first() - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post( reverse("api:transcription-entity-create", kwargs={"pk": str(self.transcription.id)}), format="json", @@ -1078,7 +1083,7 @@ class TestEntitiesAPI(FixtureAPITestCase): self.worker_run_1.process.run() task = self.worker_run_1.process.tasks.first() - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.post( reverse("api:transcription-entity-create", kwargs={"pk": str(self.transcription.id)}), format="json", @@ -1113,7 +1118,7 @@ class TestEntitiesAPI(FixtureAPITestCase): self.worker_run_1.process.run() task = self.worker_run_1.process.tasks.first() - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.post( reverse("api:transcription-entity-create", kwargs={"pk": str(self.transcription.id)}), format="json", @@ -1150,7 +1155,7 @@ class TestEntitiesAPI(FixtureAPITestCase): worker_run=self.worker_run_1, worker_version=self.worker_version_1 ) - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.post( reverse("api:transcription-entity-create", kwargs={"pk": str(self.transcription.id)}), data={ @@ -1177,7 +1182,7 @@ class TestEntitiesAPI(FixtureAPITestCase): self.client.force_login(self.user) data = self.tr_entities_sample del data["length"] - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post( reverse("api:transcription-entity-create", kwargs={"pk": str(self.transcription.id)}), data=data, @@ -1189,7 +1194,8 @@ class TestEntitiesAPI(FixtureAPITestCase): response = self.client.get(reverse("api:transcription-entities", kwargs={"pk": str(self.transcription.id)})) self.assertEqual(response.status_code, status.HTTP_200_OK) - def test_list_transcription_entities_wrong_acl(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_list_transcription_entities_wrong_acl(self, filter_rights_mock): self.transcription.element.corpus = self.private_corpus self.transcription.element.save() TranscriptionEntity.objects.create( @@ -1199,12 +1205,17 @@ class TestEntitiesAPI(FixtureAPITestCase): length=len(self.entity.name) ) self.client.force_login(self.user) - response = self.client.get(reverse("api:transcription-entities", kwargs={"pk": str(self.transcription.id)})) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + with self.assertNumQueries(2): + response = self.client.get(reverse("api:transcription-entities", kwargs={"pk": str(self.transcription.id)})) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.assertEqual(filter_rights_mock.call_count, 1) + self.assertEqual(filter_rights_mock.call_args, call(self.user, Corpus, Role.Guest.value)) def test_list_transcription_entities(self): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.get(reverse("api:transcription-entities", kwargs={"pk": str(self.transcription.id)})) self.assertEqual(response.status_code, status.HTTP_200_OK) data = response.json() @@ -1357,7 +1368,7 @@ class TestEntitiesAPI(FixtureAPITestCase): worker_version=self.worker_version_2 ) self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.get( reverse("api:transcription-entities", kwargs={"pk": str(self.transcription.id)}), {"worker_version": str(self.worker_version_2.id)} @@ -1397,7 +1408,7 @@ class TestEntitiesAPI(FixtureAPITestCase): worker_run=self.worker_run_2, ) self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.get( reverse("api:transcription-entities", kwargs={"pk": str(self.transcription.id)}), {"worker_run": str(self.worker_run_2.id)} @@ -1438,7 +1449,7 @@ class TestEntitiesAPI(FixtureAPITestCase): length=len(self.entity.name) ) self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.get( reverse("api:transcription-entities", kwargs={"pk": str(self.transcription.id)}), {"worker_version": False} @@ -1477,7 +1488,7 @@ class TestEntitiesAPI(FixtureAPITestCase): worker_version=self.worker_version_2, ) self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.get( reverse("api:transcription-entities", kwargs={"pk": str(self.transcription.id)}), {"entity_worker_version": str(self.worker_version_1.id)} @@ -1523,7 +1534,7 @@ class TestEntitiesAPI(FixtureAPITestCase): worker_run=self.worker_run_2, ) self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(7): response = self.client.get( reverse("api:transcription-entities", kwargs={"pk": str(self.transcription.id)}), {"entity_worker_run": str(self.worker_run_1.id)} @@ -1573,7 +1584,7 @@ class TestEntitiesAPI(FixtureAPITestCase): worker_version=self.worker_version_2 ) self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.get( reverse("api:transcription-entities", kwargs={"pk": str(self.transcription.id)}), {"entity_worker_version": False} @@ -1622,7 +1633,7 @@ class TestEntitiesAPI(FixtureAPITestCase): worker_version=self.worker_version_1 ) self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(7): response = self.client.get( reverse("api:transcription-entities", kwargs={"pk": str(self.transcription.id)}), {"entity_worker_version": str(self.worker_version_1.id), "worker_version": str(self.worker_version_2.id)} @@ -1692,11 +1703,15 @@ class TestEntitiesAPI(FixtureAPITestCase): ] }) - def test_list_corpus_entities_acl(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_list_corpus_entities_acl(self, has_access_mock): with self.assertNumQueries(1): response = self.client.get(reverse("api:corpus-entities", kwargs={"pk": str(self.private_corpus.id)})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(has_access_mock.call_count, 1) + self.assertEqual(has_access_mock.call_args, call(AnonymousUser(), self.private_corpus, Role.Guest.value, skip_public=False)) + def test_list_corpus_entities_parent(self): with self.assertNumQueries(4): response = self.client.get( @@ -1803,7 +1818,7 @@ class TestEntitiesAPI(FixtureAPITestCase): def test_update_entity_requires_name_and_type(self): self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.put( reverse("api:entity-details", kwargs={"pk": self.entity_bis.id}), {"name": "a new name"}, @@ -1813,7 +1828,7 @@ class TestEntitiesAPI(FixtureAPITestCase): def test_update_validate_entity(self): self.client.force_login(self.user) - with self.assertNumQueries(12): + with self.assertNumQueries(9): response = self.client.put( reverse("api:entity-details", kwargs={"pk": self.entity_bis.id}), { @@ -1829,7 +1844,7 @@ class TestEntitiesAPI(FixtureAPITestCase): def test_update_unvalidate_entity(self): self.client.force_login(self.user) - with self.assertNumQueries(12): + with self.assertNumQueries(9): response = self.client.put( reverse("api:entity-details", kwargs={"pk": self.entity_bis.id}), { @@ -1899,7 +1914,7 @@ class TestEntitiesAPI(FixtureAPITestCase): def test_update_entity_change_type(self): self.client.force_login(self.user) - with self.assertNumQueries(12): + with self.assertNumQueries(9): response = self.client.put( reverse("api:entity-details", kwargs={"pk": self.entity_bis.id}), { @@ -1918,7 +1933,7 @@ class TestEntitiesAPI(FixtureAPITestCase): def test_partial_update_validate_entity(self): self.client.force_login(self.user) - with self.assertNumQueries(11): + with self.assertNumQueries(8): response = self.client.patch( reverse("api:entity-details", kwargs={"pk": self.entity_bis.id}), {"validated": True}, @@ -1930,7 +1945,7 @@ class TestEntitiesAPI(FixtureAPITestCase): def test_partial_update_unvalidate_entity(self): self.client.force_login(self.user) - with self.assertNumQueries(11): + with self.assertNumQueries(8): response = self.client.patch( reverse("api:entity-details", kwargs={"pk": self.entity_bis.id}), {"validated": False}, @@ -1978,22 +1993,23 @@ class TestEntitiesAPI(FixtureAPITestCase): ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_list_element_links_not_guest(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_list_element_links_not_guest(self, has_access_mock): """ A guest access on the element is required to list entity links """ - private_elt = self.private_corpus.elements.create( - type=self.private_corpus.types.create(slug="page"), - name="A" - ) self.client.force_login(self.user) - with self.assertNumQueries(6): - response = self.client.get(reverse("api:element-links", kwargs={"pk": str(private_elt.id)})) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + with self.assertNumQueries(3): + response = self.client.get(reverse("api:element-links", kwargs={"pk": str(self.element.id)})) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertDictEqual( response.json(), {"detail": "You do not have access to this element."} ) + self.assertEqual(has_access_mock.call_count, 1) + self.assertEqual(has_access_mock.call_args, call(self.user, self.corpus, Role.Guest.value, skip_public=False)) def test_list_element_links(self): link = EntityLink.objects.create(parent=self.entity, child=self.entity_bis, role=self.role) diff --git a/arkindex/documents/tests/test_entity_types.py b/arkindex/documents/tests/test_entity_types.py index 7bbd833e8f6777f78182ab98f3c0cbd94b11d712..7f883e4eea68a17881374e93b8caf03cbb056f27 100644 --- a/arkindex/documents/tests/test_entity_types.py +++ b/arkindex/documents/tests/test_entity_types.py @@ -1,3 +1,5 @@ +from unittest.mock import call, patch + from django.urls import reverse from rest_framework import status @@ -31,7 +33,7 @@ class TestEntityTypesAPI(FixtureAPITestCase): # ENTITY TYPE CREATE - def test_create_entity_type_requires_login(self): + def test_create_requires_login(self): data = { "corpus": str(self.corpus.id), "name": "dagger one" @@ -40,7 +42,7 @@ class TestEntityTypesAPI(FixtureAPITestCase): response = self.client.post(reverse("api:entity-type-create"), data=data, format="json") self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_create_entity_type_requires_verified(self): + def test_create_requires_verified(self): self.client.force_login(self.unverified_user) data = { "corpus": str(self.corpus.id), @@ -54,30 +56,36 @@ class TestEntityTypesAPI(FixtureAPITestCase): {"detail": "You do not have permission to perform this action."} ) - def test_create_entity_type_requires_corpus_admin(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_create_requires_corpus_admin(self, filter_rights_mock): self.client.force_login(self.user) data = { "corpus": str(self.private_corpus.id), "name": "dagger one" } self.assertEqual(self.private_corpus.entity_types.all().count(), 1) - with self.assertNumQueries(5): + + with self.assertNumQueries(2): response = self.client.post(reverse("api:entity-type-create"), data=data, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertDictEqual( response.json(), {"corpus": [f'Invalid pk "{str(self.private_corpus.id)}" - object does not exist.']} ) self.assertEqual(self.private_corpus.entity_types.all().count(), 1) - def test_create_entity_type(self): + self.assertEqual(filter_rights_mock.call_count, 1) + self.assertEqual(filter_rights_mock.call_args, call(self.user, Corpus, Role.Admin.value)) + + def test_create(self): self.client.force_login(self.private_corpus_admin) data = { "corpus": str(self.private_corpus.id), "name": "dagger spare" } self.assertEqual(self.private_corpus.entity_types.all().count(), 1) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.post(reverse("api:entity-type-create"), data=data, format="json") self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(self.private_corpus.entity_types.all().count(), 2) @@ -85,7 +93,7 @@ class TestEntityTypesAPI(FixtureAPITestCase): self.assertEqual(response.json()["name"], "dagger spare") self.assertEqual(response.json()["corpus"], str(self.private_corpus.id)) - def test_create_entity_type_color_validation(self): + def test_create_color_validation(self): self.client.force_login(self.private_corpus_admin) cases = [ [ @@ -105,7 +113,7 @@ class TestEntityTypesAPI(FixtureAPITestCase): ] ] for color, errors in cases: - with (self.subTest(color=color, errors=errors), self.assertNumQueries(5)): + with (self.subTest(color=color, errors=errors), self.assertNumQueries(3)): response = self.client.post( reverse("api:entity-type-create"), data={ @@ -118,19 +126,19 @@ class TestEntityTypesAPI(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), {"color": errors}) - def test_create_entity_type_color_ignore_case(self): + def test_create_color_ignore_case(self): self.client.force_login(self.private_corpus_admin) data = { "corpus": str(self.private_corpus.id), "name": "dagger spare", "color": "FF0000" } - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.post(reverse("api:entity-type-create"), data=data, format="json") self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.json()["color"], "FF0000") - def test_create_entity_type_color(self): + def test_create_color(self): self.client.force_login(self.private_corpus_admin) data = { "corpus": str(self.private_corpus.id), @@ -138,7 +146,7 @@ class TestEntityTypesAPI(FixtureAPITestCase): "color": "f76719" } self.assertEqual(self.private_corpus.entity_types.all().count(), 1) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.post(reverse("api:entity-type-create"), data=data, format="json") self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(self.private_corpus.entity_types.all().count(), 2) @@ -146,21 +154,21 @@ class TestEntityTypesAPI(FixtureAPITestCase): self.assertEqual(response.json()["name"], "dagger spare") self.assertEqual(response.json()["corpus"], str(self.private_corpus.id)) - def test_create_entity_type_already_exists(self): + def test_create_already_exists(self): self.client.force_login(self.private_corpus_admin) data = { "corpus": str(self.private_corpus.id), "name": "some type", "color": "f76719" } - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post(reverse("api:entity-type-create"), data=data, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), {"name": ["Entity Type with name some type already exists in corpus private."]}) # ENTITY TYPE UPDATE - def test_update_entity_type_requires_login(self): + def test_update_requires_login(self): data = { "name": "dagger one", "color": "f76719" @@ -169,7 +177,7 @@ class TestEntityTypesAPI(FixtureAPITestCase): response = self.client.put(reverse("api:entity-type-details", kwargs={"pk": self.private_corpus_entity_type.id}), data=data, format="json") self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_update_entity_type_requires_verified(self): + def test_update_requires_verified(self): self.client.force_login(self.unverified_user) data = { "name": "dagger one", @@ -183,15 +191,18 @@ class TestEntityTypesAPI(FixtureAPITestCase): {"detail": "You do not have permission to perform this action."} ) - def test_update_entity_type_requires_corpus_admin(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_update_requires_corpus_admin(self, has_access_mock): self.client.force_login(self.user) data = { "name": "dagger one", "color": "f76719" } - with self.assertNumQueries(7): + + with self.assertNumQueries(4): response = self.client.put(reverse("api:entity-type-details", kwargs={"pk": self.private_corpus_entity_type.id}), data=data, format="json") self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertDictEqual( response.json(), {"detail": "You do not have admin access to this corpus."} @@ -200,27 +211,30 @@ class TestEntityTypesAPI(FixtureAPITestCase): self.assertEqual(self.private_corpus_entity_type.name, "some type") self.assertEqual(self.private_corpus_entity_type.color, "ff0000") - def test_update_entity_type(self): + self.assertEqual(has_access_mock.call_count, 1) + self.assertEqual(has_access_mock.call_args, call(self.user, self.private_corpus, Role.Admin.value, skip_public=False)) + + def test_update(self): self.client.force_login(self.private_corpus_admin) data = { "name": "dagger spare", "color": "f76719" } - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.put(reverse("api:entity-type-details", kwargs={"pk": self.private_corpus_entity_type.id}), data=data, format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) self.private_corpus_entity_type.refresh_from_db() self.assertEqual(self.private_corpus_entity_type.color, "f76719") self.assertEqual(self.private_corpus_entity_type.name, "dagger spare") - def test_update_entity_type_name_already_exists(self): + def test_update_name_already_exists(self): self.client.force_login(self.private_corpus_admin) EntityType.objects.create(corpus=self.private_corpus, name="dagger spare") data = { "name": "dagger spare", "color": "f76719" } - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.put(reverse("api:entity-type-details", kwargs={"pk": self.private_corpus_entity_type.id}), data=data, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), {"name": ["Entity Type with name dagger spare already exists in corpus private."]}) @@ -230,7 +244,7 @@ class TestEntityTypesAPI(FixtureAPITestCase): data = { "color": "f76719" } - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.put(reverse("api:entity-type-details", kwargs={"pk": self.private_corpus_entity_type.id}), data=data, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), {"name": ["This field is required."]}) @@ -241,14 +255,14 @@ class TestEntityTypesAPI(FixtureAPITestCase): "corpus": str(self.corpus.id), "name": "iceman" } - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.put(reverse("api:entity-type-details", kwargs={"pk": self.private_corpus_entity_type.id}), data=data, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), {"corpus": ["It is not possible to update an Entity Type's corpus."]}) # ENTITY TYPE PARTIAL UPDATE - def test_partial_update_entity_type_requires_login(self): + def test_partial_update_requires_login(self): data = { "name": "dagger one", "color": "f76719" @@ -257,7 +271,7 @@ class TestEntityTypesAPI(FixtureAPITestCase): response = self.client.patch(reverse("api:entity-type-details", kwargs={"pk": self.private_corpus_entity_type.id}), data=data, format="json") self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_partial_update_entity_type_requires_verified(self): + def test_partial_update_requires_verified(self): self.client.force_login(self.unverified_user) data = { "name": "dagger one", @@ -271,15 +285,18 @@ class TestEntityTypesAPI(FixtureAPITestCase): {"detail": "You do not have permission to perform this action."} ) - def test_partial_update_entity_type_requires_corpus_admin(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_partial_update_requires_corpus_admin(self, has_access_mock): self.client.force_login(self.user) data = { "name": "dagger one", "color": "f76719" } - with self.assertNumQueries(7): + + with self.assertNumQueries(4): response = self.client.patch(reverse("api:entity-type-details", kwargs={"pk": self.private_corpus_entity_type.id}), data=data, format="json") self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertDictEqual( response.json(), {"detail": "You do not have admin access to this corpus."} @@ -288,13 +305,16 @@ class TestEntityTypesAPI(FixtureAPITestCase): self.assertEqual(self.private_corpus_entity_type.name, "some type") self.assertEqual(self.private_corpus_entity_type.color, "ff0000") - def test_partial_update_entity_type(self): + self.assertEqual(has_access_mock.call_count, 1) + self.assertEqual(has_access_mock.call_args, call(self.user, self.private_corpus, Role.Admin.value, skip_public=False)) + + def test_partial_update(self): self.client.force_login(self.private_corpus_admin) data = { "name": "dagger spare", "color": "f76719" } - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.patch(reverse("api:entity-type-details", kwargs={"pk": self.private_corpus_entity_type.id}), data=data, format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.json()["color"], "f76719") @@ -305,7 +325,7 @@ class TestEntityTypesAPI(FixtureAPITestCase): data = { "color": "f76719" } - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.patch(reverse("api:entity-type-details", kwargs={"pk": self.private_corpus_entity_type.id}), data=data, format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) self.private_corpus_entity_type.refresh_from_db() @@ -317,14 +337,14 @@ class TestEntityTypesAPI(FixtureAPITestCase): data = { "corpus": str(self.corpus.id), } - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.patch(reverse("api:entity-type-details", kwargs={"pk": self.private_corpus_entity_type.id}), data=data, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), {"corpus": ["It is not possible to update an Entity Type's corpus."]}) # ENTITY TYPE RETRIEVE - def test_retrieve_entity_type_does_not_require_login_public_project(self): + def test_retrieve_does_not_require_login_public_project(self): with self.assertNumQueries(1): response = self.client.get(reverse("api:entity-type-details", kwargs={"pk": self.location_type.id}), format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -335,19 +355,25 @@ class TestEntityTypesAPI(FixtureAPITestCase): "color": "ff0000" }) - def test_retrieve_entity_type_requires_private_corpus_guest(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_retrieve_requires_private_corpus_guest(self, filter_rights_mock): self.client.force_login(self.no_access) - with self.assertNumQueries(5): + + with self.assertNumQueries(2): response = self.client.get(reverse("api:entity-type-details", kwargs={"pk": self.private_corpus_entity_type.id}), format="json") self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertDictEqual( response.json(), {"detail": "Not found."} ) - def test_retrieve_entity_type(self): + self.assertEqual(filter_rights_mock.call_count, 1) + self.assertEqual(filter_rights_mock.call_args, call(self.no_access, Corpus, Role.Guest.value)) + + def test_retrieve(self): self.client.force_login(self.private_corpus_guest) - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.get(reverse("api:entity-type-details", kwargs={"pk": self.private_corpus_entity_type.id}), format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.json()["color"], "ff0000") @@ -355,9 +381,10 @@ class TestEntityTypesAPI(FixtureAPITestCase): # ENTITY TYPE DELETE - def test_delete_entity_type_requires_corpus_admin(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_delete_requires_corpus_admin(self, has_access_mock): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.delete(reverse("api:entity-type-details", kwargs={"pk": self.private_corpus_entity_type.id}), format="json") self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual( @@ -365,23 +392,26 @@ class TestEntityTypesAPI(FixtureAPITestCase): {"detail": "You do not have admin access to this corpus."} ) - def test_delete_entity_type(self): + self.assertEqual(has_access_mock.call_count, 1) + self.assertEqual(has_access_mock.call_args, call(self.user, self.private_corpus, Role.Admin.value, skip_public=False)) + + def test_delete(self): self.client.force_login(self.private_corpus_admin) - with self.assertNumQueries(10): + with self.assertNumQueries(7): response = self.client.delete(reverse("api:entity-type-details", kwargs={"pk": self.private_corpus_entity_type.id}), format="json") self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) with self.assertRaises(EntityType.DoesNotExist): self.private_corpus_entity_type.refresh_from_db() - def test_delete_entity_type_has_entities(self): + def test_delete_has_entities(self): Entity.objects.create(type=self.private_corpus_entity_type, corpus=self.private_corpus, name="bob") self.client.force_login(self.private_corpus_admin) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.delete(reverse("api:entity-type-details", kwargs={"pk": self.private_corpus_entity_type.id}), format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), {"detail": ["Some entities are using this entity type."]}) - def test_delete_entity_type_has_roles(self): + def test_delete_has_roles(self): EntityRole.objects.create( corpus=self.private_corpus, parent_name="goose", @@ -390,14 +420,14 @@ class TestEntityTypesAPI(FixtureAPITestCase): child_type=self.private_corpus_entity_type ) self.client.force_login(self.private_corpus_admin) - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.delete(reverse("api:entity-type-details", kwargs={"pk": self.private_corpus_entity_type.id}), format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), {"detail": ["Some entity roles are using this entity type."]}) # LIST CORPUS ENTITY TYPES - def test_corpus_entity_types_does_not_require_login_public_project(self): + def test_list_does_not_require_login_public_project(self): with self.assertNumQueries(3): response = self.client.get(reverse("api:corpus-entity-types", kwargs={"pk": self.corpus.id}), format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -429,17 +459,21 @@ class TestEntityTypesAPI(FixtureAPITestCase): }, ]) - def test_corpus_entity_types_requires_guest_private_project(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_list_requires_guest_private_project(self, has_access_mock): self.client.force_login(self.no_access) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.get(reverse("api:corpus-entity-types", kwargs={"pk": self.private_corpus.id}), format="json") self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_corpus_entity_types(self): + self.assertEqual(has_access_mock.call_count, 1) + self.assertEqual(has_access_mock.call_args, call(self.no_access, self.private_corpus, Role.Guest.value, skip_public=False)) + + def test_list(self): type_2 = EntityType.objects.create(corpus=self.private_corpus, color="f01ef7", name="type 2") type_3 = EntityType.objects.create(corpus=self.private_corpus, color="29d911", name="person") self.client.force_login(self.private_corpus_guest) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.get(reverse("api:corpus-entity-types", kwargs={"pk": self.private_corpus.id}), format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertCountEqual(response.json()["results"], [ diff --git a/arkindex/documents/tests/test_export.py b/arkindex/documents/tests/test_export.py index 8016de5ee6662a61351a4b93347d8b06ae5d51cf..d1ab1e81d1f199d7d83aa8bc842a8fff315264b1 100644 --- a/arkindex/documents/tests/test_export.py +++ b/arkindex/documents/tests/test_export.py @@ -61,13 +61,16 @@ class TestExport(FixtureAPITestCase): self.assertFalse(delay_mock.called) @patch("arkindex.project.triggers.export.export_corpus.delay") - def test_start_requires_contributor(self, delay_mock): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_start_requires_contributor(self, has_access_mock, delay_mock): self.user.rights.update(level=Role.Guest.value) self.client.force_login(self.user) response = self.client.post(reverse("api:corpus-export", kwargs={"pk": self.corpus.id})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(has_access_mock.call_count, 1) + self.assertEqual(has_access_mock.call_args, call(self.user, self.corpus, Role.Contributor.value, skip_public=False)) self.assertFalse(self.corpus.exports.exists()) self.assertFalse(delay_mock.called) @@ -146,14 +149,19 @@ class TestExport(FixtureAPITestCase): response = self.client.get(reverse("api:corpus-export", kwargs={"pk": self.corpus.id})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_list_requires_guest(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_list_requires_guest(self, has_access_mock): self.user.rights.all().delete() self.corpus.public = False self.corpus.save() self.client.force_login(self.user) + response = self.client.get(reverse("api:corpus-export", kwargs={"pk": self.corpus.id})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(has_access_mock.call_count, 1) + self.assertEqual(has_access_mock.call_args, call(self.user, self.corpus, Role.Guest.value, skip_public=False)) + @patch("arkindex.project.aws.s3.meta.client.generate_presigned_url") def test_download_export(self, presigned_url_mock): presigned_url_mock.return_value = "http://somewhere" @@ -182,7 +190,7 @@ class TestExport(FixtureAPITestCase): self.corpus.memberships.filter(user=self.user).delete() export = self.corpus.exports.create(user=self.superuser, state=CorpusExportState.Done) - with self.assertNumQueries(4): + with self.assertNumQueries(3): response = self.client.get(reverse("api:manage-export", kwargs={"pk": export.id})) self.assertEqual(response.status_code, status.HTTP_302_FOUND) self.assertEqual(response.headers["Location"], "http://somewhere") @@ -201,16 +209,20 @@ class TestExport(FixtureAPITestCase): response = self.client.get(reverse("api:manage-export", kwargs={"pk": export.id})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_download_export_requires_guest(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_download_export_requires_guest(self, filter_rights_mock): self.user.rights.all().delete() self.corpus.public = False self.corpus.save() self.client.force_login(self.user) export = self.corpus.exports.create(user=self.user, state=CorpusExportState.Done) - with self.assertNumQueries(5): + with self.assertNumQueries(2): response = self.client.get(reverse("api:manage-export", kwargs={"pk": export.id})) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertEqual(filter_rights_mock.call_count, 1) + self.assertEqual(filter_rights_mock.call_args, call(self.user, Corpus, Role.Guest.value)) + def test_download_export_not_done(self): self.client.force_login(self.superuser) for state in (CorpusExportState.Created, CorpusExportState.Running, CorpusExportState.Failed): @@ -236,14 +248,18 @@ class TestExport(FixtureAPITestCase): response = self.client.delete(reverse("api:manage-export", kwargs={"pk": export.id})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_delete_export_private_corpus(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_delete_export_private_corpus(self, filter_rights_mock): private_corpus = Corpus.objects.create(name="private") self.client.force_login(self.user) export = private_corpus.exports.create(user=self.superuser, state=CorpusExportState.Done) - with self.assertNumQueries(5): + with self.assertNumQueries(2): response = self.client.delete(reverse("api:manage-export", kwargs={"pk": export.id})) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertEqual(filter_rights_mock.call_count, 1) + self.assertEqual(filter_rights_mock.call_args, call(self.user, Corpus, Role.Guest.value)) + def test_delete_export_wrong_state(self): self.client.force_login(self.superuser) for state in (CorpusExportState.Created, CorpusExportState.Failed): @@ -277,28 +293,52 @@ class TestExport(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) assert not self.corpus.exports.exists() - def test_delete_export_requires_rights(self): + @patch("arkindex.users.utils.get_max_level", return_value=Role.Contributor.value) + def test_delete_export_requires_admin(self, get_max_level_mock): self.corpus.memberships.filter(user=self.user).update(level=Role.Contributor.value) self.client.force_login(self.user) export = self.corpus.exports.create(user=self.superuser, state=CorpusExportState.Done) - with self.assertNumQueries(5): + + with self.assertNumQueries(3): response = self.client.delete(reverse("api:manage-export", kwargs={"pk": export.id})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertDictEqual(response.json(), {"detail": "You do not have sufficient rights to delete this export."}) - def test_delete_export_creator_contributor(self): + self.assertEqual(get_max_level_mock.call_count, 1) + self.assertEqual(get_max_level_mock.call_args, call(self.user, self.corpus)) + + @patch("arkindex.users.utils.get_max_level", return_value=Role.Guest.value) + def test_delete_export_creator_requires_contributor(self, get_max_level_mock): + self.client.force_login(self.user) + export = self.corpus.exports.create(user=self.user, state=CorpusExportState.Done) + + with self.assertNumQueries(3): + response = self.client.delete(reverse("api:manage-export", kwargs={"pk": export.id})) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + self.assertDictEqual(response.json(), {"detail": "You do not have sufficient rights to delete this export."}) + + self.assertEqual(get_max_level_mock.call_count, 1) + self.assertEqual(get_max_level_mock.call_args, call(self.user, self.corpus)) + + @patch("arkindex.users.utils.get_max_level", return_value=Role.Contributor.value) + def test_delete_export_creator_contributor(self, get_max_level_mock): self.corpus.memberships.filter(user=self.user).update(level=Role.Contributor.value) self.client.force_login(self.user) export = self.corpus.exports.create(user=self.user, state=CorpusExportState.Done) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.delete(reverse("api:manage-export", kwargs={"pk": export.id})) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) assert not self.corpus.exports.exists() + self.assertEqual(get_max_level_mock.call_count, 1) + self.assertEqual(get_max_level_mock.call_args, call(self.user, self.corpus)) + def test_delete_export_corpus_admin(self): self.client.force_login(self.user) export = self.corpus.exports.create(user=self.superuser, state=CorpusExportState.Done) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.delete(reverse("api:manage-export", kwargs={"pk": export.id})) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) assert not self.corpus.exports.exists() diff --git a/arkindex/documents/tests/test_metadata.py b/arkindex/documents/tests/test_metadata.py index a9a0ebe324e6741607d802c83d323bf99eee6224..6968b3cbbf588210a6503f4ed393617a9c5d8f81 100644 --- a/arkindex/documents/tests/test_metadata.py +++ b/arkindex/documents/tests/test_metadata.py @@ -1,5 +1,7 @@ import uuid +from unittest.mock import call, patch +from django.contrib.auth.models import AnonymousUser from django.urls import reverse from rest_framework import status @@ -54,7 +56,7 @@ class TestMetaData(FixtureAPITestCase): def test_list(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.get(reverse("api:element-metadata", kwargs={"pk": str(self.vol.id)})) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertListEqual(response.json(), [ @@ -70,27 +72,14 @@ class TestMetaData(FixtureAPITestCase): }, ]) - def test_list_public(self): - self.assertTrue(self.corpus.public) - with self.assertNumQueries(2): - response = self.client.get(reverse("api:element-metadata", kwargs={"pk": str(self.vol.id)})) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertListEqual(response.json(), [ - { - "id": str(self.metadata.id), - "name": "folio", - "type": "text", - "value": "123", - "dates": [], - "entity": None, - "worker_version": None, - "worker_run": None, - }, - ]) + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_list_private_requires_login(self, filter_rights_mock): + with self.assertNumQueries(0): + response = self.client.get(reverse("api:element-metadata", kwargs={"pk": str(self.private_vol.id)})) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_list_private_requires_login(self): - response = self.client.get(reverse("api:element-metadata", kwargs={"pk": str(self.private_vol.id)})) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertEqual(filter_rights_mock.call_count, 1) + self.assertEqual(filter_rights_mock.call_args, call(AnonymousUser(), Corpus, Role.Guest.value)) def test_list_with_entity(self): # Link an entity to metadata, db requests count should remains same @@ -106,7 +95,7 @@ class TestMetaData(FixtureAPITestCase): entity=entity, ) self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.get(reverse("api:element-metadata", kwargs={"pk": str(self.vol.id)})) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertCountEqual(response.json(), [ @@ -148,7 +137,7 @@ class TestMetaData(FixtureAPITestCase): self.metadata.worker_version = self.worker_version self.metadata.save() self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.get(reverse("api:element-metadata", kwargs={"pk": str(self.vol.id)})) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertListEqual(response.json(), [ @@ -170,7 +159,7 @@ class TestMetaData(FixtureAPITestCase): self.metadata.save() self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.get(reverse("api:element-metadata", kwargs={"pk": str(self.vol.id)})) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -190,22 +179,26 @@ class TestMetaData(FixtureAPITestCase): } ]) - def test_list_wrong_acl(self): - self.vol.corpus = self.private_corpus - self.vol.save() + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_list_wrong_acl(self, filter_rights_mock): self.client.force_login(self.user) - response = self.client.get(reverse("api:element-metadata", kwargs={"pk": str(self.vol.id)})) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + with self.assertNumQueries(2): + response = self.client.get(reverse("api:element-metadata", kwargs={"pk": str(self.vol.id)})) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.assertEqual(filter_rights_mock.call_count, 1) + self.assertEqual(filter_rights_mock.call_args, call(self.user, Corpus, Role.Guest.value)) def test_list_wrong_element_id(self): self.client.force_login(self.user) - response = self.client.get(reverse("api:element-metadata", kwargs={"pk": "12341234-1234-1234-1234-123412341234"})) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + with self.assertNumQueries(3): + response = self.client.get(reverse("api:element-metadata", kwargs={"pk": "12341234-1234-1234-1234-123412341234"})) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) def test_list_without_parents(self): self.vol.metadatas.create(type=MetaType.Numeric, name="weight", value="1337") self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.get( reverse("api:element-metadata", kwargs={"pk": str(self.page.id)}), {"load_parents": False}, @@ -230,7 +223,7 @@ class TestMetaData(FixtureAPITestCase): weight_meta = self.vol.metadatas.create(type=MetaType.Numeric, name="weight", value="1337.0") self.client.force_login(self.user) # One more request is required to list element paths - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.get( reverse("api:element-metadata", kwargs={"pk": str(self.page.id)}), {"load_parents": True}, @@ -298,17 +291,23 @@ class TestMetaData(FixtureAPITestCase): response = method(reverse("api:element-metadata", kwargs={"pk": str(self.vol.id)})) self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED) - def test_create_metadata_writable_element(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_create_metadata_writable_element(self, filter_rights_mock): self.client.force_login(self.user) hidden_book = self.private_corpus.elements.create( type=self.corpus.types.get(slug="volume"), name="Nope" ) - response = self.client.post( - reverse("api:element-metadata", kwargs={"pk": str(hidden_book.id)}), - data={"type": "text", "name": "language", "value": "Japanese"} - ) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + with self.assertNumQueries(2): + response = self.client.post( + reverse("api:element-metadata", kwargs={"pk": str(hidden_book.id)}), + data={"type": "text", "name": "language", "value": "Japanese"} + ) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.assertEqual(filter_rights_mock.call_count, 1) + self.assertEqual(filter_rights_mock.call_args, call(self.user, Corpus, Role.Contributor.value)) def test_create_metadata(self): self.client.force_login(self.user) @@ -322,7 +321,7 @@ class TestMetaData(FixtureAPITestCase): def test_create_metadata_worker_version(self): self.client.force_login(self.user) - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.post( reverse("api:element-metadata", kwargs={"pk": str(self.vol.id)}), data={ @@ -339,7 +338,7 @@ class TestMetaData(FixtureAPITestCase): def test_create_metadata_worker_run_or_version(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post( reverse("api:element-metadata", kwargs={"pk": str(self.vol.id)}), data={ @@ -418,7 +417,7 @@ class TestMetaData(FixtureAPITestCase): def test_create_duplicated_metadata(self): self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(7): response = self.client.post( reverse("api:element-metadata", kwargs={"pk": str(self.vol.id)}), data={"type": "date", "name": "edition", "value": "1986-june"} @@ -504,7 +503,7 @@ class TestMetaData(FixtureAPITestCase): other_worker_run = process2.worker_runs.create(version=self.worker_run.version, parents=[]) self.worker_run.process.run() task = self.worker_run.process.tasks.first() - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.post( reverse("api:element-metadata", kwargs={"pk": str(self.vol.id)}), data={"type": "text", "name": "color", "value": "red", "worker_run_id": str(other_worker_run.id)}, @@ -522,7 +521,7 @@ class TestMetaData(FixtureAPITestCase): A regular user can create a metadata with a WorkerRun of their own local process """ self.client.force_login(self.user) - with self.assertNumQueries(10): + with self.assertNumQueries(8): response = self.client.post( reverse("api:element-metadata", kwargs={"pk": str(self.vol.id)}), data={"type": "location", "name": "location", "value": "Texas", "worker_run_id": str(self.local_worker_run.id)} @@ -554,7 +553,7 @@ class TestMetaData(FixtureAPITestCase): self.worker_run.process.run() task = self.worker_run.process.tasks.first() - with self.assertNumQueries(12): + with self.assertNumQueries(10): response = self.client.post( reverse("api:element-metadata", kwargs={"pk": str(self.vol.id)}), data={"type": "text", "name": "color", "value": "red", "worker_run_id": str(self.worker_run.id)}, @@ -578,7 +577,7 @@ class TestMetaData(FixtureAPITestCase): self.worker_run.process.run() task = self.worker_run.process.tasks.first() - with self.assertNumQueries(12): + with self.assertNumQueries(10): response = self.client.post( reverse("api:element-metadata", kwargs={"pk": str(self.vol.id)}), data={"type": "text", "name": "color", "value": "red", "worker_run_id": str(local_worker_run.id)}, @@ -629,20 +628,27 @@ class TestMetaData(FixtureAPITestCase): ) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - def test_delete_metadata_private_corpus(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_delete_metadata_private_corpus(self, filter_rights_mock): self.client.force_login(self.user) - response = self.client.delete(reverse("api:metadata-edit", kwargs={"pk": str(self.private_metadata.id)})) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + with self.assertNumQueries(2): + response = self.client.delete(reverse("api:metadata-edit", kwargs={"pk": str(self.private_metadata.id)})) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_delete_metadata_readable_element(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_delete_metadata_readable_element(self, has_access_mock): """ An explicit message should be raised when user can read but not delete metadata """ self.client.force_login(self.user) - self.private_corpus.memberships.create(user=self.user, level=Role.Guest.value) - response = self.client.delete(reverse("api:metadata-edit", kwargs={"pk": str(self.private_metadata.id)})) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + with self.assertNumQueries(3): + response = self.client.delete(reverse("api:metadata-edit", kwargs={"pk": str(self.private_metadata.id)})) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertDictEqual(response.json(), {"detail": "You do not have write access to this corpus."}) + self.assertEqual(has_access_mock.call_count, 1) + self.assertEqual(has_access_mock.call_args, call(self.user, self.private_corpus, Role.Contributor.value, skip_public=False)) def test_admin_create_any(self): """ @@ -993,7 +999,7 @@ class TestMetaData(FixtureAPITestCase): def test_get_metadata(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.get(reverse("api:metadata-edit", kwargs={"pk": str(self.metadata.id)})) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -1019,7 +1025,7 @@ class TestMetaData(FixtureAPITestCase): self.metadata.save() self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.get(reverse("api:metadata-edit", kwargs={"pk": str(self.metadata.id)})) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -1043,7 +1049,7 @@ class TestMetaData(FixtureAPITestCase): self.metadata.save() self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.get(reverse("api:metadata-edit", kwargs={"pk": str(self.metadata.id)})) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -1305,21 +1311,27 @@ class TestMetaData(FixtureAPITestCase): response = method(reverse("api:element-metadata-bulk", kwargs={"pk": str(self.vol.id)})) self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED) - def test_bulk_create_metadata_writable_element(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_bulk_create_metadata_writable_element(self, filter_rights_mock): self.client.force_login(self.user) hidden_book = self.private_corpus.elements.create( type=self.corpus.types.get(slug="volume"), name="Nope" ) - response = self.client.post( - reverse("api:element-metadata-bulk", kwargs={"pk": str(hidden_book.id)}), - data={ - "worker_run_id": str(self.worker_run.id), - "metadata_list": [{"type": "text", "name": "language", "value": "Japanese"}], - }, - format="json" - ) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + with self.assertNumQueries(2): + response = self.client.post( + reverse("api:element-metadata-bulk", kwargs={"pk": str(hidden_book.id)}), + data={ + "worker_run_id": str(self.worker_run.id), + "metadata_list": [{"type": "text", "name": "language", "value": "Japanese"}], + }, + format="json" + ) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.assertEqual(filter_rights_mock.call_count, 1) + self.assertEqual(filter_rights_mock.call_args, call(self.user, Corpus, Role.Contributor.value)) def test_bulk_create_metadata_local(self): """ @@ -1328,7 +1340,7 @@ class TestMetaData(FixtureAPITestCase): entity = self.corpus.entities.create(name="42", type=self.number_type) self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(7): response = self.client.post( reverse("api:element-metadata-bulk", kwargs={"pk": str(self.vol.id)}), data={ @@ -1389,7 +1401,7 @@ class TestMetaData(FixtureAPITestCase): Worker run must be specified, worker version is forbidden """ self.client.force_login(self.user) - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.post( reverse("api:element-metadata-bulk", kwargs={"pk": str(self.vol.id)}), data={"metadata_list": [ @@ -1503,7 +1515,7 @@ class TestMetaData(FixtureAPITestCase): self.worker_run.process.run() task = self.worker_run.process.tasks.first() - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.post( reverse("api:element-metadata-bulk", kwargs={"pk": str(self.vol.id)}), data={ @@ -1519,7 +1531,7 @@ class TestMetaData(FixtureAPITestCase): def test_bulk_create_metadata_malicious_data(self): self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.post( reverse("api:element-metadata-bulk", kwargs={"pk": str(self.vol.id)}), data={"metadata_list": [{ @@ -1602,7 +1614,7 @@ class TestMetaData(FixtureAPITestCase): """ self.vol.metadatas.all().delete() process_worker_run = self.process.worker_runs.get() - with self.assertNumQueries(9): + with self.assertNumQueries(7): response = self.client.post( reverse("api:element-metadata-bulk", kwargs={"pk": str(self.vol.id)}), data={ @@ -1657,7 +1669,7 @@ class TestMetaData(FixtureAPITestCase): The metadata created with the bulk endpoint must be unique """ self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post( reverse("api:element-metadata-bulk", kwargs={"pk": str(self.vol.id)}), data={ @@ -1709,7 +1721,7 @@ class TestMetaData(FixtureAPITestCase): local_worker_run = local_process.worker_runs.get() self.vol.metadatas.all().delete() - with self.assertNumQueries(9): + with self.assertNumQueries(7): response = self.client.post( reverse("api:element-metadata-bulk", kwargs={"pk": str(self.vol.id)}), data={ diff --git a/arkindex/documents/tests/test_move_element.py b/arkindex/documents/tests/test_move_element.py index 9380575afc4e99fd5bed56ee94ad3e27c1d1b4a6..d667bd311f7b4f4ce5c6040a6c06e90f555f2340 100644 --- a/arkindex/documents/tests/test_move_element.py +++ b/arkindex/documents/tests/test_move_element.py @@ -29,14 +29,15 @@ class TestMoveElement(FixtureAPITestCase): response = self.client.post(reverse("api:move-element"), {"source": str(self.source.id), "destination": str(self.destination.id)}, format="json") self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_move_element_wrong_acl(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_move_element_wrong_acl(self, filter_rights_mock): private_corpus = Corpus.objects.create(name="private", public=False) private_element = private_corpus.elements.create( type=private_corpus.types.create(slug="folder"), ) self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(2): response = self.client.post(reverse("api:move-element"), {"source": str(private_element.id), "destination": str(private_element.id)}, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual( @@ -49,7 +50,7 @@ class TestMoveElement(FixtureAPITestCase): def test_move_element_wrong_source(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post(reverse("api:move-element"), {"source": "12341234-1234-1234-1234-123412341234", "destination": str(self.destination.id)}, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual( @@ -59,7 +60,7 @@ class TestMoveElement(FixtureAPITestCase): def test_move_element_wrong_destination(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post(reverse("api:move-element"), {"source": str(self.source.id), "destination": "12341234-1234-1234-1234-123412341234"}, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual( @@ -69,7 +70,7 @@ class TestMoveElement(FixtureAPITestCase): def test_move_element_same_source_destination(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post(reverse("api:move-element"), {"source": str(self.source.id), "destination": str(self.source.id)}, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual( @@ -83,7 +84,7 @@ class TestMoveElement(FixtureAPITestCase): destination = corpus2.elements.create(type=corpus2.types.create(slug="folder")) self.client.force_login(self.user) - with self.assertNumQueries(5): + with self.assertNumQueries(4): response = self.client.post(reverse("api:move-element"), {"source": str(self.source.id), "destination": str(destination.id)}, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual( @@ -95,7 +96,7 @@ class TestMoveElement(FixtureAPITestCase): destination = self.corpus.elements.get(name="Volume 1") self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.post(reverse("api:move-element"), {"source": str(self.source.id), "destination": str(destination.id)}, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual( @@ -108,7 +109,7 @@ class TestMoveElement(FixtureAPITestCase): destination_id = self.source.id self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.post(reverse("api:move-element"), {"source": str(source.id), "destination": str(destination_id)}, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual( @@ -119,7 +120,7 @@ class TestMoveElement(FixtureAPITestCase): @patch("arkindex.project.triggers.documents_tasks.move_element.delay") def test_move_element(self, delay_mock): self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.post(reverse("api:move-element"), {"source": str(self.source.id), "destination": str(self.destination.id)}, format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/arkindex/documents/tests/test_move_selection.py b/arkindex/documents/tests/test_move_selection.py index 956449c7d5b5b0a0d98b59c14a74a303840ddb5f..d524373efef234c5c5b7c9f82aa9f9d80698e86b 100644 --- a/arkindex/documents/tests/test_move_selection.py +++ b/arkindex/documents/tests/test_move_selection.py @@ -35,7 +35,7 @@ class TestMoveSelection(FixtureAPITestCase): @override_settings(ARKINDEX_FEATURES={"selection": False}) def test_move_selection_disabled(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post(reverse("api:move-selection"), {"corpus_id": str(self.corpus.id), "destination": str(self.destination.id)}, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual( @@ -43,14 +43,15 @@ class TestMoveSelection(FixtureAPITestCase): ) @override_settings(ARKINDEX_FEATURES={"selection": True}) - def test_move_selection_wrong_acl(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_move_selection_wrong_acl(self, filter_rights_mock): private_corpus = Corpus.objects.create(name="private", public=False) private_element = private_corpus.elements.create( type=private_corpus.types.create(slug="folder"), ) self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(2): response = self.client.post(reverse("api:move-selection"), {"corpus_id": str(private_corpus.id), "destination": str(private_element.id)}, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual( @@ -65,7 +66,7 @@ class TestMoveSelection(FixtureAPITestCase): def test_move_selection_wrong_destination(self): self.client.force_login(self.user) self.user.selected_elements.add(self.page) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post(reverse("api:move-selection"), {"corpus_id": str(self.corpus.id), "destination": "12341234-1234-1234-1234-123412341234"}, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual( @@ -77,7 +78,7 @@ class TestMoveSelection(FixtureAPITestCase): def test_move_selection_same_destination(self): self.client.force_login(self.user) self.user.selected_elements.add(self.page) - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.post(reverse("api:move-selection"), {"corpus_id": str(self.corpus.id), "destination": str(self.page.id)}, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual( @@ -92,7 +93,7 @@ class TestMoveSelection(FixtureAPITestCase): destination = corpus2.elements.create(type=corpus2.types.create(slug="folder")) self.client.force_login(self.user) self.user.selected_elements.add(self.page) - with self.assertNumQueries(6): + with self.assertNumQueries(5): response = self.client.post(reverse("api:move-selection"), {"corpus_id": str(self.corpus.id), "destination": str(destination.id)}, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual( @@ -105,7 +106,7 @@ class TestMoveSelection(FixtureAPITestCase): destination = self.corpus.elements.get(name="Volume 1") self.client.force_login(self.user) self.user.selected_elements.add(self.page) - with self.assertNumQueries(9): + with self.assertNumQueries(7): response = self.client.post(reverse("api:move-selection"), {"corpus_id": str(self.corpus.id), "destination": str(destination.id)}, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual( @@ -119,7 +120,7 @@ class TestMoveSelection(FixtureAPITestCase): destination_id = self.page.id self.client.force_login(self.user) self.user.selected_elements.add(target) - with self.assertNumQueries(10): + with self.assertNumQueries(8): response = self.client.post(reverse("api:move-selection"), {"corpus_id": str(self.corpus.id), "destination": str(destination_id)}, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual( @@ -134,7 +135,7 @@ class TestMoveSelection(FixtureAPITestCase): self.user.selected_elements.add(self.page) another_page = self.corpus.elements.get(name="Volume 1, page 1v") self.user.selected_elements.add(another_page) - with self.assertNumQueries(10): + with self.assertNumQueries(8): response = self.client.post(reverse("api:move-selection"), {"corpus_id": str(self.corpus.id), "destination": str(self.destination.id)}, format="json") self.assertEqual(response.status_code, status.HTTP_201_CREATED) diff --git a/arkindex/documents/tests/test_neighbors.py b/arkindex/documents/tests/test_neighbors.py index 0268555882a0450a597fdaf4192be40131a655d3..01340642c3d0ad698423baa4dcd030c8f387607c 100644 --- a/arkindex/documents/tests/test_neighbors.py +++ b/arkindex/documents/tests/test_neighbors.py @@ -1,3 +1,5 @@ +from unittest.mock import call, patch + from django.db import transaction from django.urls import reverse from rest_framework import status @@ -5,6 +7,7 @@ from rest_framework import status from arkindex.documents.models import Corpus, Element from arkindex.project.tests import FixtureAPITestCase from arkindex.project.tools import build_tree +from arkindex.users.models import Role class TestElementNeighbors(FixtureAPITestCase): @@ -15,7 +18,8 @@ class TestElementNeighbors(FixtureAPITestCase): cls.volume_type = cls.corpus.types.get(slug="volume") cls.page_type = cls.corpus.types.get(slug="page") - def test_element_neighbors_acl(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_element_neighbors_acl(self, filter_rights_mock): """ A Guest access is required to list neighbors of an element """ @@ -31,10 +35,13 @@ class TestElementNeighbors(FixtureAPITestCase): type=private_type, ) self.client.force_login(self.user) - with self.assertNumQueries(6): + + with self.assertNumQueries(2): response = self.client.get(reverse("api:elements-neighbors", kwargs={"pk": str(elements["A"].id)})) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertDictEqual(response.json(), {"detail": "You do not have a read access to this element."}) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.assertEqual(filter_rights_mock.call_count, 1) + self.assertEqual(filter_rights_mock.call_args, call(self.user, Corpus, Role.Guest.value)) def test_element_neighbors(self): r""" diff --git a/arkindex/documents/tests/test_patch_elements.py b/arkindex/documents/tests/test_patch_elements.py index be3da68ab6847c77592ab4eccf572ed4779b06e2..4a0bd1c6db80460c8a9535cc91d320c3e8c3676c 100644 --- a/arkindex/documents/tests/test_patch_elements.py +++ b/arkindex/documents/tests/test_patch_elements.py @@ -1,3 +1,5 @@ +from unittest.mock import call, patch + from django.urls import reverse from rest_framework import status @@ -55,7 +57,8 @@ class TestPatchElements(FixtureAPITestCase): {"detail": "You do not have permission to perform this action."} ) - def test_patch_no_write_access(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_patch_no_write_access(self, has_access_mock): # Create read_only corpus right self.private_corpus.memberships.create(user=self.user, level=Role.Guest.value) self.assertTrue(self.user.verified_email) @@ -71,14 +74,15 @@ class TestPatchElements(FixtureAPITestCase): {"detail": "You do not have write access to this element."} ) - def test_patch_element_no_read_access(self): + self.assertEqual(has_access_mock.call_count, 1) + self.assertEqual(has_access_mock.call_args, call(self.user, self.private_corpus, Role.Contributor.value, skip_public=False)) + + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_patch_element_no_read_access(self, filter_rights_mock): """ Check patching an element as anonymous user is not possible """ - ext_user = User.objects.create_user(email="ark@ark.net") - ext_user.verified_email = True - ext_user.save() - self.client.force_login(ext_user) + self.client.force_login(self.user) response = self.client.patch( reverse("api:element-retrieve", kwargs={"pk": str(self.private_elt.id)}), data={"name": "Untitled (2)"}, @@ -86,9 +90,12 @@ class TestPatchElements(FixtureAPITestCase): ) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertEqual(filter_rights_mock.call_count, 1) + self.assertEqual(filter_rights_mock.call_args, call(self.user, Corpus, Role.Guest.value)) + def test_patch_element(self): self.client.force_login(self.user) - with self.assertNumQueries(10): + with self.assertNumQueries(6): response = self.client.patch( reverse("api:element-retrieve", kwargs={"pk": str(self.vol.id)}), data={"name": "Untitled (2)"}, diff --git a/arkindex/documents/tests/test_put_elements.py b/arkindex/documents/tests/test_put_elements.py index 1fac8693c47cdd6eb94b77e6769b017663560ebe..c4d42407cb0e2bac4410f7ece70b4aaadabe85a3 100644 --- a/arkindex/documents/tests/test_put_elements.py +++ b/arkindex/documents/tests/test_put_elements.py @@ -1,3 +1,5 @@ +from unittest.mock import call, patch + from django.urls import reverse from rest_framework import status @@ -9,7 +11,7 @@ from arkindex.project.tests import FixtureAPITestCase from arkindex.users.models import Role, User -class TestPatchElements(FixtureAPITestCase): +class TestPutElements(FixtureAPITestCase): @classmethod def setUpTestData(cls): @@ -48,9 +50,8 @@ class TestPatchElements(FixtureAPITestCase): {"detail": "You do not have permission to perform this action."} ) - def test_put_no_write_access(self): - # Create read_only corpus right - self.private_corpus.memberships.create(user=self.user, level=Role.Guest.value) + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_put_no_write_access(self, has_access_mock): self.assertTrue(self.user.verified_email) self.client.force_login(self.user) response = self.client.put( @@ -63,15 +64,15 @@ class TestPatchElements(FixtureAPITestCase): response.json(), {"detail": "You do not have write access to this element."} ) + self.assertEqual(has_access_mock.call_count, 1) + self.assertEqual(has_access_mock.call_args, call(self.user, self.private_corpus, Role.Contributor.value, skip_public=False)) - def test_put_element_no_read_access(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_put_element_no_read_access(self, filter_rights_mock): """ Check putting an element as anonymous user is not possible """ - ext_user = User.objects.create_user(email="ark@ark.net") - ext_user.verified_email = True - ext_user.save() - self.client.force_login(ext_user) + self.client.force_login(self.user) response = self.client.put( reverse("api:element-retrieve", kwargs={"pk": str(self.private_elt.id)}), data={"name": "Untitled (2)"}, @@ -79,11 +80,14 @@ class TestPatchElements(FixtureAPITestCase): ) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertEqual(filter_rights_mock.call_count, 1) + self.assertEqual(filter_rights_mock.call_args, call(self.user, Corpus, Role.Guest.value)) + def test_put_element(self): self.client.force_login(self.user) self.assertEqual(self.vol.name, "Volume 1") self.assertEqual(self.vol.type, self.volume_type) - with self.assertNumQueries(11): + with self.assertNumQueries(7): response = self.client.put( reverse("api:element-retrieve", kwargs={"pk": str(self.vol.id)}), data={"name": "Untitled (2)", "type": "text_line"}, diff --git a/arkindex/documents/tests/test_retrieve_elements.py b/arkindex/documents/tests/test_retrieve_elements.py index 326d1134f21fd334c3e1fcc2a55967775527a91f..c8062471c5bc065bc50e7bf26612a6254e1e7fa9 100644 --- a/arkindex/documents/tests/test_retrieve_elements.py +++ b/arkindex/documents/tests/test_retrieve_elements.py @@ -1,3 +1,5 @@ +from unittest.mock import patch + from django.test import override_settings from django.urls import reverse from rest_framework import status @@ -49,7 +51,7 @@ class TestRetrieveElements(FixtureAPITestCase): "mirrored": False, "created": "2020-02-02T01:23:45.678000Z", "creator": None, - "rights": ["read"], + "rights": ["read", "write", "admin"], "metadata_count": 0, "classifications": [ { @@ -175,7 +177,8 @@ class TestRetrieveElements(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.json()["creator"], "Test user") - def test_get_element_rights(self): + @patch("arkindex.documents.serializers.elements.get_max_level") + def test_get_element_rights(self, get_max_level_mock): cases = [ (Role.Guest, self.superuser, ["read"]), (Role.Guest, self.user, ["read"]), @@ -187,12 +190,15 @@ class TestRetrieveElements(FixtureAPITestCase): self.client.force_login(self.user) for role, creator, expected_rights in cases: with self.subTest(role=role, creator=creator): + get_max_level_mock.return_value = role.value self.corpus.memberships.filter(user=self.user).update(level=role.value) self.vol.creator = creator self.vol.save() - with self.assertNumQueries(6): + + with self.assertNumQueries(4): response = self.client.get(reverse("api:element-retrieve", kwargs={"pk": str(self.vol.id)})) self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertListEqual(response.json()["rights"], expected_rights) def test_get_element_created_by_worker_run(self): @@ -222,7 +228,7 @@ class TestRetrieveElements(FixtureAPITestCase): "mirrored": False, "created": "2020-02-02T01:23:45.678000Z", "creator": None, - "rights": ["read"], + "rights": ["read", "write", "admin"], "metadata_count": 0, "classifications": [], "worker_run": { @@ -257,7 +263,7 @@ class TestRetrieveElements(FixtureAPITestCase): "mirrored": False, "created": "2020-02-02T01:23:45.678000Z", "creator": None, - "rights": ["read"], + "rights": ["read", "write", "admin"], "metadata_count": 0, "classifications": [ { diff --git a/arkindex/documents/tests/test_search_api.py b/arkindex/documents/tests/test_search_api.py index 53ac2d74994ffb02e291aacf23902485c072516b..2fe56cd2fe22fbf09f8c694c1d450e84f8da4ab7 100644 --- a/arkindex/documents/tests/test_search_api.py +++ b/arkindex/documents/tests/test_search_api.py @@ -1,5 +1,6 @@ from unittest.mock import call, patch +from django.contrib.auth.models import AnonymousUser from django.test import override_settings from django.urls import reverse from rest_framework import status @@ -70,10 +71,13 @@ class TestSearchApi(FixtureAPITestCase): self.assertEqual(response.json(), {"detail": "Not found."}) @override_settings(ARKINDEX_FEATURES={"search": True}) - def test_corpus_no_permission(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_corpus_no_permission(self, has_access_mock): private_corpus = Corpus.objects.create(name="private", public=False) response = self.client.get(reverse("api:corpus-search", kwargs={"pk": private_corpus.id})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(has_access_mock.call_count, 1) + self.assertEqual(has_access_mock.call_args, call(AnonymousUser(), private_corpus, Role.Guest.value, skip_public=False)) @override_settings(ARKINDEX_FEATURES={"search": True}) def test_corpus_not_indexable(self): @@ -331,13 +335,17 @@ class TestSearchApi(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) @override_settings(ARKINDEX_FEATURES={"search": True}) - def test_build_index_requires_corpus_admin(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_build_index_requires_corpus_admin(self, has_access_mock): self.corpus.memberships.update(level=Role.Contributor.value) self.client.force_login(self.user) - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.post(reverse("api:build-search-index", kwargs={"pk": self.corpus.id})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(has_access_mock.call_count, 1) + self.assertEqual(has_access_mock.call_args, call(self.user, self.corpus, Role.Admin.value, skip_public=False)) + @override_settings(ARKINDEX_FEATURES={"search": False}) @patch("arkindex.documents.tasks.reindex_corpus.delay") def test_build_index_requires_search_feature(self, reindex_trigger_mock): @@ -358,7 +366,7 @@ class TestSearchApi(FixtureAPITestCase): existing_job.ended_at = None job_mock.fetch.return_value = existing_job self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post(reverse("api:build-search-index", kwargs={"pk": self.corpus.id})) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), { @@ -373,7 +381,7 @@ class TestSearchApi(FixtureAPITestCase): self.corpus.indexable = False self.corpus.save() self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post(reverse("api:build-search-index", kwargs={"pk": self.corpus.id})) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), { @@ -387,7 +395,7 @@ class TestSearchApi(FixtureAPITestCase): def test_build_index_non_indexable_element_types(self, job_mock, reindex_trigger_mock): self.corpus.types.update(indexable=False) self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post(reverse("api:build-search-index", kwargs={"pk": self.corpus.id})) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), { @@ -404,7 +412,7 @@ class TestSearchApi(FixtureAPITestCase): existing_job.ended_at = "2000-01-01" job_mock.fetch.return_value = existing_job self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post(reverse("api:build-search-index", kwargs={"pk": self.corpus.id})) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertDictEqual(response.json(), {"drop": True}) @@ -424,7 +432,7 @@ class TestSearchApi(FixtureAPITestCase): def test_build_index(self, job_mock, reindex_trigger_mock): job_mock.fetch.side_effect = NoSuchJobError self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:build-search-index", kwargs={"pk": self.corpus.id}), data={"drop": True}, @@ -445,7 +453,7 @@ class TestSearchApi(FixtureAPITestCase): def test_build_index_drop_false(self, job_mock, reindex_trigger_mock): job_mock.fetch.side_effect = NoSuchJobError self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:build-search-index", kwargs={"pk": self.corpus.id}), data={"drop": False}, diff --git a/arkindex/documents/tests/test_selection_api.py b/arkindex/documents/tests/test_selection_api.py index d45eb8e8473d68dde488d78d4b04670486744a73..36970acf11db753ba11684eea97bf8862c913f72 100644 --- a/arkindex/documents/tests/test_selection_api.py +++ b/arkindex/documents/tests/test_selection_api.py @@ -1,5 +1,5 @@ - import uuid +from unittest.mock import call, patch from django.test import override_settings from django.urls import reverse @@ -10,7 +10,7 @@ from arkindex.project.tests import FixtureAPITestCase from arkindex.users.models import Role, User -class TestElementsAPI(FixtureAPITestCase): +class TestSelectionAPI(FixtureAPITestCase): @classmethod def setUpTestData(cls): @@ -55,7 +55,8 @@ class TestElementsAPI(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertListEqual(response.json(), ["Selection is not available on this instance."]) - def test_select_element_wrong_corpus(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_select_element_wrong_corpus(self, filter_rights_mock): user = User.objects.create_user("nope@nope.fr") user.verified_email = True user.save() @@ -74,6 +75,8 @@ class TestElementsAPI(FixtureAPITestCase): self.assertDictEqual(response.json(), { "ids": ["Some element IDs do not exist or you do not have permission to access them."], }) + self.assertEqual(filter_rights_mock.call_count, 1) + self.assertEqual(filter_rights_mock.call_args, call(user, Corpus, Role.Guest.value)) def test_select_element_wrong_id(self): self.client.force_login(self.user) @@ -231,7 +234,7 @@ class TestElementsAPI(FixtureAPITestCase): self.user.selected_elements.add(self.page, self.vol, self.private_page) self.client.force_login(self.user) - with self.assertNumQueries(5): + with self.assertNumQueries(4): response = self.client.delete(reverse("api:elements-selection"), data={"corpus": self.private_corpus.id}) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) response = self.client.get(reverse("api:elements-selection")) @@ -246,7 +249,7 @@ class TestElementsAPI(FixtureAPITestCase): self.user.selected_elements.add(self.page, self.vol) self.client.force_login(self.user) - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.delete(reverse("api:elements-selection"), data={"corpus": bad_id}) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.assertEqual(response.json(), {"detail": "Not found."}) @@ -345,7 +348,7 @@ class TestElementsAPI(FixtureAPITestCase): self.user.selected_elements.add(self.page, self.vol, self.private_page) self.private_corpus.memberships.create(user=self.user, level=Role.Contributor.value) self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(5): response = self.client.get(reverse("api:elements-selection"), data={"corpus": self.private_corpus.id}) self.assertEqual(response.status_code, status.HTTP_200_OK) results = response.json()["results"] @@ -356,7 +359,7 @@ class TestElementsAPI(FixtureAPITestCase): self.user.selected_elements.add(self.page, self.vol) bad_id = uuid.uuid4() self.client.force_login(self.user) - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.get(reverse("api:elements-selection"), data={"corpus": bad_id}) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.assertEqual(response.json(), {"detail": "Not found."}) diff --git a/arkindex/documents/tests/test_transcriptions.py b/arkindex/documents/tests/test_transcriptions.py index cda2b19db5803181f687ecb611f1867c32064d5e..618f9aa9eee3ba2405e0e68be69c60212be4d232 100644 --- a/arkindex/documents/tests/test_transcriptions.py +++ b/arkindex/documents/tests/test_transcriptions.py @@ -1,10 +1,12 @@ +from unittest.mock import call, patch + from django.urls import reverse from rest_framework import status from arkindex.documents.models import Corpus, TextOrientation from arkindex.process.models import ProcessMode, WorkerRun, WorkerVersion from arkindex.project.tests import FixtureAPITestCase -from arkindex.users.models import User +from arkindex.users.models import Role, User class TestTranscriptions(FixtureAPITestCase): @@ -28,13 +30,17 @@ class TestTranscriptions(FixtureAPITestCase): cls.worker_version_2 = WorkerVersion.objects.get(worker__slug="dla") cls.worker_run = WorkerRun.objects.filter(version=cls.worker_version_1, process__mode=ProcessMode.Workers).get() - def test_list_transcriptions_read_right(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_list_transcriptions_read_right(self, filter_rights_mock): # A read right on the element corpus is required to access transcriptions self.client.force_login(self.private_read_user) - url = reverse("api:element-transcriptions", kwargs={"pk": str(self.private_page.id)}) - response = self.client.get(url) + + response = self.client.get(reverse("api:element-transcriptions", kwargs={"pk": str(self.private_page.id)})) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertEqual(filter_rights_mock.call_count, 1) + self.assertEqual(filter_rights_mock.call_args, call(self.private_read_user, Corpus, Role.Guest.value)) + def test_list_element_transcriptions(self): tr1 = self.page.transcriptions.get() tr2 = self.page.transcriptions.create( @@ -44,7 +50,7 @@ class TestTranscriptions(FixtureAPITestCase): ) self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.get(reverse("api:element-transcriptions", kwargs={"pk": str(self.page.id)})) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -72,7 +78,7 @@ class TestTranscriptions(FixtureAPITestCase): def test_list_transcriptions_recursive(self): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.get( reverse("api:element-transcriptions", kwargs={"pk": str(self.page.id)}), data={"recursive": "true"} @@ -94,7 +100,7 @@ class TestTranscriptions(FixtureAPITestCase): def test_list_transcriptions_recursive_filter_element_type(self): self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.get( reverse("api:element-transcriptions", kwargs={"pk": str(self.page.id)}), data={"recursive": "true", "element_type": "page"} @@ -119,7 +125,7 @@ class TestTranscriptions(FixtureAPITestCase): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.get( reverse("api:element-transcriptions", kwargs={"pk": str(self.page.id)}), data={"recursive": "true", "worker_version": str(self.worker_version_2.id)} @@ -156,7 +162,7 @@ class TestTranscriptions(FixtureAPITestCase): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.get( reverse("api:element-transcriptions", kwargs={"pk": str(self.page.id)}), data={"recursive": "true", "worker_version": False} @@ -187,7 +193,7 @@ class TestTranscriptions(FixtureAPITestCase): def test_list_transcriptions_worker_version_validation(self): self.client.force_login(self.user) - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.get( reverse("api:element-transcriptions", kwargs={"pk": str(self.page.id)}), data={"recursive": "true", "worker_version": "oh no"} @@ -197,7 +203,7 @@ class TestTranscriptions(FixtureAPITestCase): def test_list_transcriptions_worker_run_validation(self): self.client.force_login(self.user) - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.get( reverse("api:element-transcriptions", kwargs={"pk": str(self.page.id)}), data={"recursive": "true", "worker_run": "oh no"} @@ -215,7 +221,7 @@ class TestTranscriptions(FixtureAPITestCase): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.get( reverse("api:element-transcriptions", kwargs={"pk": str(self.page.id)}), data={"recursive": "true", "worker_version": str(self.worker_version_2.id)} @@ -257,7 +263,7 @@ class TestTranscriptions(FixtureAPITestCase): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.get( reverse("api:element-transcriptions", kwargs={"pk": str(self.page.id)}), data={"recursive": "true", "worker_run": str(self.worker_run.id)} @@ -299,7 +305,7 @@ class TestTranscriptions(FixtureAPITestCase): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.get( reverse("api:element-transcriptions", kwargs={"pk": str(self.page.id)}), data={ diff --git a/arkindex/images/tests/test_image_elements.py b/arkindex/images/tests/test_image_elements.py index 86459ccc93b680e53f72eefe3f38fdf6359b9f68..2f1cb8a2a2eb9401b491e109155a64737354a117 100644 --- a/arkindex/images/tests/test_image_elements.py +++ b/arkindex/images/tests/test_image_elements.py @@ -1,3 +1,5 @@ +from unittest.mock import patch + from django.urls import reverse from rest_framework import status @@ -14,7 +16,7 @@ class TestImageElements(FixtureTestCase): def test_image_elements(self): self.client.force_login(self.user) - with self.assertNumQueries(10): + with self.assertNumQueries(8): response = self.client.get(reverse("api:image-elements", kwargs={"pk": self.img1.id})) self.assertEqual(response.status_code, status.HTTP_200_OK) data = response.json() @@ -28,7 +30,8 @@ class TestImageElements(FixtureTestCase): response = self.client.get(reverse("api:image-elements", kwargs={"pk": self.img1.id})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_image_elements_acl(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights") + def test_image_elements_acl(self, filter_rights_mock): """ A user cannot list elements on corpus they have no guest access """ @@ -41,9 +44,12 @@ class TestImageElements(FixtureTestCase): image=self.img1, polygon=[(100, 100), (142, 142), (133, 337), (100, 100)] ) - with self.assertNumQueries(10): + filter_rights_mock.return_value = Corpus.objects.filter(id=self.corpus.id) + + with self.assertNumQueries(8): response = self.client.get(reverse("api:image-elements", kwargs={"pk": self.img1.id})) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, status.HTTP_200_OK) + results = response.json()["results"] self.assertEqual(len(results), 7) response_names = [e["name"] for e in results] @@ -61,7 +67,7 @@ class TestImageElements(FixtureTestCase): polygon=[(0, 0), (42, 42), (13, 37), (0, 0)], ) - with self.assertNumQueries(10): + with self.assertNumQueries(8): response = self.client.get(reverse("api:image-elements", kwargs={"pk": self.img1.id}) + "?type=cake") self.assertEqual(response.status_code, status.HTTP_200_OK) data = response.json() @@ -79,7 +85,7 @@ class TestImageElements(FixtureTestCase): vol.polygon = [(0, 0), (0, 1), (1, 1), (0, 0)] vol.save() - with self.assertNumQueries(10): + with self.assertNumQueries(8): response = self.client.get(reverse("api:image-elements", kwargs={"pk": self.img1.id}) + "?folder") self.assertEqual(response.status_code, status.HTTP_200_OK) data = response.json() @@ -103,7 +109,7 @@ class TestImageElements(FixtureTestCase): polygon=[(i, i), (i, 200), (200, 200), (200, i), (i, i)] ) for i in range(40) ]) - with self.assertNumQueries(10): + with self.assertNumQueries(8): response = self.client.get(reverse("api:image-elements", kwargs={"pk": self.img1.id}), {"type": "duplicated"}) self.assertEqual(response.status_code, status.HTTP_200_OK) data = response.json() diff --git a/arkindex/ponos/admin.py b/arkindex/ponos/admin.py index 474b809baa905d2b332c08a11d03ba235aeb15cf..714c26b87216e70bf020872c0a2ac64e1c71089c 100644 --- a/arkindex/ponos/admin.py +++ b/arkindex/ponos/admin.py @@ -5,7 +5,6 @@ from enumfields.admin import EnumFieldListFilter from arkindex.ponos.keys import gen_nonce from arkindex.ponos.models import FINAL_STATES, GPU, Agent, Artifact, Farm, Secret, Task, encrypt -from arkindex.users.admin import GroupMembershipInline, UserMembershipInline class ArtifactInline(admin.TabularInline): @@ -179,7 +178,6 @@ class FarmAdmin(admin.ModelAdmin): list_display = ("id", "name") fields = ("id", "name", "seed") readonly_fields = ("id",) - inlines = [UserMembershipInline, GroupMembershipInline] class ClearTextSecretForm(forms.ModelForm): diff --git a/arkindex/ponos/tests/test_api.py b/arkindex/ponos/tests/test_api.py index a416be308fb2226956345c1949b09d7e852096fd..e257c88dba1fe745f3f07dfbb11c70af6b9be14c 100644 --- a/arkindex/ponos/tests/test_api.py +++ b/arkindex/ponos/tests/test_api.py @@ -5,6 +5,7 @@ import uuid from io import BytesIO from pathlib import Path from textwrap import dedent +from unittest import expectedFailure from unittest.mock import PropertyMock, call, patch, seal from botocore.exceptions import ClientError @@ -164,6 +165,7 @@ class TestAPI(FixtureAPITestCase): resp = self.client.get(reverse("api:task-details", args=[self.task1.id])) self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) + @expectedFailure def test_task_details_requires_process_guest(self): self.process.creator = self.superuser self.process.save() @@ -195,7 +197,7 @@ class TestAPI(FixtureAPITestCase): with self.subTest(role=role): self.corpus.memberships.filter(user=self.user).update(level=role.value) - with self.assertNumQueries(6): + with self.assertNumQueries(4): resp = self.client.get(reverse("api:task-details", args=[self.task1.id])) self.assertEqual(resp.status_code, status.HTTP_200_OK) @@ -249,7 +251,7 @@ class TestAPI(FixtureAPITestCase): membership.level = role.value membership.save() - with self.assertNumQueries(7): + with self.assertNumQueries(4): resp = self.client.get(reverse("api:task-details", args=[self.task1.id])) self.assertEqual(resp.status_code, status.HTTP_200_OK) @@ -1148,6 +1150,7 @@ class TestAPI(FixtureAPITestCase): ) self.assertEqual(resp.status_code, status.HTTP_401_UNAUTHORIZED) + @expectedFailure def test_update_task_requires_process_admin_corpus(self): self.process.creator = self.superuser self.process.save() @@ -1168,6 +1171,7 @@ class TestAPI(FixtureAPITestCase): ) self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) + @expectedFailure def test_update_task_requires_process_admin_repo(self): self.process.mode = ProcessMode.Repository self.process.corpus = None @@ -1426,6 +1430,7 @@ class TestAPI(FixtureAPITestCase): ) self.assertEqual(resp.status_code, status.HTTP_401_UNAUTHORIZED) + @expectedFailure def test_partial_update_task_requires_process_admin_corpus(self): self.process.creator = self.superuser self.process.save() @@ -1446,6 +1451,7 @@ class TestAPI(FixtureAPITestCase): ) self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) + @expectedFailure def test_partial_update_task_requires_process_admin_repo(self): self.process.mode = ProcessMode.Repository self.process.corpus = None @@ -2668,6 +2674,7 @@ class TestAPI(FixtureAPITestCase): }, ) + @expectedFailure def test_list_farms_guest(self): self.client.force_login(self.user) self.assertFalse(self.default_farm.is_available(self.user)) diff --git a/arkindex/ponos/tests/test_artifacts_api.py b/arkindex/ponos/tests/test_artifacts_api.py index 76cf4e6659a0a373d6bc669aefbdcd5faeacb960..4ace04665fe0131d6107a70e224cc0ed96c6ba19 100644 --- a/arkindex/ponos/tests/test_artifacts_api.py +++ b/arkindex/ponos/tests/test_artifacts_api.py @@ -1,5 +1,6 @@ import hashlib import uuid +from unittest import expectedFailure from django.conf import settings from django.test import override_settings @@ -87,6 +88,7 @@ class TestAPI(FixtureAPITestCase): response = self.client.get(reverse("api:task-artifacts", args=[self.task1.id])) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + @expectedFailure def test_list_requires_process_guest(self): self.process.creator = self.superuser self.process.save() @@ -108,7 +110,7 @@ class TestAPI(FixtureAPITestCase): with self.subTest(role=role): self.corpus.memberships.filter(user=self.user).update(level=role.value) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.get(reverse("api:task-artifacts", args=[self.task1.id])) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -145,7 +147,7 @@ class TestAPI(FixtureAPITestCase): membership.level = role.value membership.save() - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.get(reverse("api:task-artifacts", args=[self.task1.id])) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -244,7 +246,7 @@ class TestAPI(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) def test_list_other_task_with_access(self): - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.get( reverse("api:task-artifacts", args=[self.task1.id]), HTTP_AUTHORIZATION=f"Ponos {self.task2.token}", @@ -426,7 +428,7 @@ class TestAPI(FixtureAPITestCase): self.assertEqual(response.json(), {"size": [expected_error]}) def test_create_other_task(self): - with self.assertNumQueries(5): + with self.assertNumQueries(2): response = self.client.post( reverse("api:task-artifacts", args=[self.task1.id]), data={ @@ -491,6 +493,7 @@ class TestAPI(FixtureAPITestCase): ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + @expectedFailure def test_download_requires_process_guest(self): self.corpus.memberships.filter(user=self.user).delete() self.corpus.public = False @@ -510,7 +513,7 @@ class TestAPI(FixtureAPITestCase): with self.subTest(role=role): self.corpus.memberships.filter(user=self.user).update(level=role.value) - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.get( reverse("api:task-artifact-download", args=[self.task1.id, "path/to/file.json"]), ) @@ -535,7 +538,7 @@ class TestAPI(FixtureAPITestCase): membership.level = role.value membership.save() - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.get( reverse("api:task-artifact-download", args=[task.id, "path/to/file.json"]), ) @@ -590,7 +593,7 @@ class TestAPI(FixtureAPITestCase): ) def test_download_other_task_with_access(self): - with self.assertNumQueries(5): + with self.assertNumQueries(2): response = self.client.get( reverse("api:task-artifact-download", args=[self.task1.id, "path/to/file.json"]), HTTP_AUTHORIZATION=f"Ponos {self.task2.token}", diff --git a/arkindex/process/admin.py b/arkindex/process/admin.py index f02159a5c134800de106993b78dffeedfdfc4de3..3349b5f2704fd950fa7735b47f637fc139eb93ef 100644 --- a/arkindex/process/admin.py +++ b/arkindex/process/admin.py @@ -13,7 +13,6 @@ from arkindex.process.models import ( WorkerVersion, ) from arkindex.project.admin import ArchivedListFilter -from arkindex.users.admin import GroupMembershipInline, UserMembershipInline class DataFileInline(admin.StackedInline): @@ -78,7 +77,7 @@ class RepositoryAdmin(admin.ModelAdmin): list_display = ("id", "url") fields = ("id", "url") readonly_fields = ("id", ) - inlines = [WorkerInline, UserMembershipInline, GroupMembershipInline] + inlines = [WorkerInline, ] class WorkerVersionInline(admin.StackedInline): @@ -106,7 +105,7 @@ class WorkerAdmin(admin.ModelAdmin): list_filter = (ArchivedListFilter, ) fields = ("id", "name", "slug", "type", "description", "repository", "public", "archived") readonly_fields = ("id", ) - inlines = [WorkerVersionInline, UserMembershipInline, GroupMembershipInline, WorkerConfigurationInline] + inlines = [WorkerVersionInline, WorkerConfigurationInline] def get_queryset(self, *args, **kwargs): return super().get_queryset(*args, **kwargs).select_related("repository", "type") diff --git a/arkindex/process/api.py b/arkindex/process/api.py index 26001aaab9e928cf9e0296b8b42d3d6fe4be5106..5c3978eaf0220610905137998bc007c65dd9df26 100644 --- a/arkindex/process/api.py +++ b/arkindex/process/api.py @@ -1061,7 +1061,7 @@ class CorpusWorkerVersionList(CorpusACLMixin, ListAPIView): @cached_property def corpus(self): - return get_object_or_404(self.readable_corpora, pk=self.kwargs["pk"]) + return get_object_or_404(Corpus.objects.readable(self.request.user), pk=self.kwargs["pk"]) def get_queryset(self): return ( @@ -1092,7 +1092,7 @@ class CorpusWorkerVersionList(CorpusACLMixin, ListAPIView): tags=["ml"], ) ) -class CorpusWorkerRunList(CorpusACLMixin, ListAPIView): +class CorpusWorkerRunList(ListAPIView): """ List worker runs used by any ML result in a given corpus. @@ -1111,7 +1111,7 @@ class CorpusWorkerRunList(CorpusACLMixin, ListAPIView): @cached_property def corpus(self): - return get_object_or_404(self.readable_corpora, pk=self.kwargs["pk"]) + return get_object_or_404(Corpus.objects.readable(self.request.user), pk=self.kwargs["pk"]) def get_queryset(self): return ( @@ -1130,7 +1130,7 @@ class CorpusWorkerRunList(CorpusACLMixin, ListAPIView): ), "process__tasks", ) - .filter(Q(process__corpus_id=self.corpus.id) | Q(process__creator_id=self.user.id, process__mode=ProcessMode.Local), has_results=True) + .filter(Q(process__corpus_id=self.corpus.id) | Q(process__creator_id=self.request.user.id, process__mode=ProcessMode.Local), has_results=True) .annotate(process_element_count=Count("process__elements")) .order_by("summary") ) diff --git a/arkindex/process/tests/test_corpus_worker_runs.py b/arkindex/process/tests/test_corpus_worker_runs.py index 92aebf153a842179e6650a2b72a7c0d94cd63f2e..2e62a89f37514e987717854ae844b7cc906307eb 100644 --- a/arkindex/process/tests/test_corpus_worker_runs.py +++ b/arkindex/process/tests/test_corpus_worker_runs.py @@ -1,3 +1,5 @@ +from unittest.mock import call, patch + from django.urls import reverse from rest_framework import status @@ -80,16 +82,19 @@ class TestCorpusWorkerRuns(FixtureAPITestCase): cls.user_local_run.has_results = True cls.user_local_run.save() - def test_list_requires_read_access(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_list_requires_read_access(self, filter_rights_mock): self.client.force_login(self.user) - with self.assertNumQueries(5): + with self.assertNumQueries(2): response = self.client.get(reverse("api:corpus-runs", kwargs={"pk": self.private_corpus.id})) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertEqual(filter_rights_mock.call_count, 1) + self.assertEqual(filter_rights_mock.call_args, call(self.user, Corpus, Role.Guest.value)) def test_list(self): self.private_corpus.memberships.create(user=self.user, level=Role.Guest.value) self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(7): response = self.client.get(reverse("api:corpus-runs", kwargs={"pk": self.private_corpus.id})) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.json()["results"], [ diff --git a/arkindex/process/tests/test_create_process.py b/arkindex/process/tests/test_create_process.py index 79b0e98ba483fb972c6e4a4073b3122619284f73..327d2341edf4837092d62da2a4bd7bb1a2b4e4a0 100644 --- a/arkindex/process/tests/test_create_process.py +++ b/arkindex/process/tests/test_create_process.py @@ -180,7 +180,8 @@ class TestCreateProcess(FixtureAPITestCase): {"corpus": ["This field is required."]} ) - def test_create_process_requires_corpus_admin(self): + @patch("arkindex.process.serializers.imports.get_max_level", return_value=Role.Contributor.value) + def test_create_process_requires_corpus_admin(self, get_max_level_mock): """ Only an admin of the target corpus can create a workers process """ @@ -197,7 +198,11 @@ class TestCreateProcess(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), {"corpus": ["You do not have an admin access to this corpus."]}) - def test_create_process_non_readable_corpus(self): + self.assertEqual(get_max_level_mock.call_count, 1) + self.assertEqual(get_max_level_mock.call_args, call(self.user, self.corpus)) + + @patch("arkindex.process.serializers.imports.get_max_level", return_value=None) + def test_create_process_non_readable_corpus(self, get_max_level_mock): self.client.force_login(self.user) response = self.client.post( reverse("api:corpus-process"), @@ -210,6 +215,9 @@ class TestCreateProcess(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), {"corpus": ["Corpus with this ID does not exist."]}) + self.assertEqual(get_max_level_mock.call_count, 1) + self.assertEqual(get_max_level_mock.call_args, call(self.user, self.private_corpus)) + def test_create_process_name_filter(self): self.client.force_login(self.user) response = self.client.post( @@ -336,7 +344,7 @@ class TestCreateProcess(FixtureAPITestCase): def test_ml_class_filter(self): self.client.force_login(self.user) - with self.assertNumQueries(12): + with self.assertNumQueries(9): response = self.client.post( reverse("api:corpus-process"), { @@ -375,7 +383,7 @@ class TestCreateProcess(FixtureAPITestCase): """ self.client.force_login(self.user) element = self.corpus.elements.create(type=self.pages.first().type, name="Kill me please") - with self.assertNumQueries(13): + with self.assertNumQueries(10): response = self.client.post( reverse("api:corpus-process"), { @@ -576,7 +584,7 @@ class TestCreateProcess(FixtureAPITestCase): self.assertFalse(self.corpus.worker_versions.exists()) self.client.force_login(self.user) - with self.assertNumQueries(20): + with self.assertNumQueries(15): response = self.client.post( reverse("api:process-start", kwargs={"pk": str(process_2.id)}), {"worker_activity": True}, @@ -667,7 +675,7 @@ class TestCreateProcess(FixtureAPITestCase): ) self.client.force_login(self.user) - with self.assertNumQueries(20): + with self.assertNumQueries(15): response = self.client.post( reverse("api:process-start", kwargs={"pk": str(process_2.id)}), {"use_cache": True}, @@ -715,7 +723,7 @@ class TestCreateProcess(FixtureAPITestCase): ) self.client.force_login(self.user) - with self.assertNumQueries(20): + with self.assertNumQueries(15): response = self.client.post( reverse("api:process-start", kwargs={"pk": str(process_2.id)}), {"use_gpu": True}, @@ -786,7 +794,7 @@ class TestCreateProcess(FixtureAPITestCase): process.use_gpu = True process.save() self.client.force_login(self.user) - with self.assertNumQueries(11): + with self.assertNumQueries(6): response = self.client.post( reverse("api:process-start", kwargs={"pk": str(process.id)}), {"use_gpu": "true"} @@ -810,7 +818,7 @@ class TestCreateProcess(FixtureAPITestCase): process.use_gpu = True process.save() self.client.force_login(self.user) - with self.assertNumQueries(20): + with self.assertNumQueries(15): response = self.client.post( reverse("api:process-start", kwargs={"pk": str(process.id)}), {"use_gpu": "true"} @@ -829,7 +837,7 @@ class TestCreateProcess(FixtureAPITestCase): process.datasets.add(self.corpus.datasets.first()) process.versions.set([self.version_2, self.version_3]) - with self.assertNumQueries(15): + with self.assertNumQueries(10): response = self.client.post(reverse("api:process-start", kwargs={"pk": str(process.id)})) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) @@ -842,7 +850,7 @@ class TestCreateProcess(FixtureAPITestCase): process = self.corpus.processes.create(creator=self.user, mode=ProcessMode.Workers) process.versions.set([self.version_2, self.version_3]) - with self.assertNumQueries(15): + with self.assertNumQueries(10): response = self.client.post(reverse("api:process-start", kwargs={"pk": str(process.id)})) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) @@ -859,7 +867,7 @@ class TestCreateProcess(FixtureAPITestCase): process.datasets.add(self.corpus.datasets.first()) process.versions.add(self.version_1) - with self.assertNumQueries(15): + with self.assertNumQueries(10): response = self.client.post(reverse("api:process-start", kwargs={"pk": str(process.id)})) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) @@ -875,7 +883,7 @@ class TestCreateProcess(FixtureAPITestCase): process = self.corpus.processes.create(creator=self.user, mode=ProcessMode.Workers) process.versions.add(self.version_1) - with self.assertNumQueries(15): + with self.assertNumQueries(10): response = self.client.post(reverse("api:process-start", kwargs={"pk": str(process.id)})) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) @@ -897,7 +905,7 @@ class TestCreateProcess(FixtureAPITestCase): process = self.corpus.processes.create(creator=self.user, mode=ProcessMode.Workers) process.versions.add(custom_version) - with self.assertNumQueries(20): + with self.assertNumQueries(15): response = self.client.post(reverse("api:process-start", kwargs={"pk": str(process.id)})) self.assertEqual(response.status_code, status.HTTP_201_CREATED) @@ -934,7 +942,7 @@ class TestCreateProcess(FixtureAPITestCase): } if mode: data["mode"] = mode - with (self.subTest(mode=mode), self.assertNumQueries(11)): + with (self.subTest(mode=mode), self.assertNumQueries(8)): response = self.client.post( reverse("api:corpus-process"), data, @@ -966,7 +974,7 @@ class TestCreateProcess(FixtureAPITestCase): def test_create_process_dataset_mode_no_selection(self): self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.post( reverse("api:corpus-process"), { @@ -987,7 +995,7 @@ class TestCreateProcess(FixtureAPITestCase): self.client.force_login(self.user) cases = [item.value for item in ProcessMode if item.value not in ["workers", "dataset"]] for mode in cases: - with (self.subTest(mode=mode), self.assertNumQueries(9)): + with (self.subTest(mode=mode), self.assertNumQueries(6)): response = self.client.post( reverse("api:corpus-process"), { diff --git a/arkindex/process/tests/test_create_s3_import.py b/arkindex/process/tests/test_create_s3_import.py index 454f3282c9682d4bf8589930f842be04896d95af..19608844a4ac051f095d97b32745b1cc984f3221 100644 --- a/arkindex/process/tests/test_create_s3_import.py +++ b/arkindex/process/tests/test_create_s3_import.py @@ -1,3 +1,5 @@ +from unittest.mock import call, patch + from django.test import override_settings from django.urls import reverse from rest_framework import status @@ -59,7 +61,9 @@ class TestCreateS3Import(FixtureTestCase): }) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_invalid(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights") + def test_invalid(self, filter_rights_mock): + filter_rights_mock.return_value = Corpus.objects.filter(id=self.corpus.id) self.user.user_scopes.create(scope=Scope.S3Ingest) self.client.force_login(self.user) private_corpus = Corpus.objects.create(name="nope") @@ -111,10 +115,13 @@ class TestCreateS3Import(FixtureTestCase): ), ] for request, expected in cases: + filter_rights_mock.reset_mock() with self.subTest(request=request): response = self.client.post(reverse("api:s3-import-create"), request) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), expected) + self.assertEqual(filter_rights_mock.call_count, 1) + self.assertEqual(filter_rights_mock.call_args, call(self.user, Corpus, Role.Admin.value)) @override_settings( PONOS_DEFAULT_ENV={}, @@ -132,7 +139,7 @@ class TestCreateS3Import(FixtureTestCase): ImageServer.objects.create(id=999, display_name="Ingest image server", url="https://dev.null.teklia.com") element = self.corpus.elements.get(name="Volume 1") - with self.assertNumQueries(27), self.settings(IMPORTS_WORKER_VERSION=str(self.import_worker_version.id)): + with self.assertNumQueries(23), self.settings(IMPORTS_WORKER_VERSION=str(self.import_worker_version.id)): response = self.client.post(reverse("api:s3-import-create"), { "corpus_id": str(self.corpus.id), "element_id": str(element.id), @@ -196,7 +203,7 @@ class TestCreateS3Import(FixtureTestCase): self.corpus.types.create(slug="folder", display_name="Folder", folder=True) ImageServer.objects.create(id=999, display_name="Ingest image server", url="https://dev.null.teklia.com") - with self.assertNumQueries(26), self.settings(IMPORTS_WORKER_VERSION=str(self.import_worker_version.id)): + with self.assertNumQueries(22), self.settings(IMPORTS_WORKER_VERSION=str(self.import_worker_version.id)): response = self.client.post(reverse("api:s3-import-create"), { "corpus_id": str(self.corpus.id), "bucket_name": "blah", @@ -239,7 +246,8 @@ class TestCreateS3Import(FixtureTestCase): "INGEST_S3_SECRET_KEY": "its-secret-i-wont-tell-you", }) - def test_farm_guest(self): + @patch("arkindex.users.utils.get_max_level", return_value=None) + def test_farm_guest(self, get_max_level_mock): self.user.user_scopes.create(scope=Scope.S3Ingest) self.client.force_login(self.user) self.corpus.types.create(slug="folder", display_name="Folder", folder=True) @@ -247,7 +255,7 @@ class TestCreateS3Import(FixtureTestCase): self.other_farm.memberships.filter(user=self.user).delete() - with self.assertNumQueries(8), self.settings(IMPORTS_WORKER_VERSION=str(self.import_worker_version.id)): + with self.assertNumQueries(5), self.settings(IMPORTS_WORKER_VERSION=str(self.import_worker_version.id)): response = self.client.post(reverse("api:s3-import-create"), { "corpus_id": str(self.corpus.id), "bucket_name": "blah", @@ -260,7 +268,11 @@ class TestCreateS3Import(FixtureTestCase): }) self.assertFalse(Process.objects.filter(mode=ProcessMode.S3).exists()) - def test_default_farm_guest(self): + self.assertEqual(get_max_level_mock.call_count, 1) + self.assertEqual(get_max_level_mock.call_args, call(self.user, self.other_farm)) + + @patch("arkindex.users.utils.get_max_level", return_value=None) + def test_default_farm_guest(self, get_max_level_mock): self.user.user_scopes.create(scope=Scope.S3Ingest) self.client.force_login(self.user) self.corpus.types.create(slug="folder", display_name="Folder", folder=True) @@ -268,7 +280,7 @@ class TestCreateS3Import(FixtureTestCase): self.default_farm.memberships.filter(user=self.user).delete() - with self.assertNumQueries(8), self.settings(IMPORTS_WORKER_VERSION=str(self.import_worker_version.id)): + with self.assertNumQueries(5), self.settings(IMPORTS_WORKER_VERSION=str(self.import_worker_version.id)): response = self.client.post(reverse("api:s3-import-create"), { "corpus_id": str(self.corpus.id), "bucket_name": "blah", @@ -279,3 +291,6 @@ class TestCreateS3Import(FixtureTestCase): "farm_id": ["You do not have access to this farm."], }) self.assertFalse(Process.objects.filter(mode=ProcessMode.S3).exists()) + + self.assertEqual(get_max_level_mock.call_count, 1) + self.assertEqual(get_max_level_mock.call_args, call(self.user, self.default_farm)) diff --git a/arkindex/process/tests/test_datafile_api.py b/arkindex/process/tests/test_datafile_api.py index 2d673ad00c3c74a90b823e2a2494c9419301019b..97b722c5881e5342f29ee0c3bb1bcccd7ab0a22a 100644 --- a/arkindex/process/tests/test_datafile_api.py +++ b/arkindex/process/tests/test_datafile_api.py @@ -39,7 +39,8 @@ class TestDataFileApi(FixtureAPITestCase): response = self.client.post(reverse("api:file-create"), request) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_create_df_corpus_acl(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_create_df_corpus_acl(self, filter_rights_mock): self.client.force_login(self.user) private_corpus = Corpus.objects.create(name="private") request = self.build_file_create_request(corpus=str(private_corpus.id)) diff --git a/arkindex/process/tests/test_process_datasets.py b/arkindex/process/tests/test_process_datasets.py index 142708b1265e11c07eb29dd732d39d99f9fc928c..c8c4babe0cf21d796371309a0ca6a3cbe1ab2ea9 100644 --- a/arkindex/process/tests/test_process_datasets.py +++ b/arkindex/process/tests/test_process_datasets.py @@ -1,5 +1,5 @@ import uuid -from unittest.mock import patch +from unittest.mock import call, patch from django.urls import reverse from rest_framework import status @@ -65,17 +65,21 @@ class TestProcessDatasets(FixtureAPITestCase): response = self.client.get(reverse("api:process-datasets", kwargs={"pk": str(uuid.uuid4())})) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_list_process_access_level(self): + @patch("arkindex.project.mixins.get_max_level", return_value=None) + def test_list_process_access_level(self, get_max_level_mock): self.private_corpus.memberships.filter(user=self.test_user).delete() self.client.force_login(self.test_user) - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.get(reverse("api:process-datasets", kwargs={"pk": self.dataset_process.id})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), {"detail": "You do not have guest access to this process."}) + self.assertEqual(get_max_level_mock.call_count, 1) + self.assertEqual(get_max_level_mock.call_args, call(self.test_user, self.private_corpus)) + def test_list(self): self.client.force_login(self.test_user) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.get(reverse("api:process-datasets", kwargs={"pk": self.dataset_process.id})) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.json()["results"], [ @@ -125,21 +129,26 @@ class TestProcessDatasets(FixtureAPITestCase): ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_create_access_level(self): - cases = [None, Role.Guest, Role.Contributor] + @patch("arkindex.project.mixins.get_max_level") + def test_create_access_level(self, get_max_level_mock): + cases = [None, Role.Guest.value, Role.Contributor.value] for level in cases: with self.subTest(level=level): - self.private_corpus.memberships.filter(user=self.test_user).delete() - if level: - self.private_corpus.memberships.create(user=self.test_user, level=level.value) + get_max_level_mock.reset_mock() + get_max_level_mock.return_value = level self.client.force_login(self.test_user) - with self.assertNumQueries(6): + + with self.assertNumQueries(4): response = self.client.post( reverse("api:process-dataset", kwargs={"process": self.dataset_process.id, "dataset": self.dataset2.id}), ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(response.json(), {"detail": "You do not have admin access to this process."}) + self.assertEqual(get_max_level_mock.call_count, 1) + self.assertEqual(get_max_level_mock.call_args, call(self.test_user, self.private_corpus)) + def test_create_process_mode(self): cases = set(ProcessMode) - {ProcessMode.Dataset, ProcessMode.Local, ProcessMode.Repository} for mode in cases: @@ -148,7 +157,7 @@ class TestProcessDatasets(FixtureAPITestCase): self.dataset_process.save() self.client.force_login(self.test_user) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.post( reverse("api:process-dataset", kwargs={"process": self.dataset_process.id, "dataset": self.dataset2.id}), ) @@ -159,27 +168,17 @@ class TestProcessDatasets(FixtureAPITestCase): def test_create_process_mode_local(self): self.client.force_login(self.user) local_process = Process.objects.get(creator=self.user, mode=ProcessMode.Local) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post( reverse("api:process-dataset", kwargs={"process": local_process.id, "dataset": self.dataset2.id}), ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(response.json(), {"detail": "You do not have admin access to this process."}) - def test_create_process_mode_repository(self): - self.client.force_login(self.user) - process = Process.objects.create(creator=self.user, mode=ProcessMode.Repository, revision=self.rev) - with self.assertNumQueries(10): - response = self.client.post( - reverse("api:process-dataset", kwargs={"process": process.id, "dataset": self.dataset2.id}), - ) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertEqual(response.json(), {"detail": "You do not have admin access to this process."}) - def test_create_wrong_process_uuid(self): self.client.force_login(self.test_user) wrong_id = uuid.uuid4() - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post( reverse("api:process-dataset", kwargs={"process": wrong_id, "dataset": self.dataset2.id}), ) @@ -189,29 +188,35 @@ class TestProcessDatasets(FixtureAPITestCase): def test_create_wrong_dataset_uuid(self): self.client.force_login(self.test_user) wrong_id = uuid.uuid4() - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:process-dataset", kwargs={"process": self.dataset_process.id, "dataset": wrong_id}), ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.json(), {"dataset": [f'Invalid pk "{str(wrong_id)}" - object does not exist.']}) - def test_create_dataset_access(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_create_dataset_access(self, filter_rights_mock): new_corpus = Corpus.objects.create(name="NERV") new_dataset = new_corpus.datasets.create(name="Eva series", description="We created the Evas from Adam", creator=self.user) self.client.force_login(self.test_user) - with self.assertNumQueries(7): + + with self.assertNumQueries(3): response = self.client.post( reverse("api:process-dataset", kwargs={"process": self.dataset_process.id, "dataset": new_dataset.id}), ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.json(), {"dataset": [f'Invalid pk "{str(new_dataset.id)}" - object does not exist.']}) + self.assertEqual(filter_rights_mock.call_count, 1) + self.assertEqual(filter_rights_mock.call_args, call(self.test_user, Corpus, Role.Guest.value)) + def test_create_unique(self): self.client.force_login(self.test_user) self.assertTrue(self.dataset_process.datasets.filter(id=self.dataset1.id).exists()) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.post( reverse("api:process-dataset", kwargs={"process": self.dataset_process.id, "dataset": self.dataset1.id}), ) @@ -223,7 +228,7 @@ class TestProcessDatasets(FixtureAPITestCase): self.client.force_login(self.test_user) self.dataset_process.tasks.create(run=0, depth=0, slug="makrout") - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.post( reverse("api:process-dataset", kwargs={"process": self.dataset_process.id, "dataset": self.dataset2.id}), ) @@ -235,7 +240,7 @@ class TestProcessDatasets(FixtureAPITestCase): self.client.force_login(self.test_user) self.assertEqual(ProcessDataset.objects.count(), 3) self.assertFalse(ProcessDataset.objects.filter(process=self.dataset_process.id, dataset=self.dataset2.id).exists()) - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.post( reverse("api:process-dataset", kwargs={"process": self.dataset_process.id, "dataset": self.dataset2.id}), ) @@ -270,7 +275,7 @@ class TestProcessDatasets(FixtureAPITestCase): def test_destroy_dataset_does_not_exist(self): self.client.force_login(self.test_user) wrong_id = uuid.uuid4() - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.delete( reverse("api:process-dataset", kwargs={"process": self.dataset_process.id, "dataset": wrong_id}) ) @@ -280,29 +285,34 @@ class TestProcessDatasets(FixtureAPITestCase): def test_destroy_not_found(self): self.assertFalse(self.dataset_process.datasets.filter(id=self.dataset2.id).exists()) self.client.force_login(self.test_user) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.delete( reverse("api:process-dataset", kwargs={"process": self.dataset_process.id, "dataset": self.dataset2.id}), ) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_destroy_process_access_level(self): - self.private_corpus.memberships.filter(user=self.test_user).delete() + @patch("arkindex.project.mixins.get_max_level", return_value=None) + def test_destroy_process_access_level(self, get_max_level_mock): self.client.force_login(self.test_user) - with self.assertNumQueries(6): + + with self.assertNumQueries(4): response = self.client.delete( reverse("api:process-dataset", kwargs={"process": self.dataset_process.id, "dataset": self.private_dataset.id}) ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertDictEqual(response.json(), {"detail": "You do not have admin access to this process."}) + self.assertEqual(get_max_level_mock.call_count, 1) + self.assertEqual(get_max_level_mock.call_args, call(self.test_user, self.private_corpus)) + def test_destroy_no_dataset_access_requirement(self): new_corpus = Corpus.objects.create(name="NERV") new_dataset = new_corpus.datasets.create(name="Eva series", description="We created the Evas from Adam", creator=self.user) self.dataset_process.datasets.add(new_dataset) self.assertTrue(ProcessDataset.objects.filter(process=self.dataset_process, dataset=new_dataset).exists()) self.client.force_login(self.test_user) - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.delete( reverse("api:process-dataset", kwargs={"process": self.dataset_process.id, "dataset": new_dataset.id}), ) @@ -317,7 +327,7 @@ class TestProcessDatasets(FixtureAPITestCase): self.dataset_process.save() self.client.force_login(self.test_user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.delete( reverse("api:process-dataset", kwargs={"process": self.dataset_process.id, "dataset": self.dataset2.id}), ) @@ -335,21 +345,11 @@ class TestProcessDatasets(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(response.json(), {"detail": "You do not have admin access to this process."}) - def test_destroy_process_mode_repository(self): - self.client.force_login(self.user) - process = Process.objects.create(creator=self.user, mode=ProcessMode.Repository, revision=self.rev) - with self.assertNumQueries(9): - response = self.client.delete( - reverse("api:process-dataset", kwargs={"process": process.id, "dataset": self.dataset2.id}), - ) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertEqual(response.json(), {"detail": "You do not have admin access to this process."}) - def test_destroy_started(self): self.client.force_login(self.test_user) self.dataset_process.tasks.create(run=0, depth=0, slug="makrout") - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.delete( reverse("api:process-dataset", kwargs={"process": self.dataset_process.id, "dataset": self.dataset1.id}), ) @@ -359,7 +359,7 @@ class TestProcessDatasets(FixtureAPITestCase): def test_destroy(self): self.client.force_login(self.test_user) - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.delete( reverse("api:process-dataset", kwargs={"process": self.dataset_process.id, "dataset": self.dataset1.id}), ) diff --git a/arkindex/process/tests/test_process_elements.py b/arkindex/process/tests/test_process_elements.py index 89d0e44de1564b3e4539719a7dfc55f4fc54ec46..e437584d2d8385b50909505b982fac772651a117 100644 --- a/arkindex/process/tests/test_process_elements.py +++ b/arkindex/process/tests/test_process_elements.py @@ -1,4 +1,5 @@ import uuid +from unittest.mock import call, patch from django.urls import reverse from rest_framework import status @@ -7,7 +8,7 @@ from arkindex.documents.models import Corpus, Element, ElementPath from arkindex.images.models import Image from arkindex.process.models import Process, ProcessMode from arkindex.project.tests import FixtureAPITestCase -from arkindex.users.models import User +from arkindex.users.models import Role class TestProcessElements(FixtureAPITestCase): @@ -158,15 +159,19 @@ class TestProcessElements(FixtureAPITestCase): response = self.client.get(reverse("api:process-elements-list", kwargs={"pk": str(uuid.uuid4())})) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_no_access(self): - self.process.corpus = Corpus.objects.create(name="private") - self.corpus.creator = User.objects.create_user("John Doe") - self.process.save() + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_no_access(self, has_access_mock): self.client.force_login(self.user) - response = self.client.get(reverse("api:process-elements-list", kwargs={"pk": self.process.id})) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + with self.assertNumQueries(4): + response = self.client.get(reverse("api:process-elements-list", kwargs={"pk": self.process.id})) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertDictEqual(response.json(), {"detail": "You do not have an admin access to the corpus of this process."}) + self.assertEqual(has_access_mock.call_count, 1) + self.assertEqual(has_access_mock.call_args, call(self.user, self.private_corpus, Role.Admin.value, skip_public=False)) + def test_filter_elements_wrong_corpus(self): """ Selected elements must be part of the same corpus as the process diff --git a/arkindex/process/tests/test_processes.py b/arkindex/process/tests/test_processes.py index c60ac38b62feef8d5fda683bd1c0f205a99e0ad6..b0d2db1d1f13a4fdfaa35cf49ed5b80239efe44c 100644 --- a/arkindex/process/tests/test_processes.py +++ b/arkindex/process/tests/test_processes.py @@ -1,4 +1,5 @@ import uuid +from unittest import expectedFailure from unittest.mock import call, patch from django.conf import settings @@ -136,7 +137,7 @@ class TestProcesses(FixtureAPITestCase): task.save() self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.get(reverse("api:process-list")) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -218,7 +219,7 @@ class TestProcesses(FixtureAPITestCase): self.user_img_process.activity_state = ActivityState.Ready self.user_img_process.save() - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.get(reverse("api:process-list"), {"started": "true"}) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -253,7 +254,7 @@ class TestProcesses(FixtureAPITestCase): self.elts_process.run() self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.get(reverse("api:process-list"), {"started": "false"}) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -320,7 +321,7 @@ class TestProcesses(FixtureAPITestCase): self.client.force_login(self.user) assert Process.objects.exclude(mode=ProcessMode.Files).exists() - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.get(reverse("api:process-list"), {"mode": ProcessMode.Files.value, "started": False}) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -334,7 +335,7 @@ class TestProcesses(FixtureAPITestCase): """ self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.get(reverse("api:process-list"), {"mode": ProcessMode.Local.value}) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -376,7 +377,7 @@ class TestProcesses(FixtureAPITestCase): """ self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.get(reverse("api:process-list"), {"created": "true"}) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -423,7 +424,7 @@ class TestProcesses(FixtureAPITestCase): """ self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.get(reverse("api:process-list"), {"created": "false"}) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -448,7 +449,7 @@ class TestProcesses(FixtureAPITestCase): mode=ProcessMode.Files, ) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.get(reverse("api:process-list"), {"id": process_id[:10], "started": False}) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -471,7 +472,7 @@ class TestProcesses(FixtureAPITestCase): name="Numero Duo" ) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.get(reverse("api:process-list"), {"name": "Numb", "started": False}) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -509,7 +510,7 @@ class TestProcesses(FixtureAPITestCase): self.assertEqual(stopped_process.state, State.Stopped) self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.get(reverse("api:process-list"), {"state": "completed"}) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -538,7 +539,7 @@ class TestProcesses(FixtureAPITestCase): self.assertEqual(stopped_process_2.state, State.Stopped) self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.get(reverse("api:process-list"), {"state": "stopped"}) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -570,7 +571,7 @@ class TestProcesses(FixtureAPITestCase): self.assertEqual(self.elts_process.state, State.Unscheduled) self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.get(reverse("api:process-list"), {"state": "unscheduled", "started": True}) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -681,7 +682,7 @@ class TestProcesses(FixtureAPITestCase): self.user_img_process.run() task = self.user_img_process.tasks.get() - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.get(reverse("api:process-details", kwargs={"pk": self.user_img_process.id})) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -762,18 +763,10 @@ class TestProcesses(FixtureAPITestCase): {"__all__": ["Please wait activities to be initialized before deleting this process"]} ) - def test_delete_repository_import_no_permission(self): + @expectedFailure + def test_delete_process_no_permission(self): """ - Deletion of a repository import requires to be admin on the repository - """ - self.client.force_login(self.user) - response = self.client.delete(reverse("api:process-details", kwargs={"pk": self.repository_process.id})) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertDictEqual(response.json(), {"detail": "You do not have a sufficient access level to this process."}) - - def test_delete_corpus_import_no_permission(self): - """ - A user cannot delete a process linked to a corpus he has no admin access to + A user cannot delete a process linked to a corpus they have no admin access to """ self.client.force_login(self.user) self.assertFalse(self.user_img_process.corpus.memberships.filter(user=self.user).exists()) @@ -819,7 +812,7 @@ class TestProcesses(FixtureAPITestCase): If no activities exists for this process, it is deleted directly. """ self.client.force_login(self.user) - with self.assertNumQueries(22): + with self.assertNumQueries(19): response = self.client.delete(reverse("api:process-details", kwargs={"pk": self.elts_process.id})) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) with self.assertRaises(Process.DoesNotExist): @@ -846,7 +839,7 @@ class TestProcesses(FixtureAPITestCase): element=self.corpus.elements.get(name="Volume 1"), worker_version=WorkerVersion.objects.get(worker__slug="reco"), ) - with self.assertNumQueries(14): + with self.assertNumQueries(11): response = self.client.delete(reverse("api:process-details", kwargs={"pk": self.elts_process.id})) self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) self.assertEqual(delay_mock.call_count, 1) @@ -905,7 +898,7 @@ class TestProcesses(FixtureAPITestCase): self.client.force_login(self.user) self.elts_process.run() self.elts_process.tasks.update(state=State.Running) - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.patch( reverse("api:process-details", kwargs={"pk": self.elts_process.id}), {"element_name_contains": "something"}, @@ -922,7 +915,7 @@ class TestProcesses(FixtureAPITestCase): self.elts_process.run() self.elts_process.tasks.update(state=State.Running) self.assertEqual(self.elts_process.name, None) - with self.assertNumQueries(14): + with self.assertNumQueries(11): response = self.client.patch( reverse("api:process-details", kwargs={"pk": self.elts_process.id}), {"name": "newName"}, @@ -951,7 +944,7 @@ class TestProcesses(FixtureAPITestCase): # Take any element with an image: this will cause extra queries to retrieve the Image and ImageServer element = self.corpus.elements.exclude(image_id=None).first() - with self.assertNumQueries(15), self.settings(IMPORTS_WORKER_VERSION=str(self.import_worker_version.id)): + with self.assertNumQueries(12), self.settings(IMPORTS_WORKER_VERSION=str(self.import_worker_version.id)): response = self.client.patch( reverse("api:process-details", kwargs={"pk": process.id}), {"element_id": str(element.id)}, @@ -962,6 +955,7 @@ class TestProcesses(FixtureAPITestCase): process.refresh_from_db() self.assertEqual(process.element, element) + @expectedFailure def test_partial_update_no_permission(self): """ A user cannot update a process linked to a corpus he has no admin access to @@ -972,6 +966,7 @@ class TestProcesses(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), {"detail": "You do not have a sufficient access level to this process."}) + @expectedFailure def test_partial_update_repository_requires_admin(self): """ Edition of a repository import requires to be admin on the repository @@ -997,7 +992,7 @@ class TestProcesses(FixtureAPITestCase): self.elts_process.refresh_from_db() self.assertEqual(self.elts_process.state, state) - with self.assertNumQueries(16): + with self.assertNumQueries(13): response = self.client.patch( reverse("api:process-details", kwargs={"pk": self.elts_process.id}), {"state": "stopping"}, @@ -1017,7 +1012,7 @@ class TestProcesses(FixtureAPITestCase): self.elts_process.run() self.assertEqual(self.elts_process.state, State.Unscheduled) - with self.assertNumQueries(17): + with self.assertNumQueries(14): response = self.client.patch( reverse("api:process-details", kwargs={"pk": self.elts_process.id}), {"state": "stopping"}, @@ -1041,7 +1036,7 @@ class TestProcesses(FixtureAPITestCase): for state in set(State) - {State.Stopping}: with self.subTest(state=state): - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.patch( reverse("api:process-details", kwargs={"pk": self.elts_process.id}), {"state": state.value}, @@ -1069,7 +1064,7 @@ class TestProcesses(FixtureAPITestCase): self.elts_process.refresh_from_db() self.assertEqual(self.elts_process.state, state) - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.patch( reverse("api:process-details", kwargs={"pk": self.elts_process.id}), {"state": "stopping"}, @@ -1158,7 +1153,7 @@ class TestProcesses(FixtureAPITestCase): self.elts_process.run() self.assertEqual(self.elts_process.state, State.Unscheduled) - with self.assertNumQueries(18): + with self.assertNumQueries(15): response = self.client.put( reverse("api:process-details", kwargs={"pk": self.elts_process.id}), { @@ -1189,7 +1184,7 @@ class TestProcesses(FixtureAPITestCase): for state in set(State) - {State.Stopping}: with self.subTest(state=state): - with self.assertNumQueries(10): + with self.assertNumQueries(7): response = self.client.put( reverse("api:process-details", kwargs={"pk": self.elts_process.id}), { @@ -1224,7 +1219,7 @@ class TestProcesses(FixtureAPITestCase): self.elts_process.refresh_from_db() self.assertEqual(self.elts_process.state, state) - with self.assertNumQueries(10): + with self.assertNumQueries(7): response = self.client.put( reverse("api:process-details", kwargs={"pk": self.elts_process.id}), { @@ -1258,7 +1253,7 @@ class TestProcesses(FixtureAPITestCase): self.assertIsNone(process.ml_class) self.assertFalse(process.load_children) - with self.assertNumQueries(13): + with self.assertNumQueries(10): response = self.client.put( reverse("api:process-details", kwargs={"pk": process.id}), { @@ -1286,9 +1281,9 @@ class TestProcesses(FixtureAPITestCase): non_existent_id = uuid.uuid4() cases = [ - ("je says pas", "“je says pas†is not a valid UUID.", 9), - (non_existent_id, f'Invalid pk "{non_existent_id}" - object does not exist.', 10), - (self.private_ml_class.id, f'Invalid pk "{self.private_ml_class.id}" - object does not exist.', 10), + ("je says pas", "“je says pas†is not a valid UUID.", 6), + (non_existent_id, f'Invalid pk "{non_existent_id}" - object does not exist.', 7), + (self.private_ml_class.id, f'Invalid pk "{self.private_ml_class.id}" - object does not exist.', 7), ] for ml_class_id, message, queries in cases: with self.subTest(ml_class_id=ml_class_id), self.assertNumQueries(queries): @@ -1317,7 +1312,7 @@ class TestProcesses(FixtureAPITestCase): self.client.force_login(self.user) process = Process.objects.create(mode=ProcessMode.Workers, corpus=self.corpus, creator=self.user) - with self.assertNumQueries(12): + with self.assertNumQueries(9): response = self.client.put( reverse("api:process-details", kwargs={"pk": process.id}), { @@ -1441,6 +1436,7 @@ class TestProcesses(FixtureAPITestCase): process.refresh_from_db() self.assertEqual(process.name, "newName") + @expectedFailure def test_partial_update_corpus_no_write_right(self): self.client.force_login(self.user) self.corpus.memberships.filter(user=self.user).update(level=Role.Guest.value) @@ -1460,7 +1456,7 @@ class TestProcesses(FixtureAPITestCase): self.assertIsNone(process.ml_class) self.assertFalse(process.load_children) - with self.assertNumQueries(13): + with self.assertNumQueries(10): response = self.client.patch( reverse("api:process-details", kwargs={"pk": process.id}), { @@ -1529,9 +1525,9 @@ class TestProcesses(FixtureAPITestCase): non_existent_id = uuid.uuid4() cases = [ - ("je says pas", "“je says pas†is not a valid UUID.", 8), - (non_existent_id, f'Invalid pk "{non_existent_id}" - object does not exist.', 9), - (self.private_ml_class.id, f'Invalid pk "{self.private_ml_class.id}" - object does not exist.', 9), + ("je says pas", "“je says pas†is not a valid UUID.", 5), + (non_existent_id, f'Invalid pk "{non_existent_id}" - object does not exist.', 6), + (self.private_ml_class.id, f'Invalid pk "{self.private_ml_class.id}" - object does not exist.', 6), ] for ml_class_id, message, queries in cases: with self.subTest(ml_class_id=ml_class_id), self.assertNumQueries(queries): @@ -1555,7 +1551,7 @@ class TestProcesses(FixtureAPITestCase): self.client.force_login(self.user) process = Process.objects.create(mode=ProcessMode.Workers, corpus=self.corpus, creator=self.user) - with self.assertNumQueries(12): + with self.assertNumQueries(9): response = self.client.patch( reverse("api:process-details", kwargs={"pk": process.id}), { @@ -1614,6 +1610,7 @@ class TestProcesses(FixtureAPITestCase): response = self.client.post(reverse("api:process-retry", kwargs={"pk": self.user_img_process.id})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + @expectedFailure def test_retry_repository_process_no_right(self): """ A user that is not the creator nor admin cannot restart a process that is not linked to any corpus @@ -1642,7 +1639,7 @@ class TestProcesses(FixtureAPITestCase): with self.subTest(state=state): self.elts_process.tasks.all().update(state=state) self.assertEqual(self.elts_process.state, state) - with self.assertNumQueries(11): + with self.assertNumQueries(6): response = self.client.post(reverse("api:process-retry", kwargs={"pk": self.elts_process.id})) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), {"__all__": [message]}) @@ -1656,7 +1653,7 @@ class TestProcesses(FixtureAPITestCase): self.elts_process.finished = timezone.now() self.elts_process.save() - with self.assertNumQueries(16): + with self.assertNumQueries(11): response = self.client.post(reverse("api:process-retry", kwargs={"pk": self.elts_process.id})) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -1667,6 +1664,7 @@ class TestProcesses(FixtureAPITestCase): # Activity initialization runs again self.assertFalse(delay_mock.called) + @expectedFailure def test_retry_farm_guest(self): self.elts_process.run() self.elts_process.tasks.all().update(state=State.Error) @@ -1698,7 +1696,7 @@ class TestProcesses(FixtureAPITestCase): self.client.force_login(self.user) - with self.assertNumQueries(15): + with self.assertNumQueries(10): response = self.client.post(reverse("api:process-retry", kwargs={"pk": self.elts_process.id})) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) @@ -1725,7 +1723,7 @@ class TestProcesses(FixtureAPITestCase): self.client.force_login(self.user) - with self.assertNumQueries(15): + with self.assertNumQueries(10): response = self.client.post(reverse("api:process-retry", kwargs={"pk": process.id})) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) @@ -1738,7 +1736,7 @@ class TestProcesses(FixtureAPITestCase): self.client.force_login(self.user) self.assertFalse(self.elts_process.tasks.exists()) - with self.assertNumQueries(15): + with self.assertNumQueries(10): response = self.client.post(reverse("api:process-retry", kwargs={"pk": self.elts_process.id})) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -1760,7 +1758,7 @@ class TestProcesses(FixtureAPITestCase): self.workers_process.activity_state = ActivityState.Error self.workers_process.save() - with self.assertNumQueries(18): + with self.assertNumQueries(13): response = self.client.post(reverse("api:process-retry", kwargs={"pk": self.workers_process.id})) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -1784,7 +1782,7 @@ class TestProcesses(FixtureAPITestCase): with ( self.settings(IMPORTS_WORKER_VERSION=str(self.version_with_model.id)), - self.assertNumQueries(19), + self.assertNumQueries(14), ): response = self.client.post(reverse("api:process-retry", kwargs={"pk": process.id})) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -1821,7 +1819,7 @@ class TestProcesses(FixtureAPITestCase): IMPORTS_WORKER_VERSION=str(self.version_with_model.id), INGEST_IMAGESERVER_ID=str(ImageServer.objects.get(url="http://server").id), ), - self.assertNumQueries(20), + self.assertNumQueries(15), ): response = self.client.post(reverse("api:process-retry", kwargs={"pk": process.id})) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -1848,7 +1846,7 @@ class TestProcesses(FixtureAPITestCase): with ( self.settings(IMPORTS_WORKER_VERSION=str(self.version_with_model.id)), - self.assertNumQueries(19), + self.assertNumQueries(14), ): response = self.client.post(reverse("api:process-retry", kwargs={"pk": process.id})) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -1875,7 +1873,7 @@ class TestProcesses(FixtureAPITestCase): with ( self.settings(IMPORTS_WORKER_VERSION=str(self.version_with_model.id)), - self.assertNumQueries(30) + self.assertNumQueries(26), ): response = self.client.post(reverse("api:files-process"), { "files": [str(self.img_df.id)], @@ -1929,7 +1927,7 @@ class TestProcesses(FixtureAPITestCase): for content_type in settings.ARCHIVE_MIME_TYPES ) - with self.settings(IMPORTS_WORKER_VERSION=str(self.import_worker_version.id)), self.assertNumQueries(42): + with self.settings(IMPORTS_WORKER_VERSION=str(self.import_worker_version.id)), self.assertNumQueries(38): response = self.client.post(reverse("api:files-process"), { "files": [ str(self.pdf_df.id), @@ -1962,7 +1960,7 @@ class TestProcesses(FixtureAPITestCase): def test_from_files_iiif(self): self.client.force_login(self.user) - with self.assertNumQueries(30), self.settings(IMPORTS_WORKER_VERSION=str(self.import_worker_version.id)): + with self.assertNumQueries(26), self.settings(IMPORTS_WORKER_VERSION=str(self.import_worker_version.id)): response = self.client.post(reverse("api:files-process"), { "files": [str(self.iiif_df.id)], "mode": "iiif", @@ -2071,7 +2069,7 @@ class TestProcesses(FixtureAPITestCase): def test_from_files_files_wrong_type(self): self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(5): response = self.client.post(reverse("api:files-process"), { "files": [str(self.iiif_df.id)], "folder_type": "volume", @@ -2117,7 +2115,7 @@ class TestProcesses(FixtureAPITestCase): with ( self.settings(IMPORTS_WORKER_VERSION=str(self.version_with_model.id)), - self.assertNumQueries(30) + self.assertNumQueries(26), ): response = self.client.post(reverse("api:files-process"), { "files": [str(self.img_df.id)], @@ -2131,6 +2129,7 @@ class TestProcesses(FixtureAPITestCase): process = Process.objects.get(id=data["id"]) self.assertEqual(process.farm, self.other_farm) + @expectedFailure def test_from_files_farm_guest(self): self.client.force_login(self.user) self.assertEqual(self.version_with_model.worker_runs.count(), 0) @@ -2152,6 +2151,7 @@ class TestProcesses(FixtureAPITestCase): "farm_id": ["You do not have access to this farm."], }) + @expectedFailure def test_from_files_default_farm_guest(self): self.client.force_login(self.user) self.assertEqual(self.version_with_model.worker_runs.count(), 0) @@ -2250,7 +2250,7 @@ class TestProcesses(FixtureAPITestCase): self.assertFalse(process2.tasks.exists()) self.client.force_login(self.user) - with self.assertNumQueries(20): + with self.assertNumQueries(15): response = self.client.post( reverse("api:process-start", kwargs={"pk": str(process2.id)}) ) @@ -2265,7 +2265,7 @@ class TestProcesses(FixtureAPITestCase): ) self.client.force_login(self.user) - with self.assertNumQueries(11): + with self.assertNumQueries(6): response = self.client.post( reverse("api:process-start", kwargs={"pk": str(process2.id)}) ) @@ -2288,7 +2288,7 @@ class TestProcesses(FixtureAPITestCase): self.assertFalse(process2.tasks.exists()) self.client.force_login(self.user) - with self.assertNumQueries(11): + with self.assertNumQueries(6): response = self.client.post( reverse("api:process-start", kwargs={"pk": str(process2.id)}) ) @@ -2306,7 +2306,7 @@ class TestProcesses(FixtureAPITestCase): self.assertFalse(process2.tasks.exists()) self.client.force_login(self.user) - with self.assertNumQueries(11): + with self.assertNumQueries(6): response = self.client.post( reverse("api:process-start", kwargs={"pk": str(process2.id)}) ) @@ -2324,7 +2324,7 @@ class TestProcesses(FixtureAPITestCase): self.assertFalse(process2.tasks.exists()) self.client.force_login(self.user) - with self.assertNumQueries(15): + with self.assertNumQueries(10): response = self.client.post( reverse("api:process-start", kwargs={"pk": str(process2.id)}) ) @@ -2373,7 +2373,7 @@ class TestProcesses(FixtureAPITestCase): ) self.client.force_login(self.user) - with self.assertNumQueries(11): + with self.assertNumQueries(6): response = self.client.post( reverse("api:process-start", kwargs={"pk": str(process2.id)}) ) @@ -2397,7 +2397,7 @@ class TestProcesses(FixtureAPITestCase): self.client.force_login(self.user) - with self.assertNumQueries(20): + with self.assertNumQueries(15): response = self.client.post( reverse("api:process-start", kwargs={"pk": str(process2.id)}) ) @@ -2421,7 +2421,7 @@ class TestProcesses(FixtureAPITestCase): self.client.force_login(self.user) - with self.assertNumQueries(11): + with self.assertNumQueries(6): response = self.client.post( reverse("api:process-start", kwargs={"pk": str(process2.id)}) ) @@ -2438,7 +2438,7 @@ class TestProcesses(FixtureAPITestCase): self.client.force_login(self.user) - with self.assertNumQueries(11): + with self.assertNumQueries(6): response = self.client.post( reverse("api:process-start", kwargs={"pk": str(process2.id)}) ) @@ -2454,7 +2454,7 @@ class TestProcesses(FixtureAPITestCase): self.client.force_login(self.user) - with self.assertNumQueries(11): + with self.assertNumQueries(6): response = self.client.post( reverse("api:process-start", kwargs={"pk": str(process2.id)}), { @@ -2479,7 +2479,7 @@ class TestProcesses(FixtureAPITestCase): self.client.force_login(self.user) - with self.assertNumQueries(18): + with self.assertNumQueries(13): response = self.client.post( reverse("api:process-start", kwargs={"pk": str(process2.id)}) ) @@ -2511,7 +2511,7 @@ class TestProcesses(FixtureAPITestCase): self.assertFalse(process.tasks.exists()) self.client.force_login(self.user) - with self.assertNumQueries(20): + with self.assertNumQueries(15): response = self.client.post( reverse("api:process-start", kwargs={"pk": str(process.id)}) ) @@ -2532,7 +2532,7 @@ class TestProcesses(FixtureAPITestCase): workers_process.worker_runs.create(version=self.recognizer, parents=[], configuration=None) self.client.force_login(self.user) - with self.assertNumQueries(20): + with self.assertNumQueries(15): response = self.client.post( reverse("api:process-start", kwargs={"pk": str(workers_process.id)}), {"farm": str(self.other_farm.id)} @@ -2543,6 +2543,7 @@ class TestProcesses(FixtureAPITestCase): self.assertEqual(workers_process.state, State.Unscheduled) self.assertEqual(workers_process.farm_id, self.other_farm.id) + @expectedFailure def test_start_process_default_farm_guest(self): workers_process = self.corpus.processes.create(creator=self.user, mode=ProcessMode.Workers) workers_process.worker_runs.create(version=self.recognizer, parents=[], configuration=None) @@ -2562,6 +2563,7 @@ class TestProcesses(FixtureAPITestCase): self.assertEqual(workers_process.state, State.Unscheduled) self.assertIsNone(workers_process.farm) + @expectedFailure def test_start_process_farm_guest(self): workers_process = self.corpus.processes.create(creator=self.user, mode=ProcessMode.Workers) workers_process.worker_runs.create(version=self.recognizer, parents=[], configuration=None) @@ -2590,7 +2592,7 @@ class TestProcesses(FixtureAPITestCase): self.client.force_login(self.user) wrong_farm_id = uuid.uuid4() - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.post( reverse("api:process-start", kwargs={"pk": str(workers_process.id)}), {"farm": str(wrong_farm_id)} @@ -2609,7 +2611,7 @@ class TestProcesses(FixtureAPITestCase): ({"thumbnails": "gloubiboulga"}, {"thumbnails": ["Must be a valid boolean."]}) ] for (params, check) in wrong_params_checks: - with self.subTest(**params), self.assertNumQueries(11): + with self.subTest(**params), self.assertNumQueries(6): response = self.client.post(reverse("api:process-start", kwargs={"pk": str(process.id)}), params) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), check) @@ -2623,7 +2625,7 @@ class TestProcesses(FixtureAPITestCase): process.worker_runs.create(version=self.recognizer, parents=[], configuration=None) self.client.force_login(self.user) - with self.assertNumQueries(11): + with self.assertNumQueries(6): response = self.client.post( reverse("api:process-start", kwargs={"pk": str(process.id)}), {"chunks": 43}, @@ -2695,7 +2697,7 @@ class TestProcesses(FixtureAPITestCase): element_type=self.corpus.types.get(slug="page") ) self.client.force_login(self.user) - with self.assertNumQueries(10): + with self.assertNumQueries(5): response = self.client.post( reverse("api:process-start", kwargs={"pk": str(process.id)}), {"use_cache": "true", "worker_activity": "true", "use_gpu": "true"} @@ -2727,7 +2729,7 @@ class TestProcesses(FixtureAPITestCase): self.client.force_login(self.user) - with self.assertNumQueries(21): + with self.assertNumQueries(16): response = self.client.post( reverse("api:process-start", kwargs={"pk": str(process.id)}), {"use_cache": "true", "worker_activity": "true", "use_gpu": "true"} @@ -2763,7 +2765,7 @@ class TestProcesses(FixtureAPITestCase): self.assertNotEqual(run_1.task_slug, run_2.task_slug) self.client.force_login(self.user) - with self.assertNumQueries(20): + with self.assertNumQueries(15): response = self.client.post(reverse("api:process-start", kwargs={"pk": str(process.id)})) self.assertEqual(response.status_code, status.HTTP_201_CREATED) @@ -2865,7 +2867,7 @@ class TestProcesses(FixtureAPITestCase): self.assertEqual(process.worker_runs.count(), 2) self.client.force_login(self.user) - with self.assertNumQueries(11): + with self.assertNumQueries(8): response = self.client.delete(reverse("api:clear-process", kwargs={"pk": str(process.id)})) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) process.refresh_from_db() @@ -2938,7 +2940,7 @@ class TestProcesses(FixtureAPITestCase): self.assertEqual(process.state, State.Running) self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.delete(reverse("api:clear-process", kwargs={"pk": str(process.id)})) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), {"__all__": ["A process can only be cleared before getting started."]}) @@ -2960,11 +2962,12 @@ class TestProcesses(FixtureAPITestCase): self.assertEqual(process.state, State.Unscheduled) self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.delete(reverse("api:clear-process", kwargs={"pk": str(process.id)})) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), {"__all__": ["A process can only be cleared before getting started."]}) + @expectedFailure def test_clear_process_requires_permissions(self): process = self.corpus.processes.create( creator=self.user, @@ -2982,7 +2985,7 @@ class TestProcesses(FixtureAPITestCase): self.corpus.memberships.create(user=user2, level=Role.Contributor.value) self.client.force_login(user2) - with self.assertNumQueries(6): + with self.assertNumQueries(8): response = self.client.delete(reverse("api:clear-process", kwargs={"pk": str(process.id)})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), {"detail": "You do not have a sufficient access level to this process."}) @@ -3013,6 +3016,7 @@ class TestProcesses(FixtureAPITestCase): response = self.client.post(reverse("api:process-select-failures", kwargs={"pk": str(uuid.uuid4())})) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + @expectedFailure def test_select_failed_elts_requires_corpus_read_access(self): self.client.force_login(self.user) self.elts_process.corpus.memberships.filter(user=self.user).delete() @@ -3034,7 +3038,7 @@ class TestProcesses(FixtureAPITestCase): self.client.force_login(self.user) self.elts_process.activity_state = ActivityState.Disabled self.elts_process.save() - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post(reverse("api:process-select-failures", kwargs={"pk": str(self.elts_process.id)})) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual( @@ -3083,7 +3087,7 @@ class TestProcesses(FixtureAPITestCase): state=WorkerActivityState.Processed, ) self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:process-select-failures", kwargs={"pk": str(self.elts_process.id)}) ) @@ -3119,7 +3123,7 @@ class TestProcesses(FixtureAPITestCase): self.assertCountEqual(list(self.user.selections.values_list("element__name", flat=True)), []) self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.post( reverse("api:process-select-failures", kwargs={"pk": str(self.elts_process.id)}) ) diff --git a/arkindex/process/tests/test_repos.py b/arkindex/process/tests/test_repos.py index d5c32e7a163cfc05a0450efa57d33d5c5afc2444..0d1854cafb2f436491855def6e96e7cc1f132e1e 100644 --- a/arkindex/process/tests/test_repos.py +++ b/arkindex/process/tests/test_repos.py @@ -1,3 +1,4 @@ + from django.urls import reverse from rest_framework import status from rest_framework.serializers import DateTimeField @@ -54,19 +55,11 @@ class TestRepositories(FixtureTestCase): response = self.client.get(reverse("api:repository-list")) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_list_repository_external_user(self): - self.client.force_login(self.user) - with self.assertNumQueries(5): - response = self.client.get(reverse("api:repository-list")) - self.assertEqual(response.status_code, status.HTTP_200_OK) - data = response.json() - self.assertCountEqual(data["results"], []) - def test_list_repository_user_readable(self): self.repo.memberships.create(user=self.user, level=Role.Admin.value) self.repo_2.memberships.create(user=self.user, level=Role.Guest.value) self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(6): response = self.client.get(reverse("api:repository-list")) self.assertEqual(response.status_code, status.HTTP_200_OK) data = response.json() @@ -99,14 +92,6 @@ class TestRepositories(FixtureTestCase): self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.assertEqual(response.json(), {"detail": "Not found."}) - def test_repository_retrieve_no_right(self): - self.client.force_login(self.user) - self.assertFalse(self.user.is_admin) - response = self.client.get( - reverse("api:repository-retrieve", kwargs={"pk": str(self.repo.id)}) - ) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_repository_retrieve(self): """ An user with an access right on a repository is able to retrieve it without its clone URL @@ -138,7 +123,7 @@ class TestRepositories(FixtureTestCase): task = process.tasks.get() self.repo_2.memberships.create(user=self.user, level=Role.Guest.value) - with self.assertNumQueries(4): + with self.assertNumQueries(3): response = self.client.get( reverse("api:repository-retrieve", kwargs={"pk": str(self.repo_2.id)}), HTTP_AUTHORIZATION=f"Ponos {task.token}", @@ -165,7 +150,7 @@ class TestRepositories(FixtureTestCase): task = process.tasks.get() self.repo_2.memberships.create(user=self.user, level=Role.Guest.value) - with self.assertNumQueries(4): + with self.assertNumQueries(3): response = self.client.get( reverse("api:repository-retrieve", kwargs={"pk": str(self.repo_2.id)}), HTTP_AUTHORIZATION=f"Ponos {task.token}", @@ -182,7 +167,7 @@ class TestRepositories(FixtureTestCase): def test_repository_retrieve_disabled_repo(self): self.repo_2.memberships.create(user=self.user, level=Role.Guest.value) self.client.force_login(self.user) - with self.assertNumQueries(5): + with self.assertNumQueries(4): response = self.client.get(reverse("api:repository-retrieve", kwargs={"pk": str(self.repo_2.id)})) self.assertEqual(response.status_code, status.HTTP_200_OK) data = response.json() @@ -230,17 +215,6 @@ class TestRepositories(FixtureTestCase): self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.assertEqual(response.json(), {"detail": "Not found."}) - def test_revision_retrieve_read_right(self): - """ - A user with no right or a read right is not allowed to retrieve a revision - """ - self.repo.memberships.create(user=self.user, level=Role.Guest.value) - self.client.force_login(self.user) - response = self.client.get( - reverse("api:revision-retrieve", kwargs={"pk": str(self.rev.id)}) - ) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_revision_retrieve_contributor_right(self): """ A user with a contributor access to the revision repo is allowed to retrieve information diff --git a/arkindex/process/tests/test_templates.py b/arkindex/process/tests/test_templates.py index 753421c531df72d18077c8ebd6e195267c57ccc3..49f8b82e94667a81a5ffdf37533ef13bcfec8114 100644 --- a/arkindex/process/tests/test_templates.py +++ b/arkindex/process/tests/test_templates.py @@ -1,5 +1,6 @@ import json from datetime import datetime, timezone +from unittest import expectedFailure from rest_framework import status from rest_framework.reverse import reverse @@ -88,7 +89,7 @@ class TestTemplates(FixtureAPITestCase): def test_create(self): self.client.force_login(self.user) - with self.assertNumQueries(15): + with self.assertNumQueries(9): response = self.client.post( reverse( "api:create-process-template", kwargs={"pk": str(self.process_template.id)} @@ -138,6 +139,7 @@ class TestTemplates(FixtureAPITestCase): ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + @expectedFailure def test_create_requires_contributor_access_rights_process(self): new_user = User.objects.create(email="new@test.fr", verified_email=True) self.worker_1.memberships.create(user=new_user, level=Role.Contributor.value) @@ -155,6 +157,7 @@ class TestTemplates(FixtureAPITestCase): {"detail": "You do not have a contributor access to this process."}, ) + @expectedFailure def test_create_requires_access_rights_all_workers(self): new_user = User.objects.create(email="new@test.fr", verified_email=True) self.private_corpus.memberships.create(user=new_user, level=Role.Contributor.value) @@ -177,7 +180,7 @@ class TestTemplates(FixtureAPITestCase): self.process.corpus = None if mode == ProcessMode.Repository else self.corpus self.process.save() - with self.assertNumQueries(11): + with self.assertNumQueries(5): response = self.client.post( reverse("api:create-process-template", kwargs={"pk": str(self.template.id)}), data=json.dumps({"process_id": str(self.process.id)}), @@ -193,7 +196,7 @@ class TestTemplates(FixtureAPITestCase): self.client.force_login(self.user) local_process = self.user.processes.get(mode=ProcessMode.Local) - with self.assertNumQueries(11): + with self.assertNumQueries(5): response = self.client.post( reverse("api:create-process-template", kwargs={"pk": str(self.template.id)}), data=json.dumps({"process_id": str(local_process.id)}), @@ -205,6 +208,7 @@ class TestTemplates(FixtureAPITestCase): local_process.refresh_from_db() self.assertEqual(local_process.template, None) + @expectedFailure def test_apply_requires_contributor_rights_on_template(self): """Raise 403 if the user does not have rights on template """ @@ -227,6 +231,7 @@ class TestTemplates(FixtureAPITestCase): {"detail": "You do not have a contributor access to this process."}, ) + @expectedFailure def test_apply_requires_contributor_rights_on_process(self): """Raise 403 if the user does not have rights on the target process """ @@ -249,6 +254,7 @@ class TestTemplates(FixtureAPITestCase): {"detail": "You do not have a contributor access to this process."}, ) + @expectedFailure def test_apply_requires_access_rights_all_workers(self): """Raise 403 if the user does not have rights on all workers concerned """ @@ -303,7 +309,7 @@ class TestTemplates(FixtureAPITestCase): def test_apply(self): self.assertIsNotNone(self.version_2.docker_image_id) self.client.force_login(self.user) - with self.assertNumQueries(18): + with self.assertNumQueries(11): response = self.client.post( reverse("api:apply-process-template", kwargs={"pk": str(self.template.id)}), data=json.dumps({"process_id": str(self.process.id)}), @@ -336,7 +342,7 @@ class TestTemplates(FixtureAPITestCase): self.version_2.save() self.client.force_login(self.user) - with self.assertNumQueries(18): + with self.assertNumQueries(11): response = self.client.post( reverse("api:apply-process-template", kwargs={"pk": str(self.template.id)}), data=json.dumps({"process_id": str(self.process.id)}), @@ -356,7 +362,7 @@ class TestTemplates(FixtureAPITestCase): parents=[], ) # Apply a template that has two other worker runs - with self.assertNumQueries(20): + with self.assertNumQueries(13): response = self.client.post( reverse("api:apply-process-template", kwargs={"pk": str(self.template.id)}), data=json.dumps({"process_id": str(process.id)}), @@ -384,7 +390,7 @@ class TestTemplates(FixtureAPITestCase): self.version_2.state = WorkerVersionState.Error self.version_2.save() self.client.force_login(self.user) - with self.assertNumQueries(14): + with self.assertNumQueries(7): response = self.client.post( reverse("api:apply-process-template", kwargs={"pk": str(self.template.id)}), data=json.dumps({"process_id": str(self.process.id)}), @@ -399,7 +405,7 @@ class TestTemplates(FixtureAPITestCase): self.worker_2.archived = datetime.now(timezone.utc) self.worker_2.save() self.client.force_login(self.user) - with self.assertNumQueries(14): + with self.assertNumQueries(7): response = self.client.post( reverse("api:apply-process-template", kwargs={"pk": str(self.template.id)}), data=json.dumps({"process_id": str(self.process.id)}), @@ -415,7 +421,7 @@ class TestTemplates(FixtureAPITestCase): self.model_version.save() self.client.force_login(self.user) - with self.assertNumQueries(14): + with self.assertNumQueries(7): response = self.client.post( reverse("api:apply-process-template", kwargs={"pk": str(self.template.id)}), data=json.dumps({"process_id": str(self.process.id)}), @@ -433,7 +439,7 @@ class TestTemplates(FixtureAPITestCase): self.model.save() self.client.force_login(self.user) - with self.assertNumQueries(14): + with self.assertNumQueries(7): response = self.client.post( reverse("api:apply-process-template", kwargs={"pk": str(self.template.id)}), data=json.dumps({"process_id": str(self.process.id)}), @@ -452,7 +458,7 @@ class TestTemplates(FixtureAPITestCase): self.process.mode = mode self.process.save() - with self.assertNumQueries(14): + with self.assertNumQueries(7): response = self.client.post( reverse("api:apply-process-template", kwargs={"pk": str(self.template.id)}), data=json.dumps({"process_id": str(self.process.id)}), @@ -470,7 +476,7 @@ class TestTemplates(FixtureAPITestCase): self.client.force_login(self.user) local_process = self.user.processes.get(mode=ProcessMode.Local) - with self.assertNumQueries(12): + with self.assertNumQueries(6): response = self.client.post( reverse("api:apply-process-template", kwargs={"pk": str(self.template.id)}), data=json.dumps({"process_id": str(local_process.id)}), @@ -488,7 +494,7 @@ class TestTemplates(FixtureAPITestCase): self.client.force_login(self.user) process = Process.objects.filter(mode=ProcessMode.Repository).first() - with self.assertNumQueries(12): + with self.assertNumQueries(6): response = self.client.post( reverse("api:apply-process-template", kwargs={"pk": str(self.template.id)}), data=json.dumps({"process_id": str(process.id)}), @@ -504,7 +510,7 @@ class TestTemplates(FixtureAPITestCase): def test_list_ignores_configuration_filter(self): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.get( reverse("api:process-list"), data={"mode": "template", "with_tasks": True}, diff --git a/arkindex/process/tests/test_user_workerruns.py b/arkindex/process/tests/test_user_workerruns.py index f42698d2de6d3d1c1cc89cbe7ee0bbf934faf58e..69c05a1d5bd1405141330764b141cef21d5a526a 100644 --- a/arkindex/process/tests/test_user_workerruns.py +++ b/arkindex/process/tests/test_user_workerruns.py @@ -1,5 +1,5 @@ from datetime import datetime -from unittest.mock import patch +from unittest.mock import call, patch from django.urls import reverse from django.utils import timezone @@ -15,7 +15,7 @@ from arkindex.process.models import ( WorkerVersionState, ) from arkindex.project.tests import FixtureAPITestCase -from arkindex.training.models import Model, ModelVersionState +from arkindex.training.models import Model, ModelVersion, ModelVersionState from arkindex.users.models import Right, Role, User @@ -203,7 +203,7 @@ class TestUserWorkerRuns(FixtureAPITestCase): def test_create_user_run(self): self.client.force_login(self.user) - with self.assertNumQueries(10): + with self.assertNumQueries(7): response = self.client.post( reverse("api:user-worker-run-create"), data={"worker_version_id": str(self.other_version.id)} @@ -257,7 +257,7 @@ class TestUserWorkerRuns(FixtureAPITestCase): Right.objects.create(user=test_user, content_object=self.other_version.worker, level=Role.Contributor.value) self.client.force_login(test_user) self.assertFalse(Process.objects.filter(mode=ProcessMode.Local, creator=test_user).exists()) - with self.assertNumQueries(10): + with self.assertNumQueries(8): response = self.client.post( reverse("api:user-worker-run-create"), data={"worker_version_id": str(self.other_version.id)} @@ -278,7 +278,7 @@ class TestUserWorkerRuns(FixtureAPITestCase): def test_create_user_run_wv_has_revision(self): Right.objects.create(user=self.user, content_object=self.version_1.worker, level=Role.Contributor.value) self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post( reverse("api:user-worker-run-create"), data={"worker_version_id": str(self.version_1.id)} @@ -290,7 +290,7 @@ class TestUserWorkerRuns(FixtureAPITestCase): Right.objects.create(user=self.user, content_object=self.version_1.worker, level=Role.Contributor.value) test_version = self.version_1.worker.versions.create(configuration={}, version=7) self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.post( reverse("api:user-worker-run-create"), data={"worker_version_id": str(test_version.id)} @@ -303,7 +303,7 @@ class TestUserWorkerRuns(FixtureAPITestCase): self.custom_version.worker.save() self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:user-worker-run-create"), data={"worker_version_id": str(self.custom_version.id)} @@ -312,21 +312,25 @@ class TestUserWorkerRuns(FixtureAPITestCase): self.assertEqual(response.json(), {"worker_version_id": ["The worker used to create a local worker run must not be archived."]}) - def test_create_user_run_worker_not_executable(self): - test_user = User.objects.get(email="user2@user.fr") - Right.objects.create(user=test_user, content_object=self.other_version.worker, level=Role.Guest.value) - self.client.force_login(test_user) - with self.assertNumQueries(6): + @patch("arkindex.users.utils.get_max_level", return_value=Role.Guest.value) + def test_create_user_run_worker_not_executable(self, get_max_level_mock): + self.client.force_login(self.user) + + with self.assertNumQueries(4): response = self.client.post( reverse("api:user-worker-run-create"), data={"worker_version_id": str(self.other_version.id)} ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.json(), {"worker_version_id": ["You do not have contributor access to this worker."]}) + self.assertEqual(get_max_level_mock.call_count, 1) + self.assertEqual(get_max_level_mock.call_args, call(self.user, self.other_version.worker)) + def test_create_user_run_mv_doesnt_exist(self): self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.post( reverse("api:user-worker-run-create"), data={"worker_version_id": str(self.other_version.id), "model_version_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"} @@ -336,7 +340,7 @@ class TestUserWorkerRuns(FixtureAPITestCase): def test_create_user_run_wc_doesnt_exist(self): self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.post( reverse("api:user-worker-run-create"), data={"worker_version_id": str(self.other_version.id), "configuration_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"} @@ -344,23 +348,31 @@ class TestUserWorkerRuns(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.json(), {"configuration_id": ['Invalid pk "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" - object does not exist.']}) - def test_create_user_run_model_no_access(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=ModelVersion.objects.none()) + def test_create_user_run_model_no_access(self, filter_rights_mock): self.client.force_login(self.user) - with self.assertNumQueries(10): + + with self.assertNumQueries(5): response = self.client.post( reverse("api:user-worker-run-create"), data={"worker_version_id": str(self.other_version.id), "model_version_id": str(self.model_version.id)} ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.json(), {"model_version_id": ["You do not have guest access to this model."]}) + self.assertListEqual(filter_rights_mock.call_args_list, [ + call(self.user, Model, Role.Guest.value), + call(self.user, Model, Role.Contributor.value), + ]) + def test_create_user_run_model_archived(self): self.model.archived = datetime.now(timezone.utc) self.model.save() self.model.memberships.create(user=self.user, level=Role.Contributor.value) self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.post( reverse("api:user-worker-run-create"), data={"worker_version_id": str(self.other_version.id), "model_version_id": str(self.model_version.id)} @@ -378,7 +390,7 @@ class TestUserWorkerRuns(FixtureAPITestCase): self.client.force_login(self.user) - with self.assertNumQueries(13): + with self.assertNumQueries(10): response = self.client.post( reverse("api:user-worker-run-create"), data={ @@ -445,7 +457,7 @@ class TestUserWorkerRuns(FixtureAPITestCase): def test_create_user_run_duplicate(self): self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.post( reverse("api:user-worker-run-create"), data={ diff --git a/arkindex/process/tests/test_worker_configurations.py b/arkindex/process/tests/test_worker_configurations.py index e56799ee044655b397831ee3b8e6148ac0e23673..e1e7b9df25bc2a2aba5990b69019d370aad1f83a 100644 --- a/arkindex/process/tests/test_worker_configurations.py +++ b/arkindex/process/tests/test_worker_configurations.py @@ -1,3 +1,5 @@ +from unittest.mock import call, patch + from django.urls import reverse from rest_framework import status @@ -30,55 +32,32 @@ class TestWorkerConfigurations(FixtureAPITestCase): def test_list_requires_login(self): with self.assertNumQueries(0): response = self.client.get(reverse("api:worker-configurations", kwargs={"pk": str(self.worker_1.id)})) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) def test_list_requires_verified(self): self.user.verified_email = False self.user.save() self.client.force_login(self.user) + with self.assertNumQueries(2): response = self.client.get(reverse("api:worker-configurations", kwargs={"pk": str(self.worker_1.id)})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_list_no_rights(self): - self.worker_1.repository.memberships.filter(user=self.user).delete() - self.worker_1.memberships.filter(user=self.user).delete() + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_list_no_rights(self, has_access_mock): self.client.force_login(self.user) - with self.assertNumQueries(6): + + with self.assertNumQueries(3): response = self.client.get(reverse("api:worker-configurations", kwargs={"pk": str(self.worker_1.id)})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertEqual(response.json(), {"detail": "You do not have a guest access to this worker."}) - def test_list_worker_rights(self): - """ - A user is able to list worker configurations if they have a guest access on the worker - """ - config_1 = self.worker_2.configurations.create(name="config_1", configuration={"key": "value"}) - config_2 = self.worker_2.configurations.create(name="config_2", configuration={"dulce": "et decorum est"}) - self.worker_2.memberships.all().delete() - self.worker_2.repository.memberships.all().delete() - self.worker_2.memberships.create(user=self.user, level=Role.Guest.value) - - self.client.force_login(self.user) - with self.assertNumQueries(7): - response = self.client.get(reverse("api:worker-configurations", kwargs={"pk": str(self.worker_2.id)})) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertListEqual(response.json()["results"], [{ - "id": str(config_1.id), - "name": "config_1", - "configuration": {"key": "value"}, - "archived": False, - }, { - "id": str(config_2.id), - "name": "config_2", - "configuration": {"dulce": "et decorum est"}, - "archived": False, - }]) + self.assertEqual(response.json(), {"detail": "You do not have a guest access to this worker."}) + self.assertEqual(has_access_mock.call_args_list, [ + call(self.user, self.worker_1, Role.Guest.value, skip_public=False), + call(self.user, self.repo, Role.Guest.value, skip_public=False), + ]) - def test_list_repo_rights(self): - """ - A user is able to list worker configurations if they have a guest access on the repository - """ + def test_list(self): config_1 = self.worker_2.configurations.create(name="config_1", configuration={"key": "value"}) config_2 = self.worker_2.configurations.create(name="config_2", configuration={"dulce": "et decorum est"}) # Only non-archived configurations should be listed @@ -87,14 +66,12 @@ class TestWorkerConfigurations(FixtureAPITestCase): configuration={"deprecated": "config"}, archived=True, ) - self.worker_2.memberships.all().delete() - self.worker_2.repository.memberships.all().delete() - self.worker_2.repository.memberships.create(user=self.user, level=Role.Guest.value) self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.get(reverse("api:worker-configurations", kwargs={"pk": str(self.worker_2.id)})) self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertListEqual(response.json()["results"], [{ "id": str(config_1.id), "name": "config_1", @@ -119,7 +96,7 @@ class TestWorkerConfigurations(FixtureAPITestCase): ) self.client.force_login(self.user) - with self.assertNumQueries(10): + with self.assertNumQueries(5): response = self.client.get(reverse("api:worker-configurations", kwargs={"pk": str(self.worker_1.id)}), {"archived": "true"}) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -143,29 +120,23 @@ class TestWorkerConfigurations(FixtureAPITestCase): response = self.client.post(reverse("api:worker-configurations", kwargs={"pk": str(self.worker_1.id)})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_create_no_rights(self): - self.worker_1.repository.memberships.filter(user=self.user).delete() + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_create_no_rights(self, has_access_mock): self.client.force_login(self.user) - with self.assertNumQueries(7): + + with self.assertNumQueries(3): response = self.client.post(reverse("api:worker-configurations", kwargs={"pk": str(self.worker_1.id)})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertEqual(response.json(), {"detail": "You do not have contributor access to this worker."}) - def test_create_contributor_ok(self): - self.worker_1.repository.memberships.filter(user=self.user).delete() - self.worker_1.memberships.create(user=self.user, level=Role.Contributor.value) - self.client.force_login(self.user) - with self.assertNumQueries(7): - response = self.client.post( - reverse("api:worker-configurations", kwargs={"pk": str(self.worker_1.id)}), - data={"name": "test_name", "configuration": {"aa": "bb"}}, - format="json", - ) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(response.json(), {"detail": "You do not have contributor access to this worker."}) + self.assertEqual(has_access_mock.call_args_list, [ + call(self.user, self.worker_1, Role.Contributor.value, skip_public=False), + call(self.user, self.repo, Role.Contributor.value, skip_public=False), + ]) def test_create_empty(self): self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(3): response = self.client.post(reverse("api:worker-configurations", kwargs={"pk": str(self.worker_1.id)})) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), { @@ -179,7 +150,7 @@ class TestWorkerConfigurations(FixtureAPITestCase): self.worker_2.configurations.create(name=name, configuration={"key": "value"}) self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(4): response = self.client.post( reverse("api:worker-configurations", kwargs={"pk": str(self.worker_2.id)}), data={"name": name, "configuration": {"key": "value", "cahuete": "bidule"}}, @@ -197,7 +168,7 @@ class TestWorkerConfigurations(FixtureAPITestCase): test_config = self.worker_2.configurations.create(name="config-name", configuration=config) self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(4): response = self.client.post( reverse("api:worker-configurations", kwargs={"pk": str(self.worker_2.id)}), data={"name": "New configuration", "configuration": config}, @@ -217,7 +188,7 @@ class TestWorkerConfigurations(FixtureAPITestCase): test_config = self.worker_2.configurations.create(name="a config name", configuration=config) self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(4): response = self.client.post( reverse("api:worker-configurations", kwargs={"pk": str(self.worker_2.id)}), data={"name": "a config name", "configuration": config}, @@ -240,7 +211,7 @@ class TestWorkerConfigurations(FixtureAPITestCase): test_config_2 = self.worker_2.configurations.create(name="some config", configuration=config_2) self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(4): response = self.client.post( reverse("api:worker-configurations", kwargs={"pk": str(self.worker_2.id)}), data={"name": "a config name", "configuration": config_2}, @@ -266,7 +237,7 @@ class TestWorkerConfigurations(FixtureAPITestCase): self.worker_2.configurations.create(name="config_1", configuration={"key": "value"}) self.client.force_login(self.user) - with self.assertNumQueries(10): + with self.assertNumQueries(5): response = self.client.post( reverse("api:worker-configurations", kwargs={"pk": str(self.worker_2.id)}), data={"name": name, "configuration": config}, @@ -287,7 +258,7 @@ class TestWorkerConfigurations(FixtureAPITestCase): self.worker_2.configurations.create(name="config_1", configuration={"key": "value"}) self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(3): response = self.client.post( reverse("api:worker-configurations", kwargs={"pk": str(self.worker_2.id)}), data={"name": name}, @@ -298,7 +269,7 @@ class TestWorkerConfigurations(FixtureAPITestCase): def test_create_not_json(self): self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(3): response = self.client.post( reverse("api:worker-configurations", kwargs={"pk": str(self.worker_1.id)}), data={"name": "test_config", "configuration": []}, format="json" @@ -310,7 +281,7 @@ class TestWorkerConfigurations(FixtureAPITestCase): def test_create_null_config(self): self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(3): response = self.client.post( reverse("api:worker-configurations", kwargs={"pk": str(self.worker_1.id)}), data={"name": "test_config", "configuration": None}, format="json" @@ -322,7 +293,7 @@ class TestWorkerConfigurations(FixtureAPITestCase): def test_create_empty_config(self): self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(3): response = self.client.post( reverse("api:worker-configurations", kwargs={"pk": str(self.worker_1.id)}), data={"name": "test_config", "configuration": {}}, format="json" @@ -353,18 +324,24 @@ class TestWorkerConfigurations(FixtureAPITestCase): response = self.client.get(reverse("api:configuration-retrieve", kwargs={"pk": str(self.worker_config.id)})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_retrieve_no_rights(self): - self.worker_1.repository.memberships.filter(user=self.user).delete() + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_retrieve_no_rights(self, has_access_mock): self.client.force_login(self.user) - with self.assertNumQueries(7): + + with self.assertNumQueries(3): response = self.client.get(reverse("api:configuration-retrieve", kwargs={"pk": str(self.worker_config.id)})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(response.json(), {"detail": "You do not have a guest access to this worker."}) + self.assertEqual(has_access_mock.call_args_list, [ + call(self.user, self.worker_1, Role.Guest.value, skip_public=False), + call(self.user, self.repo, Role.Guest.value, skip_public=False), + ]) def test_retrieve(self): self.worker_config.worker.memberships.get_or_create(user=self.user, level=Role.Guest.value) self.client.force_login(self.user) - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.get(reverse("api:configuration-retrieve", kwargs={"pk": str(self.worker_config.id)})) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertDictEqual(response.json(), { @@ -394,79 +371,33 @@ class TestWorkerConfigurations(FixtureAPITestCase): ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_update_no_rights(self): - """ - Cannot update a configuration without any access rights - """ - self.worker_1.memberships.all().delete() - self.worker_1.repository.memberships.all().delete() + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_update_requires_contributor(self, has_access_mock): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.put( reverse("api:configuration-retrieve", kwargs={"pk": str(self.worker_config.id)}), data={"archived": True} ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertEqual(response.json(), { - "detail": "You do not have a contributor access to this worker.", - }) - def test_update_guest_rights(self): - """ - Cannot update a configuration when only having guest rights - """ - self.worker_1.memberships.all().delete() - self.worker_1.repository.memberships.all().delete() - self.worker_1.memberships.create(user=self.user, level=Role.Guest.value) - self.worker_1.repository.memberships.create(user=self.user, level=Role.Guest.value) - self.client.force_login(self.user) - - with self.assertNumQueries(6): - response = self.client.put( - reverse("api:configuration-retrieve", kwargs={"pk": str(self.worker_config.id)}), - data={"archived": True} - ) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(response.json(), { "detail": "You do not have a contributor access to this worker.", }) + self.assertEqual(has_access_mock.call_args_list, [ + call(self.user, self.worker_1, Role.Contributor.value, skip_public=False), + call(self.user, self.repo, Role.Contributor.value, skip_public=False), + ]) - def test_update_contributor_repository(self): - """ - Can update a configuration with contributor rights on the repository - """ - self.worker_1.memberships.all().delete() - self.worker_1.repository.memberships.all().delete() - self.worker_1.repository.memberships.create(user=self.user, level=Role.Contributor.value) - self.client.force_login(self.user) - - self.assertFalse(self.worker_config.archived) - - with self.assertNumQueries(8): - response = self.client.put( - reverse("api:configuration-retrieve", kwargs={"pk": str(self.worker_config.id)}), - data={"archived": True, "name": "new name"} - ) - self.assertEqual(response.status_code, status.HTTP_200_OK) - - self.worker_config.refresh_from_db() - self.assertEqual(self.worker_config.name, "new name") - self.assertDictEqual(self.worker_config.configuration, {"key": "value"}) - self.assertTrue(self.worker_config.archived) - - def test_update_contributor_worker(self): + def test_update(self): """ Can update a configuration with contributor rights on the worker """ - self.worker_1.memberships.all().delete() - self.worker_1.repository.memberships.all().delete() - self.worker_1.memberships.create(user=self.user, level=Role.Contributor.value) self.client.force_login(self.user) - self.assertFalse(self.worker_config.archived) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.put( reverse("api:configuration-retrieve", kwargs={"pk": str(self.worker_config.id)}), data={"archived": True, "name": "new name"} @@ -478,28 +409,6 @@ class TestWorkerConfigurations(FixtureAPITestCase): self.assertDictEqual(self.worker_config.configuration, {"key": "value"}) self.assertTrue(self.worker_config.archived) - def test_update_admin(self): - """ - Admins can update any configuration - """ - self.worker_1.memberships.all().delete() - self.worker_1.repository.memberships.all().delete() - self.client.force_login(self.superuser) - - self.assertFalse(self.worker_config.archived) - - with self.assertNumQueries(5): - response = self.client.put( - reverse("api:configuration-retrieve", kwargs={"pk": str(self.worker_config.id)}), - data={"archived": True, "name": "a new name"} - ) - self.assertEqual(response.status_code, status.HTTP_200_OK) - - self.worker_config.refresh_from_db() - self.assertEqual(self.worker_config.name, "a new name") - self.assertDictEqual(self.worker_config.configuration, {"key": "value"}) - self.assertTrue(self.worker_config.archived) - def test_update_ignored_fields(self): """ Fields that should not be editable are ignored when sent in update requests @@ -539,7 +448,7 @@ class TestWorkerConfigurations(FixtureAPITestCase): self.worker_config.save() self.assertTrue(self.worker_config.archived) - with self.assertNumQueries(9): + with self.assertNumQueries(4): response = self.client.put( reverse("api:configuration-retrieve", kwargs={"pk": str(self.worker_config.id)}), data={ @@ -567,7 +476,7 @@ class TestWorkerConfigurations(FixtureAPITestCase): self.worker_config.save() self.assertTrue(self.worker_config.archived) - with self.assertNumQueries(10): + with self.assertNumQueries(5): response = self.client.put( reverse("api:configuration-retrieve", kwargs={"pk": str(self.worker_config.id)}), data={ @@ -590,7 +499,7 @@ class TestWorkerConfigurations(FixtureAPITestCase): self.worker_1.configurations.create(name="new name", configuration={"key": "value", "key2": "value2"}) self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(4): response = self.client.put( reverse("api:configuration-retrieve", kwargs={"pk": str(self.worker_config.id)}), data={ @@ -625,68 +534,26 @@ class TestWorkerConfigurations(FixtureAPITestCase): ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_partial_update_no_rights(self): - """ - Cannot update a configuration without any access rights - """ - self.worker_1.memberships.all().delete() - self.worker_1.repository.memberships.all().delete() + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_partial_update_requires_contributor(self, has_access_mock): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.patch( reverse("api:configuration-retrieve", kwargs={"pk": str(self.worker_config.id)}), data={"archived": True} ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertEqual(response.json(), { - "detail": "You do not have a contributor access to this worker.", - }) - - def test_partial_update_guest_rights(self): - """ - Cannot update a configuration when only having guest rights - """ - self.worker_1.memberships.all().delete() - self.worker_1.repository.memberships.all().delete() - self.worker_1.memberships.create(user=self.user, level=Role.Guest.value) - self.worker_1.repository.memberships.create(user=self.user, level=Role.Guest.value) - self.client.force_login(self.user) - with self.assertNumQueries(6): - response = self.client.patch( - reverse("api:configuration-retrieve", kwargs={"pk": str(self.worker_config.id)}), - data={"archived": True} - ) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(response.json(), { "detail": "You do not have a contributor access to this worker.", }) + self.assertEqual(has_access_mock.call_args_list, [ + call(self.user, self.worker_1, Role.Contributor.value, skip_public=False), + call(self.user, self.repo, Role.Contributor.value, skip_public=False), + ]) - def test_partial_update_contributor_repository(self): - """ - Can partial update a configuration with contributor rights on the repository - """ - self.worker_1.memberships.all().delete() - self.worker_1.repository.memberships.all().delete() - self.worker_1.repository.memberships.create(user=self.user, level=Role.Contributor.value) - self.client.force_login(self.user) - - self.assertFalse(self.worker_config.archived) - - with self.assertNumQueries(7): - response = self.client.patch( - reverse("api:configuration-retrieve", kwargs={"pk": str(self.worker_config.id)}), - data={"archived": True} - ) - self.assertEqual(response.status_code, status.HTTP_200_OK) - - self.worker_config.refresh_from_db() - self.assertEqual(self.worker_config.name, "config time") - self.assertDictEqual(self.worker_config.configuration, {"key": "value"}) - self.assertTrue(self.worker_config.archived) - - def test_partial_update_contributor_worker(self): + def test_partial_update(self): """ Can partial update a configuration with contributor rights on the worker """ @@ -697,28 +564,6 @@ class TestWorkerConfigurations(FixtureAPITestCase): self.assertFalse(self.worker_config.archived) - with self.assertNumQueries(6): - response = self.client.patch( - reverse("api:configuration-retrieve", kwargs={"pk": str(self.worker_config.id)}), - data={"archived": True} - ) - self.assertEqual(response.status_code, status.HTTP_200_OK) - - self.worker_config.refresh_from_db() - self.assertEqual(self.worker_config.name, "config time") - self.assertDictEqual(self.worker_config.configuration, {"key": "value"}) - self.assertTrue(self.worker_config.archived) - - def test_partial_update_admin(self): - """ - Admins can partial update any configuration - """ - self.worker_1.memberships.all().delete() - self.worker_1.repository.memberships.all().delete() - self.client.force_login(self.superuser) - - self.assertFalse(self.worker_config.archived) - with self.assertNumQueries(4): response = self.client.patch( reverse("api:configuration-retrieve", kwargs={"pk": str(self.worker_config.id)}), @@ -770,7 +615,7 @@ class TestWorkerConfigurations(FixtureAPITestCase): self.worker_config.save() self.assertTrue(self.worker_config.archived) - with self.assertNumQueries(9): + with self.assertNumQueries(4): response = self.client.patch( reverse("api:configuration-retrieve", kwargs={"pk": str(self.worker_config.id)}), data={ @@ -796,7 +641,7 @@ class TestWorkerConfigurations(FixtureAPITestCase): self.worker_1.configurations.create(name="new name", configuration={"key": "value", "key2": "value2"}) self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(4): response = self.client.patch( reverse("api:configuration-retrieve", kwargs={"pk": str(self.worker_config.id)}), data={ diff --git a/arkindex/process/tests/test_workeractivity.py b/arkindex/process/tests/test_workeractivity.py index 8fbb6ea6e38f15a8ca0c468b73d091743c1f4b21..1116df9052a6886a7383621b2b94b3e7d8cc9af4 100644 --- a/arkindex/process/tests/test_workeractivity.py +++ b/arkindex/process/tests/test_workeractivity.py @@ -798,10 +798,11 @@ class TestWorkerActivity(FixtureTestCase): response = self.client.get(reverse("api:corpus-activity", kwargs={"corpus": self.corpus.id})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_list_corpus_acl(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_list_corpus_acl(self, has_access_mock): private_corpus = Corpus.objects.create(name="can't touch this") self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.get(reverse("api:corpus-activity", kwargs={"corpus": private_corpus.id})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), {"detail": "You do not have guest access to this corpus."}) @@ -895,7 +896,7 @@ class TestWorkerActivity(FixtureTestCase): ) self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.get( reverse("api:corpus-activity", kwargs={"corpus": self.corpus.id}), {"process_id": str(process.id)} diff --git a/arkindex/process/tests/test_workeractivity_stats.py b/arkindex/process/tests/test_workeractivity_stats.py index b7b9c51d1b4e40d8ce23140c5cff2e10bf4a8115..3e8e292a6e6d08e986a2107df5be98447a77f3d2 100644 --- a/arkindex/process/tests/test_workeractivity_stats.py +++ b/arkindex/process/tests/test_workeractivity_stats.py @@ -1,6 +1,7 @@ import itertools import uuid from datetime import datetime, timedelta, timezone +from unittest.mock import patch from django.db.models.functions import Now from django.urls import reverse @@ -121,9 +122,10 @@ class TestWorkerActivityStats(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), {"detail": "Authentication credentials were not provided."}) - def test_corpus_private(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_corpus_private(self, has_access_mock): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.get( reverse("api:corpus-activity-stats", kwargs={"corpus": str(self.private_corpus.id)}) ) @@ -148,7 +150,7 @@ class TestWorkerActivityStats(FixtureAPITestCase): user.save() self.private_corpus.memberships.create(user=user, level=Role.Guest.value) self.client.force_login(user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.get( reverse("api:corpus-activity-stats", kwargs={"corpus": str(self.private_corpus.id)}) ) @@ -351,16 +353,17 @@ class TestWorkerActivityStats(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), {"detail": "Authentication credentials were not provided."}) - def test_process_private(self): + @patch("arkindex.project.mixins.get_max_level", return_value=None) + def test_process_private(self, get_max_level_mock): # An inaccessible project self.process_1.corpus = self.private_corpus self.process_1.save() self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.get( reverse("api:process-activity-stats", kwargs={"pk": str(self.process_1.id)}) ) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) def test_process_not_found(self): self.client.force_login(self.user) @@ -372,7 +375,7 @@ class TestWorkerActivityStats(FixtureAPITestCase): def test_process(self): self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.get( reverse("api:process-activity-stats", kwargs={"pk": str(self.process_1.id)}) ) diff --git a/arkindex/process/tests/test_workerruns.py b/arkindex/process/tests/test_workerruns.py index e66ecfc19f7e3ca401855a6f93e66fd684b70d20..4022f9865995fcafb50aaa4a86b4f48f7bcf7525 100644 --- a/arkindex/process/tests/test_workerruns.py +++ b/arkindex/process/tests/test_workerruns.py @@ -1,5 +1,6 @@ import uuid from datetime import datetime, timezone +from unittest.mock import call, patch from django.test import override_settings from django.urls import reverse @@ -110,7 +111,7 @@ class TestWorkerRuns(FixtureAPITestCase): self.worker_1.memberships.all().delete() self.client.force_login(self.user) - with self.assertNumQueries(10): + with self.assertNumQueries(7): response = self.client.get(reverse("api:worker-run-list", kwargs={"pk": str(self.process_1.id)})) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -119,7 +120,7 @@ class TestWorkerRuns(FixtureAPITestCase): def test_list(self): self.client.force_login(self.user) - with self.assertNumQueries(10): + with self.assertNumQueries(7): response = self.client.get(reverse("api:worker-run-list", kwargs={"pk": str(self.process_1.id)})) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -175,7 +176,7 @@ class TestWorkerRuns(FixtureAPITestCase): ) self.client.force_login(self.user) - with self.assertNumQueries(10): + with self.assertNumQueries(7): response = self.client.get(reverse("api:worker-run-list", kwargs={"pk": str(self.process_2.id)})) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -194,14 +195,15 @@ class TestWorkerRuns(FixtureAPITestCase): self.assertDictEqual(response.json(), {"detail": "Authentication credentials were not provided."}) - def test_create_no_execution_right(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_create_no_execution_right(self, has_access_mock): """ An execution access on the target worker version is required to create a worker run """ self.worker_1.memberships.update(level=Role.Guest.value) self.client.force_login(self.user) - with self.assertNumQueries(12): + with self.assertNumQueries(6): response = self.client.post( reverse("api:worker-run-list", kwargs={"pk": str(self.process_2.id)}), data={"worker_version_id": str(self.version_1.id), "parents": []}, @@ -211,11 +213,16 @@ class TestWorkerRuns(FixtureAPITestCase): self.assertEqual(response.json(), {"worker_version_id": ["You do not have an execution access to this worker."]}) + self.assertListEqual(has_access_mock.call_args_list, [ + call(self.user, self.worker_1, Role.Contributor.value, skip_public=False), + call(self.user, self.repo, Role.Contributor.value, skip_public=False), + ]) + def test_create_invalid_version(self): self.client.force_login(self.user) version_id = uuid.uuid4() - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:worker-run-list", kwargs={"pk": str(self.process_2.id)}), data={"worker_version_id": version_id}, @@ -243,8 +250,8 @@ class TestWorkerRuns(FixtureAPITestCase): self.run_1.configuration = configuration self.run_1.save() - # Having a model version set adds two queries, having a configuration adds one - query_count = 11 + bool(model_version) * 2 + bool(configuration) + # Having a model version or a configuration adds one query for each + query_count = 6 + bool(model_version) + bool(configuration) with self.assertNumQueries(query_count): response = self.client.post( @@ -267,7 +274,7 @@ class TestWorkerRuns(FixtureAPITestCase): self.version_1.save() self.client.force_login(self.user) - with self.assertNumQueries(11): + with self.assertNumQueries(6): response = self.client.post( reverse("api:worker-run-list", kwargs={"pk": str(self.process_2.id)}), data={"worker_version_id": str(self.version_1.id), "parents": []}, @@ -282,7 +289,7 @@ class TestWorkerRuns(FixtureAPITestCase): self.worker_1.save() self.client.force_login(self.user) - with self.assertNumQueries(11): + with self.assertNumQueries(6): response = self.client.post( reverse("api:worker-run-list", kwargs={"pk": str(self.process_2.id)}), data={"worker_version_id": str(self.version_1.id), "parents": []}, @@ -299,7 +306,7 @@ class TestWorkerRuns(FixtureAPITestCase): self.version_1.save() self.client.force_login(self.user) - with self.assertNumQueries(13): + with self.assertNumQueries(7): response = self.client.post( reverse("api:worker-run-list", kwargs={"pk": str(self.process_2.id)}), data={"worker_version_id": str(self.version_1.id), "model_version_id": str(self.model_version_1.id)}, @@ -330,7 +337,7 @@ class TestWorkerRuns(FixtureAPITestCase): self.process_2.mode = mode self.process_2.save() - with self.assertNumQueries(11): + with self.assertNumQueries(6): response = self.client.post( reverse("api:worker-run-list", kwargs={"pk": str(self.process_2.id)}), {"worker_version_id": str(self.version_1.id)}, @@ -369,7 +376,7 @@ class TestWorkerRuns(FixtureAPITestCase): self.client.force_login(self.user) - with self.assertNumQueries(11): + with self.assertNumQueries(6): response = self.client.post( reverse("api:worker-run-list", kwargs={"pk": str(process.id)}), data={"worker_version_id": str(self.version_1.id), "parents": []}, @@ -391,7 +398,7 @@ class TestWorkerRuns(FixtureAPITestCase): self.process_2.mode = mode self.process_2.save() - with self.assertNumQueries(13): + with self.assertNumQueries(8): response = self.client.post( reverse("api:worker-run-list", kwargs={"pk": str(self.process_2.id)}), {"worker_version_id": str(self.version_1.id), "parents": []}, @@ -453,7 +460,7 @@ class TestWorkerRuns(FixtureAPITestCase): self.worker_1.repository.memberships.create(user=self.user, level=Role.Contributor.value) self.client.force_login(self.user) - with self.assertNumQueries(13): + with self.assertNumQueries(8): response = self.client.post( reverse("api:worker-run-list", kwargs={"pk": str(self.process_2.id)}), data={"worker_version_id": str(self.version_1.id), "parents": []}, @@ -467,7 +474,7 @@ class TestWorkerRuns(FixtureAPITestCase): """ self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:worker-run-list", kwargs={"pk": str(self.process_2.id)}) ) @@ -480,7 +487,7 @@ class TestWorkerRuns(FixtureAPITestCase): def test_create_configuration(self): self.client.force_login(self.user) - with self.assertNumQueries(14): + with self.assertNumQueries(9): response = self.client.post( reverse("api:worker-run-list", kwargs={"pk": str(self.process_2.id)}), data={ @@ -549,7 +556,7 @@ class TestWorkerRuns(FixtureAPITestCase): def test_create_invalid_configuration(self): self.client.force_login(self.user) - with self.assertNumQueries(12): + with self.assertNumQueries(7): response = self.client.post( reverse("api:worker-run-list", kwargs={"pk": str(self.process_2.id)}), data={"worker_version_id": str(self.version_1.id), "parents": [], "configuration_id": str(self.configuration_2.id)}, @@ -589,7 +596,7 @@ class TestWorkerRuns(FixtureAPITestCase): docker_image_id=self.version_1.docker_image_id, ) - with self.assertNumQueries(13): + with self.assertNumQueries(8): response = self.client.post( reverse("api:worker-run-list", kwargs={"pk": str(self.process_2.id)}), data={"worker_version_id": str(version.id), "parents": []}, @@ -658,7 +665,7 @@ class TestWorkerRuns(FixtureAPITestCase): self.worker_1.memberships.update(level=Role.Guest.value) self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.get( reverse("api:worker-run-details", kwargs={"pk": str(self.run_1.id)}) ) @@ -720,7 +727,7 @@ class TestWorkerRuns(FixtureAPITestCase): def test_retrieve(self): self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.get(reverse("api:worker-run-details", kwargs={"pk": str(self.run_1.id)})) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -849,7 +856,7 @@ class TestWorkerRuns(FixtureAPITestCase): # Check that the gitrefs are retrieved with RetrieveWorkerRun self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.get(reverse("api:worker-run-details", kwargs={"pk": str(self.run_1.id)})) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -1030,7 +1037,7 @@ class TestWorkerRuns(FixtureAPITestCase): def test_update_requires_nothing(self): self.client.force_login(self.user) - with self.assertNumQueries(11): + with self.assertNumQueries(8): response = self.client.put( reverse("api:worker-run-details", kwargs={"pk": str(self.run_1.id)}), data={}, @@ -1064,14 +1071,15 @@ class TestWorkerRuns(FixtureAPITestCase): ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_update_no_project_admin_right(self): + @patch("arkindex.project.mixins.get_max_level", return_value=Role.Contributor.value) + def test_update_no_project_admin_right(self, get_max_level_mock): """ - A user cannot update a worker run if he has no admin access on its process project + A user cannot update a worker run if they have no admin access on its process project """ - self.corpus.memberships.filter(user=self.user).update(level=Role.Guest.value) + self.corpus.memberships.filter(user=self.user).update(level=Role.Contributor.value) self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.put( reverse("api:worker-run-details", kwargs={"pk": str(self.run_1.id)}), data={ @@ -1082,6 +1090,9 @@ class TestWorkerRuns(FixtureAPITestCase): self.assertEqual(response.json(), {"detail": "You do not have an admin access to this process."}) + self.assertEqual(get_max_level_mock.call_count, 1) + self.assertEqual(get_max_level_mock.call_args, call(self.user, self.corpus)) + def test_update_invalid_process_mode(self): """ A user cannot update a worker run on a local process @@ -1132,7 +1143,7 @@ class TestWorkerRuns(FixtureAPITestCase): def test_update_nonexistent_parent(self): self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.put( reverse("api:worker-run-details", kwargs={"pk": str(self.run_1.id)}), data={ @@ -1152,7 +1163,7 @@ class TestWorkerRuns(FixtureAPITestCase): self.client.force_login(self.user) run_2 = self.process_1.worker_runs.create(version=self.version_2) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.put( reverse("api:worker-run-details", kwargs={"pk": str(self.run_1.id)}), data={ @@ -1188,7 +1199,7 @@ class TestWorkerRuns(FixtureAPITestCase): Process field cannot be updated """ self.client.force_login(self.user) - with self.assertNumQueries(11): + with self.assertNumQueries(8): response = self.client.put( reverse("api:worker-run-details", kwargs={"pk": str(self.run_1.id)}), data={ @@ -1251,7 +1262,7 @@ class TestWorkerRuns(FixtureAPITestCase): """ self.client.force_login(self.user) - with self.assertNumQueries(11): + with self.assertNumQueries(8): response = self.client.put( reverse("api:worker-run-details", kwargs={"pk": str(self.run_1.id)}), data={ @@ -1313,7 +1324,7 @@ class TestWorkerRuns(FixtureAPITestCase): # Check generated summary, before updating, it should not be that verbose self.assertEqual(self.run_1.summary, f"Worker Recognizer @ {str(self.run_1.version.id)[:6]}") - with self.assertNumQueries(12): + with self.assertNumQueries(9): response = self.client.put( reverse("api:worker-run-details", kwargs={"pk": self.run_1.id}), data={ @@ -1382,7 +1393,7 @@ class TestWorkerRuns(FixtureAPITestCase): self.client.force_login(self.user) self.assertEqual(self.run_1.configuration, None) - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.put( reverse("api:worker-run-details", kwargs={"pk": self.run_1.id}), data={"parents": [], "configuration_id": str(self.configuration_2.id)}, @@ -1400,7 +1411,7 @@ class TestWorkerRuns(FixtureAPITestCase): self.assertTrue(self.process_1.tasks.exists()) self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.put( reverse("api:worker-run-details", kwargs={"pk": str(self.run_1.id)}), data={ @@ -1435,7 +1446,7 @@ class TestWorkerRuns(FixtureAPITestCase): parents=[], ) - with self.assertNumQueries(10): + with self.assertNumQueries(6): response = self.client.put( reverse("api:worker-run-details", kwargs={"pk": str(run_2.id)}), data={ @@ -1472,7 +1483,7 @@ class TestWorkerRuns(FixtureAPITestCase): ) random_model_version_uuid = str(uuid.uuid4()) - with self.assertNumQueries(9): + with self.assertNumQueries(5): response = self.client.put( reverse("api:worker-run-details", kwargs={"pk": str(run_2.id)}), data={ @@ -1487,7 +1498,8 @@ class TestWorkerRuns(FixtureAPITestCase): "model_version_id": [f'Invalid pk "{random_model_version_uuid}" - object does not exist.'] }) - def test_update_model_version_no_access(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=ModelVersion.objects.none()) + def test_update_model_version_no_access(self, filter_rights_mock): """ Cannot update a worker run with a model_version UUID, when you don't have access to the model version """ @@ -1518,7 +1530,7 @@ class TestWorkerRuns(FixtureAPITestCase): archive_hash="bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", ) - with self.assertNumQueries(9): + with self.assertNumQueries(4): response = self.client.put( reverse("api:worker-run-details", kwargs={"pk": str(run_2.id)}), data={ @@ -1533,7 +1545,13 @@ class TestWorkerRuns(FixtureAPITestCase): "model_version_id": [f'Invalid pk "{model_version_no_access.id}" - object does not exist.'], }) - def test_update_model_version_guest(self): + self.assertListEqual(filter_rights_mock.call_args_list, [ + call(self.user, Model, Role.Guest.value), + call(self.user, Model, Role.Contributor.value), + ]) + + @patch("arkindex.users.managers.BaseACLManager.filter_rights") + def test_update_model_version_guest(self, filter_rights_mock): """ Cannot update a worker run with a model_version when you only have guest access to the model, and the model version has no tag or is not available @@ -1555,6 +1573,17 @@ class TestWorkerRuns(FixtureAPITestCase): parents=[], ) + def filter_rights(user, model, level): + """ + The filter_rights mock needs to return nothing when called for contributor access, + and the models we will test on when called for guest access + """ + if level == Role.Guest.value: + return Model.objects.filter(id__in=(self.model_2.id, self.model_3.id)) + return Model.objects.none() + + filter_rights_mock.side_effect = filter_rights + cases = [ # On a model with a membership giving guest access (self.model_version_2, None, ModelVersionState.Created), @@ -1571,12 +1600,13 @@ class TestWorkerRuns(FixtureAPITestCase): ] for model_version, tag, state in cases: + filter_rights_mock.reset_mock() with self.subTest(model_version=model_version, tag=tag, state=state): model_version.tag = tag model_version.state = state model_version.save() - with self.assertNumQueries(9): + with self.assertNumQueries(5): response = self.client.put( reverse("api:worker-run-details", kwargs={"pk": str(run_2.id)}), data={ @@ -1591,6 +1621,11 @@ class TestWorkerRuns(FixtureAPITestCase): "model_version_id": [f'Invalid pk "{model_version.id}" - object does not exist.'], }) + self.assertListEqual(filter_rights_mock.call_args_list, [ + call(self.user, Model, Role.Guest.value), + call(self.user, Model, Role.Contributor.value), + ]) + def test_update_model_version_unavailable(self): self.client.force_login(self.user) rev_2 = self.repo.revisions.create( @@ -1611,7 +1646,7 @@ class TestWorkerRuns(FixtureAPITestCase): self.model_version_1.state = ModelVersionState.Error self.model_version_1.save() - with self.assertNumQueries(10): + with self.assertNumQueries(6): response = self.client.put( reverse("api:worker-run-details", kwargs={"pk": str(run.id)}), data={ @@ -1643,7 +1678,7 @@ class TestWorkerRuns(FixtureAPITestCase): self.model_1.archived = datetime.now(timezone.utc) self.model_1.save() - with self.assertNumQueries(10): + with self.assertNumQueries(6): response = self.client.put( reverse("api:worker-run-details", kwargs={"pk": str(run.id)}), data={ @@ -1691,7 +1726,7 @@ class TestWorkerRuns(FixtureAPITestCase): ] for model_version in model_versions: with self.subTest(model_version=model_version): - with self.assertNumQueries(13): + with self.assertNumQueries(9): response = self.client.put( reverse("api:worker-run-details", kwargs={"pk": str(run.id)}), data={ @@ -1784,7 +1819,7 @@ class TestWorkerRuns(FixtureAPITestCase): # Check generated summary, before updating, there should be only information about the worker version self.assertEqual(run.summary, f"Worker {self.worker_1.name} @ {str(version_with_model.id)[:6]}") - with self.assertNumQueries(14): + with self.assertNumQueries(10): response = self.client.put( reverse("api:worker-run-details", kwargs={"pk": str(run.id)}), data={ @@ -1876,7 +1911,7 @@ class TestWorkerRuns(FixtureAPITestCase): ) self.client.force_login(self.user) - with self.assertNumQueries(13): + with self.assertNumQueries(10): response = self.client.put( reverse("api:worker-run-details", kwargs={"pk": str(self.run_1.id)}), data={ @@ -1957,8 +1992,8 @@ class TestWorkerRuns(FixtureAPITestCase): configuration=None if configuration else self.configuration_1, ) - # Having a model version set adds two queries, having a configuration adds one - query_count = 8 + bool(model_version) * 2 + bool(configuration) + # Having a model version or a configuration adds one query for each + query_count = 5 + bool(model_version) + bool(configuration) with self.assertNumQueries(query_count): response = self.client.put( @@ -2018,14 +2053,15 @@ class TestWorkerRuns(FixtureAPITestCase): ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_partial_update_no_project_admin_right(self): + @patch("arkindex.project.mixins.get_max_level", return_value=Role.Contributor.value) + def test_partial_update_no_project_admin_right(self, get_max_level_mock): """ - A user cannot update a worker run if he has no admin access on its process project + A user cannot update a worker run if they have no admin access on its process project """ - self.corpus.memberships.filter(user=self.user).update(level=Role.Guest.value) + self.corpus.memberships.filter(user=self.user).update(level=Role.Contributor.value) self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.patch( reverse("api:worker-run-details", kwargs={"pk": str(self.run_1.id)}) ) @@ -2033,6 +2069,9 @@ class TestWorkerRuns(FixtureAPITestCase): self.assertEqual(response.json(), {"detail": "You do not have an admin access to this process."}) + self.assertEqual(get_max_level_mock.call_count, 1) + self.assertEqual(get_max_level_mock.call_args, call(self.user, self.corpus)) + def test_partial_update_invalid_id(self): rev_2 = self.repo.revisions.create( hash="2", @@ -2082,7 +2121,7 @@ class TestWorkerRuns(FixtureAPITestCase): def test_partial_update_nonexistent_parent(self): self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.patch( reverse("api:worker-run-details", kwargs={"pk": str(self.run_1.id)}), data={ @@ -2104,7 +2143,7 @@ class TestWorkerRuns(FixtureAPITestCase): """ self.client.force_login(self.user) - with self.assertNumQueries(11): + with self.assertNumQueries(8): response = self.client.patch( reverse("api:worker-run-details", kwargs={"pk": str(self.run_1.id)}), data={ @@ -2166,7 +2205,7 @@ class TestWorkerRuns(FixtureAPITestCase): """ self.client.force_login(self.user) - with self.assertNumQueries(11): + with self.assertNumQueries(8): response = self.client.patch( reverse("api:worker-run-details", kwargs={"pk": str(self.run_1.id)}), data={ @@ -2227,7 +2266,7 @@ class TestWorkerRuns(FixtureAPITestCase): self.assertEqual(self.run_1.configuration, None) self.assertEqual(self.run_1.summary, f"Worker {self.run_1.version.worker.name} @ {str(self.run_1.version.id)[:6]}") - with self.assertNumQueries(12): + with self.assertNumQueries(9): response = self.client.patch( reverse("api:worker-run-details", kwargs={"pk": self.run_1.id}), data={ @@ -2293,7 +2332,7 @@ class TestWorkerRuns(FixtureAPITestCase): self.client.force_login(self.user) self.assertEqual(self.run_1.configuration, None) - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.patch( reverse("api:worker-run-details", kwargs={"pk": self.run_1.id}), data={"configuration_id": str(self.configuration_2.id)}, @@ -2311,7 +2350,7 @@ class TestWorkerRuns(FixtureAPITestCase): self.assertTrue(self.process_1.tasks.exists()) self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.patch( reverse("api:worker-run-details", kwargs={"pk": str(self.run_1.id)}), data={ @@ -2346,7 +2385,7 @@ class TestWorkerRuns(FixtureAPITestCase): parents=[], ) - with self.assertNumQueries(10): + with self.assertNumQueries(6): response = self.client.patch( reverse("api:worker-run-details", kwargs={"pk": str(run_2.id)}), data={ @@ -2382,7 +2421,7 @@ class TestWorkerRuns(FixtureAPITestCase): ) random_model_version_uuid = str(uuid.uuid4()) - with self.assertNumQueries(9): + with self.assertNumQueries(5): response = self.client.patch( reverse("api:worker-run-details", kwargs={"pk": str(run_2.id)}), data={ @@ -2396,7 +2435,8 @@ class TestWorkerRuns(FixtureAPITestCase): "model_version_id": [f'Invalid pk "{random_model_version_uuid}" - object does not exist.'] }) - def test_partial_update_model_version_no_access(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=ModelVersion.objects.none()) + def test_partial_update_model_version_no_access(self, filter_rights_mock): """ Cannot update a worker run with a model_version UUID, when you don't have access to the model version """ @@ -2421,7 +2461,7 @@ class TestWorkerRuns(FixtureAPITestCase): model_no_access = Model.objects.create(name="Secret model") model_version_no_access = ModelVersion.objects.create(model=model_no_access, state=ModelVersionState.Available, size=8, hash="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", archive_hash="bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb") - with self.assertNumQueries(9): + with self.assertNumQueries(4): response = self.client.patch( reverse("api:worker-run-details", kwargs={"pk": str(run_2.id)}), data={ @@ -2435,7 +2475,13 @@ class TestWorkerRuns(FixtureAPITestCase): "model_version_id": [f'Invalid pk "{model_version_no_access.id}" - object does not exist.'], }) - def test_partial_update_model_version_guest(self): + self.assertListEqual(filter_rights_mock.call_args_list, [ + call(self.user, Model, Role.Guest.value), + call(self.user, Model, Role.Contributor.value), + ]) + + @patch("arkindex.users.managers.BaseACLManager.filter_rights") + def test_partial_update_model_version_guest(self, filter_rights_mock): """ Cannot update a worker run with a model_version when you only have guest access to the model, and the model version has no tag or is not available @@ -2457,6 +2503,17 @@ class TestWorkerRuns(FixtureAPITestCase): parents=[], ) + def filter_rights(user, model, level): + """ + The filter_rights mock needs to return nothing when called for contributor access, + and the models we will test on when called for guest access + """ + if level == Role.Guest.value: + return Model.objects.filter(id__in=(self.model_2.id, self.model_3.id)) + return Model.objects.none() + + filter_rights_mock.side_effect = filter_rights + cases = [ # On a model with a membership giving guest access (self.model_version_2, None, ModelVersionState.Created), @@ -2473,12 +2530,13 @@ class TestWorkerRuns(FixtureAPITestCase): ] for model_version, tag, state in cases: + filter_rights_mock.reset_mock() with self.subTest(model_version=model_version, tag=tag, state=state): model_version.tag = tag model_version.state = state model_version.save() - with self.assertNumQueries(9): + with self.assertNumQueries(5): response = self.client.patch( reverse("api:worker-run-details", kwargs={"pk": str(run_2.id)}), data={ @@ -2492,6 +2550,11 @@ class TestWorkerRuns(FixtureAPITestCase): "model_version_id": [f'Invalid pk "{model_version.id}" - object does not exist.'], }) + self.assertListEqual(filter_rights_mock.call_args_list, [ + call(self.user, Model, Role.Guest.value), + call(self.user, Model, Role.Contributor.value), + ]) + def test_partial_update_model_version_unavailable(self): self.client.force_login(self.user) rev_2 = self.repo.revisions.create( @@ -2512,7 +2575,7 @@ class TestWorkerRuns(FixtureAPITestCase): self.model_version_1.state = ModelVersionState.Error self.model_version_1.save() - with self.assertNumQueries(10): + with self.assertNumQueries(6): response = self.client.patch( reverse("api:worker-run-details", kwargs={"pk": str(run.id)}), data={ @@ -2544,7 +2607,7 @@ class TestWorkerRuns(FixtureAPITestCase): self.model_1.archived = datetime.now(timezone.utc) self.model_1.save() - with self.assertNumQueries(10): + with self.assertNumQueries(6): response = self.client.patch( reverse("api:worker-run-details", kwargs={"pk": str(run.id)}), data={ @@ -2591,7 +2654,7 @@ class TestWorkerRuns(FixtureAPITestCase): ] for model_version in model_versions: with self.subTest(model_version=model_version): - with self.assertNumQueries(13): + with self.assertNumQueries(9): response = self.client.patch( reverse("api:worker-run-details", kwargs={"pk": str(run.id)}), data={ @@ -2682,7 +2745,7 @@ class TestWorkerRuns(FixtureAPITestCase): ) self.assertEqual(run.model_version_id, None) - with self.assertNumQueries(13): + with self.assertNumQueries(9): response = self.client.patch( reverse("api:worker-run-details", kwargs={"pk": str(run.id)}), data={ @@ -2771,7 +2834,7 @@ class TestWorkerRuns(FixtureAPITestCase): ) self.client.force_login(self.user) - with self.assertNumQueries(13): + with self.assertNumQueries(10): response = self.client.patch( reverse("api:worker-run-details", kwargs={"pk": str(self.run_1.id)}), data={ @@ -2852,8 +2915,8 @@ class TestWorkerRuns(FixtureAPITestCase): configuration=None if configuration else self.configuration_1, ) - # Having a model version set adds two queries, having a configuration adds one - query_count = 8 + bool(model_version) * 2 + bool(configuration) + # Having a model version or a configuration adds one query for each + query_count = 5 + bool(model_version) + bool(configuration) with self.assertNumQueries(query_count): response = self.client.patch( @@ -2909,7 +2972,7 @@ class TestWorkerRuns(FixtureAPITestCase): self.worker_1.memberships.update(level=Role.Guest.value) self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.delete( reverse("api:worker-run-details", kwargs={"pk": str(self.run_1.id)}) ) @@ -2936,7 +2999,7 @@ class TestWorkerRuns(FixtureAPITestCase): def test_delete(self): self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.delete( reverse("api:worker-run-details", kwargs={"pk": str(self.run_1.id)}) ) @@ -2979,7 +3042,7 @@ class TestWorkerRuns(FixtureAPITestCase): self.assertTrue(self.run_1.id in run_3.parents) self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.delete( reverse("api:worker-run-details", kwargs={"pk": str(self.run_1.id)}) ) @@ -3000,7 +3063,7 @@ class TestWorkerRuns(FixtureAPITestCase): self.process_1.run() self.process_1.tasks.update(state=State.Running) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.delete( reverse("api:worker-run-details", kwargs={"pk": str(self.run_1.id)}) ) diff --git a/arkindex/process/tests/test_workers.py b/arkindex/process/tests/test_workers.py index bae488074179f0a10a643a46a4ade0c264dfc145..98a2d31ffde4f0b32749db4cae37c35bda751646 100644 --- a/arkindex/process/tests/test_workers.py +++ b/arkindex/process/tests/test_workers.py @@ -1,9 +1,11 @@ import uuid from datetime import datetime, timezone +from unittest.mock import call, patch from django.urls import reverse from rest_framework import status +from arkindex.documents.models import Corpus from arkindex.ponos.models import Farm from arkindex.process.models import ( CorpusWorkerVersion, @@ -21,7 +23,7 @@ from arkindex.process.models import ( ) from arkindex.project.tests import FixtureAPITestCase from arkindex.training.models import Model, ModelVersionState -from arkindex.users.models import Right, Role, User +from arkindex.users.models import Role, User class TestWorkersWorkerVersions(FixtureAPITestCase): @@ -88,12 +90,14 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): def test_workers_list(self): """ - User is able to list workers on the repository as he has an admin access + User is able to list workers on the repository as they have an admin access """ self.client.force_login(self.user) - with self.assertNumQueries(7): + + with self.assertNumQueries(4): response = self.client.get(reverse("api:workers-list")) self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertDictEqual(response.json(), { "count": 6, "next": None, @@ -163,14 +167,15 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): """ self.model.memberships.create(user=self.user, level=Role.Guest.value) self.worker_generic.models.set([self.model]) - self.client.force_login(self.user) - with self.assertNumQueries(8): + + with self.assertNumQueries(5): response = self.client.get( reverse("api:workers-list"), {"compatible_model": str(self.model.id)}, ) self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertDictEqual(response.json(), { "count": 1, "next": None, @@ -192,14 +197,15 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): def test_workers_list_compatible_model_public(self): public_model = Model.objects.create(name="Public", public=True) self.worker_reco.models.set([public_model]) - self.client.force_login(self.user) - with self.assertNumQueries(9): + + with self.assertNumQueries(5): response = self.client.get( reverse("api:workers-list"), {"compatible_model": str(public_model.id)}, ) self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.json(), { "count": 1, "next": None, @@ -216,22 +222,37 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): }], }) - def test_worker_list_compatible_model_private(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights") + def test_worker_list_compatible_model_private(self, filter_rights_mock): private_model = Model.objects.create(name="Private") self.worker_reco.models.set([private_model]) self.client.force_login(self.user) - with self.assertNumQueries(5): + filter_rights_mock.side_effect = [ + Worker.objects.all(), + Repository.objects.all(), + Model.objects.none(), + ] + + with self.assertNumQueries(2): response = self.client.get( reverse("api:workers-list"), {"compatible_model": str(private_model.id)}, ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertDictEqual(response.json(), { "compatible_model": ["This model does not exist or you don't have access to it"] }) + self.assertListEqual(filter_rights_mock.call_args_list, [ + call(self.user, Worker, Role.Contributor.value), + call(self.user, Repository, Role.Contributor.value), + call(self.user, Model, Role.Guest.value), + ]) + def test_workers_list_filter_invalid_query_params(self): self.client.force_login(self.user) + with self.assertNumQueries(2): response = self.client.get( reverse("api:workers-list"), @@ -241,6 +262,7 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): }, ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.json(), { "compatible_model": ["Invalid UUID"], "repository_id": ["Invalid UUID"], @@ -251,9 +273,11 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): User is able to filter workers on the repository by worker type slug """ self.client.force_login(self.user) - with self.assertNumQueries(8): + + with self.assertNumQueries(5): response = self.client.get(reverse("api:workers-list"), {"type": "dla"}) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertDictEqual(response.json(), { "count": 1, "next": None, @@ -277,7 +301,7 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): self.worker_dla.archived = datetime.now(timezone.utc) self.worker_dla.save() - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.get(reverse("api:workers-list"), {"archived": True}) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -305,7 +329,7 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): self.worker_dla.archived = None self.worker_dla.save() - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.get(reverse("api:workers-list"), {"archived": False}) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -332,9 +356,11 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): Raise when trying to filter workers with an invalid type slug """ self.client.force_login(self.user) + with self.assertNumQueries(3): response = self.client.get(reverse("api:workers-list"), {"type": "invalid-slug"}) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertDictEqual(response.json(), {"type": ["No registered worker type with that slug."]}) def test_workers_list_filter_type_id(self): @@ -342,9 +368,11 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): User is able to filter workers on the repository by worker type id """ self.client.force_login(self.user) - with self.assertNumQueries(7): + + with self.assertNumQueries(4): response = self.client.get(reverse("api:workers-list"), {"type": self.worker_type_dla.id}) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertDictEqual(response.json(), { "count": 1, "next": None, @@ -363,16 +391,21 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): ] }) - def test_workers_list_requires_contributor(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights") + def test_workers_list_requires_contributor(self, filter_rights_mock): """ User is not able to list workers with a guest access """ - self.repo.memberships.filter(user=self.user).update(level=Role.Guest.value) - self.worker_custom.memberships.filter(user=self.user).update(level=Role.Guest.value) self.client.force_login(self.user) - with self.assertNumQueries(4): + filter_rights_mock.side_effect = [ + Worker.objects.none(), + Repository.objects.none(), + ] + + with self.assertNumQueries(3): response = self.client.get(reverse("api:workers-list")) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertDictEqual(response.json(), { "count": 0, "next": None, @@ -381,40 +414,18 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): "results": [] }) - def test_workers_list_public_attribute(self): - """ - Any authenticated user is able to list a worker with the public attribute - """ - user = User.objects.create_user("user42@test.fr", "abcd") - user.verified_email = True - user.save() - self.worker_reco.public = True - self.worker_reco.save() - self.client.force_login(user) - with self.assertNumQueries(7): - response = self.client.get(reverse("api:workers-list")) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertDictEqual(response.json(), { - "count": 1, - "next": None, - "number": 1, - "previous": None, - "results": [{ - "id": str(self.worker_reco.id), - "repository_id": str(self.repo.id), - "name": "Recognizer", - "description": "", - "slug": "reco", - "type": "recognizer", - "archived": False, - }] - }) + self.assertListEqual(filter_rights_mock.call_args_list, [ + call(self.user, Worker, Role.Contributor.value), + call(self.user, Repository, Role.Contributor.value), + ]) def test_workers_list_name_filter(self): self.client.force_login(self.user) - with self.assertNumQueries(7): + + with self.assertNumQueries(4): response = self.client.get(reverse("api:workers-list"), {"name": "layout"}) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertDictEqual(response.json(), { "count": 1, "next": None, @@ -444,10 +455,12 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): type=self.worker_type_dla ) worker_2.memberships.create(user=self.user, level=Role.Contributor.value) - self.client.force_login(self.user) - response = self.client.get(reverse("api:workers-list"), {"repository_id": str(repo2.id)}) - self.assertEqual(response.status_code, status.HTTP_200_OK) + + with self.assertNumQueries(4): + response = self.client.get(reverse("api:workers-list"), {"repository_id": str(repo2.id)}) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertDictEqual(response.json(), { "count": 1, "next": None, @@ -479,11 +492,12 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): ) worker_2 = repo2.workers.create(name="Worker 2", slug="worker_2", type=self.worker_type_dla) repo2.memberships.create(user=self.user, level=Role.Contributor.value) - self.client.force_login(self.user) - with self.assertNumQueries(5): + + with self.assertNumQueries(3): response = self.client.get(reverse("api:worker-retrieve", kwargs={"pk": str(worker_2.id)})) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertDictEqual(response.json(), { "id": str(worker_2.id), "repository_id": str(repo2.id), @@ -496,9 +510,11 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): def test_workers_retrieve_no_revision(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + + with self.assertNumQueries(3): response = self.client.get(reverse("api:worker-retrieve", kwargs={"pk": str(self.worker_custom.id)})) self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertDictEqual(response.json(), { "id": str(self.worker_custom.id), "repository_id": None, @@ -509,46 +525,50 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): "archived": False, }) - def test_workers_retrieve_no_rights(self): - new_user = User.objects.create(email="new@test.fr", verified_email=True) - self.client.force_login(new_user) - with self.assertNumQueries(6): + @patch("arkindex.users.managers.BaseACLManager.filter_rights") + def test_workers_retrieve_no_rights(self, filter_rights_mock): + filter_rights_mock.side_effect = [ + Worker.objects.none(), + Repository.objects.none(), + ] + self.client.force_login(self.user) + + with self.assertNumQueries(3): response = self.client.get(reverse("api:worker-retrieve", kwargs={"pk": str(self.worker_reco.id)})) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - self.assertEqual(response.json(), {"detail": "Not found."}) - def test_workers_retrieve_guest(self): - """ - Users cannot retrieve workers with guest access, as guest has no meaning on workers - """ - self.client.force_login(self.user) - self.worker_custom.memberships.filter(user=self.user).update(level=Role.Guest.value) - with self.assertNumQueries(5): - response = self.client.get(reverse("api:worker-retrieve", kwargs={"pk": str(self.worker_custom.id)})) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.assertEqual(response.json(), {"detail": "Not found."}) + self.assertListEqual(filter_rights_mock.call_args_list, [ + call(self.user, Worker, Role.Contributor.value), + call(self.user, Repository, Role.Contributor.value), + ]) + def test_worker_create_requires_login(self): with self.assertNumQueries(0): response = self.client.post(reverse("api:workers-list")) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(response.json(), {"detail": "Authentication credentials were not provided."}) def test_worker_create_requires_verified(self): self.user.verified_email = False self.user.save() self.client.force_login(self.user) + with self.assertNumQueries(2): response = self.client.post(reverse("api:workers-list")) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(response.json(), {"detail": "You do not have permission to perform this action."}) def test_worker_create_required_fields(self): self.client.force_login(self.user) + with self.assertNumQueries(2): response = self.client.post(reverse("api:workers-list")) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), { "name": ["This field is required."], "slug": ["This field is required."], @@ -559,7 +579,7 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): """ Repository ID is deduced from the authenticated task. """ - with self.assertNumQueries(11): + with self.assertNumQueries(8): response = self.client.post( reverse("api:workers-list"), data={ @@ -571,6 +591,7 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): HTTP_AUTHORIZATION=f"Ponos {self.task.token}", ) self.assertEqual(response.status_code, status.HTTP_201_CREATED) + worker = Worker.objects.get(id=response.json()["id"]) assert worker.repository_id == self.repo.id @@ -590,6 +611,7 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): } ) self.assertEqual(response.status_code, status.HTTP_201_CREATED) + data = response.json() worker = Worker.objects.get(id=data["id"]) self.assertEqual(worker.type, self.worker_type_dla) @@ -607,14 +629,16 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): "archived": False, }) - def test_worker_create_user_existing_no_execution_access(self): + @patch("arkindex.users.utils.get_max_level", return_value=Role.Guest.value) + def test_worker_create_user_existing_no_execution_access(self, get_max_level_mock): """ - A user cannot retrieve a worker without an execution access in case the slug already exist. + A user cannot retrieve a worker without an execution access in case the slug already exists. No worker type is created. """ self.worker_custom.memberships.filter(user=self.user).update(level=Role.Guest.value) self.client.force_login(self.user) - with self.assertNumQueries(8): + + with self.assertNumQueries(6): response = self.client.post( reverse("api:workers-list"), data={ @@ -624,19 +648,23 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): } ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertFalse(WorkerType.objects.filter(slug="new_type").exists()) self.assertQuerysetEqual(self.worker_custom.memberships.values_list("user_id", "level"), [ (self.user.id, Role.Guest.value) ]) self.assertDictEqual(response.json(), {"slug": ["You cannot use this value."]}) + self.assertEqual(get_max_level_mock.call_count, 1) + self.assertEqual(get_max_level_mock.call_args, call(self.user, self.worker_custom)) def test_worker_create_task_repository_process(self): """ - Ponos task can create a worker only on a process of Repository type + Ponos tasks can create a worker only on a process of Repository type """ process = Process.objects.get(mode=ProcessMode.Workers) process.run() task = process.tasks.first() + with self.assertNumQueries(1): response = self.client.post( reverse("api:workers-list"), @@ -648,6 +676,7 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): HTTP_AUTHORIZATION=f"Ponos {task.token}", ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertDictEqual(response.json(), { "detail": ( "Workers can only be created on processes of type Repository with a" @@ -655,15 +684,12 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): ) }) - def test_worker_create_task_rights(self): + @patch("arkindex.process.serializers.workers.get_max_level", return_value=None) + def test_worker_create_task_rights(self, get_max_level_mock): """ Ponos tasks are not allowed to create workers when they do not have a right on the repository """ - Right.objects.filter( - content_id__in=(self.repo.id, *self.repo.workers.values_list("id", flat=True)), - user=self.user - ).delete() - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:workers-list"), data={ @@ -676,11 +702,14 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): ) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertEqual(get_max_level_mock.call_count, 1) + self.assertEqual(get_max_level_mock.call_args, call(self.user, self.repo)) + def test_worker_create_task_return_existing_worker(self): """ Creation with an existing slug returns the existing worker with a HTTP status 200 """ - with self.assertNumQueries(15): + with self.assertNumQueries(8): response = self.client.post( reverse("api:workers-list"), data={ @@ -692,6 +721,7 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): HTTP_AUTHORIZATION=f"Ponos {self.task.token}", ) self.assertEqual(response.status_code, status.HTTP_200_OK) + data = response.json() self.assertEqual(data["id"], str(self.worker_reco.id)) self.assertEqual(data["name"], "Recognizer") @@ -704,7 +734,8 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): """ new_worker_type_slug = "newType" self.assertFalse(WorkerType.objects.filter(slug=new_worker_type_slug).exists()) - with self.assertNumQueries(14): + + with self.assertNumQueries(11): response = self.client.post( reverse("api:workers-list"), data={ @@ -715,6 +746,7 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): HTTP_AUTHORIZATION=f"Ponos {self.task.token}", ) self.assertEqual(response.status_code, status.HTTP_201_CREATED) + data = response.json() self.assertTrue(WorkerType.objects.filter(slug=new_worker_type_slug).exists()) self.assertNotEqual(data["id"], str(self.worker_reco.id)) @@ -734,8 +766,8 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): "archived": False, }, ) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(response.json(), {"detail": "Authentication credentials were not provided."}) def test_update_requires_verified(self): @@ -754,16 +786,21 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): "archived": False, }, ) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(response.json(), {"detail": "You do not have permission to perform this action."}) - def test_update_requires_contributor(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights") + def test_update_requires_contributor(self, filter_rights_mock): self.worker_reco.memberships.update_or_create(user=self.user, defaults={"level": Role.Guest.value}) self.repo.memberships.update_or_create(user=self.user, defaults={"level": Role.Guest.value}) self.client.force_login(self.user) + filter_rights_mock.side_effect = [ + Worker.objects.none(), + Repository.objects.none(), + ] - with self.assertNumQueries(4): + with self.assertNumQueries(3): response = self.client.put( reverse("api:worker-retrieve", kwargs={"pk": str(self.worker_reco.id)}), { @@ -774,16 +811,23 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): "archived": False, }, ) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.assertEqual(response.json(), {"detail": "Not found."}) + self.assertListEqual(filter_rights_mock.call_args_list, [ + call(self.user, Worker, Role.Contributor.value), + call(self.user, Repository, Role.Contributor.value), + ]) - def test_update(self): - self.repo.memberships.filter(user=self.user).delete() - self.worker_reco.memberships.update_or_create(user=self.user, defaults={"level": Role.Contributor.value}) + @patch("arkindex.users.managers.BaseACLManager.filter_rights") + def test_update(self, filter_rights_mock): self.client.force_login(self.user) + filter_rights_mock.side_effect = [ + Worker.objects.all(), + Repository.objects.none(), + ] - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.put( reverse("api:worker-retrieve", kwargs={"pk": str(self.worker_reco.id)}), { @@ -794,8 +838,8 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): "archived": False, }, ) + self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.json(), { "id": str(self.worker_reco.id), "name": "New name", @@ -813,12 +857,20 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): self.assertEqual(self.worker_reco.type, self.worker_type_dla) self.assertEqual(self.worker_reco.repository_id, self.repo.id) - def test_update_repository_contributor(self): - self.worker_reco.memberships.filter(user=self.user).delete() - self.repo.memberships.update_or_create(user=self.user, defaults={"level": Role.Contributor.value}) + self.assertListEqual(filter_rights_mock.call_args_list, [ + call(self.user, Worker, Role.Contributor.value), + call(self.user, Repository, Role.Contributor.value), + ]) + + @patch("arkindex.users.managers.BaseACLManager.filter_rights") + def test_update_repository_contributor(self, filter_rights_mock): self.client.force_login(self.user) + filter_rights_mock.side_effect = [ + Worker.objects.none(), + Repository.objects.all(), + ] - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.put( reverse("api:worker-retrieve", kwargs={"pk": str(self.worker_reco.id)}), { @@ -829,8 +881,8 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): "archived": False, }, ) + self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.json(), { "id": str(self.worker_reco.id), "name": "New name", @@ -848,10 +900,15 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): self.assertEqual(self.worker_reco.type, self.worker_type_dla) self.assertEqual(self.worker_reco.repository_id, self.repo.id) + self.assertListEqual(filter_rights_mock.call_args_list, [ + call(self.user, Worker, Role.Contributor.value), + call(self.user, Repository, Role.Contributor.value), + ]) + def test_update_unique_slug(self): self.client.force_login(self.user) - with self.assertNumQueries(10): + with self.assertNumQueries(5): response = self.client.put( reverse("api:worker-retrieve", kwargs={"pk": str(self.worker_reco.id)}), { @@ -862,13 +919,14 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): "archived": False, }, ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.json(), { "non_field_errors": ["A worker with the same repository and slug already exists."], }) - def test_update_archived_requires_archivable(self): + @patch("arkindex.users.utils.get_max_level", return_value=Role.Contributor.value) + def test_update_archived_requires_archivable(self, get_max_level_mock): self.client.force_login(self.user) self.assertFalse(self.worker_reco.is_archivable(self.user)) @@ -879,10 +937,11 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): for current_value, new_value in cases: with self.subTest(current_value=current_value, new_value=new_value): + get_max_level_mock.reset_mock() self.worker_reco.archived = current_value self.worker_reco.save() - with self.assertNumQueries(10): + with self.assertNumQueries(4): response = self.client.put( reverse("api:worker-retrieve", kwargs={"pk": str(self.worker_reco.id)}), { @@ -898,6 +957,8 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): self.assertEqual(response.json(), { "archived": ["You are not allowed to archive or unarchive this worker."], }) + self.assertEqual(get_max_level_mock.call_count, 2) + self.assertListEqual(get_max_level_mock.call_args_list, [call(self.user, self.worker_reco)] * 2) def test_update_archived(self): self.client.force_login(self.user) @@ -914,7 +975,7 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): self.worker_reco.archived = current_value self.worker_reco.save() - with self.assertNumQueries(11): + with self.assertNumQueries(6): response = self.client.put( reverse("api:worker-retrieve", kwargs={"pk": str(self.worker_reco.id)}), { @@ -945,8 +1006,8 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): "description": "New description", }, ) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(response.json(), {"detail": "Authentication credentials were not provided."}) def test_partial_update_requires_verified(self): @@ -961,32 +1022,47 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): "description": "New description", }, ) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(response.json(), {"detail": "You do not have permission to perform this action."}) - def test_partial_update_requires_contributor(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights") + def test_partial_update_requires_contributor(self, filter_rights_mock): self.worker_reco.memberships.update_or_create(user=self.user, defaults={"level": Role.Guest.value}) self.repo.memberships.update_or_create(user=self.user, defaults={"level": Role.Guest.value}) self.client.force_login(self.user) + filter_rights_mock.side_effect = [ + Worker.objects.none(), + Repository.objects.none(), + ] - with self.assertNumQueries(4): + with self.assertNumQueries(3): response = self.client.patch( reverse("api:worker-retrieve", kwargs={"pk": str(self.worker_reco.id)}), { "description": "New description", }, ) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.assertEqual(response.json(), {"detail": "Not found."}) - def test_partial_update(self): + self.assertListEqual(filter_rights_mock.call_args_list, [ + call(self.user, Worker, Role.Contributor.value), + call(self.user, Repository, Role.Contributor.value), + ]) + + @patch("arkindex.users.managers.BaseACLManager.filter_rights") + def test_partial_update(self, filter_rights_mock): self.repo.memberships.filter(user=self.user).delete() self.worker_reco.memberships.update_or_create(user=self.user, defaults={"level": Role.Contributor.value}) self.client.force_login(self.user) + filter_rights_mock.side_effect = [ + Worker.objects.all(), + Repository.objects.none(), + ] - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.patch( reverse("api:worker-retrieve", kwargs={"pk": str(self.worker_reco.id)}), { @@ -994,8 +1070,8 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): "type": "dla", }, ) + self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.json(), { "id": str(self.worker_reco.id), "name": "Recognizer", @@ -1013,12 +1089,22 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): self.assertEqual(self.worker_reco.type, self.worker_type_dla) self.assertEqual(self.worker_reco.repository_id, self.repo.id) - def test_partial_update_repository_contributor(self): + self.assertListEqual(filter_rights_mock.call_args_list, [ + call(self.user, Worker, Role.Contributor.value), + call(self.user, Repository, Role.Contributor.value), + ]) + + @patch("arkindex.users.managers.BaseACLManager.filter_rights") + def test_partial_update_repository_contributor(self, filter_rights_mock): self.worker_reco.memberships.filter(user=self.user).delete() self.repo.memberships.update_or_create(user=self.user, defaults={"level": Role.Contributor.value}) self.client.force_login(self.user) + filter_rights_mock.side_effect = [ + Worker.objects.none(), + Repository.objects.all(), + ] - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.patch( reverse("api:worker-retrieve", kwargs={"pk": str(self.worker_reco.id)}), { @@ -1026,8 +1112,8 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): "slug": "new_slug", }, ) + self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.json(), { "id": str(self.worker_reco.id), "name": "New name", @@ -1045,10 +1131,15 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): self.assertEqual(self.worker_reco.type.slug, "recognizer") self.assertEqual(self.worker_reco.repository_id, self.repo.id) + self.assertListEqual(filter_rights_mock.call_args_list, [ + call(self.user, Worker, Role.Contributor.value), + call(self.user, Repository, Role.Contributor.value), + ]) + def test_partial_update_unique_slug(self): self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(4): response = self.client.patch( reverse("api:worker-retrieve", kwargs={"pk": str(self.worker_reco.id)}), { @@ -1061,7 +1152,8 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): "non_field_errors": ["A worker with the same repository and slug already exists."], }) - def test_partial_update_archived_requires_archivable(self): + @patch("arkindex.users.utils.get_max_level", return_value=Role.Contributor.value) + def test_partial_update_archived_requires_archivable(self, get_max_level_mock): self.client.force_login(self.user) self.assertFalse(self.worker_reco.is_archivable(self.user)) @@ -1072,10 +1164,11 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): for current_value, new_value in cases: with self.subTest(current_value=current_value, new_value=new_value): + get_max_level_mock.reset_mock() self.worker_reco.archived = current_value self.worker_reco.save() - with self.assertNumQueries(9): + with self.assertNumQueries(3): response = self.client.patch( reverse("api:worker-retrieve", kwargs={"pk": str(self.worker_reco.id)}), { @@ -1087,6 +1180,8 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): self.assertEqual(response.json(), { "archived": ["You are not allowed to archive or unarchive this worker."], }) + self.assertEqual(get_max_level_mock.call_count, 2) + self.assertListEqual(get_max_level_mock.call_args_list, [call(self.user, self.worker_reco)] * 2) def test_partial_update_archived(self): self.client.force_login(self.user) @@ -1103,7 +1198,7 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): self.worker_reco.archived = current_value self.worker_reco.save() - with self.assertNumQueries(10): + with self.assertNumQueries(5): response = self.client.patch( reverse("api:worker-retrieve", kwargs={"pk": str(self.worker_reco.id)}), { @@ -1126,7 +1221,7 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): def test_versions_list_requires_login(self): with self.assertNumQueries(0): response = self.client.get(reverse("api:worker-versions", kwargs={"pk": str(self.worker_reco.id)})) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) def test_versions_list(self): self.client.force_login(self.user) @@ -1139,9 +1234,11 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): ) last_version.created = "1999-09-09T09:09:09.090909Z" last_version.save() - with self.assertNumQueries(14): + + with self.assertNumQueries(9): response = self.client.get(reverse("api:worker-versions", kwargs={"pk": str(self.worker_reco.id)})) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, status.HTTP_200_OK) + data = response.json() self.assertEqual(data["count"], 2) # The revision created in this test has a recent timestamp so it is displayed first @@ -1168,23 +1265,25 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): "created": "1999-09-09T09:09:09.090909Z", }) - def test_workers_versions_list_requires_contributor(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_versions_list_requires_contributor(self, has_access_mock): """ User is not able to list worker versions with a guest access """ - self.repo.memberships.filter(user=self.user).update(level=Role.Guest.value) - self.worker_custom.memberships.filter(user=self.user).update(level=Role.Guest.value) self.client.force_login(self.user) - with self.assertNumQueries(4): - response = self.client.get(reverse("api:workers-list")) - self.assertEqual(response.status_code, status.HTTP_200_OK) + + with self.assertNumQueries(3): + response = self.client.get(reverse("api:worker-versions", kwargs={"pk": str(self.worker_reco.id)})) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertDictEqual(response.json(), { - "count": 0, - "next": None, - "number": 1, - "previous": None, - "results": [] + "detail": "You do not have an execution access to this worker.", }) + self.assertEqual(has_access_mock.call_count, 2) + self.assertEqual(has_access_mock.call_args_list, [ + call(self.user, self.worker_reco, Role.Contributor.value, skip_public=False), + call(self.user, self.repo, Role.Contributor.value, skip_public=False), + ]) def test_versions_list_public_worker(self): """ @@ -1196,9 +1295,11 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): self.worker_reco.public = True self.worker_reco.save() self.client.force_login(user) + with self.assertNumQueries(9): response = self.client.get(reverse("api:worker-versions", kwargs={"pk": str(self.worker_reco.id)})) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, status.HTTP_200_OK) + data = response.json() self.assertEqual(data["count"], 1) self.assertEqual(data["results"][0]["id"], str(self.worker_reco.versions.first().id)) @@ -1218,9 +1319,10 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): gpu_usage=FeatureUsage.Disabled ) - with self.assertNumQueries(14): + with self.assertNumQueries(9): response = self.client.get(reverse("api:worker-versions", kwargs={"pk": str(worker_2.id)})) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, status.HTTP_200_OK) + data = response.json() self.assertEqual(len(data["results"]), 1) version = data["results"][0] @@ -1255,29 +1357,29 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): ]) # Complete mode - with self.assertNumQueries(13): + with self.subTest(mode="complete"), self.assertNumQueries(8): response = self.client.get( reverse("api:worker-versions", kwargs={"pk": str(self.worker_reco.id)}), {"mode": "complete"}, HTTP_AUTHORIZATION=f"Ponos {self.task.token}", ) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.json()["count"], 6) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.json()["count"], 6) # Simple mode - with self.assertNumQueries(10): + with self.subTest(mode="simple"), self.assertNumQueries(8): response = self.client.get( reverse("api:worker-versions", kwargs={"pk": str(self.worker_reco.id)}), {"mode": "simple"}, HTTP_AUTHORIZATION=f"Ponos {self.task.token}", ) - self.assertEqual(response.status_code, status.HTTP_200_OK) - data = response.json() - self.assertEqual(data["count"], 3) - self.assertCountEqual( - [version["revision"]["id"] for version in data["results"]], - [str(tagged_rev.id), str(master_rev.id), str(main_rev.id)] - ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + data = response.json() + self.assertEqual(data["count"], 3) + self.assertCountEqual( + [version["revision"]["id"] for version in data["results"]], + [str(tagged_rev.id), str(master_rev.id), str(main_rev.id)] + ) def test_create_version_non_existing_worker(self): with self.assertNumQueries(2): @@ -1385,7 +1487,7 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): self.worker_custom.memberships.filter(user=self.user).update(level=Role.Admin.value) self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.post( reverse("api:worker-versions", kwargs={"pk": str(self.worker_custom.id)}), data={"revision_id": str(self.rev2.id), "configuration": {"test": "test2"}, "model_usage": FeatureUsage.Required.value}, @@ -1401,7 +1503,7 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): self.worker_custom.memberships.filter(user=self.user).update(level=Role.Admin.value) self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(3): response = self.client.post( reverse("api:worker-versions", kwargs={"pk": str(self.worker_reco.id)}), data={"configuration": {"test": "test2"}, "model_usage": FeatureUsage.Required.value}, @@ -1417,7 +1519,7 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): self.worker_custom.memberships.filter(user=self.user).update(level=Role.Admin.value) self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.post( reverse("api:worker-versions", kwargs={"pk": str(self.worker_custom.id)}), data={"revision_id": str(self.rev2.id), "configuration": {"test": "test2"}, "model_usage": FeatureUsage.Required.value}, @@ -1434,6 +1536,7 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): Ponos Task auth cannot create a version on a worker that is not linked to a repository. """ self.worker_custom.versions.create(version=41, configuration={}) + with self.assertNumQueries(4): response = self.client.post( reverse("api:worker-versions", kwargs={"pk": str(self.worker_custom.id)}), @@ -1442,24 +1545,31 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): HTTP_AUTHORIZATION=f"Ponos {self.task.token}", ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertDictEqual(response.json(), { "revision_id": ["Task authentication requires to create a version on workers linked to a repository."] }) - def test_create_version_user_auth_requires_admin(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_create_version_user_auth_requires_admin(self, has_access_mock): self.client.force_login(self.user) - with self.assertNumQueries(6): + + with self.assertNumQueries(3): response = self.client.post( reverse("api:worker-versions", kwargs={"pk": str(self.worker_custom.id)}), data={"configuration": {"test": "val"}, "model_usage": FeatureUsage.Required.value}, format="json", ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertDictEqual(response.json(), {"detail": "You do not have an admin access to this worker."}) + self.assertEqual(has_access_mock.call_count, 1) + self.assertEqual(has_access_mock.call_args, call(self.user, self.worker_custom, Role.Admin.value, skip_public=False)) + def test_create_version_user_auth_requires_null_repository(self): self.client.force_login(self.user) - with self.assertNumQueries(10): + with self.assertNumQueries(5): response = self.client.post( reverse("api:worker-versions", kwargs={"pk": str(self.worker_dla.id)}), data={"revision_id": str(self.rev2.id), "configuration": {"test": "test2"}, "model_usage": FeatureUsage.Required.value}, @@ -1478,7 +1588,7 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): self.worker_custom.memberships.filter(user=self.user).update(level=Role.Admin.value) self.worker_custom.versions.create(version=41, configuration={}) self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(7): response = self.client.post( reverse("api:worker-versions", kwargs={"pk": str(self.worker_custom.id)}), data={"configuration": {"test": "val"}, "model_usage": FeatureUsage.Required.value}, @@ -1512,7 +1622,7 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): Configuration body must be an object """ self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(3): response = self.client.post( reverse("api:worker-versions", kwargs={"pk": str(self.worker_reco.id)}), data={"configuration": "test"}, @@ -2483,7 +2593,8 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): }] }) - def test_corpus_worker_version_no_login_private(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.filter(public=True)) + def test_corpus_worker_version_no_login_private(self, filter_rights_mock): self.corpus.public = False self.corpus.save() self.corpus.worker_versions.set([self.version_1, self.version_2]) @@ -2502,7 +2613,7 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): corpus_worker_version_1 = self.corpus.worker_version_cache.create(worker_version=self.version_1) corpus_worker_version_2 = self.corpus.worker_version_cache.create(worker_version=self.version_2) - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.get(reverse("api:corpus-versions", kwargs={"pk": self.corpus.id})) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -2594,7 +2705,7 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): worker_configuration=conf_2, ) - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.get(reverse("api:corpus-versions", kwargs={"pk": self.corpus.id})) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -2703,7 +2814,7 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): self.client.force_login(self.user) corpus_worker_version = self.corpus.worker_version_cache.create(worker_version=self.version_1) - with self.assertNumQueries(8): + with self.assertNumQueries(6): response = self.client.get(reverse("api:corpus-versions", kwargs={"pk": self.corpus.id})) self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/arkindex/project/api_v1.py b/arkindex/project/api_v1.py index 616d44640bd7553d2075caa5b877cfbf0534848b..3677b5bb7d6c8887c04db27f45d702bc81095a18 100644 --- a/arkindex/project/api_v1.py +++ b/arkindex/project/api_v1.py @@ -136,19 +136,13 @@ from arkindex.training.api import ( ValidateModelVersion, ) from arkindex.users.api import ( - GenericMembershipCreate, - GenericMembershipsList, - GroupDetails, - GroupsCreate, JobList, JobRetrieve, - MembershipDetails, PasswordReset, PasswordResetConfirm, UserCreate, UserEmailLogin, UserEmailVerification, - UserMemberships, UserRetrieve, ) @@ -326,14 +320,6 @@ api = [ path("user/password-reset/", PasswordReset.as_view(), name="password-reset"), path("user/password-reset/confirm/", PasswordResetConfirm.as_view(), name="password-reset-confirm"), - # Rights management - path("groups/", GroupsCreate.as_view(), name="groups-create"), - path("group/<uuid:pk>/", GroupDetails.as_view(), name="group-details"), - path("user/memberships/", UserMemberships.as_view(), name="user-memberships"), - path("membership/<uuid:pk>/", MembershipDetails.as_view(), name="membership-details"), - path("memberships/", GenericMembershipsList.as_view(), name="memberships-list"), - path("memberships/create/", GenericMembershipCreate.as_view(), name="membership-create"), - # Asynchronous jobs path("jobs/", JobList.as_view(), name="jobs-list"), path("jobs/<path:pk>/", JobRetrieve.as_view(), name="jobs-retrieve"), diff --git a/arkindex/project/mixins.py b/arkindex/project/mixins.py index 6276b450d8a1b34ac86c10b14f9dba2d0ded5d5f..b26f626dd65f02ab92b92cec71445b6c4cc109cb 100644 --- a/arkindex/project/mixins.py +++ b/arkindex/project/mixins.py @@ -11,7 +11,7 @@ from arkindex.documents.models import Corpus from arkindex.process.models import Process, ProcessMode, Repository, Worker from arkindex.training.models import Model from arkindex.users.models import Role -from arkindex.users.utils import check_level_param, filter_rights, get_max_level +from arkindex.users.utils import filter_rights, get_max_level, has_access class ACLMixin(object): @@ -29,33 +29,7 @@ class ACLMixin(object): return self._user or self.request.user def has_access(self, instance, level, skip_public=False): - """ - Check if the user has access to a generic instance with a minimum level - If skip_public parameter is set to true, exclude rights on public instances - """ - check_level_param(level) - - # Handle special authentications - if not skip_public and level <= Role.Guest.value and getattr(instance, "public", False): - return True - if self.user.is_anonymous: - return False - elif self.user.is_admin: - return True - - return instance.memberships.filter( - Q( - # Right directly owned by this user - Q(user=self.user) - & Q(level__gte=level) - ) - | Q( - # Right owned by the group and by the user - Q(group__memberships__user=self.user) - & Q(level__gte=level) - & Q(group__memberships__level__gte=level) - ) - ).exists() + return has_access(self.user, instance, level, skip_public=skip_public) class RepositoryACLMixin(ACLMixin): diff --git a/arkindex/project/tests/test_acl_mixin.py b/arkindex/project/tests/test_acl_mixin.py index 60856b17d760be196f99b884fd374506398366dc..a36975c7b25dc51aed69b1f6049f8e7333a6e8f0 100644 --- a/arkindex/project/tests/test_acl_mixin.py +++ b/arkindex/project/tests/test_acl_mixin.py @@ -1,18 +1,12 @@ import uuid +from unittest import expectedFailure from django.contrib.auth.models import AnonymousUser from django.contrib.contenttypes.models import ContentType from arkindex.documents.models import Corpus from arkindex.process.models import Process, ProcessMode, Repository, Revision, WorkerType -from arkindex.project.mixins import ( - ACLMixin, - CorpusACLMixin, - ProcessACLMixin, - RepositoryACLMixin, - TrainingModelMixin, - WorkerACLMixin, -) +from arkindex.project.mixins import ACLMixin, CorpusACLMixin, ProcessACLMixin, TrainingModelMixin from arkindex.project.tests import FixtureTestCase from arkindex.training.models import Model from arkindex.users.models import Group, Right, Role, User @@ -79,6 +73,7 @@ class TestACLMixin(FixtureTestCase): cls.corpus_type = ContentType.objects.get_for_model(Corpus) cls.group_type = ContentType.objects.get_for_model(Group) + @expectedFailure def test_filter_right(self): # List all the rights with a privilege greater than 80 for user 3 params = { @@ -93,6 +88,7 @@ class TestACLMixin(FixtureTestCase): corpora = list(filter_rights(self.user3, Corpus, 80)) self.assertCountEqual(corpora, [self.corpus1]) + @expectedFailure def test_filter_right_including_public(self): # Listing readable objects involve listing public objects with a SQL Union params = { @@ -109,6 +105,7 @@ class TestACLMixin(FixtureTestCase): [self.corpus1, self.corpus] ) + @expectedFailure def test_right_direct_access(self): # User 2 has a direct access to the above corpus with a level of 90 acl_mixin = ACLMixin(user=self.user2) @@ -124,6 +121,7 @@ class TestACLMixin(FixtureTestCase): has_access = acl_mixin.has_access(self.corpus1, 80) self.assertTrue(has_access) + @expectedFailure def test_right_group_members_restriction(self): # User rights on corpora via a group are restricted to user level inside the group acl_mixin = ACLMixin(user=self.user1) @@ -133,7 +131,7 @@ class TestACLMixin(FixtureTestCase): def test_corpus_acl_mixin_has_read_access(self): corpus_acl_mixin = CorpusACLMixin(user=self.user2) - with self.assertNumQueries(3): + with self.assertNumQueries(0): read_access = corpus_acl_mixin.has_read_access(self.corpus1) self.assertTrue(read_access) @@ -147,10 +145,11 @@ class TestACLMixin(FixtureTestCase): def test_corpus_acl_mixin_has_write_access(self): # User2 has a direct access to Corpus1 with an adequate level corpus_acl_mixin = CorpusACLMixin(user=self.user2) - with self.assertNumQueries(3): + with self.assertNumQueries(0): write_access = corpus_acl_mixin.has_write_access(self.corpus1) self.assertTrue(write_access) + @expectedFailure def test_corpus_acl_mixin_has_admin_access(self): # Admin access requires either to have an admin right or to be a django admin/internal user admin_access = [ @@ -167,84 +166,20 @@ class TestACLMixin(FixtureTestCase): self.assertEqual(admin_access, access_check) ContentType.objects.clear_cache() + @expectedFailure def test_corpus_acl_mixin_writable(self): corpus_acl_mixin = CorpusACLMixin(user=self.user1) - with self.assertNumQueries(3): + with self.assertNumQueries(1): corpora = list(corpus_acl_mixin.writable_corpora) self.assertCountEqual( list(corpora), [self.corpus1] ) - def test_repo_acl_mixin_has_read_access(self): - repo_acl_mixin = RepositoryACLMixin(user=self.user2) - with self.assertNumQueries(3): - read_access = repo_acl_mixin.has_read_access(self.repo1) - self.assertTrue(read_access) - - def test_repo_acl_mixin_has_write_access(self): - repo_acl_mixin = RepositoryACLMixin(user=self.user2) - with self.assertNumQueries(3): - exec_access = repo_acl_mixin.has_execution_access(self.repo1) - self.assertFalse(exec_access) - - def test_repo_acl_mixin_has_admin_access(self): - # Admin access requires either to have an admin right or to be a django admin/internal user - admin_access = [ - (self.user, 3, False), - (self.superuser, 0, True), - (self.user1, 3, False), - (self.user2, 3, False), - (self.user3, 3, False) - ] - for user, queries, access_check in admin_access: - repo_acl_mixin = RepositoryACLMixin(user=user) - with self.assertNumQueries(queries): - admin_access = repo_acl_mixin.has_admin_access(self.repo1) - self.assertEqual(admin_access, access_check) - ContentType.objects.clear_cache() - - def test_repo_acl_mixin_readable(self): - repo_acl_mixin = RepositoryACLMixin(user=self.user2) - with self.assertNumQueries(3): - repos = list(repo_acl_mixin.readable_repositories) - self.assertCountEqual( - list(repos), - [self.repo1] - ) - - def test_repo_acl_mixin_executable(self): - repo_acl_mixin = RepositoryACLMixin(user=self.user2) - with self.assertNumQueries(3): - repos = list(repo_acl_mixin.executable_repositories) - self.assertEqual(repos, []) - - def test_worker_acl_mixin_has_worker_access(self): - # Worker access may be determined via its repository - - group1_admin = User.objects.create_user("group1+admin@test.test") - self.group1.memberships.create(user=group1_admin, level=Role.Admin.value) - access_cases = [ - (self.superuser, 100, 0, True), - (self.user1, 100, 3, True), - (self.user, 10, 5, False), - (self.user2, 80, 5, False), - (self.user3, 10, 5, False), - (self.user2, 10, 5, True), - (group1_admin, 100, 5, False), - (group1_admin, 80, 5, True), - ] - for user, level, queries, access_check in access_cases: - worker_acl_mixin = WorkerACLMixin(user=user) - with self.assertNumQueries(queries): - access = worker_acl_mixin.has_worker_access(self.worker, level) - self.assertEqual(access, access_check) - ContentType.objects.clear_cache() - def test_corpus_readable_orderable(self): # Assert corpora retrieved via the mixin are still orderable corpus_acl_mixin = CorpusACLMixin(user=self.user3) - with self.assertNumQueries(3): + with self.assertNumQueries(1): corpora = list(corpus_acl_mixin.readable_corpora.order_by("name")) self.assertListEqual( [c.name for c in corpora], @@ -261,6 +196,7 @@ class TestACLMixin(FixtureTestCase): list(Corpus.objects.all()) ) + @expectedFailure def test_anonymous_user_readable_corpora(self): # An anonymous user should have guest access to any public corpora corpus_acl_mixin = CorpusACLMixin(user=AnonymousUser()) @@ -275,33 +211,39 @@ class TestACLMixin(FixtureTestCase): # User specific rights should be returned instead of the the defaults access for public rights Right.objects.create(user=self.user3, content_object=self.corpus, level=42) corpus_acl_mixin = CorpusACLMixin(user=self.user3) - with self.assertNumQueries(2): + with self.assertNumQueries(1): corpora = list(corpus_acl_mixin.readable_corpora) self.assertCountEqual( list(corpora), [self.corpus1, self.corpus2, self.corpus] ) + @expectedFailure def test_max_level_does_not_exists(self): with self.assertNumQueries(3): self.assertEqual(get_max_level(self.user1, self.corpus2), None) + @expectedFailure def test_max_level_user1(self): with self.assertNumQueries(3): self.assertEqual(get_max_level(self.user1, self.repo1), 80) + @expectedFailure def test_max_level_user2(self): with self.assertNumQueries(3): self.assertEqual(get_max_level(self.user2, self.repo1), 10) + @expectedFailure def test_max_level_user3(self): with self.assertNumQueries(3): self.assertEqual(get_max_level(self.user3, self.corpus2), 75) + @expectedFailure def test_max_level_public(self): with self.assertNumQueries(3): self.assertEqual(get_max_level(self.user1, self.corpus), Role.Guest.value) + @expectedFailure def test_process_access_project(self): """ A project attached to a process defines its access rights @@ -310,18 +252,7 @@ class TestACLMixin(FixtureTestCase): with self.assertNumQueries(3): self.assertEqual(ProcessACLMixin(user=self.user1).process_access_level(process), 75) - def test_process_access_docker_build(self): - """ - Access to a docker build process is possible for someone with an access to the origin repository - """ - process = Process.objects.create( - mode=ProcessMode.Repository, - creator=self.user2, - revision=self.repo1.revisions.create(hash="42", message="Message", author="User 1") - ) - with self.assertNumQueries(3): - self.assertEqual(ProcessACLMixin(user=self.user1).process_access_level(process), 80) - + @expectedFailure def test_process_access_list(self): """ A user can list process if they have rights on its corresponding project or repository @@ -345,7 +276,7 @@ class TestACLMixin(FixtureTestCase): revision=Revision.objects.get(message="My w0rk3r") ) - with self.assertNumQueries(4): + with self.assertNumQueries(1): readable_process_ids = list( ProcessACLMixin(user=self.user1).readable_processes.values_list("id", flat=True) ) @@ -358,15 +289,16 @@ class TestACLMixin(FixtureTestCase): """ User5 has guest access to Model1 via Group3 """ - with self.assertNumQueries(3): + with self.assertNumQueries(0): read_access = TrainingModelMixin(user=self.user5).has_read_access(self.model1) self.assertTrue(read_access) + @expectedFailure def test_models_no_access_user(self): """ User3 has no guest access to Model1 """ - with self.assertNumQueries(3): + with self.assertNumQueries(0): read_access = TrainingModelMixin(user=self.user3).has_read_access(self.model1) self.assertFalse(read_access) @@ -374,7 +306,7 @@ class TestACLMixin(FixtureTestCase): """ User4 has contributor access to Model1 """ - with self.assertNumQueries(3): + with self.assertNumQueries(0): write_access = TrainingModelMixin(user=self.user4).has_write_access(self.model1) self.assertTrue(write_access) @@ -382,23 +314,25 @@ class TestACLMixin(FixtureTestCase): """ User5 has admin access to Model2 via Group3's access rights """ - with self.assertNumQueries(3): + with self.assertNumQueries(0): admin_access = TrainingModelMixin(user=self.user5).has_admin_access(self.model2) self.assertTrue(admin_access) + @expectedFailure def test_models_readable(self): """ To view a model, a user needs guest access. """ - with self.assertNumQueries(3): + with self.assertNumQueries(1): readable_models = list(TrainingModelMixin(user=self.user4).readable_models) self.assertListEqual(readable_models, [self.model1]) + @expectedFailure def test_models_editable(self): """ To edit a model, a user needs contributor access. User5 only has that access on model2. """ - with self.assertNumQueries(3): + with self.assertNumQueries(1): editable_models = list(TrainingModelMixin(user=self.user5).editable_models) self.assertListEqual(editable_models, [self.model2]) diff --git a/arkindex/training/api.py b/arkindex/training/api.py index 9e71b20fda5d40cc692055df8595ef8c1551ccf2..f0d7c7980b0e3fb39598b0c7637acd0f628afabf 100644 --- a/arkindex/training/api.py +++ b/arkindex/training/api.py @@ -333,7 +333,7 @@ class ModelsList(TrainingModelMixin, ListCreateAPIView): filters &= Q(archived__isnull=self.request.query_params["archived"].lower().strip() in ("false", "0")) # Use the default database to prevent a stale read when a model has just been created - return self.readable_models.using("default").filter(filters).order_by("name") + return Model.objects.readable(self.request.user).using("default").filter(filters).order_by("name") def perform_create(self, serializer): model_name = serializer.validated_data.get("name") @@ -384,7 +384,7 @@ class ModelRetrieve(TrainingModelMixin, RetrieveUpdateAPIView): serializer_class = ModelSerializer def get_queryset(self): - return self.readable_models + return Model.objects.readable(self.request.user) def check_object_permissions(self, request, obj): super().check_object_permissions(request, obj) diff --git a/arkindex/training/tests/test_datasets_api.py b/arkindex/training/tests/test_datasets_api.py index dde3c5abaf78821d9364c54e5cc3733515886d4a..b406a43d20667c08a6a1285deac62a727d166ef5 100644 --- a/arkindex/training/tests/test_datasets_api.py +++ b/arkindex/training/tests/test_datasets_api.py @@ -1,5 +1,5 @@ import uuid -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock, call, patch from django.urls import reverse from django.utils import timezone as DjangoTimeZone @@ -60,13 +60,18 @@ class TestDatasetsAPI(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.assertDictEqual(response.json(), {"detail": "Not found."}) - def test_list_private_corpus(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_list_private_corpus(self, has_access_mock): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.get(reverse("api:corpus-datasets", kwargs={"pk": self.private_corpus.pk})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertDictEqual(response.json(), {"detail": "You do not have guest access to this corpus."}) + self.assertEqual(has_access_mock.call_count, 1) + self.assertEqual(has_access_mock.call_args, call(self.user, self.private_corpus, Role.Guest.value, skip_public=False)) + def test_list(self): self.client.force_login(self.user) with self.assertNumQueries(5): @@ -136,20 +141,25 @@ class TestDatasetsAPI(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.assertDictEqual(response.json(), {"detail": "Not found."}) - def test_create_private_corpus(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_create_private_corpus(self, has_access_mock): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:corpus-datasets", kwargs={"pk": self.private_corpus.pk}), data={"name": "My dataset", "description": "My dataset for my experiments."}, format="json" ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertDictEqual(response.json(), {"detail": "You do not have contributor access to this corpus."}) + self.assertEqual(has_access_mock.call_count, 1) + self.assertEqual(has_access_mock.call_args, call(self.user, self.private_corpus, Role.Contributor.value, skip_public=False)) + def test_create_name_required(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:corpus-datasets", kwargs={"pk": self.corpus.pk}), data={"description": "My dataset for my experiments."}, @@ -160,7 +170,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_create_name_empty(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:corpus-datasets", kwargs={"pk": self.corpus.pk}), data={"name": "", "description": "My dataset for my experiments."}, @@ -171,7 +181,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_create_name_blank(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:corpus-datasets", kwargs={"pk": self.corpus.pk}), data={"name": " ", "description": "My dataset for my experiments."}, @@ -182,7 +192,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_create_name_too_long(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:corpus-datasets", kwargs={"pk": self.corpus.pk}), data={ @@ -196,7 +206,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_create_name_already_exists_in_corpus(self): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:corpus-datasets", kwargs={"pk": self.corpus.pk}), data={ @@ -210,7 +220,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_create_description_required(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:corpus-datasets", kwargs={"pk": self.corpus.pk}), data={"name": "My dataset"}, @@ -221,7 +231,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_create_description_empty(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:corpus-datasets", kwargs={"pk": self.corpus.pk}), data={"name": "My dataset", "description": ""}, @@ -232,7 +242,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_create_description_blank(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:corpus-datasets", kwargs={"pk": self.corpus.pk}), data={"name": "My dataset", "description": " "}, @@ -243,7 +253,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_create(self): self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.post( reverse("api:corpus-datasets", kwargs={"pk": self.corpus.pk}), data={"name": "My dataset", "description": "My dataset for my experiments."}, @@ -271,7 +281,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_create_state_ignored(self): self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.post( reverse("api:corpus-datasets", kwargs={"pk": self.corpus.pk}), data={"name": "My dataset", "description": "My dataset for my experiments.", "state": "complete"}, @@ -296,7 +306,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_create_sets(self): self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.post( reverse("api:corpus-datasets", kwargs={"pk": self.corpus.pk}), data={"name": "My dataset", "description": "My dataset for my experiments.", "sets": ["a", "b", "c", "d"]}, @@ -320,7 +330,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_create_sets_length(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:corpus-datasets", kwargs={"pk": self.corpus.pk}), data={"name": "My dataset", "description": "My dataset for my experiments.", "sets": []}, @@ -331,7 +341,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_create_sets_unique_names(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:corpus-datasets", kwargs={"pk": self.corpus.pk}), data={"name": "My dataset", "description": "My dataset for my experiments.", "sets": ["a", "a", "b"]}, @@ -342,7 +352,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_create_sets_blank_names(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:corpus-datasets", kwargs={"pk": self.corpus.pk}), data={"name": "My dataset", "description": "My dataset for my experiments.", "sets": [" ", " ", "b"]}, @@ -359,7 +369,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_create_sets_name_too_long(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:corpus-datasets", kwargs={"pk": self.corpus.pk}), data={ @@ -408,9 +418,10 @@ class TestDatasetsAPI(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), {"detail": "You do not have permission to perform this action."}) - def test_update_requires_write_corpus(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_update_requires_write_corpus(self, has_access_mock): self.client.force_login(self.read_user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.put( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), data={ @@ -423,9 +434,12 @@ class TestDatasetsAPI(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), {"detail": "You do not have contributor access to corpus Unit Tests."}) + self.assertEqual(has_access_mock.call_count, 1) + self.assertEqual(has_access_mock.call_args, call(self.read_user, self.corpus, Role.Contributor.value, skip_public=False)) + def test_update_doesnt_exist(self): self.client.force_login(self.read_user) - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.put( reverse("api:dataset-update", kwargs={"pk": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"}), data={ @@ -440,7 +454,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_update_name_too_long(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.put( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), data={ @@ -455,7 +469,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_update_name_already_exists_in_corpus(self): Dataset.objects.create(name="Another Dataset", description="A set of data", corpus=self.corpus, creator=self.dataset_creator) self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.put( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), data={ @@ -470,7 +484,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_update_requires_all_fields(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.put( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), data={"name": "Shin Seiki Evangelion"}, @@ -482,7 +496,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_update_add_sets(self): self.client.force_login(self.user) self.assertIsNone(self.dataset.task_id) - with self.assertNumQueries(11): + with self.assertNumQueries(8): response = self.client.put( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), data={ @@ -506,7 +520,7 @@ class TestDatasetsAPI(FixtureAPITestCase): """ self.client.force_login(self.user) dataset_elt = self.dataset.dataset_elements.create(element=self.page1, set="training") - with self.assertNumQueries(11): + with self.assertNumQueries(8): response = self.client.put( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), data={ @@ -538,7 +552,7 @@ class TestDatasetsAPI(FixtureAPITestCase): self.dataset.dataset_elements.create(element_id=self.page1.id, set="training") self.dataset.dataset_elements.create(element_id=self.page2.id, set="validation") self.dataset.dataset_elements.create(element_id=self.page3.id, set="validation") - with self.assertNumQueries(12): + with self.assertNumQueries(9): response = self.client.put( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), data={ @@ -570,7 +584,7 @@ class TestDatasetsAPI(FixtureAPITestCase): No more than one set can be updated """ self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.put( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), data={ @@ -585,7 +599,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_update_sets_length(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.put( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), data={ @@ -600,7 +614,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_update_sets_unique_names(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.put( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), data={ @@ -615,7 +629,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_update_sets_name_too_long(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.put( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), data={ @@ -634,7 +648,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_update_empty_or_blank_description_or_name(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.put( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), data={ @@ -653,7 +667,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_update_all_errors(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.put( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), data={ @@ -674,7 +688,7 @@ class TestDatasetsAPI(FixtureAPITestCase): self.client.force_login(self.user) self.dataset.state = DatasetState.Building self.dataset.save() - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.put( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), data={ @@ -692,7 +706,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_update_ponos_task_state_update(self): self.dataset.state = DatasetState.Building self.dataset.save() - with self.assertNumQueries(11): + with self.assertNumQueries(8): response = self.client.put( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), HTTP_AUTHORIZATION=f"Ponos {self.task.token}", @@ -757,7 +771,7 @@ class TestDatasetsAPI(FixtureAPITestCase): self.client.force_login(self.user) for new_state in DatasetState: with self.subTest(new_state=new_state): - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.put( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), data={ @@ -776,7 +790,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_update_ponos_task_state_requires_dataset_in_process(self): self.process.process_datasets.all().delete() - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.put( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), HTTP_AUTHORIZATION=f"Ponos {self.task.token}", @@ -793,7 +807,7 @@ class TestDatasetsAPI(FixtureAPITestCase): }) def test_update_ponos_task_bad_state(self): - with self.assertNumQueries(5): + with self.assertNumQueries(2): response = self.client.put( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), HTTP_AUTHORIZATION=f"Ponos {self.task.token}", @@ -833,9 +847,10 @@ class TestDatasetsAPI(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), {"detail": "You do not have permission to perform this action."}) - def test_partial_update_requires_write_corpus(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_partial_update_requires_write_corpus(self, has_access_mock): self.client.force_login(self.read_user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.patch( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), data={"name": "Shin Seiki Evangelion"}, @@ -844,9 +859,12 @@ class TestDatasetsAPI(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), {"detail": "You do not have contributor access to corpus Unit Tests."}) + self.assertEqual(has_access_mock.call_count, 1) + self.assertEqual(has_access_mock.call_args, call(self.read_user, self.corpus, Role.Contributor.value, skip_public=False)) + def test_partial_update_name_too_long(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.patch( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), data={ @@ -860,7 +878,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_partial_update_name_already_exists_in_corpus(self): Dataset.objects.create(name="Another Dataset", description="A set of data", corpus=self.corpus, creator=self.dataset_creator) self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.patch( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), data={ @@ -873,7 +891,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_partial_update(self): self.client.force_login(self.user) - with self.assertNumQueries(11): + with self.assertNumQueries(8): response = self.client.patch( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), data={ @@ -890,7 +908,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_partial_update_empty_or_blank_description_or_name(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.patch( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), data={ @@ -904,7 +922,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_partial_update_requires_ponos_auth(self): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.patch( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), data={ @@ -918,7 +936,7 @@ class TestDatasetsAPI(FixtureAPITestCase): }) def test_partial_update_ponos_task_state_update(self): - with self.assertNumQueries(11): + with self.assertNumQueries(8): response = self.client.patch( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), HTTP_AUTHORIZATION=f"Ponos {self.task.token}", @@ -933,7 +951,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_partial_update_ponos_task_state_requires_dataset_in_process(self): self.process.process_datasets.all().delete() - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.patch( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), HTTP_AUTHORIZATION=f"Ponos {self.task.token}", @@ -948,7 +966,7 @@ class TestDatasetsAPI(FixtureAPITestCase): }) def test_partial_update_ponos_task_bad_state(self): - with self.assertNumQueries(5): + with self.assertNumQueries(2): response = self.client.patch( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), HTTP_AUTHORIZATION=f"Ponos {self.task.token}", @@ -964,7 +982,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_partial_update_sets_name_too_long(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.patch( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), data={ @@ -1027,7 +1045,7 @@ class TestDatasetsAPI(FixtureAPITestCase): self.client.force_login(self.user) for new_state in DatasetState: with self.subTest(new_state=new_state): - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.patch( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), data={ @@ -1064,18 +1082,24 @@ class TestDatasetsAPI(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), {"detail": "You do not have permission to perform this action."}) - def test_retrieve_requires_read_corpus(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_retrieve_requires_read_corpus(self, filter_rights_mock): self.client.force_login(self.user) - with self.assertNumQueries(5): + + with self.assertNumQueries(2): response = self.client.get( reverse("api:dataset-update", kwargs={"pk": self.private_dataset.pk}) ) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertDictEqual(response.json(), {"detail": "Not found."}) - def test_retrieve_doesnt_exists(self): + self.assertEqual(filter_rights_mock.call_count, 1) + self.assertEqual(filter_rights_mock.call_args, call(self.user, Corpus, Role.Guest.value)) + + def test_retrieve_doesnt_exist(self): self.client.force_login(self.user) - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.get( reverse("api:dataset-update", kwargs={"pk": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"}) ) @@ -1084,7 +1108,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_retrieve(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.get( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}) ) @@ -1107,7 +1131,7 @@ class TestDatasetsAPI(FixtureAPITestCase): self.client.force_login(self.user) self.dataset.task = self.task self.dataset.save() - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.get( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}) ) @@ -1134,18 +1158,22 @@ class TestDatasetsAPI(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), {"detail": "You do not have permission to perform this action."}) - def test_delete_requires_write_corpus(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_delete_requires_corpus_admin(self, has_access_mock): self.client.force_login(self.write_user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.delete( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), {"detail": "You do not have admin access to corpus Unit Tests."}) + self.assertEqual(has_access_mock.call_count, 1) + self.assertEqual(has_access_mock.call_args, call(self.write_user, self.corpus, Role.Admin.value, skip_public=False)) + def test_delete_doesnt_exist(self): self.client.force_login(self.user) - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.delete( reverse("api:dataset-update", kwargs={"pk": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"}), ) @@ -1154,7 +1182,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_delete(self): self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.delete( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), ) @@ -1169,7 +1197,7 @@ class TestDatasetsAPI(FixtureAPITestCase): with self.subTest(state=state): self.dataset.state = state self.dataset.save() - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.delete( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), ) @@ -1187,7 +1215,7 @@ class TestDatasetsAPI(FixtureAPITestCase): self.dataset.dataset_elements.create(element_id=self.page3.id, set="validation") self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.delete( reverse("api:dataset-update", kwargs={"pk": self.dataset.pk}), ) @@ -1211,20 +1239,24 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_list_elements_invalid_dataset_id(self): self.client.force_login(self.user) - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.get(reverse("api:dataset-elements", kwargs={"pk": str(uuid.uuid4())})) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_list_elements_readable_corpus(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_list_elements_readable_corpus(self, filter_rights_mock): self.client.force_login(self.user) - with self.assertNumQueries(5): + with self.assertNumQueries(2): response = self.client.get(reverse("api:dataset-elements", kwargs={"pk": str(self.private_dataset.id)})) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertEqual(filter_rights_mock.call_count, 1) + self.assertEqual(filter_rights_mock.call_args, call(self.user, Corpus, Role.Guest.value)) + def test_list_elements_set_filter_wrong_set(self): self.dataset.dataset_elements.create(element_id=self.page1.id, set="test") self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.get( reverse("api:dataset-elements", kwargs={"pk": str(self.dataset.id)}), data={"set": "aaaaa"} @@ -1241,7 +1273,7 @@ class TestDatasetsAPI(FixtureAPITestCase): self.dataset.dataset_elements.create(element_id=self.page1.id, set="test") self.dataset.dataset_elements.create(element_id=self.page2.id, set="training") self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.get( reverse("api:dataset-elements", kwargs={"pk": self.dataset.pk}), data={"set": "training", "with_count": "true"}, @@ -1449,7 +1481,7 @@ class TestDatasetsAPI(FixtureAPITestCase): self.dataset.state = state self.dataset.save() with self.subTest(state=state): - with self.assertNumQueries(6): + with self.assertNumQueries(4): response = self.client.get( reverse("api:dataset-elements", kwargs={"pk": self.dataset.pk}), {"page_size": 3}, @@ -1474,18 +1506,23 @@ class TestDatasetsAPI(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), {"detail": "You do not have permission to perform this action."}) - def test_add_element_private_dataset(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_add_element_private_dataset(self, filter_rights_mock): self.client.force_login(self.user) - with self.assertNumQueries(5): + with self.assertNumQueries(2): response = self.client.post( reverse("api:dataset-elements", kwargs={"pk": self.private_dataset.id}) ) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_add_element_requires_writable_corpus(self): + self.assertEqual(filter_rights_mock.call_count, 1) + self.assertEqual(filter_rights_mock.call_args, call(self.user, Corpus, Role.Guest.value)) + + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_add_element_requires_writable_corpus(self, has_access_mock): self.corpus.memberships.filter(user=self.user).update(level=Role.Guest.value) self.client.force_login(self.user) - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.post( reverse("api:dataset-elements", kwargs={"pk": self.dataset.id}) ) @@ -1494,9 +1531,12 @@ class TestDatasetsAPI(FixtureAPITestCase): "detail": "You do not have contributor access to the corpus of this dataset." }) + self.assertEqual(has_access_mock.call_count, 1) + self.assertEqual(has_access_mock.call_args, call(self.user, self.corpus, Role.Contributor.value, skip_public=False)) + def test_add_element_required_fields(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post(reverse("api:dataset-elements", kwargs={"pk": self.dataset.id})) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), { @@ -1507,7 +1547,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_add_element_wrong_element(self): element = self.private_corpus.elements.create(type=self.private_corpus.types.create(slug="folder")) self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:dataset-elements", kwargs={"pk": self.dataset.id}), data={"set": "test", "element_id": str(element.id)}, @@ -1520,7 +1560,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_add_element_wrong_set(self): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:dataset-elements", kwargs={"pk": self.dataset.id}), data={"set": "aaaaaaaaaaa", "element_id": str(self.vol.id)}, @@ -1535,7 +1575,7 @@ class TestDatasetsAPI(FixtureAPITestCase): self.client.force_login(self.user) self.dataset.state = DatasetState.Complete self.dataset.save() - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:dataset-elements", kwargs={"pk": self.dataset.id}), data={"set": "test", "element_id": str(self.vol.id)}, @@ -1547,7 +1587,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_add_element_already_exists(self): self.dataset.dataset_elements.create(element=self.page1, set="test") self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.post( reverse("api:dataset-elements", kwargs={"pk": self.dataset.id}), data={"set": "test", "element_id": str(self.page1.id)}, @@ -1558,7 +1598,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_add_element(self): self.client.force_login(self.user) - with self.assertNumQueries(13): + with self.assertNumQueries(10): response = self.client.post( reverse("api:dataset-elements", kwargs={"pk": self.dataset.id}), data={"set": "training", "element_id": str(self.page1.id)}, @@ -1594,18 +1634,23 @@ class TestDatasetsAPI(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), {"detail": "You do not have permission to perform this action."}) - def test_add_from_selection_private_corpus(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_add_from_selection_private_corpus(self, filter_rights_mock): self.client.force_login(self.user) - with self.assertNumQueries(5): + with self.assertNumQueries(2): response = self.client.post( reverse("api:dataset-elements-selection", kwargs={"pk": self.private_corpus.id}) ) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_add_from_selection_requires_writable_corpus(self): + self.assertEqual(filter_rights_mock.call_count, 1) + self.assertEqual(filter_rights_mock.call_args, call(self.user, Corpus, Role.Guest.value)) + + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_add_from_selection_requires_writable_corpus(self, has_access_mock): self.corpus.memberships.filter(user=self.user).update(level=Role.Guest.value) self.client.force_login(self.user) - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.post( reverse("api:dataset-elements-selection", kwargs={"pk": self.corpus.id}) ) @@ -1614,9 +1659,12 @@ class TestDatasetsAPI(FixtureAPITestCase): "detail": "You need a Contributor access to the corpus to perform this action." }) + self.assertEqual(has_access_mock.call_count, 1) + self.assertEqual(has_access_mock.call_args, call(self.user, self.corpus, Role.Contributor.value, skip_public=False)) + def test_add_from_selection_required_fields(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post(reverse("api:dataset-elements-selection", kwargs={"pk": self.corpus.id})) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), { @@ -1626,7 +1674,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_add_from_selection_wrong_values(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:dataset-elements-selection", kwargs={"pk": self.corpus.id}), data={"set": {}, "dataset_id": "AAA"}, @@ -1640,7 +1688,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_add_from_selection_wrong_dataset(self): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:dataset-elements-selection", kwargs={"pk": self.corpus.id}), data={"set": "aaa", "dataset_id": self.private_dataset.id}, @@ -1656,7 +1704,7 @@ class TestDatasetsAPI(FixtureAPITestCase): self.client.force_login(self.user) self.dataset.state = DatasetState.Complete self.dataset.save() - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:dataset-elements-selection", kwargs={"pk": self.corpus.id}), data={"set": "aaa", "dataset_id": self.dataset.id}, @@ -1669,7 +1717,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_add_from_selection_wrong_set(self): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:dataset-elements-selection", kwargs={"pk": self.corpus.id}), data={"set": "aaa", "dataset_id": self.dataset.id}, @@ -1689,7 +1737,7 @@ class TestDatasetsAPI(FixtureAPITestCase): self.user.selected_elements.set([self.vol, self.page1, self.page2]) self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.post( reverse("api:dataset-elements-selection", kwargs={"pk": self.corpus.id}), data={"set": "training", "dataset_id": self.dataset.id}, @@ -1705,13 +1753,17 @@ class TestDatasetsAPI(FixtureAPITestCase): ] ) - def test_element_datasets_requires_read_access(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_element_datasets_requires_read_access(self, filter_rights_mock): self.client.force_login(self.user) private_elt = self.private_corpus.elements.create(type=self.private_corpus.types.create(slug="t"), name="elt") - with self.assertNumQueries(5): + with self.assertNumQueries(2): response = self.client.get(reverse("api:element-datasets", kwargs={"pk": private_elt.id})) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertEqual(filter_rights_mock.call_count, 1) + self.assertEqual(filter_rights_mock.call_args, call(self.user, Corpus, Role.Guest.value)) + def test_element_datasets_methods(self): self.client.force_login(self.user) forbidden_methods = ("post", "patch", "put", "delete") @@ -1757,7 +1809,7 @@ class TestDatasetsAPI(FixtureAPITestCase): self.dataset.dataset_elements.create(element=self.page1, set="train") self.dataset.dataset_elements.create(element=self.page1, set="validation") self.dataset2.dataset_elements.create(element=self.page1, set="train") - with self.assertNumQueries(7): + with self.assertNumQueries(5): response = self.client.get(reverse("api:element-datasets", kwargs={"pk": str(self.page1.id)})) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertDictEqual(response.json(), { @@ -1839,26 +1891,36 @@ class TestDatasetsAPI(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), {"detail": "You do not have permission to perform this action."}) - def test_clone_private_corpus(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_clone_private_corpus(self, filter_rights_mock): self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.post( reverse("api:dataset-clone", kwargs={"pk": self.private_dataset.id}) ) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_clone_requires_writable_corpus(self): + self.assertEqual(filter_rights_mock.call_count, 1) + self.assertEqual(filter_rights_mock.call_args, call(self.user, Corpus, Role.Guest.value)) + + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_clone_requires_writable_corpus(self, has_access_mock): self.corpus.memberships.filter(user=self.user).update(level=Role.Guest.value) self.client.force_login(self.user) - with self.assertNumQueries(9): + + with self.assertNumQueries(7): response = self.client.post( reverse("api:dataset-clone", kwargs={"pk": self.dataset.id}) ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertDictEqual(response.json(), { "detail": "You need a Contributor access to the dataset's corpus." }) + self.assertEqual(has_access_mock.call_count, 1) + self.assertEqual(has_access_mock.call_args, call(self.user, self.corpus, Role.Contributor.value, skip_public=False)) + def test_clone(self): self.dataset.creator = self.superuser self.dataset.state = DatasetState.Error @@ -1870,7 +1932,7 @@ class TestDatasetsAPI(FixtureAPITestCase): self.assertCountEqual(self.corpus.datasets.values_list("name", flat=True), ["First Dataset", "Second Dataset"]) self.client.force_login(self.user) - with self.assertNumQueries(15): + with self.assertNumQueries(12): response = self.client.post( reverse("api:dataset-clone", kwargs={"pk": self.dataset.id}), format="json", @@ -1912,7 +1974,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_clone_existing_name(self): self.corpus.datasets.create(name="Clone of First Dataset", creator=self.user) self.client.force_login(self.user) - with self.assertNumQueries(14): + with self.assertNumQueries(11): response = self.client.post( reverse("api:dataset-clone", kwargs={"pk": self.dataset.id}), format="json", @@ -1946,7 +2008,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_clone_name_too_long(self): dataset = self.corpus.datasets.create(name="A" * 99, creator=self.user) self.client.force_login(self.user) - with self.assertNumQueries(14): + with self.assertNumQueries(11): response = self.client.post( reverse("api:dataset-clone", kwargs={"pk": dataset.id}), format="json", @@ -1979,13 +2041,14 @@ class TestDatasetsAPI(FixtureAPITestCase): ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_destroy_dataset_element_requires_contributor(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_destroy_dataset_element_requires_contributor(self, has_access_mock): self.client.force_login(self.read_user) self.dataset.dataset_elements.create(element=self.page1, set="train") self.dataset.dataset_elements.create(element=self.page1, set="validation") self.assertEqual(self.dataset.dataset_elements.filter(set="train").count(), 1) self.assertEqual(self.dataset.dataset_elements.filter(set="validation").count(), 1) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.delete(reverse( "api:dataset-element", kwargs={"dataset": str(self.dataset.id), "element": str(self.page1.id)}) @@ -1997,6 +2060,9 @@ class TestDatasetsAPI(FixtureAPITestCase): self.assertEqual(self.dataset.dataset_elements.filter(set="train").count(), 1) self.assertEqual(self.dataset.dataset_elements.filter(set="validation").count(), 1) + self.assertEqual(has_access_mock.call_count, 1) + self.assertEqual(has_access_mock.call_args, call(self.read_user, self.corpus, Role.Contributor.value, skip_public=False)) + def test_destroy_dataset_element_set_required(self): self.client.force_login(self.user) with self.assertNumQueries(2): @@ -2091,7 +2157,7 @@ class TestDatasetsAPI(FixtureAPITestCase): self.dataset.dataset_elements.create(element=self.page1, set="validation") self.assertEqual(self.dataset.dataset_elements.filter(set="train").count(), 1) self.assertEqual(self.dataset.dataset_elements.filter(set="validation").count(), 1) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.delete(reverse( "api:dataset-element", kwargs={"dataset": str(self.dataset.id), "element": str(self.page1.id)}) diff --git a/arkindex/training/tests/test_metrics_api.py b/arkindex/training/tests/test_metrics_api.py index 4876b9904bfe6e5e6647b9336598948e6f3c1557..a1efc6de3c6b6ceeb0f515a73d59536f4f73ad53 100644 --- a/arkindex/training/tests/test_metrics_api.py +++ b/arkindex/training/tests/test_metrics_api.py @@ -1,5 +1,5 @@ from datetime import datetime, timezone -from unittest.mock import patch +from unittest.mock import call, patch from django.db import IntegrityError from django.urls import reverse @@ -73,10 +73,10 @@ class TestMetricsAPI(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), {"model_version_id": ['Invalid pk "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" - object does not exist.']}) - def test_create_metric_value_requires_contributor(self): - self.user.rights.all().delete() + @patch("arkindex.training.serializers.get_max_level", return_value=None) + def test_create_metric_value_requires_contributor(self, get_max_level_mock): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:metric-create"), data={ @@ -89,12 +89,15 @@ class TestMetricsAPI(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), {"detail": "You do not have contributor access to this model."}) + self.assertEqual(get_max_level_mock.call_count, 1) + self.assertEqual(get_max_level_mock.call_args, call(self.user, self.model)) + def test_create_metric_value_archived(self): self.model.archived = datetime.now(timezone.utc) self.model.save() self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:metric-create"), data={ @@ -118,7 +121,7 @@ class TestMetricsAPI(FixtureAPITestCase): self.client.force_login(self.user) self.assertEqual(len(MetricKey.objects.filter(model_version=self.model_version)), 1) self.assertEqual(len(MetricValue.objects.filter(metric=self.metric_key)), 0) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.post( reverse("api:metric-create"), data={ @@ -147,7 +150,7 @@ class TestMetricsAPI(FixtureAPITestCase): self.client.force_login(self.user) with self.assertRaises(MetricKey.DoesNotExist): MetricKey.objects.get(name="my_metric") - with self.assertNumQueries(11): + with self.assertNumQueries(8): response = self.client.post( reverse("api:metric-create"), data={ @@ -176,7 +179,7 @@ class TestMetricsAPI(FixtureAPITestCase): self.client.force_login(self.user) with self.assertRaises(MetricKey.DoesNotExist): MetricKey.objects.get(name="my_metric") - with self.assertNumQueries(11): + with self.assertNumQueries(8): response = self.client.post( reverse("api:metric-create"), data={ @@ -206,7 +209,7 @@ class TestMetricsAPI(FixtureAPITestCase): Cannot create a metric value of type point using the step property """ self.client.force_login(self.user) - with self.assertNumQueries(10): + with self.assertNumQueries(7): response = self.client.post( reverse("api:metric-create"), data={ @@ -226,7 +229,7 @@ class TestMetricsAPI(FixtureAPITestCase): Cannot create a metric value using the step property for a metric key of type point """ self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:metric-create"), data={ @@ -245,7 +248,7 @@ class TestMetricsAPI(FixtureAPITestCase): Cannot create a metric value for an existing metric key with a different mode """ self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:metric-create"), data={ @@ -266,7 +269,7 @@ class TestMetricsAPI(FixtureAPITestCase): MetricValue.objects.create(metric=self.metric_key, value=2.3) self.assertEqual(MetricKey.objects.get(name="a test metric").values.count(), 1) self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:metric-create"), data={ @@ -296,7 +299,7 @@ class TestMetricsAPI(FixtureAPITestCase): metric_key = MetricKey.objects.create(name="a series metric", mode=MetricMode.Series, model_version=self.model_version) MetricValue.objects.create(metric=metric_key, value=3, step=1) self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.post( reverse("api:metric-create"), data={ @@ -324,7 +327,7 @@ class TestMetricsAPI(FixtureAPITestCase): metric_key = MetricKey.objects.create(name="a series metric", mode=MetricMode.Series, model_version=self.model_version) MetricValue.objects.create(metric=metric_key, value=3, step=1) self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:metric-create"), data={ @@ -348,7 +351,7 @@ class TestMetricsAPI(FixtureAPITestCase): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:metric-create"), data={ @@ -371,7 +374,7 @@ class TestMetricsAPI(FixtureAPITestCase): self.assertEqual(len(MetricValue.objects.filter(metric=series_metric)), 1) self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:metric-create"), data={ @@ -435,10 +438,11 @@ class TestMetricsAPI(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), {"model_version_id": ['Invalid pk "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" - object does not exist.']}) - def test_bulk_create_metric_value_requires_contributor(self): + @patch("arkindex.training.serializers.get_max_level", return_value=None) + def test_bulk_create_metric_value_requires_contributor(self, get_max_level_mock): self.user.rights.all().delete() self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:metrics-create"), data={ @@ -460,12 +464,15 @@ class TestMetricsAPI(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), {"detail": "You do not have contributor access to this model."}) + self.assertEqual(get_max_level_mock.call_count, 1) + self.assertEqual(get_max_level_mock.call_args, call(self.user, self.model)) + def test_bulk_create_metric_value_archived(self): self.model.archived = datetime.now(timezone.utc) self.model.save() self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:metrics-create"), data={ @@ -493,7 +500,7 @@ class TestMetricsAPI(FixtureAPITestCase): Cannot send two metrics values for the same metric key / name """ self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:metrics-create"), data={ @@ -525,7 +532,7 @@ class TestMetricsAPI(FixtureAPITestCase): test_metric = MetricKey.objects.create(name="metric numero duo", model_version=self.model_version, mode=MetricMode.Series) self.client.force_login(self.user) self.assertEqual(len(MetricKey.objects.filter(model_version=self.model_version)), 2) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.post( reverse("api:metrics-create"), data={ @@ -569,7 +576,7 @@ class TestMetricsAPI(FixtureAPITestCase): MetricKey.objects.get(name="some metric") with self.assertRaises(MetricKey.DoesNotExist): MetricKey.objects.get(name="another metric") - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.post( reverse("api:metrics-create"), data={ @@ -621,7 +628,7 @@ class TestMetricsAPI(FixtureAPITestCase): test_metric_2 = MetricKey.objects.create(name="metric number three", model_version=self.model_version, mode=MetricMode.Series) datetime_mock.return_value = datetime(2046, 1, 1, 12, 34, 56, tzinfo=timezone.utc) self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.post( reverse("api:metrics-create"), data={ @@ -661,7 +668,7 @@ class TestMetricsAPI(FixtureAPITestCase): MetricKey.objects.create(name="metric numero duo", model_version=self.model_version, mode=MetricMode.Point) MetricKey.objects.create(name="another metric", model_version=self.model_version, mode=MetricMode.Series) self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:metrics-create"), data={ @@ -703,7 +710,7 @@ class TestMetricsAPI(FixtureAPITestCase): MetricValue.objects.create(metric=self.metric_key, value=2.3) self.assertEqual(MetricKey.objects.get(name="a test metric").values.count(), 1) self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:metrics-create"), data={ @@ -730,7 +737,7 @@ class TestMetricsAPI(FixtureAPITestCase): Cannot create a metric value for an existing metric key with a different mode (bulk edition) """ self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:metrics-create"), data={ @@ -756,7 +763,7 @@ class TestMetricsAPI(FixtureAPITestCase): MetricValue.objects.create(metric=series_metric, step=1, value=0.2) self.assertEqual(len(MetricValue.objects.filter(metric=series_metric)), 1) self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:metrics-create"), data={ @@ -783,7 +790,7 @@ class TestMetricsAPI(FixtureAPITestCase): MetricKey.objects.create(name="another metric", model_version=self.model_version, mode=MetricMode.Series) MetricValue.objects.create(metric=metric_duo, value=56) self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:metrics-create"), data={ @@ -844,7 +851,7 @@ class TestMetricsAPI(FixtureAPITestCase): self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:metrics-create"), data={ @@ -871,7 +878,7 @@ class TestMetricsAPI(FixtureAPITestCase): self.assertEqual(len(MetricValue.objects.filter(metric=series_metric)), 1) self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:metrics-create"), data={ diff --git a/arkindex/training/tests/test_model_api.py b/arkindex/training/tests/test_model_api.py index b69071b15881665c0ef02af8e5e8bab00a7be58f..7a264b2f3f26fa529d8000960acfe5e8b73f84ee 100644 --- a/arkindex/training/tests/test_model_api.py +++ b/arkindex/training/tests/test_model_api.py @@ -1,4 +1,4 @@ -from unittest.mock import patch +from unittest.mock import call, patch from uuid import uuid4 from django.urls import reverse @@ -94,6 +94,7 @@ class TestModelAPI(FixtureAPITestCase): def test_create_model_version_requires_verified(self): user = User.objects.create(email="not_verified@mail.com", display_name="Not Verified", verified_email=False) self.client.force_login(user) + with self.assertNumQueries(2): response = self.client.post( reverse("api:model-versions", kwargs={"pk": str(self.model2.id)}), @@ -104,25 +105,31 @@ class TestModelAPI(FixtureAPITestCase): }, format="json", ) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertDictEqual(response.json(), {"detail": "You do not have permission to perform this action."}) - def test_create_model_version_requires_contributor(self): + @patch("arkindex.training.api.get_max_level", return_value=Role.Guest.value) + def test_create_model_version_requires_contributor(self, get_max_level_mock): """ Can't create model version as guest """ self.client.force_login(self.user1) - with self.assertNumQueries(6): + + with self.assertNumQueries(3): response = self.client.post(reverse("api:model-versions", kwargs={"pk": str(self.model2.id)})) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertDictEqual(response.json(), {"detail": "You need a Contributor access to the model to create a new version."}) + self.assertEqual(get_max_level_mock.call_count, 1) + self.assertEqual(get_max_level_mock.call_args, call(self.user1, self.model2)) def test_create_model_version_archived(self): self.model1.archived = timezone.now() self.model1.save() self.client.force_login(self.user1) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post(reverse("api:model-versions", kwargs={"pk": str(self.model1.id)})) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) @@ -140,7 +147,7 @@ class TestModelAPI(FixtureAPITestCase): # To mock the creation date with patch("django.utils.timezone.now") as mock_now: mock_now.return_value = fake_now - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:model-versions", kwargs={"pk": str(self.model1.id)}), {}, @@ -175,7 +182,7 @@ class TestModelAPI(FixtureAPITestCase): Raise 400 when creating a model version with a blank tag """ self.client.force_login(self.user1) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:model-versions", kwargs={"pk": str(self.model1.id)}), {"tag": ""}, @@ -190,7 +197,7 @@ class TestModelAPI(FixtureAPITestCase): Raise 400 when creating a model version with an existing tag for the same model """ self.client.force_login(self.user1) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:model-versions", kwargs={"pk": str(self.model1.id)}), {"tag": "tagged"}, @@ -211,7 +218,7 @@ class TestModelAPI(FixtureAPITestCase): # To mock the creation date with patch("django.utils.timezone.now") as mock_now: mock_now.return_value = fake_now - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.post( reverse("api:model-versions", kwargs={"pk": str(self.model1.id)}), { @@ -249,7 +256,7 @@ class TestModelAPI(FixtureAPITestCase): Raises 400 when creating a model version that already exists, same model_id and tag """ self.client.force_login(self.user1) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.post( reverse("api:model-versions", kwargs={"pk": str(self.model1.id)}), {"tag": self.model_version2.tag}, @@ -273,41 +280,21 @@ class TestModelAPI(FixtureAPITestCase): response = self.client.get(reverse("api:model-retrieve", kwargs={"pk": str(self.model2.id)})) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_retrieve_model_requires_guest(self): - self.assertFalse(self.model1.public) - self.assertFalse(self.model1.memberships.filter(user=self.user3).exists()) + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Model.objects.none()) + def test_retrieve_model_requires_guest(self, filter_rights_mock): self.client.force_login(self.user3) - with self.assertNumQueries(4): + with self.assertNumQueries(2): response = self.client.get(reverse("api:model-retrieve", kwargs={"pk": str(self.model1.id)})) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_retrieve_model_public(self): - self.assertFalse(self.model1.memberships.filter(user=self.user3).exists()) - self.model1.public = True - self.model1.save() - self.client.force_login(self.user3) - - with self.assertNumQueries(5): - response = self.client.get(reverse("api:model-retrieve", kwargs={"pk": str(self.model1.id)})) - self.assertEqual(response.status_code, status.HTTP_200_OK) - - self.assertDictEqual(response.json(), { - "id": str(self.model1.id), - "created": self.model1.created.isoformat().replace("+00:00", "Z"), - "updated": self.model1.updated.isoformat().replace("+00:00", "Z"), - "name": "First Model", - "description": "first", - "rights": ["read"], - "archived": False, - }) + self.assertEqual(filter_rights_mock.call_count, 1) + self.assertEqual(filter_rights_mock.call_args, call(self.user3, Model, Role.Guest.value)) def test_retrieve_model(self): - self.assertFalse(self.model2.public) - self.assertTrue(self.model2.memberships.filter(user=self.user3).exists()) self.client.force_login(self.user3) - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.get(reverse("api:model-retrieve", kwargs={"pk": str(self.model2.id)})) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -317,7 +304,7 @@ class TestModelAPI(FixtureAPITestCase): "updated": self.model2.updated.isoformat().replace("+00:00", "Z"), "name": "Second Model", "description": "", - "rights": ["read"], + "rights": ["read", "write", "admin"], "archived": False, }) @@ -336,24 +323,29 @@ class TestModelAPI(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), {"detail": "You do not have permission to perform this action."}) - def test_update_model_requires_contrib(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_update_model_requires_contributor(self, has_access_mock): self.assertFalse(self.model1.public) self.model1.memberships.create(user=self.user3, level=Role.Guest.value) self.client.force_login(self.user3) - with self.assertNumQueries(5): + + with self.assertNumQueries(3): response = self.client.put( reverse("api:model-retrieve", kwargs={"pk": str(self.model1.id)}), {"name": "new name", "description": "test"}, format="json", ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.model1.refresh_from_db() self.assertEqual((self.model1.name, self.model1.description), ("First Model", "first")) self.assertDictEqual(response.json(), {"detail": "You do not have a contributor access to this model."}) + self.assertEqual(has_access_mock.call_count, 1) + self.assertEqual(has_access_mock.call_args, call(self.user3, self.model1, Role.Contributor.value, skip_public=False)) def test_update_model_unique_name(self): self.client.force_login(self.user1) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.put( reverse("api:model-retrieve", kwargs={"pk": str(self.model1.id)}), {"name": self.model2.name, "description": "", "public": True}, @@ -362,21 +354,22 @@ class TestModelAPI(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), {"name": ["A model with this name already exists"]}) - def test_update_model_archived_requires_archivable(self): - self.assertFalse(self.model1.is_archivable(self.user2)) + @patch("arkindex.users.utils.get_max_level", return_value=Role.Contributor.value) + def test_update_model_archived_requires_archivable(self, get_max_level_mock): self.client.force_login(self.user2) + self.assertFalse(self.model1.is_archivable(self.user2)) cases = [ (timezone.now(), False), (None, True), ] - for current_value, new_value in cases: with self.subTest(current_value=current_value, new_value=new_value): + get_max_level_mock.reset_mock() self.model1.archived = current_value self.model1.save() - with self.assertNumQueries(8): + with self.assertNumQueries(4): response = self.client.put( reverse("api:model-retrieve", kwargs={"pk": str(self.model1.id)}), {"name": "new name", "description": "test", "archived": new_value}, @@ -385,6 +378,8 @@ class TestModelAPI(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), {"archived": ["You are not allowed to archive or unarchive this model."]}) + self.assertEqual(get_max_level_mock.call_count, 1) + self.assertEqual(get_max_level_mock.call_args, call(self.user2, self.model1)) def test_update_model_archived(self): self.assertTrue(self.model1.is_archivable(self.user1)) @@ -400,7 +395,7 @@ class TestModelAPI(FixtureAPITestCase): self.model1.archived = current_value self.model1.save() - with self.assertNumQueries(10): + with self.assertNumQueries(5): response = self.client.put( reverse("api:model-retrieve", kwargs={"pk": str(self.model1.id)}), {"name": "new name", "description": "test", "archived": new_value}, @@ -425,7 +420,7 @@ class TestModelAPI(FixtureAPITestCase): def test_update_model(self): self.assertFalse(self.model1.public) self.client.force_login(self.user1) - with self.assertNumQueries(9): + with self.assertNumQueries(5): response = self.client.put( reverse("api:model-retrieve", kwargs={"pk": str(self.model1.id)}), {"name": "new name", "description": "", "public": True}, @@ -465,24 +460,29 @@ class TestModelAPI(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), {"detail": "You do not have permission to perform this action."}) - def test_partial_update_model_requires_contrib(self): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_partial_update_model_requires_contributor(self, has_access_mock): self.assertFalse(self.model1.public) - self.model1.memberships.create(user=self.user3, level=Role.Guest.value) self.client.force_login(self.user3) - with self.assertNumQueries(5): + + with self.assertNumQueries(3): response = self.client.patch( reverse("api:model-retrieve", kwargs={"pk": str(self.model1.id)}), {"description": "test"}, format="json", ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.model1.refresh_from_db() self.assertEqual((self.model1.name, self.model1.description), ("First Model", "first")) self.assertDictEqual(response.json(), {"detail": "You do not have a contributor access to this model."}) + self.assertEqual(has_access_mock.call_count, 1) + self.assertEqual(has_access_mock.call_args, call(self.user3, self.model1, Role.Contributor.value, skip_public=False)) + def test_partial_update_model(self): self.client.force_login(self.user1) - with self.assertNumQueries(8): + with self.assertNumQueries(4): response = self.client.patch( reverse("api:model-retrieve", kwargs={"pk": str(self.model1.id)}), {"description": "new desc", "public": True}, @@ -506,7 +506,8 @@ class TestModelAPI(FixtureAPITestCase): } ) - def test_partial_update_model_archived_requires_archivable(self): + @patch("arkindex.users.utils.get_max_level", return_value=Role.Contributor.value) + def test_partial_update_model_archived_requires_archivable(self, get_max_level_mock): self.assertFalse(self.model1.is_archivable(self.user2)) self.client.force_login(self.user2) @@ -514,13 +515,13 @@ class TestModelAPI(FixtureAPITestCase): (timezone.now(), False), (None, True), ] - for current_value, new_value in cases: with self.subTest(current_value=current_value, new_value=new_value): + get_max_level_mock.reset_mock() self.model1.archived = current_value self.model1.save() - with self.assertNumQueries(7): + with self.assertNumQueries(3): response = self.client.patch( reverse("api:model-retrieve", kwargs={"pk": str(self.model1.id)}), {"archived": new_value}, @@ -529,6 +530,8 @@ class TestModelAPI(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), {"archived": ["You are not allowed to archive or unarchive this model."]}) + self.assertEqual(get_max_level_mock.call_count, 1) + self.assertEqual(get_max_level_mock.call_args, call(self.user2, self.model1)) def test_partial_update_model_archived(self): self.assertTrue(self.model1.is_archivable(self.user1)) @@ -544,7 +547,7 @@ class TestModelAPI(FixtureAPITestCase): self.model1.archived = current_value self.model1.save() - with self.assertNumQueries(9): + with self.assertNumQueries(4): response = self.client.patch( reverse("api:model-retrieve", kwargs={"pk": str(self.model1.id)}), {"archived": new_value}, @@ -569,59 +572,77 @@ class TestModelAPI(FixtureAPITestCase): def test_list_model_versions_requires_logged_in(self): """To list a model's versions, you need to be logged in. """ - response = self.client.get(reverse("api:model-versions", kwargs={"pk": str(self.model1.id)})) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + with self.assertNumQueries(0): + response = self.client.get(reverse("api:model-versions", kwargs={"pk": str(self.model1.id)})) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertDictEqual(response.json(), {"detail": "Authentication credentials were not provided."}) - def test_list_model_versions_requires_guest_access(self): - """To view a model's version, you need guest access on the model - """ + @patch("arkindex.training.api.get_max_level", return_value=None) + def test_list_model_versions_requires_guest_access(self, get_max_level_mock): self.client.force_login(self.user1) - with self.assertNumQueries(6): + + with self.assertNumQueries(3): response = self.client.get(reverse("api:model-versions", kwargs={"pk": str(self.model2.id)})) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), {"detail": "You need a Guest access to list versions of a model."}) - def test_list_model_versions_low_access(self): + @patch("arkindex.training.api.get_max_level", return_value=Role.Guest.value) + def test_list_model_versions_low_access(self, get_max_level_mock): """With only guest access rights on a model, you only see the available versions with a set tag """ self.client.force_login(self.user3) - with self.assertNumQueries(8): + + with self.assertNumQueries(5): response = self.client.get(reverse("api:model-versions", kwargs={"pk": str(self.model2.id)})) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertListEqual( [version["id"] for version in response.json()["results"]], [str(self.model_version3.id)], ) - def test_list_model_versions_full_access(self): + self.assertEqual(get_max_level_mock.call_count, 1) + self.assertEqual(get_max_level_mock.call_args, call(self.user3, self.model2)) + + @patch("arkindex.training.api.get_max_level", return_value=Role.Contributor.value) + def test_list_model_versions_full_access(self, get_max_level_mock): """With contributor access rights, you see all versions. """ self.client.force_login(self.user2) - with self.assertNumQueries(8): + + with self.assertNumQueries(5): response = self.client.get(reverse("api:model-versions", kwargs={"pk": str(self.model2.id)})) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertListEqual( [version["id"] for version in response.json()["results"]], [str(self.model_version4.id), str(self.model_version3.id)], ) - def test_destroy_model_versions_requires_admin(self): - """To destroy a model version, you need admin rights on the model. - """ + self.assertEqual(get_max_level_mock.call_count, 1) + self.assertEqual(get_max_level_mock.call_args, call(self.user2, self.model2)) + + @patch("arkindex.training.api.get_max_level", return_value=Role.Contributor.value) + def test_destroy_model_versions_requires_admin(self, get_max_level_mock): self.client.force_login(self.user2) - with self.assertNumQueries(6): + + with self.assertNumQueries(3): response = self.client.delete(reverse("api:model-version-retrieve", kwargs={"pk": str(self.model_version1.id)})) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertDictEqual(response.json(), {"detail": "You need an Admin access to the model to destroy this version."}) + self.assertEqual(get_max_level_mock.call_count, 1) + self.assertEqual(get_max_level_mock.call_args, call(self.user2, self.model1)) + def test_destroy_model_versions_archived(self): self.model1.archived = timezone.now() self.model1.save() self.client.force_login(self.user1) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.delete(reverse("api:model-version-retrieve", kwargs={"pk": str(self.model_version1.id)})) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) @@ -632,20 +653,27 @@ class TestModelAPI(FixtureAPITestCase): This also deletes every worker run that used this model version """ self.client.force_login(self.user1) - with self.assertNumQueries(12): + with self.assertNumQueries(9): response = self.client.delete(reverse("api:model-version-retrieve", kwargs={"pk": str(self.model_version1.id)})) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - def test_retrieve_model_versions_require_guest(self): - """To retrieve a model version with a set tag and state==Available, you need guest rights on the model. + @patch("arkindex.training.api.get_max_level", return_value=None) + def test_retrieve_model_versions_require_guest(self, get_max_level_mock): + """ + To retrieve a model version with a set tag and state==Available, you need guest rights on the model. """ self.client.force_login(self.user1) - with self.assertNumQueries(6): + + with self.assertNumQueries(3): response = self.client.get(reverse("api:model-version-retrieve", kwargs={"pk": str(self.model_version3.id)})) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertDictEqual(response.json(), {"detail": "You need a Guest access to the model to retrieve this version."}) + self.assertEqual(get_max_level_mock.call_count, 1) + self.assertEqual(get_max_level_mock.call_args, call(self.user1, self.model2)) - def test_retrieve_model_versions_tag_available(self): + @patch("arkindex.training.api.get_max_level", return_value=Role.Guest.value) + def test_retrieve_model_versions_tag_available(self, get_max_level_mock): """ Retrieve a model version with a set tag and state==Available with guest rights on the model. No s3 URL is set with a guest access. @@ -653,9 +681,11 @@ class TestModelAPI(FixtureAPITestCase): self.assertIsNotNone(self.model_version3.tag) self.assertEqual(self.model_version3.state, ModelVersionState.Available) self.client.force_login(self.user3) - with self.assertNumQueries(6): + + with self.assertNumQueries(3): response = self.client.get(reverse("api:model-version-retrieve", kwargs={"pk": str(self.model_version3.id)})) self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertDictEqual(response.json(), { "id": str(self.model_version3.id), "model_id": str(self.model2.id), @@ -670,38 +700,55 @@ class TestModelAPI(FixtureAPITestCase): "s3_url": None, "s3_put_url": None, }) + self.assertEqual(get_max_level_mock.call_count, 1) + self.assertEqual(get_max_level_mock.call_args, call(self.user3, self.model2)) - def test_retrieve_model_versions_no_tag_requires_contributor(self): - """To retrieve a model version with no set tag or state!=Available, you need contributor rights on the model. + @patch("arkindex.training.api.get_max_level", return_value=Role.Guest.value) + def test_retrieve_model_versions_no_tag_requires_contributor(self, get_max_level_mock): + """ + To retrieve a model version with no set tag or state!=Available, you need contributor rights on the model. """ self.assertEqual(self.model_version4.tag, None) self.client.force_login(self.user3) - with self.assertNumQueries(6): + + with self.assertNumQueries(3): response = self.client.get(reverse("api:model-version-retrieve", kwargs={"pk": str(self.model_version4.id)})) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertDictEqual(response.json(), {"detail": "You need a Contributor access to the model to retrieve this version."}) + self.assertEqual(get_max_level_mock.call_count, 1) + self.assertEqual(get_max_level_mock.call_args, call(self.user3, self.model2)) - def test_retrieve_model_versions_no_validated_requires_contributor(self): - """To retrieve a model version with no set tag or state!=Available, you need contributor rights on the model. + @patch("arkindex.training.api.get_max_level", return_value=Role.Guest.value) + def test_retrieve_model_versions_no_validated_requires_contributor(self, get_max_level_mock): + """ + To retrieve a model version with no set tag or state!=Available, you need contributor rights on the model. """ self.model_version4.tag = "A tag" self.model_version4.state = ModelVersionState.Error self.model_version4.save() self.client.force_login(self.user3) - with self.assertNumQueries(6): + + with self.assertNumQueries(3): response = self.client.get(reverse("api:model-version-retrieve", kwargs={"pk": str(self.model_version4.id)})) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertDictEqual(response.json(), {"detail": "You need a Contributor access to the model to retrieve this version."}) + self.assertEqual(get_max_level_mock.call_count, 1) + self.assertEqual(get_max_level_mock.call_args, call(self.user3, self.model2)) - @patch("arkindex.project.aws.s3.meta.client.generate_presigned_url") - def test_retrieve_model_versions(self, s3_presigned_url): - """Retrieve a model version with no set tag or state!=Available with contributor rights on the model. + @patch("arkindex.training.api.get_max_level", return_value=Role.Contributor.value) + @patch("arkindex.project.aws.s3.meta.client.generate_presigned_url", return_value="http://s3/get_url") + def test_retrieve_model_versions(self, s3_presigned_url, get_max_level_mock): + """ + Retrieve a model version with no set tag or state!=Available with contributor rights on the model. """ - s3_presigned_url.return_value = "http://s3/get_url" self.client.force_login(self.user2) - with self.assertNumQueries(6): + + with self.assertNumQueries(3): response = self.client.get(reverse("api:model-version-retrieve", kwargs={"pk": str(self.model_version4.id)})) self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertDictEqual(response.json(), { "id": str(self.model_version4.id), "model_id": str(self.model2.id), @@ -717,24 +764,28 @@ class TestModelAPI(FixtureAPITestCase): "s3_put_url": None, }) - @patch("arkindex.project.aws.s3.Object") - @patch("arkindex.project.aws.S3FileMixin.exists") - def test_partial_update_model_version_requires_contributor(self, exists, s3_object): + self.assertEqual(get_max_level_mock.call_count, 1) + self.assertEqual(get_max_level_mock.call_args, call(self.user2, self.model2)) + + @patch("arkindex.training.api.get_max_level", return_value=Role.Guest.value) + def test_partial_update_model_version_requires_contributor(self, get_max_level_mock): """ Can't partial update a model version as guest """ - s3_object().content_length = self.model_version3.size - s3_object().e_tag = self.model_version3.archive_hash - exists.return_value = True self.client.force_login(self.user3) - with self.assertNumQueries(6): + + with self.assertNumQueries(3): response = self.client.patch(reverse("api:model-version-retrieve", kwargs={"pk": str(self.model_version3.id)}), {"state": "available"}) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertDictEqual( response.json(), {"detail": "You need a Contributor access to the model to update this version."} ) + self.assertEqual(get_max_level_mock.call_count, 1) + self.assertEqual(get_max_level_mock.call_args, call(self.user3, self.model2)) + @patch("arkindex.project.aws.s3.Object") @patch("arkindex.project.aws.S3FileMixin.exists") def test_partial_update_model_version_archived(self, exists, s3_object): @@ -745,7 +796,7 @@ class TestModelAPI(FixtureAPITestCase): self.model1.save() self.client.force_login(self.user1) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.patch(reverse("api:model-version-retrieve", kwargs={"pk": str(self.model_version1.id)}), {"state": "available"}) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) @@ -775,7 +826,7 @@ class TestModelAPI(FixtureAPITestCase): "archive_hash": "n" * 32, } - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.patch( reverse("api:model-version-retrieve", kwargs={"pk": str(self.model_version1.id)}), request, @@ -824,7 +875,7 @@ class TestModelAPI(FixtureAPITestCase): "archive_hash": "n" * 32, } - with self.assertNumQueries(9): + with self.assertNumQueries(6): response = self.client.patch( reverse("api:model-version-retrieve", kwargs={"pk": str(self.model_version3.id)}), request, @@ -860,7 +911,7 @@ class TestModelAPI(FixtureAPITestCase): self.model_version3.parent = self.model_version2 self.model_version3.save() - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.patch( reverse("api:model-version-retrieve", kwargs={"pk": str(self.model_version3.id)}), {"parent": None}, @@ -886,17 +937,14 @@ class TestModelAPI(FixtureAPITestCase): self.model_version3.refresh_from_db() self.assertIsNone(self.model_version3.parent_id) - @patch("arkindex.project.aws.s3.Object") - @patch("arkindex.project.aws.S3FileMixin.exists") - def test_update_model_version_requires_contributor(self, exists, s3_object): + @patch("arkindex.training.api.get_max_level", return_value=None) + def test_update_model_version_requires_contributor(self, get_max_level_mock): """ Can't update a model version with guest access rights to the model """ - s3_object().content_length = self.model_version3.size - s3_object().e_tag = self.model_version3.archive_hash - exists.return_value = True self.client.force_login(self.user3) - with self.assertNumQueries(6): + + with self.assertNumQueries(3): response = self.client.put( reverse("api:model-version-retrieve", kwargs={"pk": str(self.model_version3.id)}), { @@ -905,12 +953,15 @@ class TestModelAPI(FixtureAPITestCase): "size": 8, "state": ModelVersionState.Available.value, "description": "other description", - "configuration": {"hi": "Who am I ?"}, + "configuration": {"hi": "Who am I?"}, }, format="json", ) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertDictEqual(response.json(), {"detail": "You need a Contributor access to the model to update this version."}) + self.assertEqual(get_max_level_mock.call_count, 1) + self.assertEqual(get_max_level_mock.call_args, call(self.user3, self.model2)) @patch("arkindex.project.aws.s3.Object") @patch("arkindex.project.aws.S3FileMixin.exists") @@ -922,7 +973,7 @@ class TestModelAPI(FixtureAPITestCase): self.model1.save() self.client.force_login(self.user1) - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.put( reverse("api:model-version-retrieve", kwargs={"pk": str(self.model_version1.id)}), { @@ -939,21 +990,23 @@ class TestModelAPI(FixtureAPITestCase): self.assertDictEqual(response.json(), {"model": ["This model is archived."]}) - @patch("arkindex.project.aws.S3FileMixin.exists") - def test_validate_model_version_requires_contributor(self, exists): + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_validate_model_version_requires_contributor(self, has_access_mock): self.client.force_login(self.user3) - exists.return_value = False - with self.assertNumQueries(6): + + with self.assertNumQueries(3): response = self.client.post( reverse("api:model-version-validate", kwargs={"pk": str(self.model_version2.id)}), {"archive_hash": "x" * 32, "hash": "y" * 32, "size": 32}, format="json" ) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), { "detail": "You need a Contributor access to the model to validate this version." }) + self.assertEqual(has_access_mock.call_count, 1) + self.assertEqual(has_access_mock.call_args, call(self.user3, self.model1, Role.Contributor.value, skip_public=False)) @patch("arkindex.project.aws.S3FileMixin.exists") def test_validate_model_version_archived(self, exists): @@ -962,7 +1015,7 @@ class TestModelAPI(FixtureAPITestCase): self.client.force_login(self.user1) exists.return_value = False - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:model-version-validate", kwargs={"pk": str(self.model_version1.id)}), {"archive_hash": "x" * 32, "hash": "y" * 32, "size": 32}, @@ -976,7 +1029,7 @@ class TestModelAPI(FixtureAPITestCase): def test_validate_model_version_required_fields(self, exists): self.client.force_login(self.user1) exists.return_value = False - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post( reverse("api:model-version-validate", kwargs={"pk": str(self.model_version1.id)}), {}, @@ -996,7 +1049,7 @@ class TestModelAPI(FixtureAPITestCase): """ self.client.force_login(self.user1) exists.return_value = False - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.post( reverse("api:model-version-validate", kwargs={"pk": str(self.model_version1.id)}), {"archive_hash": "d" * 32, "hash": "e" * 32, "size": 32}, @@ -1016,7 +1069,7 @@ class TestModelAPI(FixtureAPITestCase): """ self.client.force_login(self.user1) s3_url_mock.return_value = "http://s3/archive_url" - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.post( reverse("api:model-version-validate", kwargs={"pk": str(self.model_version1.id)}), {"archive_hash": "d" * 32, "hash": str(self.model_version5.hash), "size": 32}, @@ -1050,7 +1103,7 @@ class TestModelAPI(FixtureAPITestCase): exists.return_value = True s3_object().content_length = 31 self.client.force_login(self.user1) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.post( reverse("api:model-version-validate", kwargs={"pk": str(self.model_version1.id)}), {"archive_hash": "d" * 32, "hash": "e" * 32, "size": 32}, @@ -1072,7 +1125,7 @@ class TestModelAPI(FixtureAPITestCase): s3_object().content_length = 32 s3_object().e_tag = "a" * 32 self.client.force_login(self.user1) - with self.assertNumQueries(8): + with self.assertNumQueries(5): response = self.client.post( reverse("api:model-version-validate", kwargs={"pk": str(self.model_version1.id)}), {"archive_hash": "d" * 32, "hash": "e" * 32, "size": 32}, @@ -1151,23 +1204,28 @@ class TestModelAPI(FixtureAPITestCase): request = { "name": self.model1.name, } - with self.assertNumQueries(6): + with self.assertNumQueries(3): response = self.client.post(reverse("api:models"), request) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), {"id": str(self.model1.id), "name": "A model with this name already exists"}) - def test_create_model_name_taken_no_access(self): - """Raises a 403 with no additional information when creating a model with a name that is already used for another model + @patch("arkindex.project.mixins.has_access", return_value=False) + def test_create_model_name_taken_no_access(self, has_access_mock): + """ + Raises a 403 with no additional information when creating a model with a name that is already used for another model but without access rights on this model """ self.client.force_login(self.user3) - request = { - "name": self.model1.name, - } - with self.assertNumQueries(6): - response = self.client.post(reverse("api:models"), request) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + with self.assertNumQueries(3): + response = self.client.post(reverse("api:models"), { + "name": self.model1.name, + }) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertDictEqual(response.json(), {"detail": "You do not have permission to perform this action."}) + self.assertEqual(has_access_mock.call_count, 1) + self.assertEqual(has_access_mock.call_args, call(self.user3, self.model1, Role.Guest.value, skip_public=False)) def test_list_models_requires_verified(self): user = User.objects.create(display_name="Not Verified", verified_email=False) @@ -1181,38 +1239,13 @@ class TestModelAPI(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), {"detail": "Authentication credentials were not provided."}) - def test_list_models_low_access(self): - """User 3 has only access to Model2 with a guest access. - He only sees model_version3 of Model2 since it's the only one that - has a set tag and is in Available state. - """ - self.client.force_login(self.user3) - with self.assertNumQueries(7): - response = self.client.get(reverse("api:models")) - self.assertEqual(response.status_code, status.HTTP_200_OK) - models = response.json()["results"] - - self.assertListEqual(models, [ - { - "id": str(self.model2.id), - "created": self.model2.created.isoformat().replace("+00:00", "Z"), - "updated": self.model2.updated.isoformat().replace("+00:00", "Z"), - "name": "Second Model", - "description": "", - "rights": ["read"], - "archived": False, - } - ]) - - def test_list_models_contrib_access(self): - """User 2 has contributor access to Model1 and Model2. - He has contributor access on both that's why he sees all related versions. - Models list is ordered by name, first Model1 (named 'First Model') then Model2 (named 'Second Model'). - """ + def test_list(self): self.client.force_login(self.user2) - with self.assertNumQueries(8): + + with self.assertNumQueries(4): response = self.client.get(reverse("api:models")) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, status.HTTP_200_OK) + models = response.json()["results"] self.assertListEqual(models, [ @@ -1222,7 +1255,7 @@ class TestModelAPI(FixtureAPITestCase): "updated": self.model1.updated.isoformat().replace("+00:00", "Z"), "name": "First Model", "description": "first", - "rights": ["read", "write"], + "rights": ["read", "write", "admin"], "archived": False, }, { @@ -1233,14 +1266,23 @@ class TestModelAPI(FixtureAPITestCase): "description": "", "rights": ["read", "write", "admin"], "archived": False, - } + }, + { + "id": str(self.model3.id), + "created": self.model3.created.isoformat().replace("+00:00", "Z"), + "updated": self.model3.updated.isoformat().replace("+00:00", "Z"), + "name": "Third Model", + "description": "", + "rights": ["read", "write", "admin"], + "archived": False, + }, ]) def test_list_models_filter_name(self): """User 2 has access to both Models, use search parameter name=Second returns only Model2 """ self.client.force_login(self.user2) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.get(reverse("api:models"), {"name": "second"}) self.assertEqual(response.status_code, status.HTTP_200_OK) models = response.json()["results"] @@ -1259,7 +1301,7 @@ class TestModelAPI(FixtureAPITestCase): def test_list_models_filter_compatible_worker(self): self.client.force_login(self.user2) - with self.assertNumQueries(7): + with self.assertNumQueries(4): response = self.client.get(reverse("api:models"), {"compatible_worker": str(self.worker.id)}) self.assertEqual(response.status_code, status.HTTP_200_OK) models = response.json()["results"] @@ -1289,7 +1331,7 @@ class TestModelAPI(FixtureAPITestCase): "updated": self.model1.updated.isoformat().replace("+00:00", "Z"), "name": "First Model", "description": "first", - "rights": ["read", "write"], + "rights": ["read", "write", "admin"], "archived": True, } ]), @@ -1302,19 +1344,28 @@ class TestModelAPI(FixtureAPITestCase): "description": "", "rights": ["read", "write", "admin"], "archived": False, - } + }, + { + "id": str(self.model3.id), + "created": self.model3.created.isoformat().replace("+00:00", "Z"), + "updated": self.model3.updated.isoformat().replace("+00:00", "Z"), + "name": "Third Model", + "description": "", + "rights": ["read", "write", "admin"], + "archived": False, + }, ]), ] for archived, expected_results in cases: - with self.subTest(archived=archived), self.assertNumQueries(7): + with self.subTest(archived=archived), self.assertNumQueries(4): response = self.client.get(reverse("api:models"), {"archived": archived}) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.json()["results"], expected_results) def test_list_models_filter_compatible_worker_doesnt_exist(self): self.client.force_login(self.user2) - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.get(reverse("api:models"), {"compatible_worker": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"}) self.assertEqual(response.status_code, status.HTTP_200_OK) models = response.json()["results"] @@ -1324,7 +1375,7 @@ class TestModelAPI(FixtureAPITestCase): def test_list_models_filter_compatible_worker_no_models(self): self.client.force_login(self.user2) dla_worker = Worker.objects.get(slug="dla") - with self.assertNumQueries(5): + with self.assertNumQueries(3): response = self.client.get(reverse("api:models"), {"compatible_worker": str(dla_worker.id)}) self.assertEqual(response.status_code, status.HTTP_200_OK) models = response.json()["results"] diff --git a/arkindex/training/tests/test_model_compatible_worker.py b/arkindex/training/tests/test_model_compatible_worker.py index f163dc0e0cc4fccc498ffa1c515a2c00bdab9fed..20a19ff5b7ea233d6b306dc088aaa8ec97a3c4b9 100644 --- a/arkindex/training/tests/test_model_compatible_worker.py +++ b/arkindex/training/tests/test_model_compatible_worker.py @@ -1,9 +1,10 @@ from datetime import datetime, timezone +from unittest.mock import call, patch from django.urls import reverse from rest_framework import status -from arkindex.process.models import Worker +from arkindex.process.models import Repository, Worker from arkindex.project.tests import FixtureAPITestCase from arkindex.training.models import Model from arkindex.users.models import Role @@ -63,7 +64,7 @@ class TestModelCompatibleWorkerManage(FixtureAPITestCase): def test_create_model_not_found(self): self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(4): response = self.client.post( reverse("api:model-compatible-worker-manage", kwargs={ "model": str(self.worker1.id), @@ -76,11 +77,18 @@ class TestModelCompatibleWorkerManage(FixtureAPITestCase): "model": [f'Invalid pk "{self.worker1.id}" - object does not exist.'], }) - def test_create_requires_model_contributor(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights") + def test_create_requires_model_contributor(self, filter_rights_mock): self.model1.memberships.filter(user=self.user).update(level=Role.Guest.value) self.client.force_login(self.user) - with self.assertNumQueries(7): + filter_rights_mock.side_effect = [ + Model.objects.none(), + Worker.objects.all(), + Repository.objects.all(), + ] + + with self.assertNumQueries(3): response = self.client.post( reverse("api:model-compatible-worker-manage", kwargs={ "model": str(self.model1.id), @@ -93,27 +101,16 @@ class TestModelCompatibleWorkerManage(FixtureAPITestCase): "model": [f'Invalid pk "{self.model1.id}" - object does not exist.'], }) - def test_create_ignores_model_public(self): - self.client.force_login(self.user) - - with self.assertNumQueries(8): - response = self.client.post( - reverse("api:model-compatible-worker-manage", kwargs={ - # User has no access to model2 and model2 is public - "model": str(self.model2.id), - "worker": str(self.worker2.id), - }) - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - - self.assertEqual(response.json(), { - "model": [f'Invalid pk "{self.model2.id}" - object does not exist.'], - }) + self.assertListEqual(filter_rights_mock.call_args_list, [ + call(self.user, Model, Role.Contributor.value), + call(self.user, Worker, Role.Contributor.value), + call(self.user, Repository, Role.Contributor.value), + ]) def test_create_worker_not_found(self): self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(4): response = self.client.post( reverse("api:model-compatible-worker-manage", kwargs={ "model": str(self.model1.id), @@ -126,11 +123,18 @@ class TestModelCompatibleWorkerManage(FixtureAPITestCase): "worker": [f'Invalid pk "{self.model1.id}" - object does not exist.'], }) - def test_create_requires_worker_contributor(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights") + def test_create_requires_worker_contributor(self, filter_rights_mock): self.worker2.memberships.filter(user=self.user).update(level=Role.Guest.value) self.client.force_login(self.user) - with self.assertNumQueries(7): + filter_rights_mock.side_effect = [ + Model.objects.all(), + Worker.objects.none(), + Repository.objects.none(), + ] + + with self.assertNumQueries(4): response = self.client.post( reverse("api:model-compatible-worker-manage", kwargs={ "model": str(self.model1.id), @@ -143,10 +147,16 @@ class TestModelCompatibleWorkerManage(FixtureAPITestCase): "worker": [f'Invalid pk "{self.worker2.id}" - object does not exist.'], }) + self.assertListEqual(filter_rights_mock.call_args_list, [ + call(self.user, Model, Role.Contributor.value), + call(self.user, Worker, Role.Contributor.value), + call(self.user, Repository, Role.Contributor.value), + ]) + def test_create_nothing_found(self): self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(4): response = self.client.post( reverse("api:model-compatible-worker-manage", kwargs={ "model": str(self.worker2.id), @@ -164,12 +174,10 @@ class TestModelCompatibleWorkerManage(FixtureAPITestCase): self.client.force_login(self.user) # 2 queries to retrieve the user and session - # 4 queries for the various content types for memberships, - # which are normally cached between requests but reset between each unit test # 2 queries to retrieve the model and worker # 1 query to check that the relationship does not already exist # 1 query to insert - with self.assertNumQueries(10): + with self.assertNumQueries(6): response = self.client.post( reverse("api:model-compatible-worker-manage", kwargs={ "model": str(self.model1.id), @@ -199,7 +207,7 @@ class TestModelCompatibleWorkerManage(FixtureAPITestCase): self.worker1.save() self.client.force_login(self.user) - with self.assertNumQueries(10): + with self.assertNumQueries(6): response = self.client.post( reverse("api:model-compatible-worker-manage", kwargs={ "model": str(self.model1.id), @@ -255,7 +263,7 @@ class TestModelCompatibleWorkerManage(FixtureAPITestCase): self.model1.compatible_workers.add(self.worker2) self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(5): response = self.client.post( reverse("api:model-compatible-worker-manage", kwargs={ "model": str(self.model1.id), @@ -283,7 +291,7 @@ class TestModelCompatibleWorkerManage(FixtureAPITestCase): self.model1.save() self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(4): response = self.client.post( reverse("api:model-compatible-worker-manage", kwargs={ "model": str(self.model1.id), @@ -341,7 +349,7 @@ class TestModelCompatibleWorkerManage(FixtureAPITestCase): def test_destroy_model_not_found(self): self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(4): response = self.client.delete( reverse("api:model-compatible-worker-manage", kwargs={ "model": str(self.worker2.id), @@ -354,11 +362,18 @@ class TestModelCompatibleWorkerManage(FixtureAPITestCase): "model": [f'Invalid pk "{self.worker2.id}" - object does not exist.'], }) - def test_destroy_requires_model_contributor(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights") + def test_destroy_requires_model_contributor(self, filter_rights_mock): self.model1.memberships.filter(user=self.user).update(level=Role.Guest.value) self.client.force_login(self.user) - with self.assertNumQueries(7): + filter_rights_mock.side_effect = [ + Model.objects.none(), + Worker.objects.all(), + Repository.objects.all(), + ] + + with self.assertNumQueries(3): response = self.client.delete( reverse("api:model-compatible-worker-manage", kwargs={ "model": str(self.model1.id), @@ -371,27 +386,16 @@ class TestModelCompatibleWorkerManage(FixtureAPITestCase): "model": [f'Invalid pk "{self.model1.id}" - object does not exist.'], }) - def test_destroy_ignores_model_public(self): - self.client.force_login(self.user) - - with self.assertNumQueries(8): - response = self.client.delete( - reverse("api:model-compatible-worker-manage", kwargs={ - # User has no access to model2 and model2 is public - "model": str(self.model2.id), - "worker": str(self.worker2.id), - }) - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - - self.assertEqual(response.json(), { - "model": [f'Invalid pk "{self.model2.id}" - object does not exist.'], - }) + self.assertListEqual(filter_rights_mock.call_args_list, [ + call(self.user, Model, Role.Contributor.value), + call(self.user, Worker, Role.Contributor.value), + call(self.user, Repository, Role.Contributor.value), + ]) def test_destroy_worker_not_found(self): self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(4): response = self.client.delete( reverse("api:model-compatible-worker-manage", kwargs={ "model": str(self.model1.id), @@ -404,11 +408,18 @@ class TestModelCompatibleWorkerManage(FixtureAPITestCase): "worker": [f'Invalid pk "{self.model1.id}" - object does not exist.'], }) - def test_destroy_requires_worker_contributor(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights") + def test_destroy_requires_worker_contributor(self, filter_rights_mock): self.worker2.memberships.filter(user=self.user).update(level=Role.Guest.value) self.client.force_login(self.user) - with self.assertNumQueries(7): + filter_rights_mock.side_effect = [ + Model.objects.all(), + Worker.objects.none(), + Repository.objects.none(), + ] + + with self.assertNumQueries(4): response = self.client.delete( reverse("api:model-compatible-worker-manage", kwargs={ "model": str(self.model1.id), @@ -421,10 +432,16 @@ class TestModelCompatibleWorkerManage(FixtureAPITestCase): "worker": [f'Invalid pk "{self.worker2.id}" - object does not exist.'], }) + self.assertListEqual(filter_rights_mock.call_args_list, [ + call(self.user, Model, Role.Contributor.value), + call(self.user, Worker, Role.Contributor.value), + call(self.user, Repository, Role.Contributor.value), + ]) + def test_destroy_nothing_found(self): self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(4): response = self.client.delete( reverse("api:model-compatible-worker-manage", kwargs={ "model": str(self.worker2.id), @@ -444,12 +461,10 @@ class TestModelCompatibleWorkerManage(FixtureAPITestCase): self.model1.compatible_workers.set([self.worker1, self.worker2]) # 2 queries to retrieve the user and session - # 4 queries for the various content types for memberships, - # which are normally cached between requests but reset between each unit test # 2 queries to retrieve the model and worker # 1 query to retrieve the existing relationship # 1 query to delete - with self.assertNumQueries(10): + with self.assertNumQueries(6): response = self.client.delete( reverse("api:model-compatible-worker-manage", kwargs={ "model": str(self.model1.id), @@ -476,7 +491,7 @@ class TestModelCompatibleWorkerManage(FixtureAPITestCase): self.model1.compatible_workers.add(self.worker2) self.client.force_login(self.user) - with self.assertNumQueries(10): + with self.assertNumQueries(6): response = self.client.delete( reverse("api:model-compatible-worker-manage", kwargs={ "model": str(self.model1.id), @@ -524,7 +539,7 @@ class TestModelCompatibleWorkerManage(FixtureAPITestCase): def test_destroy_does_not_exist(self): self.client.force_login(self.user) - with self.assertNumQueries(9): + with self.assertNumQueries(5): response = self.client.delete( reverse("api:model-compatible-worker-manage", kwargs={ "model": str(self.model1.id), @@ -553,7 +568,7 @@ class TestModelCompatibleWorkerManage(FixtureAPITestCase): self.model1.compatible_workers.add(self.worker2) self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(4): response = self.client.delete( reverse("api:model-compatible-worker-manage", kwargs={ "model": str(self.model1.id), diff --git a/arkindex/users/admin.py b/arkindex/users/admin.py index 3d9d1a9129b7b809772b12aaec6ed14c4605fd4f..a50a388cf896b6d984f81d4871f161c8a73252c7 100644 --- a/arkindex/users/admin.py +++ b/arkindex/users/admin.py @@ -3,11 +3,10 @@ from django.contrib import admin from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.contrib.auth.forms import ReadOnlyPasswordHashField from django.contrib.auth.models import Group as BaseGroup -from django.contrib.contenttypes.admin import GenericTabularInline from django.db.models.functions import Collate from enumfields.admin import EnumFieldListFilter -from arkindex.users.models import Group, Right, User, UserScope +from arkindex.users.models import User, UserScope class UserCreationForm(forms.ModelForm): @@ -95,36 +94,7 @@ class UserScopeAdmin(admin.ModelAdmin): list_filter = [("scope", EnumFieldListFilter), ] -class GroupMembershipInline(GenericTabularInline): - ct_fk_field = "content_id" - model = Right - fields = ("group", "level") - extra = 1 - - def get_queryset(self, request): - qs = super().get_queryset(request) - return qs.filter(user__isnull=True) - - -class UserMembershipInline(GenericTabularInline): - ct_fk_field = "content_id" - model = Right - fields = ("user", "level") - extra = 1 - - def get_queryset(self, request): - qs = super().get_queryset(request) - return qs.filter(group__isnull=True) - - -class GroupAdmin(admin.ModelAdmin): - list_display = ("id", "name", "public") - inlines = (UserMembershipInline, ) - - admin.site.register(User, UserAdmin) -# Register the custom GroupAdmin -admin.site.register(Group, GroupAdmin) -# and hide base GroupAdmin form contrib.auth -admin.site.unregister(BaseGroup) admin.site.register(UserScope, UserScopeAdmin) +# Remove the admin page for the default Django groups +admin.site.unregister(BaseGroup) diff --git a/arkindex/users/allow_all.py b/arkindex/users/allow_all.py new file mode 100644 index 0000000000000000000000000000000000000000..2704635a321093439715980147af74697ffb9bdd --- /dev/null +++ b/arkindex/users/allow_all.py @@ -0,0 +1,28 @@ +from typing import Optional + +from django.db.models import IntegerField, Value + +from arkindex.users.models import Role, User + + +def has_access(user: User, instance, level: int, skip_public: bool = False) -> bool: + """ + Check if the user has access to a generic instance with a minimum level + If skip_public parameter is set to true, exclude rights on public instances + """ + return True + + +def filter_rights(user: User, model, level: int): + """ + Return a generic queryset of objects with access rights for this user. + Level filtering parameter should be an integer between 1 and 100. + """ + return model.objects.annotate(max_level=Value(Role.Admin.value, IntegerField())) + + +def get_max_level(user: User, instance) -> Optional[int]: + """ + Returns the maximum access level on a given model instance + """ + return Role.Admin.value diff --git a/arkindex/users/api.py b/arkindex/users/api.py index 656111cd18123ce7a18a64dcb6afcf4eb628e380..443c756abfc761a9176c781b996b6afac5a8dd8c 100644 --- a/arkindex/users/api.py +++ b/arkindex/users/api.py @@ -1,15 +1,11 @@ import logging import urllib.parse -from uuid import UUID from django.conf import settings from django.contrib.auth import login, logout from django.contrib.auth.forms import PasswordResetForm from django.contrib.auth.tokens import default_token_generator -from django.contrib.contenttypes.models import ContentType from django.core.mail import send_mail -from django.db.models import Count, Prefetch, Q -from django.shortcuts import get_object_or_404 from django.template.loader import render_to_string from django.urls import reverse from django.utils.http import urlsafe_base64_encode @@ -19,37 +15,22 @@ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter, extend_schema, extend_schema_view from rest_framework import serializers, status from rest_framework.exceptions import AuthenticationFailed, NotFound, PermissionDenied, ValidationError -from rest_framework.generics import ( - CreateAPIView, - ListAPIView, - ListCreateAPIView, - RetrieveDestroyAPIView, - RetrieveUpdateDestroyAPIView, -) -from rest_framework.permissions import SAFE_METHODS +from rest_framework.generics import CreateAPIView, ListAPIView, RetrieveDestroyAPIView, RetrieveUpdateDestroyAPIView from rest_framework.response import Response from rest_framework.views import APIView from rq.job import JobStatus from arkindex.documents.models import Corpus -from arkindex.process.models import Worker -from arkindex.project.mixins import ACLMixin, WorkerACLMixin from arkindex.project.permissions import IsAuthenticatedOrReadOnly, IsVerified -from arkindex.users.models import Group, Right, Role, Scope, User, UserScope +from arkindex.users.models import Group, Role, Scope, User, UserScope from arkindex.users.serializers import ( EmailLoginSerializer, - GenericMembershipSerializer, - GroupSerializer, JobSerializer, - MemberGroupSerializer, - MembershipCreateSerializer, - MembershipSerializer, NewUserSerializer, PasswordResetConfirmSerializer, PasswordResetSerializer, UserSerializer, ) -from arkindex.users.utils import RightContent, get_max_level logger = logging.getLogger(__name__) @@ -290,23 +271,6 @@ class PasswordResetConfirm(CreateAPIView): serializer_class = PasswordResetConfirmSerializer -@extend_schema(tags=["users"]) -class GroupsList(ListCreateAPIView): - """ - List existing groups either if they are public or the request user is a member. - A POST request allow to create a new group. - """ - serializer_class = GroupSerializer - permission_classes = (IsVerified, ) - - def get_queryset(self): - # List public groups and ones to which user belongs - return Group.objects \ - .annotate(members_count=Count("users")) \ - .filter(Q(public=True) | Q(users__in=[self.request.user])) \ - .order_by("name", "id") - - @extend_schema(tags=["jobs"]) class JobList(ListAPIView): """ @@ -353,287 +317,3 @@ class JobRetrieve(RetrieveDestroyAPIView): if instance.get_status(refresh=False) == JobStatus.STARTED: raise ValidationError(["Cannot delete a running job."]) instance.delete() - - -@extend_schema(tags=["users"]) -class GroupsCreate(CreateAPIView): - """ - Create a new group. The request user will be added as a member of this group. - """ - serializer_class = GroupSerializer - permission_classes = (IsVerified, ) - # For OpenAPI type discovery - queryset = Group.objects.none() - - -@extend_schema(tags=["users"]) -@extend_schema_view( - patch=extend_schema( - description="Partially update details of a group. " - "Requires to have `admin` privileges on this group." - ), - put=extend_schema( - description="Update details of a group. " - "Requires to have `admin` privileges on this group." - ), - delete=extend_schema( - description="Delete a group. " - "Requires to have `admin` privileges on this group." - ) -) -class GroupDetails(RetrieveUpdateDestroyAPIView): - """ - Retrieve details about a specific group - """ - serializer_class = GroupSerializer - permission_classes = (IsVerified, ) - # For OpenAPI type discovery - queryset = Group.objects.none() - - def get_membership(self, group): - try: - return group.memberships.get(user_id=self.request.user.id) - except Right.DoesNotExist: - raise PermissionDenied(detail="Only members of a group can retrieve their details") - - def get_object(self): - if not hasattr(self, "_group"): - self._group = super().get_object() - - if self.request.user.is_admin: - self._group.level = Role.Admin.value - else: - # Retrieve the membership to know the privilege level for this user - if not hasattr(self, "_member"): - self._member = self.get_membership(self._group) - # Add request member level to the group - self._group.level = self._member.level - - return self._group - - def check_object_permissions(self, request, obj): - if request.user.is_admin: - return - if not hasattr(self, "_member"): - self._member = self.get_membership(obj) - # Check the user has the right to delete or update a group - super().check_object_permissions(request, obj) - if request.method not in SAFE_METHODS and self._member.level < Role.Admin.value: - raise PermissionDenied(detail="Only members with an admin privilege may update or delete this group.") - - def get_queryset(self): - # Superusers have access to all groups - if self.request.user.is_authenticated and self.request.user.is_admin: - return Group.objects.all() \ - .annotate(members_count=Count("memberships")) - # Filter groups that are public or for which the user is a member - return Group.objects \ - .annotate(members_count=Count("memberships")) \ - .filter( - Q(public=True) - | Q(memberships__user=self.request.user) - ) - - -@extend_schema(tags=["users"]) -class UserMemberships(ListAPIView): - """ - List groups to which the request user belongs with its privileges on each group - """ - serializer_class = MemberGroupSerializer - permission_classes = (IsVerified, ) - - def get_queryset(self): - - def _build_fake_right(group): - # Serialize a fake memberships for admin user with no needs for ID and level - admin_membership = Right( - content_object=group, - user=self.request.user, - level=None, - ) - admin_membership.id = None - admin_membership.members_count = group.members_count - return admin_membership - - if self.request.user.is_authenticated and self.request.user.is_admin: - # Allow super admins to access all groups as if they had an admin access level - groups = Group.objects.all() \ - .annotate(members_count=Count("memberships")) \ - .order_by("name", "id") - return [_build_fake_right(group) for group in groups] - # Annotate rights with the group member count as Prefetch is not available with the Generic FK - return self.request.user.rights \ - .prefetch_related("content_object") \ - .filter(content_type=ContentType.objects.get_for_model(Group)) \ - .annotate(members_count=Count("group_target__memberships")) \ - .order_by("group_target__name", "group_target__id") - - -@extend_schema(tags=["users"]) -@extend_schema_view( - patch=extend_schema( - description="Partially update a generic membership." - ), - put=extend_schema( - description="Update a generic membership." - ), - delete=extend_schema( - description="Delete a generic membership." - ) -) -class MembershipDetails(ACLMixin, RetrieveUpdateDestroyAPIView): - """ - Retrieve details of a generic membership - """ - serializer_class = MembershipSerializer - permission_classes = (IsVerified, ) - # Perform access checks with the permissions - queryset = Right.objects.select_related("content_type").all() - - def check_object_permissions(self, request, membership): - super().check_object_permissions(request, membership) - - # Retrieve access level - access_level = None - if isinstance(membership.content_object, Worker): - # Use WorkerACLMixin method as the worker access level may be inherited from its repository - access_level = WorkerACLMixin.get_max_level(self, membership.content_object) - else: - access_level = get_max_level(self.request.user, membership.content_object) - - # Check the access level of the request user on the content object - if not access_level or access_level < Role.Guest.value: - raise NotFound - - # Allow any guest to retrieve information - if self.request.method in SAFE_METHODS: - return - - # Allow a user to remove its own membership - if self.request.method == "DELETE" and membership.user_id == self.request.user.id: - return - - # Only allow admins to edit the access level of a membership - if access_level < Role.Admin.value: - raise PermissionDenied( - detail="Only admins of the target membership group can perform this action." - ) - - def perform_destroy(self, instance): - # At least one admin member must exist except for workers which depends on their repositories - if ( - instance.content_type != ContentType.objects.get_for_model(Worker) - and instance.level >= Role.Admin.value - and instance.content_object.memberships.using("default").filter(level__gte=Role.Admin.value).count() < 2 - ): - raise ValidationError({"detail": ["Removing all memberships with an admin privilege is not possible."]}) - return super().perform_destroy(instance) - - -MEMBERSHIPS_TYPE_FILTERS = ["user", "group"] - - -@extend_schema( - parameters=[ - OpenApiParameter( - content.name, - type=OpenApiTypes.UUID, - description=f"List user members for a {content}", - ) - for content in RightContent - ] + [ - OpenApiParameter( - "type", - description="Filter memberships by owner type", - enum=MEMBERSHIPS_TYPE_FILTERS, - ) - ], - tags=["users"] -) -class GenericMembershipsList(WorkerACLMixin, ListAPIView): - """ - List memberships for a specific content with their privileges. - Target of the right must be defined as a query parameter. - """ - permission_classes = (IsVerified, ) - serializer_class = GenericMembershipSerializer - - def get_content_object(self): - """ - Retrieve a generic content based on query parameters. - This method does not perform any permission check. - """ - allowed_contents = [c.name for c in RightContent] - content_keys = [key for key in self.request.query_params if key in allowed_contents] - # Assert exactly one of the content object parameter has been set - if len(content_keys) != 1: - raise ValidationError({ - "__all__": [ - "Exactly one of those query parameters must be defined: " - f"{', '.join(allowed_contents)}." - ] - }) - - key, = content_keys - content_model = RightContent[key].value - content_id = self.request.query_params[key] - try: - content_uuid = UUID(content_id) - except (AttributeError, ValueError): - raise ValidationError({key: [f"'{content_id}' is not a valid UUID."]}) - - # Use default database in case the content object has just been created - return get_object_or_404(content_model.objects.using("default"), id=content_uuid) - - def get_queryset(self): - content_object = self.get_content_object() - - # Determine the user right on the generic content - has_access = None - if isinstance(content_object, Worker): - # Workers rights may be determined by their repository - has_access = self.has_worker_access(content_object, Role.Guest.value) - else: - access_params = {} - if isinstance(content_object, Group): - # A user may not be able to list group members if he is not a member - access_params["skip_public"] = True - has_access = self.has_access(content_object, Role.Guest.value, **access_params) - - # Ensure the user has a guest access to the content - if not has_access: - raise PermissionDenied( - detail="You do not have the required access level to list members for this content." - ) - - # Avoid a stale read when adding/deletig a member - qs = content_object.memberships \ - .using("default") \ - .prefetch_related( - "user", - Prefetch("group", queryset=Group.objects.annotate(members_count=Count("memberships"))) - ) \ - .order_by("user__display_name", "group__name", "id") - - # Handle a simple owner type filter - type_filter = self.request.query_params.get("type") - if type_filter and type_filter in MEMBERSHIPS_TYPE_FILTERS: - type_filter = {f"{type_filter}__isnull": False} - qs = qs.filter(**type_filter) - - return qs - - -@extend_schema_view( - post=extend_schema( - operation_id="CreateMembership", - tags=["users"], - ) -) -class GenericMembershipCreate(CreateAPIView): - """ - Create a new generic membership. - """ - permission_classes = (IsVerified, ) - serializer_class = MembershipCreateSerializer diff --git a/arkindex/users/serializers.py b/arkindex/users/serializers.py index 42fb755c08f3c5362302457d54297763a913ca71..705ef7d16f9a211fd79a7054c2254431922a9a34 100644 --- a/arkindex/users/serializers.py +++ b/arkindex/users/serializers.py @@ -1,18 +1,12 @@ from django.conf import settings from django.contrib.auth.password_validation import validate_password from django.contrib.auth.tokens import default_token_generator -from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError -from django.db import transaction from django.utils.http import urlsafe_base64_decode from drf_spectacular.utils import extend_schema_field, inline_serializer from rest_framework import serializers -from rest_framework.exceptions import PermissionDenied -from arkindex.process.models import Worker -from arkindex.project.mixins import WorkerACLMixin -from arkindex.users.models import Group, Right, Role, User -from arkindex.users.utils import RightContent, get_max_level +from arkindex.users.models import User def validate_user_password(user, data): @@ -171,197 +165,3 @@ class JobSerializer(serializers.Serializer): but the enum is just a plain object and not an Enum for Py2 compatibility. """ return instance.get_status(refresh=False) - - -class SimpleGroupSerializer(serializers.ModelSerializer): - """ - A light group serializer with its members count - """ - members_count = serializers.IntegerField(default=None, read_only=True) - - class Meta: - model = Group - read_only_fields = ("id", "members_count") - fields = ( - "id", - "name", - "public", - "members_count" - ) - - -class GroupSerializer(SimpleGroupSerializer): - """ - A group serializer with the request member level - """ - level = serializers.IntegerField(default=None, read_only=True) - - class Meta(SimpleGroupSerializer.Meta): - fields = SimpleGroupSerializer.Meta.fields + ("level", ) - - @transaction.atomic - def create(self, validated_data): - group = super().create(validated_data) - # Associate the creator to the group - group.add_member(self.context["request"].user, Role.Admin.value) - # Manually set fields required by the serializer - group.members_count = 1 - group.level = Role.Admin.value - return group - - -class MembershipSerializer(serializers.ModelSerializer): - """ - Simple serializer for a membership - """ - - class Meta: - model = Right - fields = ( - "id", - "level", - ) - - def validate(self, data): - data = super().validate(data) - level_field = data.get("level") - if not self.instance or not level_field: - return data - # Assert an update will not remove the last group admin except for a worker - if (self.instance.level >= Role.Admin.value - and level_field < Role.Admin.value - and self.instance.content_type != ContentType.objects.get_for_model(Worker) - and self.instance.content_object.memberships.filter(level__gte=Role.Admin.value).count() < 2): - raise ValidationError({"level": ["Removing all memberships with an admin privilege is not possible."]}) - return data - - -class MemberGroupSerializer(MembershipSerializer): - """ - Serialize a group for a specific member with its privilege level - """ - group = SimpleGroupSerializer(source="content_object") - - class Meta(MembershipSerializer.Meta): - fields = MembershipSerializer.Meta.fields + ("group", ) - - def __init__(self, instance=None, *args, **kwargs): - if instance and isinstance(instance, list): - # Annotate each target group with the memberships count - for right in instance: - right.content_object.members_count = right.members_count - super().__init__(instance, *args, **kwargs) - - -class GenericMembershipSerializer(MembershipSerializer): - """ - Serialize a generic membership - """ - user = SimpleUserSerializer(allow_null=True) - group = SimpleGroupSerializer(allow_null=True) - - class Meta(MembershipSerializer.Meta): - fields = MembershipSerializer.Meta.fields + ("user", "group") - - -class MembershipContentType(serializers.ChoiceField): - """ - Specific field to handle a membership generic content - """ - - def __init__(self, *args, **kwargs): - choices = [c.name for c in RightContent] - super().__init__(choices, *args, **kwargs) - - def to_representation(self, obj): - if not isinstance(obj, ContentType): - return None - model = obj.model_class() - if model not in [c.value for c in RightContent]: - return None - return RightContent(model).name - - def to_internal_value(self, data): - try: - return ContentType.objects.get_for_model(RightContent[data].value) - except Exception: - raise ValidationError([f"'{data}' is not a valid content type."]) - - -class MembershipCreateSerializer(MembershipSerializer): - """ - Create a new generic membership - """ - user_email = serializers.EmailField(source="user.email", required=False, allow_null=True) - group_id = serializers.UUIDField(required=False, allow_null=True) - content_type = MembershipContentType() - content_id = serializers.UUIDField(help_text="The ID of the right target.") - - class Meta(MembershipSerializer.Meta): - fields = ( - "id", - "level", - "user_email", - "group_id", - "content_type", - "content_id" - ) - - def validate(self, data): - data = super().validate(data) - - user_email = data.pop("user", {"email": None})["email"] - group_id = data.pop("group_id", None) - - # Assert one of user/group identifier is defined - if not (user_email is None) ^ (group_id is None): - raise ValidationError({"detail": ["Exactly one of those fields must be defined: user_email, group_id"]}) - - # Retrieve the right generic content object - content_type = data.pop("content_type") - content_model = content_type.model_class() - content_id = data.pop("content_id") - try: - data["content_object"] = content_model.objects.get(id=content_id) - except content_model.DoesNotExist: - raise ValidationError({"content_id": [f"{content_type.name.capitalize()} with this ID could not be found."]}) - - # This is required to use workers ACL mixin out of an API view - request_user = self.context["request"].user - - # Retrieve access level - access_level = None - if content_model is Worker: - # Use WorkerACLMixin method as the worker access level may be inherited from its repository - access_level = WorkerACLMixin(user=request_user).get_max_level(data["content_object"]) - else: - access_level = get_max_level(request_user, data["content_object"]) - # Assert the request user a an admin access to the content object - if not access_level: - raise PermissionDenied(detail="You are not a member of the target content object.") - if not access_level or access_level < Role.Admin.value: - raise ValidationError({"content_id": ["Only members with an admin privilege are allowed to add other members."]}) - - # Special restriction for group memberships - if content_model is Group and group_id is not None: - raise ValidationError({"group_id": ["It is not possible to create a membership between two groups."]}) - - # Retrieve owner of the right - owner_data = {} - if user_email: - try: - owner_data["user"] = User.objects.get(email=user_email) - except User.DoesNotExist: - raise ValidationError({"user_email": ["No user matching this email could be found."]}) - elif group_id: - try: - owner_data["group"] = Group.objects.get(id=group_id) - except Group.DoesNotExist: - raise ValidationError({"group_id": ["No group matching this id could be found."]}) - - # Handle existing memberships - if data["content_object"].memberships.filter(**owner_data).exists(): - raise ValidationError({"__all__": ["This membership already exists."]}) - - data.update(owner_data) - return data diff --git a/arkindex/users/tests/test_generic_memberships.py b/arkindex/users/tests/test_generic_memberships.py deleted file mode 100644 index 9371345afe2b98f829fce64c3169cb2c45b8d97b..0000000000000000000000000000000000000000 --- a/arkindex/users/tests/test_generic_memberships.py +++ /dev/null @@ -1,1078 +0,0 @@ -import uuid - -from django.db import IntegrityError -from django.urls import reverse -from rest_framework import status - -from arkindex.documents.models import Corpus -from arkindex.process.models import Repository, Worker -from arkindex.project.tests import FixtureAPITestCase -from arkindex.users.models import Group, Right, Role, User - - -class TestMembership(FixtureAPITestCase): - """ - Test generic rights management methods - """ - - @classmethod - def setUpTestData(cls): - super().setUpTestData() - cls.unverified = User.objects.create_user("user@address.com", "P4$5w0Rd") - cls.admin = User.objects.create_user("admin@address.com", "P4$5w0Rd") - cls.non_admin = User.objects.filter( - rights__group_target=cls.group, - rights__level=Role.Contributor.value - ).first() - cls.non_admin.verified_email = True - cls.non_admin.save() - - cls.admin_group = Group.objects.create(name="Admin group", public=True) - cls.admin_group.add_member(user=cls.admin, level=Role.Admin.value) - - cls.membership = cls.group.memberships.get(user=cls.user) - cls.admin_membership = cls.admin_group.memberships.get(user=cls.admin) - - def test_verified_user_group_create(self): - """ - Anonymous and non verified users should not be able to create a group - """ - self.client.force_login(self.unverified) - with self.assertNumQueries(2): - response = self.client.post(reverse("api:groups-create"), {"name": "new group"}) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.client.logout() - with self.assertNumQueries(0): - response = self.client.post(reverse("api:groups-create"), {"name": "new group"}) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_create_group_wrong_fields(self): - checks = ( - ({"name": 100 * "A"}, {"name": ["Ensure this field has no more than 64 characters."]}), - ({"name": ""}, {"name": ["This field may not be blank."]}) - ) - self.client.force_login(self.user) - for payload, error in checks: - response = self.client.post(reverse("api:groups-create"), payload) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.json(), error) - - def test_create_group(self): - """ - A user creating a group is granted a membership with the maximum privileges level - """ - self.client.force_login(self.user) - with self.assertNumQueries(7): - response = self.client.post(reverse("api:groups-create"), {"name": "new group", "public": True}) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - - group = Group.objects.get(name="new group") - self.assertEqual(response.json(), { - "id": str(group.id), - "name": "new group", - "public": True, - "members_count": 1, - "level": 100 - }) - self.assertCountEqual( - group.memberships.values_list("user", "level"), - [(self.user.id, Role.Admin.value)] - ) - - def test_crate_group_duplicated_name(self): - """ - Assert multiple groups can have a similar name - """ - self.client.force_login(self.user) - with self.assertNumQueries(7): - response = self.client.post(reverse("api:groups-create"), {"name": self.group.name}) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - - def test_list_members_requires_member_user(self): - """ - Only users that belong to a group have the ability to list members, otherwise we return a 403 - """ - private_group = Group.objects.create(name="Admin group", public=False) - self.client.force_login(self.user) - with self.assertNumQueries(5): - response = self.client.get(reverse("api:memberships-list"), {"group": str(private_group.id)}) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_list_members_superuser(self): - """ - A superadmin is able to list members of any group - """ - private_group = Group.objects.create(name="Private group", public=False) - member = private_group.memberships.create(user=self.user, level=Role.Admin.value) - self.client.force_login(self.superuser) - with self.assertNumQueries(6): - response = self.client.get(reverse("api:memberships-list"), {"group": str(private_group.id)}) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertDictEqual(response.json(), { - "count": 1, - "next": None, - "number": 1, - "previous": None, - "results": [{ - "id": str(member.id), - "level": Role.Admin.value, - "user": { - "display_name": self.user.display_name, - "email": self.user.email, - "id": self.user.id, - }, - "group": None, - }] - }) - - def test_list_members_invalid_uuid(self): - self.client.force_login(self.user) - with self.assertNumQueries(2): - response = self.client.get(reverse("api:memberships-list"), {"group": "A"}) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertDictEqual(response.json(), {"group": ["'A' is not a valid UUID."]}) - - def test_list_members_wrong_parameters(self): - """ - Exactly one of valid content types should be provided as an URL parameter - """ - cases = ( - {}, - {"corpus": str(self.corpus.id), "group": str(self.group.id)}, - {"non_existing_content_type": str(self.corpus.id)} - ) - self.client.force_login(self.user) - for wrong_params in cases: - response = self.client.get(reverse("api:memberships-list"), wrong_params) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertDictEqual(response.json(), { - "__all__": ["Exactly one of those query parameters must be defined: corpus, repository, group, worker, model, farm."] - }) - - def test_list_members_public_content(self): - """ - Any user may be able to list all members on a public content (except for groups in the test above) - """ - corpus = Corpus.objects.create(name="Public corpus", public=True) - user_right = Right.objects.create(user=self.user, content_object=corpus, level=42) - group_right = Right.objects.create(group=self.group, content_object=corpus, level=77) - self.client.force_login(self.user) - with self.assertNumQueries(8): - response = self.client.get(reverse("api:memberships-list"), {"corpus": str(corpus.id)}) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertDictEqual(response.json(), { - "count": 2, - "next": None, - "number": 1, - "previous": None, - "results": [ - { - "id": str(user_right.id), - "level": 42, - "user": { - "display_name": self.user.display_name, - "email": self.user.email, - "id": self.user.id, - }, - "group": None, - }, - { - "id": str(group_right.id), - "level": 77, - "user": None, - "group": { - "id": str(self.group.id), - "members_count": self.group.memberships.count(), - "name": self.group.name, - "public": self.group.public, - }, - } - ] - }) - - def test_list_members_public_group(self): - """ - Any user may not be able to list all members on a public group - This behavior is specific to groups - """ - self.client.force_login(self.user) - with self.assertNumQueries(5): - response = self.client.get(reverse("api:memberships-list"), {"group": str(self.admin_group.id)}) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertDictEqual(response.json(), { - "detail": "You do not have the required access level to list members for this content." - }) - - def test_list_members_level_right(self): - """ - List members of a group with their right corresponding to privileges level - Members are ordered by display name - """ - self.client.force_login(self.user) - with self.assertNumQueries(8): - response = self.client.get(reverse("api:memberships-list"), {"group": str(self.group.id)}) - read_member, write_member, admin_member = self.group.memberships.order_by("level") - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.json(), { - "count": 3, - "next": None, - "number": 1, - "previous": None, - "results": [ - { - "id": str(admin_member.id), - "level": Role.Admin.value, - "user": { - "display_name": "Test user", - "email": admin_member.user.email, - "id": admin_member.user.id, - }, - "group": None, - }, { - "id": str(read_member.id), - "level": read_member.level, - "user": { - "display_name": "Test user read", - "email": read_member.user.email, - "id": read_member.user.id, - }, - "group": None, - }, { - "id": str(write_member.id), - "level": write_member.level, - "user": { - "display_name": "Test user write", - "email": write_member.user.email, - "id": write_member.user.id, - }, - "group": None, - } - ] - }) - - def test_list_members_owner_type_filter(self): - """ - Only list group owners of a specific repository - """ - repo = Repository.objects.create(url="http://repo1") - Right.objects.bulk_create([ - Right(user=self.user, content_object=repo, level=42), - Right(user=self.non_admin, content_object=repo, level=43), - Right(user=self.admin, content_object=repo, level=44), - Right(group=self.group, content_object=repo, level=100), - Right(group=self.admin_group, content_object=repo, level=24), - ]) - self.client.force_login(self.user) - with self.assertNumQueries(8): - response = self.client.get(reverse("api:memberships-list"), {"repository": str(repo.id), "type": "group"}) - self.assertEqual(response.status_code, status.HTTP_200_OK) - data = response.json() - self.assertEqual(data["count"], 2) - self.assertCountEqual( - [membership["group"] for membership in data["results"]], - [ - { - "id": str(self.group.id), - "name": "User group", - "public": False, - "members_count": self.group.memberships.count() - }, { - "id": str(self.admin_group.id), - "name": "Admin group", - "public": True, - "members_count": self.admin_group.memberships.count() - } - ] - ) - - def test_list_worker_members_via_repo_right(self): - """ - A user is able to list members of a worker if he is a member of its repository - """ - repo = Repository.objects.get(url="http://my_repo.fake/workers/worker") - worker = repo.workers.get(slug="reco") - repo.memberships.create(user=self.user, level=Role.Guest.value) - self.client.force_login(self.user) - with self.assertNumQueries(9): - response = self.client.get(reverse("api:memberships-list"), {"worker": str(worker.id)}) - self.assertEqual(response.status_code, status.HTTP_200_OK) - data = response.json() - # A worker may have no member - self.assertEqual(data["count"], 0) - - def test_retrieve_group_wrong_id(self): - self.client.force_login(self.user) - with self.assertNumQueries(4): - response = self.client.get(reverse("api:group-details", kwargs={"pk": str(uuid.uuid4())})) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - - def test_retrieve_group_no_member(self): - """ - A non member of a group may not retrieve its details, even if it is public - """ - self.client.force_login(self.user) - with self.assertNumQueries(5): - response = self.client.get(reverse("api:group-details", kwargs={"pk": str(self.admin_group.id)})) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_retrieve_group_details(self): - self.client.force_login(self.user) - with self.assertNumQueries(5): - response = self.client.get(reverse("api:group-details", kwargs={"pk": str(self.group.id)})) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertDictEqual(response.json(), { - "id": str(self.group.id), - "members_count": 3, - "name": self.group.name, - "public": self.group.public, - "level": Role.Admin.value - }) - - def test_retrieve_group_superuser(self): - """ - A superuser is allowed to retrieve any group. Its level is always 100 - """ - self.client.force_login(self.superuser) - with self.assertNumQueries(4): - response = self.client.get(reverse("api:group-details", kwargs={"pk": str(self.admin_group.id)})) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertDictEqual(response.json(), { - "id": str(self.admin_group.id), - "members_count": self.admin_group.memberships.count(), - "name": self.admin_group.name, - "public": self.admin_group.public, - "level": Role.Admin.value - }) - - def test_update_group_no_admin(self): - """ - User must be a group administrator to edit its properties - """ - self.client.force_login(self.non_admin) - with self.assertNumQueries(5): - response = self.client.patch(reverse("api:group-details", kwargs={"pk": str(self.group.id)}), {"name": "A"}) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertDictEqual(response.json(), { - "detail": "Only members with an admin privilege may update or delete this group." - }) - - def test_update_group(self): - """ - Group name and public attributes may be updated by an admin member - """ - payload = { - "name": "Renamed group", - "public": True, - "id": uuid.uuid4(), - "members_count": 42, - "level": 42 - } - self.client.force_login(self.user) - with self.assertNumQueries(6): - response = self.client.put( - reverse("api:group-details", kwargs={"pk": str(self.group.id)}), - payload - ) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertDictEqual(response.json(), { - "id": str(self.group.id), - "members_count": 3, - "name": "Renamed group", - "public": True, - "level": Role.Admin.value - }) - - def test_update_group_superuser(self): - """ - A superuser is allowed to retrieve any group. Its level is always 100 - """ - self.client.force_login(self.superuser) - with self.assertNumQueries(5): - response = self.client.put( - reverse("api:group-details", kwargs={"pk": str(self.admin_group.id)}), - {"public": False, "name": "I got you"} - ) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertDictEqual(response.json(), { - "id": str(self.admin_group.id), - "members_count": self.admin_group.memberships.count(), - "name": "I got you", - "public": False, - "level": Role.Admin.value - }) - - def test_delete_group_no_admin(self): - self.client.force_login(self.non_admin) - with self.assertNumQueries(5): - response = self.client.delete(reverse("api:group-details", kwargs={"pk": str(self.group.id)})) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertDictEqual(response.json(), { - "detail": "Only members with an admin privilege may update or delete this group." - }) - - def test_delete_group(self): - """ - A group admin is allowed to delete the group - """ - group = Group.objects.create(name="Another group") - user = User.objects.create(email="user42@test.test", verified_email=True) - group.add_member(user=user, level=Role.Admin.value) - self.client.force_login(user) - with self.assertNumQueries(7): - response = self.client.delete(reverse("api:group-details", kwargs={"pk": str(group.id)})) - self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - with self.assertRaises(Group.DoesNotExist): - group.refresh_from_db() - self.assertTrue(User.objects.filter(id=user.id).exists()) - - def test_delete_group_superuser(self): - """ - A superuser is allowed to delete an existing group - """ - self.client.force_login(self.superuser) - with self.assertNumQueries(7): - response = self.client.delete(reverse("api:group-details", kwargs={"pk": str(self.admin_group.id)})) - self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - with self.assertRaises(Group.DoesNotExist): - self.admin_group.refresh_from_db() - - def test_list_user_memberships_requires_login(self): - """ - User must be logged in to list its memberships - """ - with self.assertNumQueries(0): - response = self.client.delete(reverse("api:group-details", kwargs={"pk": str(self.group.id)})) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertEqual(response.json(), {"detail": "Authentication credentials were not provided."}) - - def test_list_user_memberships(self): - """ - List groups for which the user is a member - """ - new_group = Group.objects.create(name="Another group", public=False) - new_group_member = new_group.add_member(user=self.user, level=10) - self.client.force_login(self.user) - with self.assertNumQueries(5): - response = self.client.get(reverse("api:user-memberships")) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertDictEqual(response.json(), { - "count": 2, - "number": 1, - "next": None, - "previous": None, - "results": [ - { - "group": - { - "id": str(new_group.id), - "members_count": 1, - "name": "Another group", - "public": False - }, - "id": str(new_group_member.id), - "level": new_group_member.level, - }, { - "group": - { - "id": str(self.group.id), - "members_count": self.group.memberships.count(), - "name": self.group.name, - "public": self.group.public - }, - "id": str(self.membership.id), - "level": self.membership.level, - } - ] - }) - - def test_list_user_memberships_superuser(self): - """ - A superuser is able to list all groups. Memberships have no ID nor level - """ - new_group = Group.objects.create(name="Another group", public=False) - new_group.add_member(user=self.user, level=10) - self.client.force_login(self.superuser) - with self.assertNumQueries(3): - response = self.client.get(reverse("api:user-memberships")) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertDictEqual(response.json(), { - "count": 3, - "number": 1, - "next": None, - "previous": None, - "results": [ - { - "group": { - "id": str(self.admin_group.id), - "members_count": 1, - "name": "Admin group", - "public": True - }, - "id": None, - "level": None, - }, { - "group": - { - "id": str(new_group.id), - "members_count": 1, - "name": "Another group", - "public": False - }, - "id": None, - "level": None, - }, { - "group": - { - "id": str(self.group.id), - "members_count": self.group.memberships.count(), - "name": self.group.name, - "public": self.group.public - }, - "id": None, - "level": None, - } - ] - }) - - def test_add_member_non_existing_group(self): - """ - Cannot add a member to a non existing group - """ - self.client.force_login(self.user) - with self.assertNumQueries(4): - response = self.client.post(reverse("api:membership-create"), { - "level": 42, - "user_email": self.non_admin.email, - "content_type": "group", - "content_id": str(uuid.uuid4()) - }) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertDictEqual( - response.json(), - {"content_id": ["Group with this ID could not be found."]} - ) - - def test_add_member_non_member(self): - """ - Raise a 403 in case the user is not a member of the target content - """ - self.client.force_login(self.user) - worker = Worker.objects.get(slug="reco") - with self.assertNumQueries(9): - response = self.client.post(reverse("api:membership-create"), { - "level": 42, - "user_email": self.non_admin.email, - "content_type": "worker", - "content_id": str(worker.id) - }) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertDictEqual(response.json(), {"detail": "You are not a member of the target content object."}) - - def test_add_member_requires_login(self): - with self.assertNumQueries(0): - response = self.client.post(reverse("api:membership-create"), { - "level": 42, - "user_email": self.non_admin.email, - "content_type": "group", - "content_id": str(self.group.id) - }) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertDictEqual(response.json(), {"detail": "Authentication credentials were not provided."}) - - def test_add_member_requires_verified(self): - self.client.force_login(self.unverified) - with self.assertNumQueries(2): - response = self.client.post(reverse("api:membership-create"), { - "level": 42, - "user_email": self.non_admin.email, - "content_type": "group", - "content_id": str(uuid.uuid4()) - }) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertDictEqual(response.json(), {"detail": "You do not have permission to perform this action."}) - - def test_add_member_requires_group_admin(self): - """ - Only group admins have the ability to add a new member - Note that the user is already a member but this check is performed after - """ - self.client.force_login(self.non_admin) - with self.assertNumQueries(5): - response = self.client.post(reverse("api:membership-create"), { - "level": 42, - "user_email": self.non_admin.email, - "content_type": "group", - "content_id": str(self.group.id) - }) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertDictEqual(response.json(), { - "content_id": ["Only members with an admin privilege are allowed to add other members."] - }) - - def test_add_member_already_member(self): - """ - A member cannot be added twice - """ - self.client.force_login(self.user) - with self.assertNumQueries(7): - response = self.client.post(reverse("api:membership-create"), { - "level": 10, - "user_email": self.non_admin.email, - "content_type": "group", - "content_id": str(self.group.id) - }) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertDictEqual(response.json(), { - "__all__": ["This membership already exists."] - }) - - def test_add_member_non_existing_user(self): - """ - Cannot add a non existing user - """ - self.client.force_login(self.user) - with self.assertNumQueries(6): - response = self.client.post(reverse("api:membership-create"), { - "level": 10, - "user_email": "undefined@nowhe.re", - "content_type": "group", - "content_id": str(self.group.id) - }) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertDictEqual(response.json(), { - "user_email": ["No user matching this email could be found."] - }) - - def test_add_member_required_fields(self): - """ - Content type, id and level are required fields - """ - self.client.force_login(self.user) - with self.assertNumQueries(2): - response = self.client.post(reverse("api:membership-create"), { - "access": 42, - "id": 1337, - }) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertDictEqual(response.json(), { - "content_id": ["This field is required."], - "content_type": ["This field is required."], - "level": ["This field is required."] - }) - - def test_add_member_wrong_content_type(self): - self.client.force_login(self.user) - with self.assertNumQueries(2): - response = self.client.post(reverse("api:membership-create"), { - "content_type": "gloubiboulga", - "content_id": uuid.uuid4(), - "level": 42 - }) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertDictEqual(response.json(), { - "content_type": ["'gloubiboulga' is not a valid content type."] - }) - - def test_add_member_right_target_xor(self): - """ - Exactly one of group_id and user_email field must be defined - """ - wrong_targets = ( - {}, - {"user_email": "aaa@aa.com", "group_id": str(uuid.uuid4())} - ) - self.client.force_login(self.user) - for wrong_target in wrong_targets: - response = self.client.post(reverse("api:membership-create"), { - "level": 42, - "content_type": "corpus", - "content_id": str(self.corpus.id), - **wrong_target - }) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertDictEqual(response.json(), { - "detail": ["Exactly one of those fields must be defined: user_email, group_id"] - }) - - def test_add_member_level_validation(self): - """ - Level should be an integer between 0 and 100 (included) - """ - checks = ( - (101, {"level": ["Ensure this value is less than or equal to 100."]}), - (-1, {"level": ["Ensure this value is greater than or equal to 1."]}) - ) - self.client.force_login(self.user) - for level, error in checks: - response = self.client.post(reverse("api:membership-create"), { - "level": level, - "user_email": self.admin.email, - "content_type": "group", - "content_id": str(self.group.id) - }) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertDictEqual(response.json(), error) - - def test_add_member_by_email(self): - """ - Adds a new member referenced by its email - """ - user = User.objects.create_user("test@test.de", "Pa$$w0rd") - self.client.force_login(self.user) - with self.assertNumQueries(8): - response = self.client.post(reverse("api:membership-create"), { - "level": 10, - "user_email": user.email, - "content_type": "group", - "content_id": str(self.group.id) - }) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - membership = user.rights.get(group_target=self.group) - self.assertDictEqual(response.json(), { - "id": str(membership.id), - "content_id": str(self.group.id), - "content_type": "group", - "level": 10, - "user_email": user.email, - "group_id": None, - }) - - def test_add_member_by_email_uppercase_letters(self): - """ - Adds a new member referenced by its email - Asserts the endpoint is case insensitive for the email - """ - user = User.objects.create_user("test@test.de", "Pa$$w0rd") - self.client.force_login(self.user) - with self.assertNumQueries(8): - response = self.client.post(reverse("api:membership-create"), { - "level": 10, - # Introducing uppercase letters in the user's email - "user_email": "tEsT@TeSt.DE", - "content_type": "group", - "content_id": str(self.group.id) - }) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - membership = user.rights.get(group_target=self.group) - self.assertDictEqual(response.json(), { - "id": str(membership.id), - "content_id": str(self.group.id), - "content_type": "group", - "level": 10, - "user_email": user.email, - "group_id": None, - }) - - def test_add_member_superuser(self): - """ - A superuser is able to add a member to a groups he has no right on - """ - user = User.objects.create_user("test@test.de", "Pa$$w0rd") - self.client.force_login(self.superuser) - with self.assertNumQueries(7): - response = self.client.post(reverse("api:membership-create"), { - "level": 10, - "user_email": user.email, - "content_type": "group", - "content_id": str(self.admin_group.id) - }) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - - def test_add_member_via_repo_right(self): - """ - A worker member can inherit the right to add a member from its repository - """ - user = User.objects.create_user("test@test.de", "Pa$$w0rd") - - repo = Repository.objects.get(url="http://my_repo.fake/workers/worker") - worker = repo.workers.get(slug="reco") - repo.memberships.create(user=self.user, level=Role.Admin.value) - - self.client.force_login(self.user) - with self.assertNumQueries(11): - response = self.client.post(reverse("api:membership-create"), { - "level": 10, - "user_email": user.email, - "content_type": "worker", - "content_id": str(worker.id) - }) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - - def test_retrieve_membership_not_found(self): - """ - Only memberships on groups the user has a read access can be retrieved - """ - hidden_group = Group.objects.create(name="Hidden group", public=False) - hidden_member = hidden_group.add_member(user=self.admin, level=Role.Guest.value) - self.client.force_login(self.user) - with self.assertNumQueries(5): - response = self.client.get(reverse("api:membership-details", kwargs={"pk": str(hidden_member.id)})) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - - def test_retrieve_membership_details(self): - """ - Any group member can retrieve a specific membership - """ - self.client.force_login(self.non_admin) - with self.assertNumQueries(6): - response = self.client.get(reverse("api:membership-details", kwargs={"pk": str(self.membership.id)})) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertDictEqual(response.json(), { - "id": str(self.membership.id), - "level": Role.Admin.value - }) - - def test_retrieve_membership_details_superuser(self): - """ - Any group member can retrieve a specific membership - """ - self.client.force_login(self.superuser) - with self.assertNumQueries(5): - response = self.client.get(reverse("api:membership-details", kwargs={"pk": str(self.membership.id)})) - self.assertEqual(response.status_code, status.HTTP_200_OK) - - def test_edit_membership_requires_verified(self): - self.client.force_login(self.unverified) - with self.assertNumQueries(2): - response = self.client.patch(reverse("api:membership-details", kwargs={"pk": str(uuid.uuid4())}), {}) - self.assertDictEqual(response.json(), {"detail": "You do not have permission to perform this action."}) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_edit_membership_requires_admin(self): - """ - The request user has to be an admin of the target member group to edit its membership - """ - self.client.force_login(self.non_admin) - with self.assertNumQueries(6): - response = self.client.patch(reverse("api:membership-details", kwargs={"pk": str(self.membership.id)}), {}) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertDictEqual(response.json(), { - "detail": "Only admins of the target membership group can perform this action." - }) - - def test_edit_membership_last_admin(self): - """ - At least one admin should be present in the group on a member edition - """ - self.client.force_login(self.user) - with self.assertNumQueries(8): - response = self.client.patch( - reverse("api:membership-details", kwargs={"pk": str(self.membership.id)}), - {"level": 1} - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertDictEqual( - response.json(), - {"level": ["Removing all memberships with an admin privilege is not possible."]} - ) - - def test_edit_membership_last_admin_workers(self): - """ - Workers may specially have no member as they depends on a repository - """ - repo = Repository.objects.get(url="http://my_repo.fake/workers/worker") - worker = repo.workers.get(slug="reco") - membership = worker.memberships.create(user=self.user, level=Role.Admin.value) - self.client.force_login(self.user) - with self.assertNumQueries(10): - response = self.client.patch( - reverse("api:membership-details", kwargs={"pk": str(membership.id)}), - {"level": Role.Guest.value} - ) - self.assertEqual(response.status_code, status.HTTP_200_OK) - # Only one guest right exists on the worker - self.assertEqual([m.level for m in worker.memberships.all()], [Role.Guest.value]) - - def test_edit_membership_wrong_fields(self): - self.non_admin.rights.update(level=Role.Admin.value) - self.client.force_login(self.user) - with self.assertNumQueries(6): - response = self.client.patch( - reverse("api:membership-details", kwargs={"pk": str(self.membership.id)}), - {"level": 101} - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertDictEqual(response.json(), { - "level": ["Ensure this value is less than or equal to 100."] - }) - - def test_edit_membership(self): - """ - Only level field may be edited - """ - self.client.force_login(self.user) - non_admin_member = self.non_admin.rights.get(group_target=self.group) - with self.assertNumQueries(6): - response = self.client.patch( - reverse("api:membership-details", kwargs={"pk": str(non_admin_member.id)}), - {"level": 11, "id": uuid.uuid4(), "content_type": "A", "content_id": uuid.uuid4()} - ) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertDictEqual(response.json(), { - "id": str(non_admin_member.id), - "level": 11 - }) - - def test_edit_privilege_escalation(self): - """ - A user cannot update its own access level if he has no admin access - """ - second_admin = User.objects.create(email="another_user@test.fr", verified_email=True) - self.group.memberships.create(user=second_admin, level=Role.Admin.value) - cases = [ - (self.non_admin, Role.Guest.value, status.HTTP_403_FORBIDDEN), - (self.non_admin, Role.Admin.value, status.HTTP_403_FORBIDDEN), - (second_admin, Role.Guest.value, status.HTTP_200_OK), - ] - for user, level, response_status in cases: - self.client.force_login(user) - membership = self.group.memberships.get(user=user) - response = self.client.put( - reverse("api:membership-details", kwargs={"pk": str(membership.id)}), - {"level": level} - ) - self.assertEqual(response.status_code, response_status) - - def test_edit_membership_superadmin(self): - """ - Superadmins are allowed to update any membership level - """ - new_member = self.admin_group.add_member(user=self.user, level=10) - self.client.force_login(self.superuser) - with self.assertNumQueries(5): - response = self.client.patch( - reverse("api:membership-details", kwargs={"pk": str(new_member.id)}), {"level": 42} - ) - self.assertEqual(response.status_code, status.HTTP_200_OK) - new_member.refresh_from_db() - self.assertEqual(new_member.level, 42) - - def test_edit_member_via_repo_right(self): - """ - A worker member can inherit the right to add a member from its repository to edit a worker member - """ - repo = Repository.objects.get(url="http://my_repo.fake/workers/worker") - worker = repo.workers.get(slug="reco") - repo.memberships.create(user=self.user, level=Role.Admin.value) - - new_user = User.objects.create_user("test@test.de", "Pa$$w0rd") - new_member = worker.memberships.create(user=new_user, level=Role.Guest.value) - - self.client.force_login(self.user) - with self.assertNumQueries(9): - response = self.client.patch(reverse("api:membership-details", kwargs={"pk": str(new_member.id)}), { - "level": Role.Admin.value, - }) - self.assertEqual(response.status_code, status.HTTP_200_OK) - new_member.refresh_from_db() - self.assertEqual(new_member.level, Role.Admin.value) - - def test_delete_membership_last_admin(self): - """ - At least one admin should be present for the corresponding object on a member deletion - """ - # The user is the only admin for this corpus - corpus_membership = self.corpus.memberships.get(user=self.user) - self.client.force_login(self.user) - with self.assertNumQueries(8): - response = self.client.delete(reverse("api:membership-details", kwargs={"pk": str(corpus_membership.id)})) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertDictEqual( - response.json(), - {"detail": ["Removing all memberships with an admin privilege is not possible."]} - ) - - def test_delete_membership_remaining_admins(self): - """ - It is possible to downgrade an admin privilege if there are other admins for the corresponding object - """ - admin_group = Group.objects.create(name="Another group") - # We add a group as an admin of the user corpus - group_membership = admin_group.rights.create(content_object=self.corpus, level=Role.Admin.value) - self.client.force_login(self.user) - with self.assertNumQueries(9): - response = self.client.delete(reverse("api:membership-details", kwargs={"pk": str(group_membership.id)})) - self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - self.assertEqual(admin_group.rights.count(), 0) - - def test_delete_membership_last_admin_workers(self): - """ - Workers may have no member as they depends on a repository - """ - repo = Repository.objects.get(url="http://my_repo.fake/workers/worker") - worker = repo.workers.get(slug="reco") - membership = worker.memberships.create(user=self.user, level=Role.Admin.value) - self.client.force_login(self.user) - with self.assertNumQueries(10): - response = self.client.delete( - reverse("api:membership-details", kwargs={"pk": str(membership.id)}), - {"level": Role.Guest.value} - ) - self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - # Only one guest right exists on the worker - self.assertEqual(worker.memberships.count(), 0) - - def test_delete_membership_non_admin(self): - """ - Non admin members are not allowed to remove another member - """ - self.client.force_login(self.non_admin) - with self.assertNumQueries(6): - response = self.client.delete(reverse("api:membership-details", kwargs={"pk": str(self.membership.id)})) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertDictEqual( - response.json(), - {"detail": "Only admins of the target membership group can perform this action."} - ) - - def test_delete_membership_superuser(self): - """ - Superadmins are allowed to delete any existing membership - """ - new_member = self.admin_group.add_member(user=self.user, level=10) - self.client.force_login(self.superuser) - with self.assertNumQueries(6): - response = self.client.delete(reverse("api:membership-details", kwargs={"pk": str(new_member.id)})) - self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - - def test_delete_own_membership(self): - """ - Any member is able to remove its own membership - """ - non_admin_membership = self.group.memberships.get(user=self.non_admin) - self.client.force_login(self.non_admin) - with self.assertNumQueries(7): - response = self.client.delete(reverse("api:membership-details", kwargs={"pk": str(non_admin_membership.id)})) - self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - with self.assertRaises(Right.DoesNotExist): - non_admin_membership.refresh_from_db() - - def test_delete_member_via_repo_right(self): - """ - A worker member can inherit the right to add a member from its repository to remove a worker member - """ - repo = Repository.objects.get(url="http://my_repo.fake/workers/worker") - worker = repo.workers.get(slug="reco") - repo.memberships.create(user=self.user, level=Role.Admin.value) - - new_user = User.objects.create_user("test@test.de", "Pa$$w0rd") - new_member = worker.memberships.create(user=new_user, level=Role.Guest.value) - - self.client.force_login(self.user) - with self.assertNumQueries(9): - response = self.client.delete(reverse("api:membership-details", kwargs={"pk": str(new_member.id)})) - self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - with self.assertRaises(Right.DoesNotExist): - new_member.refresh_from_db() - - def test_right_unique_user(self): - repo = Repository.objects.get(url="http://my_repo.fake/workers/worker") - repo.memberships.create(user=self.user, level=Role.Admin.value) - with self.assertRaises(IntegrityError): - repo.memberships.create(user=self.user, level=Role.Admin.value) - - def test_right_unique_group(self): - repo = Repository.objects.get(url="http://my_repo.fake/workers/worker") - repo.memberships.create(group=self.group, level=Role.Admin.value) - with self.assertRaises(IntegrityError): - repo.memberships.create(group=self.group, level=Role.Admin.value) diff --git a/arkindex/users/tests/test_manager_acl.py b/arkindex/users/tests/test_manager_acl.py deleted file mode 100644 index fe7665abb5f2cd0f04083079f37e6b51df883c66..0000000000000000000000000000000000000000 --- a/arkindex/users/tests/test_manager_acl.py +++ /dev/null @@ -1,49 +0,0 @@ -from django.contrib.auth.models import AnonymousUser -from django.test import TestCase - -from arkindex.documents.models import Corpus -from arkindex.users.models import Role, User - - -class TestACL(TestCase): - """ - Test Corpus manager ACL helpers - """ - - @classmethod - def setUpTestData(cls): - super().setUpTestData() - cls.anon = AnonymousUser() - cls.user = User.objects.create_user("user@address.com", "P4$5w0Rd") - cls.admin = User.objects.create_superuser("admin@address.com", "P4$5w0Rd") - - cls.corpus_public = Corpus.objects.create(name="A Public", public=True) - cls.corpus_private = Corpus.objects.create(name="B Private") - cls.corpus_private.memberships.create(user=cls.user, level=Role.Guest.value) - cls.corpus_hidden = Corpus.objects.create(name="C Hidden") - - def test_anon(self): - # An anonymous user has only access to public - self.assertCountEqual( - Corpus.objects.readable(self.anon).values_list("id", flat=True), - [self.corpus_public.id] - ) - - def test_user(self): - # An anonymous user has access to public & private - self.assertFalse(self.user.is_admin) - self.assertCountEqual( - Corpus.objects.readable(self.user).values_list("id", flat=True), - [ - self.corpus_private.id, - *Corpus.objects.filter(public=True).values_list("id", flat=True) - ] - ) - - def test_admin(self): - # An admin has access with admin privileges to all corpora - self.assertTrue(self.admin.is_admin) - self.assertCountEqual( - Corpus.objects.admin(self.admin).values_list("id"), - Corpus.objects.all().values_list("id") - ) diff --git a/arkindex/users/tests/test_registration.py b/arkindex/users/tests/test_registration.py index f0f9e6206e17a792c565bf63ee2cf2975ac77d3c..7376532c48c63f881f7eb40cfc7a5818b10c54bc 100644 --- a/arkindex/users/tests/test_registration.py +++ b/arkindex/users/tests/test_registration.py @@ -1,4 +1,5 @@ import urllib.parse +from unittest.mock import call, patch from django.contrib import auth from django.contrib.auth.tokens import default_token_generator @@ -117,7 +118,8 @@ class TestRegistration(FixtureAPITestCase): self.assertTrue(auth.get_user(self.client).is_authenticated) self.assertEqual(auth.get_user(self.client).email, "email@address.com") - def test_email_verification(self): + @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none()) + def test_email_verification(self, filter_rights_mock): newuser = User.objects.create_user("newuser@example.com", password="hunter2") self.assertFalse(newuser.verified_email) self.assertTrue(newuser.has_usable_password()) @@ -135,18 +137,19 @@ class TestRegistration(FixtureAPITestCase): newuser.refresh_from_db() self.assertTrue(newuser.verified_email) + self.assertEqual(filter_rights_mock.call_count, 1) + self.assertEqual(filter_rights_mock.call_args, call(newuser, Corpus, Role.Admin.value)) + # Assert a trial corpus is created for the new user - new_corpus = Corpus.objects.exclude(id__in=corpora) - self.assertEqual(new_corpus.count(), 1) - self.assertEqual(new_corpus.get().name, "My Project") - self.assertEqual(new_corpus.get().description, "Project for newuser@example.com") - self.assertCountEqual( - list(Corpus.objects.admin(newuser)), - list(new_corpus) - ) + new_corpus = Corpus.objects.exclude(id__in=corpora).get() + self.assertEqual(new_corpus.name, "My Project") + self.assertEqual(new_corpus.description, "Project for newuser@example.com") + membership = new_corpus.memberships.get() + self.assertEqual(membership.user, newuser) + self.assertEqual(membership.level, Role.Admin.value) # Assert defaults types are set on the new corpus self.assertCountEqual( - list(new_corpus.get().types.values( + list(new_corpus.types.values( "slug", "display_name", "folder", @@ -172,7 +175,7 @@ class TestRegistration(FixtureAPITestCase): newuser = User.objects.create_user("newuser@example.com", password="hunter2") self.assertFalse(newuser.verified_email) - with (self.settings(SIGNUP_DEFAULT_GROUP=str(test_group.id)), self.assertNumQueries(19)): + with (self.settings(SIGNUP_DEFAULT_GROUP=str(test_group.id)), self.assertNumQueries(15)): response = self.client.get("{}?{}".format( reverse("api:user-token"), urllib.parse.urlencode({ @@ -192,7 +195,7 @@ class TestRegistration(FixtureAPITestCase): with ( self.settings(SIGNUP_DEFAULT_GROUP="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"), - self.assertNumQueries(15), + self.assertNumQueries(10), self.assertRaises(Group.DoesNotExist) ): self.client.get("{}?{}".format( diff --git a/arkindex/users/utils.py b/arkindex/users/utils.py index 201708f558ad103de15a08db58d1485f181c101f..9fcf143b03b683259c653bf224b8b3f68c35af23 100644 --- a/arkindex/users/utils.py +++ b/arkindex/users/utils.py @@ -1,119 +1,8 @@ -from django.db.models import IntegerField, Q, Value, functions -from django.db.models.query_utils import DeferredAttribute -from enumfields import Enum +from django.conf import settings +from django.utils.module_loading import cached_import -from arkindex.documents.models import Corpus -from arkindex.ponos.models import Farm -from arkindex.process.models import Repository, Worker -from arkindex.training.models import Model -from arkindex.users.models import Group, Role +_filter_module = getattr(settings, "RIGHTS_FILTER_MODULE", None) or "arkindex.users.allow_all" -PUBLIC_LEVEL = Role.Guest.value - - -class RightContent(Enum): - corpus = Corpus - repository = Repository - group = Group - worker = Worker - model = Model - farm = Farm - - -def has_public_field(model): - return isinstance( - getattr(model, "public", None), - DeferredAttribute - ) - - -def get_public_instances(model): - return model.objects \ - .filter(public=True) \ - .annotate(max_level=Value(PUBLIC_LEVEL, IntegerField())) - - -def check_level_param(level): - assert isinstance(level, int), "An integer level is required to compare access rights." - assert level >= 1, "Level integer should be greater than or equal to 1." - assert level <= 100, "level integer should be lower than or equal to 100" - - -def filter_rights(user, model, level): - """ - Return a generic queryset of objects with access rights for this user. - Level filtering parameter should be an integer between 1 and 100. - """ - check_level_param(level) - - public = level <= PUBLIC_LEVEL and has_public_field(model) - - # Handle special authentications - if user.is_anonymous: - # Anonymous users have Guest access on public instances only - if not public: - return model.objects.none() - return get_public_instances(model) - elif user.is_admin: - # Superusers have an Admin access to all corpora - return model.objects.all() \ - .annotate(max_level=Value(Role.Admin.value, IntegerField())) - - # Filter users rights and annotate the resulting level for those rights - queryset = model.objects \ - .filter( - # Filter instances with rights concerning this user - Q(memberships__user=user) - | Q(memberships__group__memberships__user=user) - ) \ - .annotate( - # Keep only the lowest level for each right via group - max_level=functions.Least( - "memberships__level", - # In case of direct right, the group level will be skipped (Null value) - "memberships__group__memberships__level" - ) - ) \ - .filter(max_level__gte=level) - - # Use a join to add public instances as this is the more elegant solution - if public: - queryset = queryset.union(get_public_instances(model)) - - return queryset - - -def get_max_level(user, instance): - """ - Returns the maximum access level on a given instance - """ - default_level = None - if getattr(instance, "public", False): - default_level = PUBLIC_LEVEL - - # Handle special authentications - if user.is_anonymous: - return default_level - elif user.is_admin: - # Superusers have an Admin access to all corpora - return Role.Admin.value - - rights = instance.memberships \ - .filter( - # Filter instances with rights concerning this user - Q(user=user) - | Q(group__memberships__user=user) - ) \ - .annotate( - # Keep only the lowest level for each right via group - max_level=functions.Least( - "level", - # In case of direct right, the group level will be skipped (Null value) - "group__memberships__level" - ) - ) - - # Filter out the right with the maximum level - max_right = max(rights, key=lambda r: r.max_level, default=None) - - return max_right and max_right.max_level or default_level +filter_rights = cached_import(_filter_module, "filter_rights") +get_max_level = cached_import(_filter_module, "get_max_level") +has_access = cached_import(_filter_module, "has_access")