diff --git a/arkindex/process/tests/test_docker_worker_version.py b/arkindex/process/tests/test_docker_worker_version.py index 59bd8b58cb22f47f4bd6e5b93786953b4083d359..bea809bbacc6cf5ad02407964f8e1c85857b9133 100644 --- a/arkindex/process/tests/test_docker_worker_version.py +++ b/arkindex/process/tests/test_docker_worker_version.py @@ -549,9 +549,13 @@ class TestDockerWorkerVersion(FixtureAPITestCase): (self.user.id, Role.Admin.value) ]) - # test version user configuration + # Test user configuration def test_create_version_valid_user_configuration(self): + test_model = Model.objects.create( + name="Generic model", + public=False, + ) self.client.force_login(self.user) response = self.client.post( reverse("api:version-from-docker"), @@ -564,6 +568,12 @@ class TestDockerWorkerVersion(FixtureAPITestCase): "user_configuration": { "demo_integer": {"title": "Demo Integer", "type": "int", "required": True, "default": 1}, "demo_boolean": {"title": "Demo Boolean", "type": "bool", "required": False, "default": True}, + "demo_dict": {"title": "Demo Dict", "type": "dict", "required": True, "default": {"a": "b", "c": "d"}}, + "demo_choice": {"title": "Decisions", "type": "enum", "required": True, "default": 1, "choices": [1, 2, 3]}, + "demo_list": {"title": "Demo List", "type": "list", "required": True, "subtype": "int", "default": [1, 2, 3, 4]}, + "boolean_list": {"title": "It's a list of booleans", "type": "list", "required": False, "subtype": "bool", "default": [True, False, False]}, + "demo_model": {"title": "Model for training", "type": "model", "required": True}, + "other_model": {"title": "Model the second", "type": "model", "default": str(test_model.id)} } }, }, @@ -583,109 +593,20 @@ class TestDockerWorkerVersion(FixtureAPITestCase): "type": "bool", "required": False, "default": True - } - } - }) - - def test_create_version_valid_user_configuration_dict(self): - self.client.force_login(self.user) - response = self.client.post( - reverse("api:version-from-docker"), - data={ - "docker_image_iid": "a_docker_image", - "repository_url": self.repo.url, - "revision_hash": "new_revision_hash", - "worker_slug": self.worker.slug, - "configuration": { - "user_configuration": { - "demo_dict": {"title": "Demo Dict", "type": "dict", "required": True, "default": {"a": "b", "c": "d"}}, - } }, - }, - format="json", - ) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertEqual(response.json()["configuration"], { - "user_configuration": { "demo_dict": { "title": "Demo Dict", "type": "dict", "required": True, "default": {"a": "b", "c": "d"} - } - } - }) - - def test_create_version_user_configuration_dict_strings_only(self): - self.client.force_login(self.user) - response = self.client.post( - reverse("api:version-from-docker"), - data={ - "docker_image_iid": "a_docker_image", - "repository_url": self.repo.url, - "revision_hash": "new_revision_hash", - "worker_slug": self.worker.slug, - "configuration": { - "user_configuration": { - "demo_dict": {"title": "Demo Dict", "type": "dict", "required": True, "default": {"a": ["12", "13"], "c": "d"}}, - } - }, - }, - format="json", - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - - def test_create_version_valid_user_configuration_enum(self): - self.client.force_login(self.user) - response = self.client.post( - reverse("api:version-from-docker"), - data={ - "docker_image_iid": "a_docker_image", - "repository_url": self.repo.url, - "revision_hash": "new_revision_hash", - "worker_slug": self.worker.slug, - "configuration": { - "user_configuration": { - "demo_choice": {"title": "Decisions", "type": "enum", "required": True, "default": 1, "choices": [1, 2, 3]} - } }, - }, - format="json", - ) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertEqual(response.json()["configuration"], { - "user_configuration": { "demo_choice": { "title": "Decisions", "type": "enum", "required": True, "default": 1, "choices": [1, 2, 3] - } - } - }) - - def test_create_version_valid_user_configuration_list(self): - self.client.force_login(self.user) - response = self.client.post( - reverse("api:version-from-docker"), - data={ - "docker_image_iid": "a_docker_image", - "repository_url": self.repo.url, - "revision_hash": "new_revision_hash", - "worker_slug": self.worker.slug, - "configuration": { - "user_configuration": { - "demo_list": {"title": "Demo List", "type": "list", "required": True, "subtype": "int", "default": [1, 2, 3, 4]}, - "boolean_list": {"title": "It's a list of booleans", "type": "list", "required": False, "subtype": "bool", "default": [True, False, False]} - } }, - }, - format="json", - ) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertEqual(response.json()["configuration"], { - "user_configuration": { "demo_list": { "title": "Demo List", "type": "list", @@ -699,35 +620,7 @@ class TestDockerWorkerVersion(FixtureAPITestCase): "subtype": "bool", "required": False, "default": [True, False, False] - } - } - }) - - def test_create_version_valid_user_configuration_model(self): - test_model = Model.objects.create( - name="Generic model", - public=False, - ) - self.client.force_login(self.user) - response = self.client.post( - reverse("api:version-from-docker"), - data={ - "docker_image_iid": "a_docker_image", - "repository_url": self.repo.url, - "revision_hash": "new_revision_hash", - "worker_slug": self.worker.slug, - "configuration": { - "user_configuration": { - "demo_model": {"title": "Model for training", "type": "model", "required": True}, - "other_model": {"title": "Model the second", "type": "model", "default": str(test_model.id)} - } }, - }, - format="json", - ) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertEqual(response.json()["configuration"], { - "user_configuration": { "demo_model": { "title": "Model for training", "type": "model", @@ -741,354 +634,91 @@ class TestDockerWorkerVersion(FixtureAPITestCase): } }) - def test_create_version_invalid_user_configuration_list_requires_subtype(self): - self.client.force_login(self.user) - response = self.client.post( - reverse("api:version-from-docker"), - data={ - "docker_image_iid": "a_docker_image", - "repository_url": self.repo.url, - "revision_hash": "new_revision_hash", - "worker_slug": self.worker.slug, - "configuration": { - "user_configuration": { - "demo_list": {"title": "Demo List", "type": "list", "required": True, "default": [1, 2, 3, 4]}, - } - }, - }, - format="json", - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.json(), { - "configuration": { - "user_configuration": [{ - "demo_list": { - "subtype": ['The "subtype" field must be set for "list" type properties.'] - } - }] - } - }) - - def test_create_version_invalid_user_configuration_list_wrong_default(self): - self.client.force_login(self.user) - response = self.client.post( - reverse("api:version-from-docker"), - data={ - "docker_image_iid": "a_docker_image", - "repository_url": self.repo.url, - "revision_hash": "new_revision_hash", - "worker_slug": self.worker.slug, - "configuration": { - "user_configuration": { - "demo_list": {"title": "Demo List", "type": "list", "required": True, "subtype": "int", "default": 12}, - } - }, - }, - format="json", - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.json(), { - "configuration": { - "user_configuration": [{ - "demo_list": { - "default": ["This is not a valid value for a field of type list."] - } - }] - } - }) - - def test_create_version_invalid_user_configuration_list_wrong_subtype(self): - self.client.force_login(self.user) - response = self.client.post( - reverse("api:version-from-docker"), - data={ - "docker_image_iid": "a_docker_image", - "repository_url": self.repo.url, - "revision_hash": "new_revision_hash", - "worker_slug": self.worker.slug, - "configuration": { - "user_configuration": { - "demo_list": {"title": "Demo List", "type": "list", "required": True, "subtype": "dict", "default": [1, 2, 3, 4]}, - } - }, - }, - format="json", - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.json(), { - "configuration": { - "user_configuration": [{ - "demo_list": { - "subtype": ["Subtype can only be int, float, bool or string."] - } - }] - } - }) - - def test_create_version_invalid_user_configuration_list_wrong_default_subtype(self): - self.client.force_login(self.user) - response = self.client.post( - reverse("api:version-from-docker"), - data={ - "docker_image_iid": "a_docker_image", - "repository_url": self.repo.url, - "revision_hash": "new_revision_hash", - "worker_slug": self.worker.slug, - "configuration": { - "user_configuration": { - "demo_list": {"title": "Demo List", "type": "list", "required": True, "subtype": "int", "default": [1, 2, "three", 4]}, - } - }, - }, - format="json", - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.json(), { - "configuration": { - "user_configuration": [{ - "demo_list": { - "default": ["All items in the default value must be of type int."] - } - }] - } - }) - - def test_create_version_invalid_user_configuration_not_list_choices(self): - self.client.force_login(self.user) - response = self.client.post( - reverse("api:version-from-docker"), - data={ - "docker_image_iid": "a_docker_image", - "repository_url": self.repo.url, - "revision_hash": "new_revision_hash", - "worker_slug": self.worker.slug, - "configuration": { - "user_configuration": { - "demo_choice": {"title": "Decisions", "type": "enum", "required": True, "default": 1, "choices": "eeee"} - } - }, - }, - format="json", - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.json(), { - "configuration": { - "user_configuration": [{ - "demo_choice": { - "choices": ['Expected a list of items but got type "str".'] - } - }] - } - }) - - def test_create_version_invalid_user_configuration_not_dict(self): - self.client.force_login(self.user) - response = self.client.post( - reverse("api:version-from-docker"), - data={ - "docker_image_iid": "a_docker_image", - "repository_url": self.repo.url, - "revision_hash": "new_revision_hash", - "worker_slug": self.worker.slug, - "configuration": { - "user_configuration": "non" - }, - }, - format="json", - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.json(), {"configuration": {"user_configuration": [['Expected a dictionary of items but got type "str".']]}}) - - def test_create_version_invalid_user_configuration_item_not_dict(self): - self.client.force_login(self.user) - response = self.client.post( - reverse("api:version-from-docker"), - data={ - "docker_image_iid": "a_docker_image", - "repository_url": self.repo.url, - "revision_hash": "new_revision_hash", - "worker_slug": self.worker.slug, - "configuration": { - "user_configuration": { - "secrets": ["aaaaaa"] - } - }, - }, - format="json", - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.json(), {"configuration": { - "user_configuration": [{ - "secrets": - {"__all__": ["User configuration field definitions should be of type dict, not list."]} - }] - }}) - - def test_create_version_invalid_user_configuration_wrong_field_type(self): - self.client.force_login(self.user) - response = self.client.post( - reverse("api:version-from-docker"), - data={ - "docker_image_iid": "a_docker_image", - "repository_url": self.repo.url, - "revision_hash": "new_revision_hash", - "worker_slug": self.worker.slug, - "configuration": { - "user_configuration": { - "something": { - "title": "some thing", - "type": "uh oh", - "required": 2 - } - } - }, - }, - format="json", - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.json(), { - "configuration": { - "user_configuration": [{ - "something": { - "required": ["Must be a valid boolean."], - "type": ["Value is not of type UserConfigurationFieldType"] - } - }] - } - }) - - def test_create_version_invalid_user_configuration_wrong_default_type(self): - self.client.force_login(self.user) - response = self.client.post( - reverse("api:version-from-docker"), - data={ - "docker_image_iid": "a_docker_image", - "repository_url": self.repo.url, - "revision_hash": "new_revision_hash", - "worker_slug": self.worker.slug, - "configuration": { - "user_configuration": { - "one_float": { - "title": "a float", - "type": "float", - "default": "bonjour", - "required": True - } - } - }, - }, - format="json", - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.json(), { - "configuration": { - "user_configuration": [{ - "one_float": { - "default": ["This is not a valid value for a field of type float."] - } - }] - } - }) - - def test_create_version_invalid_user_configuration_choices_no_enum(self): - self.client.force_login(self.user) - response = self.client.post( - reverse("api:version-from-docker"), - data={ - "docker_image_iid": "a_docker_image", - "repository_url": self.repo.url, - "revision_hash": "new_revision_hash", - "worker_slug": self.worker.slug, - "configuration": { - "user_configuration": { - "something": { - "title": "some thing", - "type": "int", - "required": False, - "choices": [1, 2, 3] - } - } - }, - }, - format="json", - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.json(), { - "configuration": { - "user_configuration": [{ - "something": { - "choices": ['The "choices" field can only be set for an "enum" type property.'] - } - }] - } - }) - - def test_create_version_invalid_user_configuration_missing_key(self): - self.client.force_login(self.user) - response = self.client.post( - reverse("api:version-from-docker"), - data={ - "docker_image_iid": "a_docker_image", - "repository_url": self.repo.url, - "revision_hash": "new_revision_hash", - "worker_slug": self.worker.slug, - "configuration": { - "user_configuration": { - "demo_integer": {"type": "int", "required": True, "default": 1} - } - }, - }, - format="json", - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.json(), - { - "configuration": { - "user_configuration": [{ - "demo_integer": { - "title": ["This field is required."] - } - }] - } - } - ) + def test_create_invalid_user_configuration(self): + cases = [ + ( + "non", + ['Expected a dictionary of items but got type "str".'] + ), + ( + {"demo_list": {"title": "Demo List", "type": "list", "required": True, "default": [1, 2, 3, 4]}}, + {"demo_list": { + "subtype": ['The "subtype" field must be set for "list" type properties.'] + }} + ), + ( + {"demo_list": {"title": "Demo List", "type": "list", "required": True, "subtype": "dict", "default": [1, 2, 3, 4]}}, + {"demo_list": { + "subtype": ["Subtype can only be int, float, bool or string."] + }} + ), + ( + {"demo_list": {"title": "Demo List", "type": "list", "required": True, "subtype": "int", "default": [1, 2, "three", 4]}}, + {"demo_list": { + "default": ["All items in the default value must be of type int."] + }} + ), + ( + {"demo_choice": {"title": "Decisions", "type": "enum", "required": True, "default": 1, "choices": "eeee"}}, + {"demo_choice": { + "choices": ['Expected a list of items but got type "str".'] + }} + ), + ( + {"secrets": ["aaaaaa"]}, + {"secrets": {"__all__": ["User configuration field definitions should be of type dict, not list."]}} + ), + ( + {"something": {"title": "some thing", "type": "uh oh", "required": 2}}, + {"something": { + "required": ["Must be a valid boolean."], + "type": ["Value is not of type UserConfigurationFieldType"] + }} + ), + ( + {"something": {"title": "some thing", "type": "int", "required": False, "choices": [1, 2, 3]}}, + {"something": { + "choices": ['The "choices" field can only be set for an "enum" type property.'] + }} + ), + ( + {"demo_integer": {"type": "int", "required": True, "default": 1}}, + {"demo_integer": {"title": ["This field is required."]}} + ), + ( + {"demo_integer": {"title": "an integer", "type": "int", "required": True, "default": 1, "some_key": "oh no"}}, + {"demo_integer": { + "some_key": ["Configurable properties can only be defined using the following keys: title, type, required, default, subtype, choices."] + }} + ), + ( + {"param": {"title": "Model to train", "type": "model", "default": "12341234-1234-1234-1234-123412341234"}}, + {"param": {"default": ["Model 12341234-1234-1234-1234-123412341234 not found."]}} + ) + ] - def test_create_version_invalid_user_configuration_invalid_key(self): self.client.force_login(self.user) - response = self.client.post( - reverse("api:version-from-docker"), - data={ - "docker_image_iid": "a_docker_image", - "repository_url": self.repo.url, - "revision_hash": "new_revision_hash", - "worker_slug": self.worker.slug, - "configuration": { - "user_configuration": { - "demo_integer": { - "title": "an integer", - "type": "int", - "required": True, - "default": 1, - "some_key": "oh no", - } - } - }, - }, - format="json", - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.json(), { - "configuration": { - "user_configuration": [ - { - "demo_integer": { - "some_key": ["Configurable properties can only be defined using the following keys: title, type, required, default, subtype, choices."] - } + for user_configuration, error in cases: + with self.subTest(user_configuration=user_configuration, error=error): + response = self.client.post( + reverse("api:version-from-docker"), + data={ + "docker_image_iid": "a_docker_image", + "repository_url": self.repo.url, + "revision_hash": "new_revision_hash", + "worker_slug": self.worker.slug, + "configuration": { + "user_configuration": user_configuration + }, + }, + format="json", + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.json(), { + "configuration": { + "user_configuration": [error] } - ] - } - }) + }) def test_create_version_invalid_user_configuration_default_value(self): self.client.force_login(self.user) @@ -1102,7 +732,8 @@ class TestDockerWorkerVersion(FixtureAPITestCase): ({"type": "string", "default": 1}, "string"), ({"type": "dict", "default": ["a", "b"]}, "dict"), ({"type": "model", "default": "gigi hadid"}, "model"), - ({"type": "model", "default": False}, "model") + ({"type": "model", "default": False}, "model"), + ({"type": "list", "subtype": "int", "default": 12}, "list") ] for params, expected in cases: with self.subTest(**params): @@ -1123,25 +754,3 @@ class TestDockerWorkerVersion(FixtureAPITestCase): ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.json(), {"configuration": {"user_configuration": [{"param": {"default": [f"This is not a valid value for a field of type {expected}."]}}]}}) - - def test_create_version_user_configuration_model_default_doesnt_exist(self): - self.client.force_login(self.user) - response = self.client.post( - reverse("api:version-from-docker"), - data={ - "docker_image_iid": "a_docker_image", - "repository_url": self.repo.url, - "revision_hash": "new_revision_hash", - "worker_slug": self.worker.slug, - "configuration": { - "user_configuration": { - "param": {"title": "Model to train", "type": "model", "default": "12341234-1234-1234-1234-123412341234"} - } - }, - }, - format="json", - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.json(), {"configuration": {"user_configuration": [{"param": {"default": [ - "Model 12341234-1234-1234-1234-123412341234 not found." - ]}}]}})