From 5cd74b397dd6a370ec5e4187f8d215b877571ea9 Mon Sep 17 00:00:00 2001 From: DmitriyOparin <35344996+DmitriyOparin@users.noreply.github.com> Date: Wed, 17 Mar 2021 23:16:19 +0300 Subject: [PATCH] Server tests: Lambda manager (#2748) Co-authored-by: Dmitriy Oparin --- cvat/apps/lambda_manager/tests/__init__.py | 0 .../tests/assets/functions.json | 201 ++++ .../lambda_manager/tests/assets/tasks.json | 61 ++ cvat/apps/lambda_manager/tests/test_lambda.py | 970 ++++++++++++++++++ 4 files changed, 1232 insertions(+) create mode 100644 cvat/apps/lambda_manager/tests/__init__.py create mode 100644 cvat/apps/lambda_manager/tests/assets/functions.json create mode 100644 cvat/apps/lambda_manager/tests/assets/tasks.json create mode 100644 cvat/apps/lambda_manager/tests/test_lambda.py diff --git a/cvat/apps/lambda_manager/tests/__init__.py b/cvat/apps/lambda_manager/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cvat/apps/lambda_manager/tests/assets/functions.json b/cvat/apps/lambda_manager/tests/assets/functions.json new file mode 100644 index 00000000..15e9cbf1 --- /dev/null +++ b/cvat/apps/lambda_manager/tests/assets/functions.json @@ -0,0 +1,201 @@ +{ + "positive": { + "test-openvino-omz-public-yolo-v3-tf": { + "metadata": { + "name": "test-openvino-omz-public-yolo-v3-tf", + "annotations": { + "framework": "openvino", + "name": "YOLO v3", + "spec": "[\n { \"id\": 0, \"name\": \"person\" }, \n { \"id\": 1, \"name\": \"bicycle\" }, \n { \"id\": 2, \"name\": \"car\" } \n]\n", + "type": "detector" + } + }, + "spec": { + "description": "YOLO v3 via Intel OpenVINO" + }, + "status": { + "state": "ready", + "httpPort": 49155 + } + }, + "test-openvino-omz-intel-person-reidentification-retail-0300": { + "metadata": { + "name": "test-openvino-omz-intel-person-reidentification-retail-0300", + "annotations": { + "framework": "openvino", + "name": "Person reidentification", + "spec": "", + "type": "reid" + } + }, + "spec": { + "description": "Person reidentification model for a general scenario" + }, + "status": { + "state": "ready", + "httpPort": 49156 + } + }, + "test-openvino-omz-intel-person-reidentification-retail-1234": { + "metadata": { + "name": "test-openvino-omz-intel-person-reidentification-retail-1234", + "annotations": { + "framework": "openvino", + "name": "Person reidentification", + "spec": "", + "type": "reid" + } + }, + "spec": { + "description": "Person reidentification model for a general scenario" + }, + "status": { + "state": "ready", + "httpPort": 49156 + } + }, + "test-openvino-dextr": { + "metadata": { + "name": "test-openvino-dextr", + "annotations": { + "framework": "openvino", + "name": "DEXTR", + "spec": "", + "type": "interactor" + } + }, + "spec": { + "description": "Deep Extreme Cut" + }, + "status": { + "state": "ready", + "httpPort": 49157 + } + }, + "test-pth-foolwood-siammask": { + "metadata": { + "name": "test-pth-foolwood-siammask", + "annotations": { + "framework": "pytorch", + "name": "SiamMask", + "spec": "", + "type": "tracker" + } + }, + "spec": { + "description": "Fast Online Object Tracking and Segmentation" + }, + "status": { + "state": "ready", + "httpPort": 49158 + } + }, + "test-model-has-non-type": { + "metadata": { + "name": "test-model-has-non-type", + "annotations": { + "framework": "openvino", + "name": "Non type", + "spec": "[\n { \"id\": 0, \"name\": \"person\" }, \n { \"id\": 1, \"name\": \"bicycle\" }, \n { \"id\": 2, \"name\": \"car\" } \n]\n" + } + }, + "spec": { + "description": "Test non type" + }, + "status": { + "state": "ready", + "httpPort": 49160 + } + }, + "test-model-has-wrong-type": { + "metadata": { + "name": "test-model-has-wrong-type", + "annotations": { + "framework": "openvino", + "name": "Non type", + "spec": "[\n { \"id\": 0, \"name\": \"person\" }, \n { \"id\": 1, \"name\": \"bicycle\" }, \n { \"id\": 2, \"name\": \"car\" } \n]\n", + "type": "car-bicycle-person-detector" + } + }, + "spec": { + "description": "Test wrong type" + }, + "status": { + "state": "ready", + "httpPort": 49161 + } + }, + "test-model-has-unknown-type": { + "metadata": { + "name": "test-model-has-unknown-type", + "annotations": { + "framework": "openvino", + "name": "Non type", + "spec": "[\n { \"id\": 0, \"name\": \"person\" }, \n { \"id\": 1, \"name\": \"bicycle\" }, \n { \"id\": 2, \"name\": \"car\" } \n]\n", + "type": "unknown" + } + }, + "spec": { + "description": "Test unknown type" + }, + "status": { + "state": "ready", + "httpPort": 49162 + } + }, + "test-model-has-state-building": { + "metadata": { + "name": "test-model-has-state-building", + "annotations": { + "framework": "openvino", + "name": "State is building", + "spec": "[\n { \"id\": 0, \"name\": \"person\" }, \n { \"id\": 1, \"name\": \"bicycle\" }, \n { \"id\": 2, \"name\": \"car\" } \n]\n", + "type": "detector" + } + }, + "spec": { + "description": "Test state building" + }, + "status": { + "state": "building" + } + }, + "test-model-has-state-error": { + "metadata": { + "name": "test-model-has-state-building", + "annotations": { + "framework": "openvino", + "name": "State is error", + "spec": "[\n { \"id\": 0, \"name\": \"person\" }, \n { \"id\": 1, \"name\": \"bicycle\" }, \n { \"id\": 2, \"name\": \"car\" } \n]\n", + "type": "detector" + } + }, + "spec": { + "description": "Test state error" + }, + "status": { + "state": "error" + } + } + }, + "negative": { + "test-model-has-non-unique-labels": { + "metadata": { + "name": "test-model-has-non-unique-labels", + "annotations": { + "framework": "openvino", + "name": "Non-unique labels", + "spec": "[\n { \"id\": 0, \"name\": \"person\" }, \n { \"id\": 1, \"name\": \"bicycle\" }, \n { \"id\": 2, \"name\": \"car\" }, \n { \"id\": 3, \"name\": \"car\" } \n]\n", + "type": "detector" + } + }, + "spec": { + "description": "Test non-unique labels" + }, + "status": { + "state": "ready", + "httpPort": 49159 + } + } + } +} diff --git a/cvat/apps/lambda_manager/tests/assets/tasks.json b/cvat/apps/lambda_manager/tests/assets/tasks.json new file mode 100644 index 00000000..7e2976e1 --- /dev/null +++ b/cvat/apps/lambda_manager/tests/assets/tasks.json @@ -0,0 +1,61 @@ +{ + "main": { + "name": "main task", + "overlap": 0, + "segment_size": 100, + "owner_id": 1, + "labels": [ + { + "name": "car", + "attributes": [ + { + "name": "model", + "mutable": false, + "input_type": "select", + "default_value": "mazda", + "values": ["bmw", "mazda", "renault"] + }, + { + "name": "parked", + "mutable": true, + "input_type": "checkbox", + "default_value": false + } + ] + }, + { + "name": "person" + } + ] + }, + "assigneed_to_user": { + "name": "assignee to user task", + "overlap": 0, + "segment_size": 100, + "owner_id": 1, + "assignee_id": 2, + "labels": [ + { + "name": "car", + "attributes": [ + { + "name": "model", + "mutable": false, + "input_type": "select", + "default_value": "mazda", + "values": ["bmw", "mazda", "renault"] + }, + { + "name": "parked", + "mutable": true, + "input_type": "checkbox", + "default_value": false + } + ] + }, + { + "name": "person" + } + ] + } +} diff --git a/cvat/apps/lambda_manager/tests/test_lambda.py b/cvat/apps/lambda_manager/tests/test_lambda.py new file mode 100644 index 00000000..bbb70178 --- /dev/null +++ b/cvat/apps/lambda_manager/tests/test_lambda.py @@ -0,0 +1,970 @@ + +# Copyright (C) 2021 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import json +from collections import OrderedDict +from io import BytesIO +from unittest import mock, skip +import os + +import requests +from django.contrib.auth.models import Group, User +from django.http import HttpResponseNotFound, HttpResponseServerError +from PIL import Image +from rest_framework import status +from rest_framework.test import APIClient, APITestCase + +LAMBDA_ROOT_PATH = '/api/v1/lambda' +LAMBDA_FUNCTIONS_PATH = f'{LAMBDA_ROOT_PATH}/functions' +LAMBDA_REQUESTS_PATH = f'{LAMBDA_ROOT_PATH}/requests' + +id_function_detector = "test-openvino-omz-public-yolo-v3-tf" +id_function_reid_response_data = "test-openvino-omz-intel-person-reidentification-retail-0300" +id_function_reid_response_no_data = "test-openvino-omz-intel-person-reidentification-retail-1234" +id_function_interactor = "test-openvino-dextr" +id_function_tracker = "test-pth-foolwood-siammask" +id_function_non_type = "test-model-has-non-type" +id_function_wrong_type = "test-model-has-wrong-type" +id_function_unknown_type = "test-model-has-unknown-type" +id_function_non_unique_labels = "test-model-has-non-unique-labels" +id_function_state_building = "test-model-has-state-building" +id_function_state_error = "test-model-has-state-error" + +expected_keys_in_response_all_functions = ["id", "kind", "labels", "description", "framework", "name"] +expected_keys_in_response_function_interactor = expected_keys_in_response_all_functions + ["min_pos_points", "startswith_box"] +expected_keys_in_response_function_tracker = expected_keys_in_response_all_functions + ["state"] +expected_keys_in_response_requests = ["id", "function", "status", "progress", "enqueued", "started", "ended", "exc_info"] + +path = os.path.join(os.path.dirname(__file__), 'assets', 'tasks.json') +with open(path) as f: + tasks = json.load(f) + +# removed unnecessary data +path = os.path.join(os.path.dirname(__file__), 'assets', 'functions.json') +with open(path) as f: + functions = json.load(f) + + +def generate_image_file(filename, size=(100, 100)): + f = BytesIO() + image = Image.new('RGB', size=size) + image.save(f, 'jpeg') + f.name = filename + f.seek(0) + return f + + +class ForceLogin: + def __init__(self, user, client): + self.user = user + self.client = client + + def __enter__(self): + if self.user: + self.client.force_login(self.user, backend='django.contrib.auth.backends.ModelBackend') + + return self + + def __exit__(self, exception_type, exception_value, traceback): + if self.user: + self.client.logout() + + +class LambdaTestCase(APITestCase): + def setUp(self): + self.client = APIClient() + + patcher = mock.patch('cvat.apps.lambda_manager.views.LambdaGateway._http', side_effect = self.__get_response_data_from_lambda_gateway_http) + self.addCleanup(patcher.stop) + patcher.start() + + images_main_task = self._generate_task_images(3) + images_assigneed_to_user_task = self._generate_task_images(3) + self.main_task = self._create_task(tasks["main"], images_main_task) + self.assigneed_to_user_task = self._create_task(tasks["assigneed_to_user"], images_assigneed_to_user_task) + + + def __get_response_data_from_lambda_gateway_http(self, *args, **kwargs): + url = kwargs["url"] + # POST query for get annotations + if url == "/api/function_invocations": + data = [] + id_function = kwargs["headers"]["x-nuclio-function-name"] + type_function = functions["positive"][id_function]["metadata"]["annotations"]["type"] + if type_function == "reid": + if id_function == id_function_reid_response_data: + data = [0, 1] + else: + data = [] + elif type_function == "tracker": + data = { + "shape": [12.34, 34.0, 35.01, 41.99], + "state": {"key": "value"}, + } + elif type_function == "interactor": + data = [ + [8, 12], + [34, 56], + [77, 77], + ] + elif type_function == "detector": + data = [ + {'confidence': '0.9959098', 'label': 'car', 'points': [3, 3, 15, 15], 'type': 'rectangle'}, + {'confidence': '0.89535173', 'label': 'car', 'points': [20, 25, 30, 35], 'type': 'rectangle'}, + {'confidence': '0.59464583', 'label': 'car', 'points': [12.17, 45.0, 69.80, 18.99], 'type': 'polygon'}, + ] + return data + # GET query for get all functions + elif url == "/api/functions": + return functions["positive"] + # GET query for get function + else: + id_function = url.split("/")[-1] + if id_function in functions["positive"]: + # raise 500 Internal_Server error + if id_function in [id_function_state_building, id_function_state_error]: + r = requests.RequestException() + r.response = HttpResponseServerError() + raise r + # return values + return functions["positive"][id_function] + # raise 404 Not Found error + else: + r = requests.HTTPError() + r.response = HttpResponseNotFound() + raise r + + + @classmethod + def _create_db_users(cls): + (group_admin, _) = Group.objects.get_or_create(name="admin") + (group_user, _) = Group.objects.get_or_create(name="user") + + user_admin = User.objects.create_superuser(username="admin", email="", + password="admin") + user_admin.groups.add(group_admin) + user_dummy = User.objects.create_user(username="user", password="user") + user_dummy.groups.add(group_user) + + cls.admin = user_admin + cls.user = user_dummy + + + def _create_task(self, data, image_data): + with ForceLogin(self.admin, self.client): + response = self.client.post('/api/v1/tasks', data=data, format="json") + assert response.status_code == status.HTTP_201_CREATED, response.status_code + tid = response.data["id"] + + response = self.client.post("/api/v1/tasks/%s/data" % tid, + data=image_data) + assert response.status_code == status.HTTP_202_ACCEPTED, response.status_code + + response = self.client.get("/api/v1/tasks/%s" % tid) + task = response.data + + return task + + + def _generate_task_images(self, count): # pylint: disable=no-self-use + images = { + "client_files[%d]" % i: generate_image_file("image_%d.jpg" % i) + for i in range(count) + } + images["image_quality"] = 75 + return images + + + @classmethod + def setUpTestData(cls): + cls._create_db_users() + + + def _get_request(self, path, user): + with ForceLogin(user, self.client): + response = self.client.get(path) + return response + + + def _delete_request(self, path, user): + with ForceLogin(user, self.client): + response = self.client.delete(path) + return response + + + def _post_request(self, path, user, data): + data = json.dumps(data) + with ForceLogin(user, self.client): + response = self.client.post(path, data=data, content_type='application/json') + return response + + + def __check_expected_keys_in_response_function(self, data): + kind = data["kind"] + if kind == "interactor": + for key in expected_keys_in_response_function_interactor: + self.assertIn(key, data) + elif kind == "tracker": + for key in expected_keys_in_response_function_tracker: + self.assertIn(key, data) + else: + for key in expected_keys_in_response_all_functions: + self.assertIn(key, data) + + + def test_api_v1_lambda_functions_list(self): + response = self._get_request(LAMBDA_FUNCTIONS_PATH, self.admin) + self.assertEqual(response.status_code, status.HTTP_200_OK) + for data in response.data: + self.__check_expected_keys_in_response_function(data) + + response = self._get_request(LAMBDA_FUNCTIONS_PATH, self.user) + self.assertEqual(response.status_code, status.HTTP_200_OK) + for data in response.data: + self.__check_expected_keys_in_response_function(data) + + response = self._get_request(LAMBDA_FUNCTIONS_PATH, None) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + + @mock.patch('cvat.apps.lambda_manager.views.LambdaGateway._http', return_value = {}) + def test_api_v1_lambda_functions_list_empty(self, mock_http): + response = self._get_request(LAMBDA_FUNCTIONS_PATH, self.admin) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 0) + + response = self._get_request(LAMBDA_FUNCTIONS_PATH, self.user) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 0) + + response = self._get_request(LAMBDA_FUNCTIONS_PATH, None) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + + @mock.patch('cvat.apps.lambda_manager.views.LambdaGateway._http', return_value = functions["negative"]) + def test_api_v1_lambda_functions_list_wrong(self, mock_http): + response = self._get_request(LAMBDA_FUNCTIONS_PATH, self.admin) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + + def test_api_v1_lambda_functions_read(self): + ids_functions = [id_function_detector, id_function_interactor,\ + id_function_tracker, id_function_reid_response_data, \ + id_function_non_type, id_function_wrong_type, id_function_unknown_type] + + for id_func in ids_functions: + path = f'{LAMBDA_FUNCTIONS_PATH}/{id_func}' + + response = self._get_request(path, self.admin) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.__check_expected_keys_in_response_function(response.data) + + response = self._get_request(path, self.user) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.__check_expected_keys_in_response_function(response.data) + + response = self._get_request(path, None) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + + def test_api_v1_lambda_functions_read_wrong_id(self): + id_wrong_function = "test-functions-wrong-id" + response = self._get_request(f'{LAMBDA_FUNCTIONS_PATH}/{id_wrong_function}', self.admin) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + response = self._get_request(f'{LAMBDA_FUNCTIONS_PATH}/{id_wrong_function}', self.user) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + response = self._get_request(f'{LAMBDA_FUNCTIONS_PATH}/{id_wrong_function}', None) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + + @mock.patch('cvat.apps.lambda_manager.views.LambdaGateway._http', return_value = functions["negative"][id_function_non_unique_labels]) + def test_api_v1_lambda_functions_read_non_unique_labels(self, mock_http): + response = self._get_request(f'{LAMBDA_FUNCTIONS_PATH}/{id_function_non_unique_labels}', self.admin) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + + @skip("Fail: add mock") + def test_api_v1_lambda_requests_list(self): + response = self._get_request(LAMBDA_REQUESTS_PATH, self.admin) + self.assertEqual(response.status_code, status.HTTP_200_OK) + for key in expected_keys_in_response_requests: + self.assertIn(key, response.data[0]) + + response = self._get_request(LAMBDA_REQUESTS_PATH, self.user) + self.assertEqual(response.status_code, status.HTTP_200_OK) + for key in expected_keys_in_response_requests: + self.assertIn(key, response.data[0]) + + response = self._get_request(LAMBDA_REQUESTS_PATH, None) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + + def test_api_v1_lambda_requests_list_empty(self): + response = self._get_request(LAMBDA_REQUESTS_PATH, self.admin) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 0) + + response = self._get_request(LAMBDA_REQUESTS_PATH, self.user) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 0) + + response = self._get_request(LAMBDA_REQUESTS_PATH, None) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + + def test_api_v1_lambda_requests_read(self): + # create request + data_main_task = { + "function": id_function_detector, + "task": self.main_task["id"], + "cleanup": True, + "threshold": 55, + "quality": "original", + "mapping": { + "car": "car", + }, + } + response = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data_main_task) + self.assertEqual(response.status_code, status.HTTP_200_OK) + id_request = response.data["id"] + + response = self._get_request(f'{LAMBDA_REQUESTS_PATH}/{id_request}', self.admin) + self.assertEqual(response.status_code, status.HTTP_200_OK) + for key in expected_keys_in_response_requests: + self.assertIn(key, response.data) + + response = self._get_request(f'{LAMBDA_REQUESTS_PATH}/{id_request}', self.user) + self.assertEqual(response.status_code, status.HTTP_200_OK) + for key in expected_keys_in_response_requests: + self.assertIn(key, response.data) + + response = self._get_request(f'{LAMBDA_REQUESTS_PATH}/{id_request}', None) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + + def test_api_v1_lambda_requests_read_wrong_id(self): + id_request = "cf343b95-afeb-475e-ab53-8d7e64991d30-wrong-id" + + response = self._get_request(f'{LAMBDA_REQUESTS_PATH}/{id_request}', self.admin) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + response = self._get_request(f'{LAMBDA_REQUESTS_PATH}/{id_request}', self.user) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + response = self._get_request(f'{LAMBDA_REQUESTS_PATH}/{id_request}', None) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + + def test_api_v1_lambda_requests_delete_finished_request(self): + data = { + "function": id_function_detector, + "task": self.main_task["id"], + "cleanup": True, + "mapping": { + "car": "car", + }, + } + response = self._post_request(f'{LAMBDA_REQUESTS_PATH}', self.admin, data) + id_request = response.data["id"] + + response = self._delete_request(f'{LAMBDA_REQUESTS_PATH}/{id_request}', None) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + response = self._delete_request(f'{LAMBDA_REQUESTS_PATH}/{id_request}', self.admin) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + response = self._get_request(f'{LAMBDA_REQUESTS_PATH}/{id_request}', self.admin) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + response = self._post_request(f'{LAMBDA_REQUESTS_PATH}', self.admin, data) + id_request = response.data["id"] + response = self._delete_request(f'{LAMBDA_REQUESTS_PATH}/{id_request}', self.user) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + response = self._get_request(f'{LAMBDA_REQUESTS_PATH}/{id_request}', self.user) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + + @skip("Fail: add mock") + def test_api_v1_lambda_requests_delete_not_finished_request(self): + pass + + + def test_api_v1_lambda_requests_create(self): + ids_functions = [id_function_detector, id_function_interactor, id_function_tracker, \ + id_function_reid_response_data, id_function_detector, id_function_reid_response_no_data, \ + id_function_non_type, id_function_wrong_type, id_function_unknown_type] + + for id_func in ids_functions: + data_main_task = { + "function": id_func, + "task": self.main_task["id"], + "cleanup": True, + "threshold": 55, + "quality": "original", + "mapping": { + "car": "car", + }, + } + data_assigneed_to_user_task = { + "function": id_func, + "task": self.assigneed_to_user_task["id"], + "cleanup": False, + "quality": "compressed", + "max_distance": 70, + "mapping": { + "car": "car", + }, + } + + response = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data_main_task) + self.assertEqual(response.status_code, status.HTTP_200_OK) + for key in expected_keys_in_response_requests: + self.assertIn(key, response.data) + + response = self._post_request(LAMBDA_REQUESTS_PATH, self.user, data_assigneed_to_user_task) + self.assertEqual(response.status_code, status.HTTP_200_OK) + for key in expected_keys_in_response_requests: + self.assertIn(key, response.data) + + response = self._post_request(LAMBDA_REQUESTS_PATH, self.user, data_main_task) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + response = self._post_request(LAMBDA_REQUESTS_PATH, None, data_main_task) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + + @mock.patch('cvat.apps.lambda_manager.views.LambdaGateway._http', return_value = functions["negative"]["test-model-has-non-unique-labels"]) + def test_api_v1_lambda_requests_create_non_unique_labels(self, mock_http): + data = { + "function": id_function_non_unique_labels, + "task": self.main_task["id"], + "cleanup": True, + "mapping": { + "car": "car", + }, + } + + response = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + + def test_api_v1_lambda_requests_create_empty_data(self): + data = {} + response = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + + def test_api_v1_lambda_requests_create_without_function(self): + data = { + "task": self.main_task["id"], + "cleanup": True, + "mapping": { + "car": "car", + }, + } + response = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + + def test_api_v1_lambda_requests_create_wrong_id_function(self): + data = { + "function": "test-requests-wrong-id", + "task": self.main_task["id"], + "cleanup": True, + "mapping": { + "car": "car", + }, + } + response = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + + @skip("Fail: add mock") + def test_api_v1_lambda_requests_create_two_requests(self): + data = { + "function": id_function_detector, + "task": self.main_task["id"], + "cleanup": True, + "mapping": { + "car": "car", + }, + } + self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data) + response = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data) + self.assertEqual(response.status_code, status.HTTP_409_CONFLICT) + + + def test_api_v1_lambda_requests_create_empty_mapping(self): + data = { + "function": id_function_detector, + "task": self.main_task["id"], + "cleanup": True, + "mapping": {}, + } + response = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + for key in expected_keys_in_response_requests: + self.assertIn(key, response.data) + + + def test_api_v1_lambda_requests_create_without_cleanup(self): + data = { + "function": id_function_detector, + "task": self.main_task["id"], + "mapping": { + "car": "car", + }, + } + response = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + for key in expected_keys_in_response_requests: + self.assertIn(key, response.data) + + + def test_api_v1_lambda_requests_create_without_mapping(self): + data = { + "function": id_function_detector, + "task": self.main_task["id"], + "cleanup": True, + } + response = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + for key in expected_keys_in_response_requests: + self.assertIn(key, response.data) + + + def test_api_v1_lambda_requests_create_without_task(self): + data = { + "function": id_function_detector, + "cleanup": True, + "mapping": { + "car": "car", + }, + } + response = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + + def test_api_v1_lambda_requests_create_wrong_id_task(self): + data = { + "function": id_function_detector, + "task": 12345, + "cleanup": True, + "mapping": { + "car": "car", + }, + } + response = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + + def test_api_v1_lambda_requests_create_is_not_ready(self): + ids_functions = [id_function_state_building, id_function_state_error] + + for id_func in ids_functions: + data = { + "function": id_func, + "task": self.main_task["id"], + "cleanup": True, + "mapping": { + "car": "car", + }, + } + + response = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data) + self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR) + + + def test_api_v1_lambda_functions_create_detector(self): + data_main_task = { + "task": self.main_task["id"], + "frame": 0, + "cleanup": True, + "threshold": 0.55, + "mapping": { + "car": "car", + }, + } + data_assigneed_to_user_task = { + "task": self.assigneed_to_user_task["id"], + "frame": 0, + "cleanup": True, + "mapping": { + "car": "car", + }, + } + + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", self.admin, data_main_task) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", self.user, data_assigneed_to_user_task) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", None, data_main_task) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + @skip("Fail: expected result != actual result") # TODO move test to test_api_v1_lambda_functions_create + def test_api_v1_lambda_functions_create_user_assigned_to_no_user(self): + data = { + "task": self.main_task["id"], + "frame": 0, + "cleanup": True, + "mapping": { + "car": "car", + }, + } + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", self.user, data) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + + def test_api_v1_lambda_functions_create_interactor(self): + data_main_task = { + "task": self.main_task["id"], + "frame": 0, + "pos_points": [ + [3.45, 6.78], + [12.1, 12.1], + [34.1, 41.0], + [43.01, 43.99], + ], + "neg_points": [ + [3.25, 6.58], + [11.1, 11.0], + [35.5, 44.44], + [45.01, 45.99], + ], + } + data_assigneed_to_user_task = { + "task": self.assigneed_to_user_task["id"], + "frame": 0, + "threshold": 0.1, + "pos_points": [ + [3.45, 6.78], + [12.1, 12.1], + [34.1, 41.0], + [43.01, 43.99], + ], + "neg_points": [ + [3.25, 6.58], + [11.1, 11.0], + [35.5, 44.44], + [45.01, 45.99], + ], + } + + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_interactor}", self.admin, data_main_task) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_interactor}", self.user, data_assigneed_to_user_task) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_interactor}", None, data_main_task) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + + def test_api_v1_lambda_functions_create_tracker(self): + data_main_task = { + "task": self.main_task["id"], + "frame": 0, + "shape": [ + 12.12, + 34.45, + 54.0, + 76.12, + ], + } + data_assigneed_to_user_task = { + "task": self.assigneed_to_user_task["id"], + "frame": 0, + "shape": [ + 12.12, + 34.45, + 54.0, + 76.12, + ], + } + + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_tracker}", self.admin, data_main_task) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_tracker}", self.user, data_assigneed_to_user_task) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_tracker}", None, data_main_task) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + + def test_api_v1_lambda_functions_create_reid(self): + data_main_task = { + "task": self.main_task["id"], + "frame0": 0, + "frame1": 1, + "boxes0": [ + OrderedDict([('attributes', []), ('frame', 0), ('group', None), ('id', 11258), ('label_id', 8), ('occluded', False), ('path_id', 0), ('points', [137.0, 129.0, 457.0, 676.0]), ('source', 'auto'), ('type', 'rectangle'), ('z_order', 0)]), + OrderedDict([('attributes', []), ('frame', 0), ('group', None), ('id', 11259), ('label_id', 8), ('occluded', False), ('path_id', 1), ('points', [1511.0, 224.0, 1537.0, 437.0]), ('source', 'auto'), ('type', 'rectangle'), ('z_order', 0)]), + ], + "boxes1": [ + OrderedDict([('attributes', []), ('frame', 1), ('group', None), ('id', 11260), ('label_id', 8), ('occluded', False), ('points', [1076.0, 199.0, 1218.0, 593.0]), ('source', 'auto'), ('type', 'rectangle'), ('z_order', 0)]), + OrderedDict([('attributes', []), ('frame', 1), ('group', None), ('id', 11261), ('label_id', 8), ('occluded', False), ('points', [924.0, 177.0, 1090.0, 615.0]), ('source', 'auto'), ('type', 'rectangle'), ('z_order', 0)]), + ], + "quality": None, + "threshold": 0.5, + "max_distance": 55, + } + data_assigneed_to_user_task = { + "task": self.assigneed_to_user_task["id"], + "frame0": 0, + "frame1": 1, + "boxes0": [ + OrderedDict([('attributes', []), ('frame', 0), ('group', None), ('id', 11258), ('label_id', 8), ('occluded', False), ('path_id', 0), ('points', [137.0, 129.0, 457.0, 676.0]), ('source', 'auto'), ('type', 'rectangle'), ('z_order', 0)]), + OrderedDict([('attributes', []), ('frame', 0), ('group', None), ('id', 11259), ('label_id', 8), ('occluded', False), ('path_id', 1), ('points', [1511.0, 224.0, 1537.0, 437.0]), ('source', 'auto'), ('type', 'rectangle'), ('z_order', 0)]), + ], + "boxes1": [ + OrderedDict([('attributes', []), ('frame', 1), ('group', None), ('id', 11260), ('label_id', 8), ('occluded', False), ('points', [1076.0, 199.0, 1218.0, 593.0]), ('source', 'auto'), ('type', 'rectangle'), ('z_order', 0)]), + OrderedDict([('attributes', []), ('frame', 1), ('group', 0), ('id', 11398), ('label_id', 8), ('occluded', False), ('points', [184.3935546875, 211.5048828125, 331.64968722073354, 97.27792672028772, 445.87667560321825, 126.17873100983161, 454.13404825737416, 691.8087578194827, 180.26452189455085]), ('source', 'manual'), ('type', 'polygon'), ('z_order', 0)]), + ], + "quality": None, + } + + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_reid_response_data}", self.admin, data_main_task) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_reid_response_data}", self.user, data_assigneed_to_user_task) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_reid_response_data}", None, data_main_task) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_reid_response_no_data}", self.admin, data_main_task) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_reid_response_no_data}", self.user, data_assigneed_to_user_task) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_reid_response_no_data}", None, data_main_task) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + + def test_api_v1_lambda_functions_create_non_type(self): + data = { + "task": self.main_task["id"], + "frame": 0, + "cleanup": True, + "mapping": { + "car": "car", + }, + } + + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_non_type}", self.admin, data) + self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR) + + + def test_api_v1_lambda_functions_create_wrong_type(self): + data = { + "task": self.main_task["id"], + "frame": 0, + "cleanup": True, + "mapping": { + "car": "car", + }, + } + + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_wrong_type}", self.admin, data) + self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR) + + + def test_api_v1_lambda_functions_create_unknown_type(self): + data = { + "task": self.main_task["id"], + "frame": 0, + "cleanup": True, + "mapping": { + "car": "car", + }, + } + + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_unknown_type}", self.admin, data) + self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR) + + + @mock.patch('cvat.apps.lambda_manager.views.LambdaGateway._http', return_value = functions["negative"]["test-model-has-non-unique-labels"]) + def test_api_v1_lambda_functions_create_non_unique_labels(self, mock_http): + data = { + "task": self.main_task["id"], + "frame": 0, + "cleanup": True, + "mapping": { + "car": "car", + }, + } + + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_non_unique_labels}", self.admin, data) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + + def test_api_v1_lambda_functions_create_quality(self): + qualities = [None, "original", "compressed"] + + for quality in qualities: + data = { + "task": self.main_task["id"], + "frame": 0, + "cleanup": True, + "quality": quality, + "mapping": { + "car": "car", + }, + } + + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", self.admin, data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + data = { + "task": self.main_task["id"], + "frame": 0, + "cleanup": True, + "quality": "test-error-quality", + "mapping": { + "car": "car", + }, + } + + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", self.admin, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + + def test_api_v1_lambda_functions_create_empty_data(self): + data = {} + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", self.admin, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + + def test_api_v1_lambda_functions_create_detector_empty_mapping(self): + data = { + "task": self.main_task["id"], + "frame": 0, + "cleanup": True, + "mapping": {}, + } + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", self.admin, data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + + def test_api_v1_lambda_functions_create_detector_without_cleanup(self): + data = { + "task": self.main_task["id"], + "frame": 0, + "mapping": { + "car": "car", + }, + } + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", self.admin, data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + + def test_api_v1_lambda_functions_create_detector_without_mapping(self): + data = { + "task": self.main_task["id"], + "frame": 0, + "cleanup": True, + } + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", self.admin, data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + + def test_api_v1_lambda_functions_create_detector_without_task(self): + data = { + "frame": 0, + "cleanup": True, + "mapping": { + "car": "car", + }, + } + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", self.admin, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + + def test_api_v1_lambda_functions_create_detector_without_id_frame(self): + data = { + "task": self.main_task["id"], + "cleanup": True, + "mapping": { + "car": "car", + }, + } + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", self.admin, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + + def test_api_v1_lambda_functions_create_wrong_id_function(self): + data = { + "task": self.main_task["id"], + "frame": 0, + "cleanup": True, + "mapping": { + "car": "car", + }, + } + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/test-functions-wrong-id", self.admin, data) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + + def test_api_v1_lambda_functions_create_wrong_id_task(self): + data = { + "task": 12345, + "frame": 0, + "cleanup": True, + "mapping": { + "car": "car", + }, + } + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", self.admin, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + + @skip("Fail: expected result != actual result, issue #2770") + def test_api_v1_lambda_functions_create_detector_wrong_id_frame(self): + data = { + "task": self.main_task["id"], + "frame": 12345, + "cleanup": True, + "mapping": { + "car": "car", + }, + } + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", self.admin, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + + @skip("Fail: add mock and expected result != actual result") + def test_api_v1_lambda_functions_create_two_functions(self): + data = { + "task": self.main_task["id"], + "frame": 0, + "cleanup": True, + "mapping": { + "car": "car", + }, + } + self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", self.admin, data) + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", self.admin, data) + self.assertEqual(response.status_code, status.HTTP_409_CONFLICT) + + + def test_api_v1_lambda_functions_create_function_is_not_ready(self): + data = { + "task": self.main_task["id"], + "frame": 0, + "cleanup": True, + "mapping": { + "car": "car", + }, + } + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_state_building}", self.admin, data) + self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR) + + response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_state_error}", self.admin, data) + self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR)