From 1b53da9f7503e079d0f72101670a01f02ab7c2a8 Mon Sep 17 00:00:00 2001 From: Tadeas Hejnic Date: Tue, 26 Nov 2024 14:08:20 +0100 Subject: [PATCH 01/13] EntityHub --- tests/test_entity_hub.py | 501 +++++++++++++++++++++++++++++++++++---- 1 file changed, 449 insertions(+), 52 deletions(-) diff --git a/tests/test_entity_hub.py b/tests/test_entity_hub.py index 896349d7f..6b79c6dcd 100644 --- a/tests/test_entity_hub.py +++ b/tests/test_entity_hub.py @@ -1,9 +1,12 @@ +from traceback import print_exc import uuid +from requests import delete + import pytest import ayon_api -from ayon_api.entity_hub import EntityHub +from ayon_api.entity_hub import EntityHub, UNKNOWN_VALUE from .conftest import project_entity_fixture @@ -11,29 +14,29 @@ def test_rename_status(project_entity_fixture): # Change statuses - add prefix 'new_' project_name = project_entity_fixture["name"] - e = EntityHub(project_name) + hub = EntityHub(project_name) status_mapping = {} - for status in e.project_entity.statuses: + for status in hub.project_entity.statuses: orig_name = status.name new_name = f"new_{orig_name}" status_mapping[new_name] = orig_name status.name = new_name - e.commit_changes() + hub.commit_changes() # Create new entity hub for same project and validate the changes # are propagated - e = EntityHub(project_name) + hub = EntityHub(project_name) statuses_by_name = { status.name: status - for status in e.project_entity.statuses + for status in hub.project_entity.statuses } if set(statuses_by_name) != set(status_mapping.keys()): raise AssertionError("Statuses were not renamed correctly.") # Change statuses back - for status in e.project_entity.statuses: + for status in hub.project_entity.statuses: status.name = status_mapping[status.name] - e.commit_changes() + hub.commit_changes() @pytest.mark.parametrize( @@ -51,51 +54,51 @@ def test_simple_operations( """Test of simple operations with folders - create, move, delete. """ project_name = project_entity_fixture["name"] - e = EntityHub(project_name) + hub = EntityHub(project_name) folders = [] subfolders = [] # create folders with subfolder for folder_number in range(folders_count): - folder = e.add_new_folder( + folder = hub.add_new_folder( "Folder", name=f"{folder_name}{folder_number:03}" ) folders.append(folder) - e.commit_changes() + hub.commit_changes() - subfolder = e.add_new_folder( + subfolder = hub.add_new_folder( "Folder", name=f"{folder_name}{folder_number:03}", parent_id=folder["id"] ) subfolders.append(subfolder) - e.commit_changes() + hub.commit_changes() # move subfolders for index, subfolder in enumerate(subfolders): new_parent_id = folders[(index + 1) % folders_count]["id"] - e.set_entity_parent( + hub.set_entity_parent( subfolder["id"], new_parent_id, subfolder["parent_id"]) - e.commit_changes() + hub.commit_changes() - assert e.get_entity_by_id( + assert hub.get_entity_by_id( subfolder["id"] )["parent_id"] == new_parent_id # delete subfolders for subfolder in subfolders: - e.delete_entity(e.get_entity_by_id(subfolder["id"])) - e.commit_changes() - assert e.get_entity_by_id(subfolder["id"]) is None + hub.delete_entity(hub.get_entity_by_id(subfolder["id"])) + hub.commit_changes() + assert hub.get_entity_by_id(subfolder["id"]) is None # delete folders for folder in folders: - e.delete_entity(e.get_entity_by_id(folder["id"])) - e.commit_changes() - assert e.get_entity_by_id(folder["id"]) is None + hub.delete_entity(hub.get_entity_by_id(folder["id"])) + hub.commit_changes() + assert hub.get_entity_by_id(folder["id"]) is None def test_custom_values_on_entities(project_entity_fixture): @@ -110,7 +113,8 @@ def test_custom_values_on_entities(project_entity_fixture): # --- CREATE --- folder_type = project_entity_fixture["folderTypes"][-1]["name"] root_folder = hub.add_new_folder( - folder_type, name="custom_values_root_folder" + folder_type=folder_type, + name="custom_values_root_folder" ) folder_id = uuid.uuid1().hex @@ -132,7 +136,7 @@ def test_custom_values_on_entities(project_entity_fixture): task_data = {"MyTaskKey": "MyTaskValue"} folder = hub.add_new_folder( - folder_type, + folder_type=folder_type, name=folder_name, label=folder_label, parent_id=root_folder.id, @@ -144,7 +148,7 @@ def test_custom_values_on_entities(project_entity_fixture): task_type = project_entity_fixture["taskTypes"][-1]["name"] task = hub.add_new_task( - task_type, + task_type=task_type, name=task_name, label=task_label, parent_id=folder.id, @@ -270,7 +274,8 @@ def test_label_eq_name_on_entities(project_entity_fixture): folder_type = project_entity_fixture["folderTypes"][-1]["name"] root_folder = hub.add_new_folder( - folder_type, name="label_eq_name_root_folder" + folder_type=folder_type, + name="label_eq_name_root_folder" ) folder_id = uuid.uuid1().hex @@ -282,7 +287,7 @@ def test_label_eq_name_on_entities(project_entity_fixture): task_label = "my_task" folder = hub.add_new_folder( - folder_type, + folder_type=folder_type, name=folder_name, label=folder_label, parent_id=root_folder.id, @@ -291,7 +296,7 @@ def test_label_eq_name_on_entities(project_entity_fixture): task_type = project_entity_fixture["taskTypes"][-1]["name"] task = hub.add_new_task( - task_type, + task_type=task_type, name=task_name, label=task_label, parent_id=folder.id, @@ -325,7 +330,8 @@ def test_data_changes_on_entities(project_entity_fixture): folder_type = project_entity_fixture["folderTypes"][-1]["name"] root_folder = hub.add_new_folder( - folder_type, name="data_changes_on_entities" + folder_type=folder_type, + name="data_changes_on_entities" ) folder_id = uuid.uuid1().hex @@ -337,7 +343,7 @@ def test_data_changes_on_entities(project_entity_fixture): task_data = {"key2": "value2"} folder = hub.add_new_folder( - folder_type, + folder_type=folder_type, name=folder_name, data=folder_data, parent_id=root_folder.id, @@ -346,7 +352,7 @@ def test_data_changes_on_entities(project_entity_fixture): task_type = project_entity_fixture["taskTypes"][-1]["name"] task = hub.add_new_task( - task_type, + task_type=task_type, name=task_name, data=task_data, parent_id=folder.id, @@ -405,7 +411,7 @@ def test_label_eq_name_on_entities(project_entity_fixture): folder_id = uuid.uuid1().hex task_id = uuid.uuid1().hex folder = hub.add_new_folder( - folder_type, + folder_type=folder_type, name="status_root_folder", entity_id=folder_id, status=init_status_name, @@ -414,7 +420,7 @@ def test_label_eq_name_on_entities(project_entity_fixture): task_name = "my_task" task_label = "my_task" task = hub.add_new_task( - task_type, + task_type=task_type, name=task_name, label=task_label, parent_id=folder.id, @@ -491,39 +497,39 @@ def test_create_delete_with_duplicated_names( Exception should not be raised. """ project_name = project_entity_fixture["name"] - e = EntityHub(project_name) + hub = EntityHub(project_name) - folder1 = e.add_new_folder("Folder", name=folder_name) + folder1 = hub.add_new_folder("Folder", name=folder_name) subfolders = [] for folder_number in range(num_of_subfolders): - subfolder = e.add_new_folder( + subfolder = hub.add_new_folder( "Folder", parent_id=folder1["id"], name=f"{subfolder_name}{folder_number:03}" ) subfolders.append(subfolder) - e.commit_changes() + hub.commit_changes() # create and delete folder with same name - subfolder = e.add_new_folder( + subfolder = hub.add_new_folder( "Folder", parent_id=folder1["id"], name=f"{subfolder_name}{folder_number:03}" ) - e.delete_entity(subfolder) - e.commit_changes() + hub.delete_entity(subfolder) + hub.commit_changes() - assert e.get_folder_by_id(project_name, folder1["id"]) is not None + assert hub.get_folder_by_id(project_name, folder1["id"]) is not None for subfolder in subfolders: - assert e.get_folder_by_id( + assert hub.get_folder_by_id( project_name, subfolder["id"]) is not None # clean up - e.delete_entity(folder1) - e.commit_changes() + hub.delete_entity(folder1) + hub.commit_changes() # @pytest.mark.parametrize( @@ -726,15 +732,14 @@ def test_create_delete_with_duplicated_names( # with pytest.raises(HTTPRequestError): # e.commit_changes() # # print(list(e.project_entity.statuses)[0]) -# -# -# def test_rename_status(): -# e = EntityHub(PROJECT_NAME) -# -# for status in e.project_entity.statuses: + + +# def test_rename_status(project_entity_fixture): +# hub = EntityHub(project_entity_fixture["name"]) + +# for status in hub.project_entity.statuses: # print(status.name) -# -# + # def test_task_types(): # raise NotImplementedError() # @@ -746,3 +751,395 @@ def test_create_delete_with_duplicated_names( # # def test_status_icon(): # raise NotImplementedError() + + +# def test_project_statuses(project_entity_fixture): +# statuses = project_entity_fixture.get_statuses() +# pass + +test_names = [ + ("test_name"), + # ("test_123"), +] + +test_product_types = [ + ("animation"), + ("camera"), + ("render"), + ("workfile"), +] + +@pytest.mark.parametrize("folder_name", test_names) +@pytest.mark.parametrize("product_name", test_names) +@pytest.mark.parametrize("product_type", test_product_types) +def test_create_delete_products( + project_entity_fixture, + folder_name, + product_name, + product_type +): + project_name = project_entity_fixture["name"] + folder_type = project_entity_fixture["folderTypes"][0]["name"] + hub = EntityHub(project_name) + + for folder in ayon_api.get_folders( + project_name, + folder_names=[folder_name] + ): + # delete tasks + for task in ayon_api.get_tasks( + project_name, + folder_ids=[folder["id"]] + ): + hub.delete_entity(hub.get_task_by_id(task["id"])) + + # delete products + for product in list(ayon_api.get_products( + project_name, folder_ids=[folder["id"]] + )): + product_entity = hub.get_product_by_id(product["id"]) + hub.delete_entity(product_entity) + + entity = hub.get_folder_by_id(folder["id"]) + hub.delete_entity(entity) + + hub.commit_changes() + + folder = hub.add_new_folder( + folder_type=folder_type, + name=folder_name, + ) + + product = hub.add_new_product( + name=product_name, + product_type=product_type, + folder_id=folder["id"] + ) + + hub.commit_changes() + + assert hub.get_product_by_id(product["id"]) + assert product.get_name() == product_name + assert product.get_product_type() == product_type + assert product.get_folder_id() == folder["id"] + + # bonus test: + # create new entity hub for same project and validate the changes + # are propagated + hub = EntityHub(project_name) + product = hub.get_product_by_id(product["id"]) + assert product.get_name() == product_name + assert product.get_product_type() == product_type + assert product.get_folder_id() == folder["id"] + + +@pytest.mark.parametrize("name", test_names) +def test_create_delete_folders(project_entity_fixture, name): + project_name = project_entity_fixture["name"] + folder_types = [ + type["name"] for type in project_entity_fixture["folderTypes"] + ] + + hub = EntityHub(project_name) + + folder = hub.add_new_folder( + folder_type=folder_types[0], + name=name, + ) + + hub.commit_changes() + + assert ayon_api.get_folders( + project_name, + folder_names=[name], + folder_types=folder_types[0:1], + folder_ids=[folder["id"]] + ) + + for folder in ayon_api.get_folders( + project_name, + folder_names=[name] + ): + # delete tasks + for task in ayon_api.get_tasks( + project_name, + folder_ids=[folder["id"]] + ): + hub.delete_entity(hub.get_task_by_id(task["id"])) + + entity = hub.get_folder_by_id(folder["id"]) + + for id in entity.children_ids: + hub.delete_entity(hub.get_entity_by_id(id)) + + hub.delete_entity(entity) + + hub.commit_changes() + + # new folder + folder = hub.add_new_folder( + folder_type=folder_types[1], + name=name, + ) + + hub.commit_changes() + + assert ayon_api.get_folders( + project_name, + folder_names=[name], + folder_types=folder_types[1:2], + folder_ids=[folder["id"]] + ) + + +test_version_numbers = [ + ([1, 2, 3, 4]) +] + + +@pytest.mark.parametrize("version_numbers", test_version_numbers) +def test_create_delete_versions(project_entity_fixture, version_numbers): + # prepare hierarchy + folder_types = [ + type["name"] for type in project_entity_fixture["folderTypes"] + ] + hub = EntityHub(project_entity_fixture["name"]) + + folder = hub.add_new_folder( + folder_type=folder_types[0], + name="test_folder", + ) + + product = hub.add_new_product( + name="test_product", + product_type="animation", + folder_id=folder["id"] + ) + + assert product.get_children_ids() == set() + + # add + versions = [] + for version in version_numbers: + versions.append( + hub.add_new_version( + version, + product["id"] + ) + ) + + hub.commit_changes() + + res = product.get_children_ids() + + assert len(versions) == len(res) + for version in versions: + assert version + assert hub.get_version_by_id(version["id"]) + assert version["id"] in res + + # delete + hub.delete_entity(hub.get_version_by_id(version["id"])) + hub.commit_changes() + + assert hub.get_version_by_id(version["id"]) is None + # assert + + +test_invalid_version_number = [ + ("a"), + (None), + ("my_version_number") +] + + +@pytest.mark.parametrize("version_number", test_invalid_version_number) +def test_create_invalid_versions(project_entity_fixture, version_number): + # prepare hierarchy + folder_types = [ + type["name"] for type in project_entity_fixture["folderTypes"] + ] + hub = EntityHub(project_entity_fixture["name"]) + + folder = hub.add_new_folder( + folder_type=folder_types[0], + name="test_folder", + ) + + product = hub.add_new_product( + name="test_product", + product_type="animation", + folder_id=folder["id"] + ) + + assert product.get_children_ids() == set() + + hub.add_new_version( + version_number, + product["id"] + ) + + with pytest.raises(ayon_api.exceptions.FailedOperations): + hub.commit_changes() + + +def test_change_status_on_version(project_entity_fixture): + folder_types = [ + type["name"] for type in project_entity_fixture["folderTypes"] + ] + status_names = [ + status["name"] + for status in project_entity_fixture["statuses"] + if "version" in status["scope"] + ] + + hub = EntityHub(project_entity_fixture["name"]) + + folder = hub.add_new_folder( + folder_type=folder_types[0], + name="test_folder", + ) + + product = hub.add_new_product( + name="test_product", + product_type="animation", + folder_id=folder["id"] + ) + + version = hub.add_new_version( + 1, + product["id"] + ) + + hub.commit_changes + + for status_name in status_names: + version.set_status(status_name) + hub.commit_changes() + + assert version.get_status() == status_name + + +@pytest.mark.parametrize("version", test_version_numbers) +def test_set_invalid_status_on_version(project_entity_fixture, version): + folder_types = [ + type["name"] for type in project_entity_fixture["folderTypes"] + ] + valid_names = [ + status["name"] + for status in project_entity_fixture["statuses"] + if "version" in status["scope"] + ] + invalid_names = [ + status["name"] + for status in project_entity_fixture["statuses"] + if "version" not in status["scope"] + ] + + hub = EntityHub(project_entity_fixture["name"]) + + folder = hub.add_new_folder( + folder_type=folder_types[0], + name="test_folder", + ) + + product = hub.add_new_product( + name="test_product", + product_type="animation", + folder_id=folder["id"] + ) + + version = hub.add_new_version( + 1, + product["id"] + ) + + # test on version without status + for status_name in invalid_names: + with pytest.raises(ValueError): + version.set_status(status_name) + hub.commit_changes() + + assert version.get_status() == UNKNOWN_VALUE + + # test valid statuses + for status_name in valid_names: + version.set_status(status_name) + hub.commit_changes() + + assert version.get_status() == status_name + + current_status = version.get_status() + + # test on version with status + for status_name in invalid_names: + with pytest.raises(ValueError): + version.set_status(status_name) + hub.commit_changes() + + assert version.get_status() == current_status + + +test_tags = [ + (["tag1", "tag2", "tag3"]), + (["tag4"]), + (["tag5", "tag6"]), +] + + +@pytest.mark.parametrize("tags", test_tags) +def test_set_tag_on_version(project_entity_fixture, tags): + folder_types = [ + type["name"] for type in project_entity_fixture["folderTypes"] + ] + + + hub = EntityHub(project_entity_fixture["name"]) + + folder = hub.add_new_folder( + folder_type=folder_types[0], + name="test_folder", + ) + + product = hub.add_new_product( + name="test_product", + product_type="animation", + folder_id=folder["id"] + ) + + version = hub.add_new_version( + 1, + product["id"] + ) + + assert version.get_tags() == [] + + for tag in tags: + version.set_tags([tag]) + hub.commit_changes() + + assert tag in version.get_tags() + + +def test_set_invalid_tag_on_version(): + raise NotImplementedError() + + +def test_status_definition_on_project(project_entity_fixture): + hub = EntityHub(project_entity_fixture["name"]) + + project = hub.project_entity + project.status = "test_status" + print(project.status) + + # project.set_status() + # project_status_obj = hub.project_entity.get_statuses() + # project_status_obj.set_state() + # print(type(project_status_obj), project_status_obj) + + +# definice status na projects +# zmena statusu a tagu na entitach - verzich +# vytvareni a mazani produktu a verzi + + From b62ac244f1281db77a706fa17fccb4908cff4f11 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 26 Nov 2024 14:15:57 +0100 Subject: [PATCH 02/13] fix args --- tests/test_entity_hub.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/test_entity_hub.py b/tests/test_entity_hub.py index 6b79c6dcd..8f2383ecb 100644 --- a/tests/test_entity_hub.py +++ b/tests/test_entity_hub.py @@ -499,12 +499,12 @@ def test_create_delete_with_duplicated_names( project_name = project_entity_fixture["name"] hub = EntityHub(project_name) - folder1 = hub.add_new_folder("Folder", name=folder_name) + folder1 = hub.add_new_folder(folder_type="Folder", name=folder_name) subfolders = [] for folder_number in range(num_of_subfolders): subfolder = hub.add_new_folder( - "Folder", + folder_type="Folder", parent_id=folder1["id"], name=f"{subfolder_name}{folder_number:03}" ) @@ -513,7 +513,7 @@ def test_create_delete_with_duplicated_names( # create and delete folder with same name subfolder = hub.add_new_folder( - "Folder", + folder_type="Folder", parent_id=folder1["id"], name=f"{subfolder_name}{folder_number:03}" ) @@ -546,7 +546,7 @@ def test_create_delete_with_duplicated_names( # """ # e = EntityHub(PROJECT_NAME) # -# parent_folder = e.add_new_folder("Folder", name=folder_name) +# parent_folder = e.add_new_folder(folder_type="Folder", name=folder_name) # e.commit_changes() # # @@ -554,14 +554,14 @@ def test_create_delete_with_duplicated_names( # subfolders = [] # for folder_number in range(2): # folder = e.add_new_folder( -# "Folder", +# folder_type="Folder", # name=f"test{folder_number:03}", # parent_id=parent_folder["id"] # ) # folders.append(folder) # # subfolder = e.add_new_folder( -# "Folder", +# folder_type="Folder", # name="duplicated", # parent_id=folder["id"] # ) @@ -628,14 +628,14 @@ def test_create_delete_with_duplicated_names( # e = EntityHub(PROJECT_NAME) # # parent_folder = e.add_new_folder( -# "Folder", +# folder_type="Folder", # name=parent_folder_name # ) # # folder_ids = [] # for folder_number in range(num_of_folders): # folder = e.add_new_folder( -# "Folder", +# folder_type="Folder", # parent_id=parent_folder["id"], # name=f"{folder_name}{folder_number:03}" # ) @@ -645,7 +645,7 @@ def test_create_delete_with_duplicated_names( # for folder_id in folder_ids: # for subfolder_number in range(num_of_subfolders): # subfolder = e.add_new_folder( -# "Folder", +# folder_type="Folder", # parent_id=folder_id, # name=f"{subfolder_name}{subfolder_number:03}" # ) From 4acd5135eaf6d7aec49ac1bd0cbf2d56c20aeba0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 26 Nov 2024 14:22:13 +0100 Subject: [PATCH 03/13] keep backwards compatibility for parent id --- ayon_api/entity_hub.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ayon_api/entity_hub.py b/ayon_api/entity_hub.py index e9a5197f5..02d13129a 100644 --- a/ayon_api/entity_hub.py +++ b/ayon_api/entity_hub.py @@ -3312,7 +3312,18 @@ def __init__( entity_id: Optional[str] = None, created: Optional[bool] = None, entity_hub: EntityHub = None, + parent_id: Optional[str] = UNKNOWN_VALUE, ): + if folder_id is UNKNOWN_VALUE and parent_id is not UNKNOWN_VALUE: + warnings.warn( + ( + "DEV WARNING: Used 'parent_id' instead of 'folder_id' in" + " TaskEntity. Please use 'folder_id' instead." + ), + DeprecationWarning + ) + folder_id = parent_id + super().__init__( name=name, parent_id=folder_id, From 7f309c00b785f8f5e5ba8fee10ebf7a448267a6e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 26 Nov 2024 14:24:12 +0100 Subject: [PATCH 04/13] revert modifications --- ayon_api/entity_hub.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/ayon_api/entity_hub.py b/ayon_api/entity_hub.py index 02d13129a..5a66004d5 100644 --- a/ayon_api/entity_hub.py +++ b/ayon_api/entity_hub.py @@ -3286,6 +3286,8 @@ class TaskEntity(BaseEntity): value is defined based on value of 'entity_id'. entity_hub (EntityHub): Object of entity hub which created object of the entity. + parent_id (Union[str, None]): DEPRECATED please use 'folder_id' + instead. """ _supports_name = True @@ -3312,18 +3314,7 @@ def __init__( entity_id: Optional[str] = None, created: Optional[bool] = None, entity_hub: EntityHub = None, - parent_id: Optional[str] = UNKNOWN_VALUE, ): - if folder_id is UNKNOWN_VALUE and parent_id is not UNKNOWN_VALUE: - warnings.warn( - ( - "DEV WARNING: Used 'parent_id' instead of 'folder_id' in" - " TaskEntity. Please use 'folder_id' instead." - ), - DeprecationWarning - ) - folder_id = parent_id - super().__init__( name=name, parent_id=folder_id, From f5994a830fe23817e8b9e63430acebb3646d22a0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 26 Nov 2024 14:24:19 +0100 Subject: [PATCH 05/13] changed docstring --- ayon_api/entity_hub.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ayon_api/entity_hub.py b/ayon_api/entity_hub.py index 5a66004d5..c4d7c46de 100644 --- a/ayon_api/entity_hub.py +++ b/ayon_api/entity_hub.py @@ -3271,7 +3271,7 @@ class TaskEntity(BaseEntity): name (str): Name of entity. task_type (str): Type of task. Task type must be available in config of project task types. - parent_id (Union[str, None]): Id of parent entity. + folder_id (Union[str, None]): Parent folder id. label (Optional[str]): Task label. status (Optional[str]): Task status. tags (Optional[Iterable[str]]): Folder tags. From 766c8eb713f9c6908a37aa8c2c4f44ac05b6ac59 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 26 Nov 2024 14:24:27 +0100 Subject: [PATCH 06/13] use folder id for new tasks in tests --- tests/test_entity_hub.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_entity_hub.py b/tests/test_entity_hub.py index 8f2383ecb..ddac2894b 100644 --- a/tests/test_entity_hub.py +++ b/tests/test_entity_hub.py @@ -151,7 +151,7 @@ def test_custom_values_on_entities(project_entity_fixture): task_type=task_type, name=task_name, label=task_label, - parent_id=folder.id, + folder_id=folder.id, data=task_data, attribs=task_attrib, entity_id=task_id, @@ -299,7 +299,7 @@ def test_label_eq_name_on_entities(project_entity_fixture): task_type=task_type, name=task_name, label=task_label, - parent_id=folder.id, + folder_id=folder.id, entity_id=task_id, ) hub.commit_changes() @@ -355,7 +355,7 @@ def test_data_changes_on_entities(project_entity_fixture): task_type=task_type, name=task_name, data=task_data, - parent_id=folder.id, + folder_id=folder.id, entity_id=task_id, ) hub.commit_changes() @@ -423,7 +423,7 @@ def test_label_eq_name_on_entities(project_entity_fixture): task_type=task_type, name=task_name, label=task_label, - parent_id=folder.id, + folder_id=folder.id, entity_id=task_id, status=init_status_name, ) From 7df8b37ed90df63eb33da424cf9a2b85393a810c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 26 Nov 2024 14:34:09 +0100 Subject: [PATCH 07/13] fix args in tests --- tests/test_entity_hub.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_entity_hub.py b/tests/test_entity_hub.py index ddac2894b..3a9ef0afb 100644 --- a/tests/test_entity_hub.py +++ b/tests/test_entity_hub.py @@ -61,14 +61,14 @@ def test_simple_operations( # create folders with subfolder for folder_number in range(folders_count): folder = hub.add_new_folder( - "Folder", + folder_type="Folder", name=f"{folder_name}{folder_number:03}" ) folders.append(folder) hub.commit_changes() subfolder = hub.add_new_folder( - "Folder", + folder_type="Folder", name=f"{folder_name}{folder_number:03}", parent_id=folder["id"] ) From 8291ce28d0cdd091816d604068d8bc4ad4d744f3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 26 Nov 2024 14:42:23 +0100 Subject: [PATCH 08/13] implemented name setter --- ayon_api/entity_hub.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/ayon_api/entity_hub.py b/ayon_api/entity_hub.py index c4d7c46de..f3094824c 100644 --- a/ayon_api/entity_hub.py +++ b/ayon_api/entity_hub.py @@ -1775,14 +1775,6 @@ def lock(self): def _get_entity_by_id(self, entity_id): return self._entity_hub.get_entity_by_id(entity_id) - def get_name(self): - return self._name - - def set_name(self, name): - self._name = name - - name = property(get_name, set_name) - def get_parent_id(self): """Parent entity id. @@ -1972,7 +1964,17 @@ def get_name(self): ) return self._name - name = property(get_name) + def set_name(self, name): + if not self._supports_name: + raise NotImplementedError( + f"Name is not supported for '{self.entity_type}'." + ) + + if not isinstance(name, str): + raise TypeError("Name must be a string.") + self._name = name + + name = property(get_name, set_name) def get_label(self) -> Optional[str]: if not self._supports_label: From 55520898dcb6c1731e1d1cc646181f3b249d8eb5 Mon Sep 17 00:00:00 2001 From: Tadeas Hejnic Date: Tue, 26 Nov 2024 17:58:23 +0100 Subject: [PATCH 09/13] The largest test test_get_vents_all_filter_combinations moved to seperate file, new tests added, old tests edited --- tests/conftest.py | 188 +++++++++++++++ tests/test_entity_hub.py | 57 ++++- tests/test_get_events.py | 159 +++++++++++++ tests/test_server.py | 481 ++++++++------------------------------- 4 files changed, 486 insertions(+), 399 deletions(-) create mode 100644 tests/test_get_events.py diff --git a/tests/conftest.py b/tests/conftest.py index ed6306375..ecb3dd013 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,11 +1,17 @@ +from datetime import datetime, timedelta, timezone import pytest +from xml.dom.minidom import Entity from ayon_api import ( get_project, create_project, update_project, delete_project, + get_folders, + get_products, + get_tasks ) +from ayon_api.entity_hub import EntityHub class _Cache: @@ -60,3 +66,185 @@ def project_entity_fixture(project_name_fixture): yield project_entity if created: delete_project(project_name_fixture) + + +@pytest.fixture +def clean_project(project_name_fixture): + hub = EntityHub(project_name_fixture) + + for folder in get_folders( + project_name_fixture + ): + # delete tasks + for task in get_tasks( + project_name_fixture, + folder_ids=[folder["id"]] + ): + hub.delete_entity(hub.get_task_by_id(task["id"])) + + # delete products + for product in list(get_products( + project_name_fixture, folder_ids=[folder["id"]] + )): + product_entity = hub.get_product_by_id(product["id"]) + hub.delete_entity(product_entity) + + entity = hub.get_folder_by_id(folder["id"]) + hub.delete_entity(entity) + + hub.commit_changes() + + +class TestEventFilters: + project_names = [ + (None), + ([]), + (["demo_Big_Episodic"]), + (["demo_Big_Feature"]), + (["demo_Commercial"]), + (["AY_Tests"]), + (["demo_Big_Episodic", "demo_Big_Feature", "demo_Commercial", "AY_Tests"]) + ] + + topics = [ + (None), + ([]), + (["entity.folder.attrib_changed"]), + (["entity.task.created", "entity.project.created"]), + (["settings.changed", "entity.version.status_changed"]), + (["entity.task.status_changed", "entity.folder.deleted"]), + ([ + "entity.project.changed", + "entity.task.tags_changed", + "entity.product.created" + ]) + ] + + users = [ + (None), + ([]), + (["admin"]), + (["mkolar", "tadeas.8964"]), + (["roy", "luke.inderwick", "ynbot"]), + ([ + "entity.folder.attrib_changed", + "entity.project.created", + "entity.task.created", + "settings.changed" + ]), + ] + + # states is incorrect name for statuses + states = [ + (None), + ([]), + (["pending", "in_progress", "finished", "failed", "aborted", "restarted"]), + (["failed", "aborted"]), + (["pending", "in_progress"]), + (["finished", "failed", "restarted"]), + (["finished"]), + ] + + include_logs = [ + (None), + (True), + (False), + ] + + has_children = [ + (None), + (True), + (False), + ] + + now = datetime.now(timezone.utc) + + newer_than = [ + (None), + ((now - timedelta(days=2)).isoformat()), + ((now - timedelta(days=5)).isoformat()), + ((now - timedelta(days=10)).isoformat()), + ((now - timedelta(days=20)).isoformat()), + ((now - timedelta(days=30)).isoformat()), + ] + + older_than = [ + (None), + ((now - timedelta(days=0)).isoformat()), + ((now - timedelta(days=5)).isoformat()), + ((now - timedelta(days=10)).isoformat()), + ((now - timedelta(days=20)).isoformat()), + ((now - timedelta(days=30)).isoformat()), + ] + + fields = [ + (None), + ([]), + ] + + +class TestInvalidEventFilters: + topics = [ + (None), + (["invalid_topic_name_1", "invalid_topic_name_2"]), + (["invalid_topic_name_1"]), + ] + + project_names = [ + (None), + (["invalid_project"]), + (["invalid_project", "demo_Big_Episodic", "demo_Big_Feature"]), + (["invalid_name_2", "demo_Commercial"]), + (["demo_Commercial"]), + ] + + states = [ + (None), + (["pending_invalid"]), + (["in_progress_invalid"]), + (["finished_invalid", "failed_invalid"]), + ] + + users = [ + (None), + (["ayon_invalid_user"]), + (["ayon_invalid_user1", "ayon_invalid_user2"]), + (["ayon_invalid_user1", "ayon_invalid_user2", "admin"]), + ] + + newer_than = [ + (None), + ((datetime.now(timezone.utc) + timedelta(days=2)).isoformat()), + ((datetime.now(timezone.utc) + timedelta(days=5)).isoformat()), + ((datetime.now(timezone.utc) - timedelta(days=5)).isoformat()), + ] + + +class TestUpdateEventData: + update_sender = [ + ("test.server.api"), + ] + + update_username = [ + ("testing_user"), + ] + + update_status = [ + ("pending"), + ("in_progress"), + ("finished"), + ("failed"), + ("aborted"), + ("restarted") + ] + + update_description = [ + ("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce vivera."), + ("Updated description test...") + ] + + update_retries = [ + (1), + (0), + (10), + ] diff --git a/tests/test_entity_hub.py b/tests/test_entity_hub.py index 3a9ef0afb..dcc8d959c 100644 --- a/tests/test_entity_hub.py +++ b/tests/test_entity_hub.py @@ -2,6 +2,7 @@ import uuid from requests import delete +import test import pytest @@ -264,7 +265,7 @@ def test_custom_values_on_entities(project_entity_fixture): hub.commit_changes() -def test_label_eq_name_on_entities(project_entity_fixture): +def test_label_eq_name_on_entities_1(project_entity_fixture): """Test label that have same values as name on folder and task. When the entity has same name and label, the label should be set to None. @@ -372,11 +373,11 @@ def test_data_changes_on_entities(project_entity_fixture): hub = EntityHub(project_name) - folder = hub.get_or_query_entity_by_id(folder_id, {"folder"}) + folder = hub.get_or_fetch_entity_by_id(folder_id, {"folder"}) folder.data["key3"] = "value3" folder.data.pop("key1") - task = hub.get_or_query_entity_by_id(task_id, {"task"}) + task = hub.get_or_fetch_entity_by_id(task_id, {"task"}) task.data["key4"] = "value4" task.data.pop("key2") hub.commit_changes() @@ -397,7 +398,7 @@ def test_data_changes_on_entities(project_entity_fixture): hub.commit_changes() -def test_label_eq_name_on_entities(project_entity_fixture): +def test_label_eq_name_on_entities_2(project_entity_fixture): """Test label that have same values as name on folder and task. When the entity has same name and label, the label should be set to None. @@ -430,8 +431,8 @@ def test_label_eq_name_on_entities(project_entity_fixture): hub.commit_changes() hub = EntityHub(project_name) - folder = hub.get_or_query_entity_by_id(folder_id, {"folder"}) - task = hub.get_or_query_entity_by_id(task_id, {"task"}) + folder = hub.get_or_fetch_entity_by_id(folder_id, {"folder"}) + task = hub.get_or_fetch_entity_by_id(task_id, {"task"}) assert folder.status == init_status_name, ( "Folder status set on create was not propagated" @@ -759,7 +760,7 @@ def test_create_delete_with_duplicated_names( test_names = [ ("test_name"), - # ("test_123"), + ("test_123"), ] test_product_types = [ @@ -769,6 +770,8 @@ def test_create_delete_with_duplicated_names( ("workfile"), ] + +@pytest.mark.usefixtures("clean_project") @pytest.mark.parametrize("folder_name", test_names) @pytest.mark.parametrize("product_name", test_names) @pytest.mark.parametrize("product_type", test_product_types) @@ -833,6 +836,7 @@ def test_create_delete_products( assert product.get_folder_id() == folder["id"] +@pytest.mark.usefixtures("clean_project") @pytest.mark.parametrize("name", test_names) def test_create_delete_folders(project_entity_fixture, name): project_name = project_entity_fixture["name"] @@ -893,10 +897,12 @@ def test_create_delete_folders(project_entity_fixture, name): test_version_numbers = [ - ([1, 2, 3, 4]) + ([1, 2, 3, 4]), + ([8, 10, 4, 5]), ] +@pytest.mark.usefixtures("clean_project") @pytest.mark.parametrize("version_numbers", test_version_numbers) def test_create_delete_versions(project_entity_fixture, version_numbers): # prepare hierarchy @@ -953,6 +959,7 @@ def test_create_delete_versions(project_entity_fixture, version_numbers): ] +@pytest.mark.usefixtures("clean_project") @pytest.mark.parametrize("version_number", test_invalid_version_number) def test_create_invalid_versions(project_entity_fixture, version_number): # prepare hierarchy @@ -983,6 +990,7 @@ def test_create_invalid_versions(project_entity_fixture, version_number): hub.commit_changes() +@pytest.mark.usefixtures("clean_project") def test_change_status_on_version(project_entity_fixture): folder_types = [ type["name"] for type in project_entity_fixture["folderTypes"] @@ -1020,6 +1028,7 @@ def test_change_status_on_version(project_entity_fixture): assert version.get_status() == status_name +@pytest.mark.usefixtures("clean_project") @pytest.mark.parametrize("version", test_version_numbers) def test_set_invalid_status_on_version(project_entity_fixture, version): folder_types = [ @@ -1087,6 +1096,7 @@ def test_set_invalid_status_on_version(project_entity_fixture, version): ] +@pytest.mark.usefixtures("clean_project") @pytest.mark.parametrize("tags", test_tags) def test_set_tag_on_version(project_entity_fixture, tags): folder_types = [ @@ -1125,12 +1135,37 @@ def test_set_invalid_tag_on_version(): raise NotImplementedError() -def test_status_definition_on_project(project_entity_fixture): +test_statuses = [ + ("status1"), + ("status2"), + ("status3"), +] + +test_icon = [ + ("arrow_forward"), + ("expand_circle_down"), + ("done_outline"), +] + + +@pytest.mark.parametrize("status_name", test_statuses) +@pytest.mark.parametrize("icon_name", test_icon) +def test_status_definition_on_project( + project_entity_fixture, + status_name, + icon_name +): hub = EntityHub(project_entity_fixture["name"]) project = hub.project_entity - project.status = "test_status" - print(project.status) + project.get_statuses().create( + status_name, + icon_name + ) + assert status_name == project.get_statuses().get(status_name).get_name() + assert icon_name == project.get_statuses().get(status_name).get_icon() + + # print(project.status) # project.set_status() # project_status_obj = hub.project_entity.get_statuses() diff --git a/tests/test_get_events.py b/tests/test_get_events.py new file mode 100644 index 000000000..dd315fe97 --- /dev/null +++ b/tests/test_get_events.py @@ -0,0 +1,159 @@ +from datetime import datetime +import pytest + +from ayon_api import ( + get_events, + get_default_fields_for_type, + exceptions, + set_timeout, + get_timeout +) +from .conftest import TestEventFilters + + +@pytest.mark.parametrize("topics", TestEventFilters.topics[-3:]) +@pytest.mark.parametrize( + "event_ids", + [None] + [pytest.param(None, marks=pytest.mark.usefixtures("event_ids"))] +) +@pytest.mark.parametrize("project_names", TestEventFilters.project_names[-3:]) +@pytest.mark.parametrize("states", TestEventFilters.states[-3:]) +@pytest.mark.parametrize("users", TestEventFilters.users[-3:]) +@pytest.mark.parametrize("include_logs", TestEventFilters.include_logs[-3:]) +@pytest.mark.parametrize("has_children", TestEventFilters.has_children[-3:]) +@pytest.mark.parametrize("newer_than", TestEventFilters.newer_than[-2:]) +@pytest.mark.parametrize("older_than", TestEventFilters.older_than[-2:]) +@pytest.mark.parametrize("fields", TestEventFilters.fields[0:1]) +def test_get_events_all_filter_combinations( + topics, + event_ids, + project_names, + states, + users, + include_logs, + has_children, + newer_than, + older_than, + fields +): + """Tests all combinations of possible filters for `get_events`. + + Verifies: + - Calls `get_events` with the provided filter parameters. + - Ensures each event in the result set matches the specified filters. + - Checks that the number of returned events matches the expected count + based on the filters applied. + - Confirms that each event contains only the specified fields, with + no extra keys. + + Note: + - Adjusts the timeout setting if necessary to handle a large number + of tests and avoid timeout errors. + - Some combinations of filter parameters may lead to a server timeout + error. When this occurs, the test will skip instead of failing. + - Currently, a ServerError due to timeout may occur when `has_children` + is set to False. + + """ + if get_timeout() < 5: + set_timeout(None) # default timeout + + try: + res = list(get_events( + topics=topics, + event_ids=event_ids, + project_names=project_names, + states=states, + users=users, + include_logs=include_logs, + has_children=has_children, + newer_than=newer_than, + older_than=older_than, + fields=fields + )) + except exceptions.ServerError as exc: + assert has_children is False, ( + f"{exc} even if has_children is {has_children}." + ) + print("Warning: ServerError encountered, test skipped due to timeout.") + pytest.skip("Skipping test due to server timeout.") + + for item in res: + assert item.get("topic") in topics + assert item.get("project") in project_names + assert item.get("user") in users + assert item.get("status") in states + + assert (newer_than is None) or ( + datetime.fromisoformat(item.get("createdAt")) + > datetime.fromisoformat(newer_than) + ) + assert (older_than is None) or ( + datetime.fromisoformat(item.get("createdAt")) + < datetime.fromisoformat(older_than) + ) + + assert topics is None or len(res) == sum(len( + list(get_events( + topics=[topic], + project_names=project_names, + states=states, + users=users, + include_logs=include_logs, + has_children=has_children, + newer_than=newer_than, + older_than=older_than, + fields=fields + )) or [] + ) for topic in topics) + + assert project_names is None or len(res) == sum(len( + list(get_events( + topics=topics, + project_names=[project_name], + states=states, + users=users, + include_logs=include_logs, + has_children=has_children, + newer_than=newer_than, + older_than=older_than, + fields=fields + )) or [] + ) for project_name in project_names) + + assert states is None or len(res) == sum(len( + list(get_events( + topics=topics, + project_names=project_names, + states=[state], + users=users, + include_logs=include_logs, + has_children=has_children, + newer_than=newer_than, + older_than=older_than, + fields=fields + )) or [] + ) for state in states) + + assert users is None or len(res) == sum(len( + list(get_events( + topics=topics, + project_names=project_names, + states=states, + users=[user], + include_logs=include_logs, + has_children=has_children, + newer_than=newer_than, + older_than=older_than, + fields=fields + )) or [] + ) for user in users) + + if fields == []: + fields = get_default_fields_for_type("event") + + assert fields is None \ + or all( + set(event.keys()) == set(fields) + for event in res + ) diff --git a/tests/test_server.py b/tests/test_server.py index 64d1187f8..9e0b64e87 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -15,14 +15,14 @@ create_folder, create_project, create_thumbnail, - delete, + delete_addon_version, + delete_event, delete_project, dispatch_event, download_addon_private_file, enroll_event_job, get, get_addons_info, - get_default_fields_for_type, get_event, get_events, get_folder_thumbnail, @@ -41,6 +41,14 @@ ServerAPI, exceptions ) +from .conftest import ( + TestEventFilters, + TestInvalidEventFilters, + TestUpdateEventData, + event_id, + event_ids +) + AYON_BASE_URL = os.getenv("AYON_SERVER_URL") AYON_REST_URL = "{}/api".format(AYON_BASE_URL) @@ -104,256 +112,7 @@ def test_get(): assert isinstance(res.data, dict) -test_project_names = [ - (None), - ([]), - (["demo_Big_Episodic"]), - (["demo_Big_Feature"]), - (["demo_Commercial"]), - (["AY_Tests"]), - (["demo_Big_Episodic", "demo_Big_Feature", "demo_Commercial", "AY_Tests"]) -] - -test_topics = [ - (None), - ([]), - (["entity.folder.attrib_changed"]), - (["entity.task.created", "entity.project.created"]), - (["settings.changed", "entity.version.status_changed"]), - (["entity.task.status_changed", "entity.folder.deleted"]), - ([ - "entity.project.changed", - "entity.task.tags_changed", - "entity.product.created" - ]) -] - -test_users = [ - (None), - ([]), - (["admin"]), - (["mkolar", "tadeas.8964"]), - (["roy", "luke.inderwick", "ynbot"]), - ([ - "entity.folder.attrib_changed", - "entity.project.created", - "entity.task.created", - "settings.changed" - ]), -] - -# states is incorrect name for statuses -test_states = [ - (None), - ([]), - (["pending", "in_progress", "finished", "failed", "aborted", "restarted"]), - (["failed", "aborted"]), - (["pending", "in_progress"]), - (["finished", "failed", "restarted"]), - (["finished"]), -] - -test_include_logs = [ - (None), - (True), - (False), -] - -test_has_children = [ - (None), - (True), - (False), -] - -now = datetime.now(timezone.utc) - -test_newer_than = [ - (None), - ((now - timedelta(days=2)).isoformat()), - ((now - timedelta(days=5)).isoformat()), - ((now - timedelta(days=10)).isoformat()), - ((now - timedelta(days=20)).isoformat()), - ((now - timedelta(days=30)).isoformat()), -] - -test_older_than = [ - (None), - ((now - timedelta(days=0)).isoformat()), - ((now - timedelta(days=5)).isoformat()), - ((now - timedelta(days=10)).isoformat()), - ((now - timedelta(days=20)).isoformat()), - ((now - timedelta(days=30)).isoformat()), -] - -test_fields = [ - (None), - ([]), - ([]) -] - -@pytest.fixture(params=[3, 4, 5]) -def event_ids(request): - length = request.param - if length == 0: - return None - - recent_events = list(get_events( - newer_than=(datetime.now(timezone.utc) - timedelta(days=5)).isoformat() - )) - - return [recent_event["id"] for recent_event in recent_events[:length]] - - -# takes max 3 items in a list to reduce the number of combinations -@pytest.mark.parametrize("topics", test_topics[-3:]) -@pytest.mark.parametrize( - "event_ids", - [None] + [pytest.param(None, marks=pytest.mark.usefixtures("event_ids"))] -) -@pytest.mark.parametrize("project_names", test_project_names[-3:]) -@pytest.mark.parametrize("states", test_states[-3:]) -@pytest.mark.parametrize("users", test_users[-3:]) -@pytest.mark.parametrize("include_logs", test_include_logs[-3:]) -@pytest.mark.parametrize("has_children", test_has_children[2:3]) -@pytest.mark.parametrize("newer_than", test_newer_than[-3:]) -@pytest.mark.parametrize("older_than", test_older_than[-3:]) -@pytest.mark.parametrize("fields", test_fields[-3:]) -def test_get_events_all_filter_combinations( - topics, - event_ids, - project_names, - states, - users, - include_logs, - has_children, - newer_than, - older_than, - fields -): - """Tests all combinations of possible filters for `get_events`. - - Verifies: - - Calls `get_events` with the provided filter parameters. - - Ensures each event in the result set matches the specified filters. - - Checks that the number of returned events matches the expected count - based on the filters applied. - - Confirms that each event contains only the specified fields, with - no extra keys. - - Note: - - Adjusts the timeout setting if necessary to handle a large number - of tests and avoid timeout errors. - - Some combinations of filter parameters may lead to a server timeout - error. When this occurs, the test will skip instead of failing. - - Currently, a ServerError due to timeout may occur when `has_children` - is set to False. - - """ - if get_timeout() < 5: - set_timeout(None) # default timeout - - try: - res = list(get_events( - topics=topics, - event_ids=event_ids, - project_names=project_names, - states=states, - users=users, - include_logs=include_logs, - has_children=has_children, - newer_than=newer_than, - older_than=older_than, - fields=fields - )) - except exceptions.ServerError as exc: - assert has_children is False, ( - f"{exc} even if has_children is {has_children}." - ) - print("Warning: ServerError encountered, test skipped due to timeout.") - pytest.skip("Skipping test due to server timeout.") - - for item in res: - assert item.get("topic") in topics - assert item.get("project") in project_names - assert item.get("user") in users - assert item.get("status") in states - - assert (newer_than is None) or ( - datetime.fromisoformat(item.get("createdAt")) - > datetime.fromisoformat(newer_than) - ) - assert (older_than is None) or ( - datetime.fromisoformat(item.get("createdAt")) - < datetime.fromisoformat(older_than) - ) - - assert topics is None or len(res) == sum(len(list( - get_events( - topics=[topic], - project_names=project_names, - states=states, - users=users, - include_logs=include_logs, - has_children=has_children, - newer_than=newer_than, - older_than=older_than, - fields=fields - ) - )) for topic in topics) - - assert project_names is None or len(res) == sum(len(list( - get_events( - topics=topics, - project_names=[project_name], - states=states, - users=users, - include_logs=include_logs, - has_children=has_children, - newer_than=newer_than, - older_than=older_than, - fields=fields - ) - )) for project_name in project_names) - - assert states is None or len(res) == sum(len(list( - get_events( - topics=topics, - project_names=project_names, - states=[state], - users=users, - include_logs=include_logs, - has_children=has_children, - newer_than=newer_than, - older_than=older_than, - fields=fields - ) - )) for state in states) - - assert users is None or len(res) == sum(len(list( - get_events( - topics=topics, - project_names=project_names, - states=states, - users=[user], - include_logs=include_logs, - has_children=has_children, - newer_than=newer_than, - older_than=older_than, - fields=fields - ) - )) for user in users) - - if fields == []: - fields = get_default_fields_for_type("event") - - assert fields is None \ - or all( - set(event.keys()) == set(fields) - for event in res - ) - - -@pytest.mark.parametrize("has_children", test_has_children) +@pytest.mark.parametrize("has_children", TestEventFilters.has_children) def test_get_events_timeout_has_children(has_children): """Test `get_events` function with the `has_children` filter. @@ -396,14 +155,14 @@ def test_get_events_event_ids(event_ids): for item in res: assert item.get("id") in event_ids - assert len(res) == sum(len(list( - get_events( + assert event_ids is None or len(res) == sum(len( + list(get_events( event_ids=[event_id] - ) - )) for event_id in event_ids) + )) or [] + ) for event_id in event_ids) -@pytest.mark.parametrize("project_names", test_project_names) +@pytest.mark.parametrize("project_names", TestEventFilters.project_names) def test_get_events_project_name(project_names): """Test `get_events` function using specified project names. @@ -419,15 +178,15 @@ def test_get_events_project_name(project_names): assert item.get("project") in project_names # test if the legths are equal - assert len(res) == sum(len(list( - get_events( + assert project_names is None or len(res) == sum(len( + list(get_events( project_names=[project_name] - ) - )) for project_name in project_names) + )) or [] + ) for project_name in project_names) -@pytest.mark.parametrize("project_names", test_project_names) -@pytest.mark.parametrize("topics", test_topics) +@pytest.mark.parametrize("project_names", TestEventFilters.project_names) +@pytest.mark.parametrize("topics", TestEventFilters.topics) def test_get_events_project_name_topic(project_names, topics): """Test `get_events` function using both project names and topics. @@ -448,24 +207,24 @@ def test_get_events_project_name_topic(project_names, topics): assert item.get("project") in project_names # test if the legths are equal - assert len(res) == sum(len(list( - get_events( + assert project_names is None or len(res) == sum(len( + list(get_events( project_names=[project_name], topics=topics - ) - )) for project_name in project_names) + )) or [] + ) for project_name in project_names) - assert len(res) == sum(len(list( - get_events( + assert topics is None or len(res) == sum(len( + list(get_events( project_names=project_names, topics=[topic] - ) - )) for topic in topics) + )) or [] + ) for topic in topics) -@pytest.mark.parametrize("project_names", test_project_names) -@pytest.mark.parametrize("topics", test_topics) -@pytest.mark.parametrize("users", test_users) +@pytest.mark.parametrize("project_names", TestEventFilters.project_names) +@pytest.mark.parametrize("topics", TestEventFilters.topics) +@pytest.mark.parametrize("users", TestEventFilters.users) def test_get_events_project_name_topic_user(project_names, topics, users): """Test `get_events` function using project names, topics, and users. @@ -483,35 +242,38 @@ def test_get_events_project_name_topic_user(project_names, topics, users): )) for item in res: - assert item.get("topic") in topics - assert item.get("project") in project_names - assert item.get("user") in project_names + assert topics is None or item.get("topic") in topics + assert project_names is None or item.get("project") in project_names + assert users is None or item.get("user") in users # test if the legths are equal - assert len(res) == sum(len(list( - get_events( + assert project_names is None or len(res) == sum(len( + list(get_events( project_names=[project_name], - topics=topics - ) - )) for project_name in project_names) + topics=topics, + users=users + )) or [] + ) for project_name in project_names) - assert len(res) == sum(len(list( - get_events( + assert topics is None or len(res) == sum(len( + list(get_events( project_names=project_names, - topics=[topic] - ) - )) for topic in topics) + topics=[topic], + users=users + )) or [] + ) for topic in topics) - assert len(res) == sum(len(list( - get_events( + assert users is None or len(res) == sum(len( + list(get_events( project_names=project_names, - topics=[topic] - ) - )) for topic in topics) + topics=topics, + users=[user] + )) or [] + ) for user in users) -@pytest.mark.parametrize("newer_than", test_newer_than) -@pytest.mark.parametrize("older_than", test_older_than) +@pytest.mark.parametrize("newer_than", TestEventFilters.newer_than) +@pytest.mark.parametrize("older_than", TestEventFilters.older_than) def test_get_events_timestamps(newer_than, older_than): """Test `get_events` function using date filters `newer_than` and `older_than`. @@ -537,47 +299,11 @@ def test_get_events_timestamps(newer_than, older_than): ) -test_invalid_topics = [ - (None), - (["invalid_topic_name_1", "invalid_topic_name_2"]), - (["invalid_topic_name_1"]), -] - -test_invalid_project_names = [ - (None), - (["invalid_project"]), - (["invalid_project", "demo_Big_Episodic", "demo_Big_Feature"]), - (["invalid_name_2", "demo_Commercial"]), - (["demo_Commercial"]), -] - -test_invalid_states = [ - (None), - (["pending_invalid"]), - (["in_progress_invalid"]), - (["finished_invalid", "failed_invalid"]), -] - -test_invalid_users = [ - (None), - (["ayon_invalid_user"]), - (["ayon_invalid_user1", "ayon_invalid_user2"]), - (["ayon_invalid_user1", "ayon_invalid_user2", "admin"]), -] - -test_invalid_newer_than = [ - (None), - ((datetime.now(timezone.utc) + timedelta(days=2)).isoformat()), - ((datetime.now(timezone.utc) + timedelta(days=5)).isoformat()), - ((datetime.now(timezone.utc) - timedelta(days=5)).isoformat()), -] - - -@pytest.mark.parametrize("topics", test_invalid_topics) -@pytest.mark.parametrize("project_names", test_invalid_project_names) -@pytest.mark.parametrize("states", test_invalid_states) -@pytest.mark.parametrize("users", test_invalid_users) -@pytest.mark.parametrize("newer_than", test_invalid_newer_than) +@pytest.mark.parametrize("topics", TestInvalidEventFilters.topics) +@pytest.mark.parametrize("project_names", TestInvalidEventFilters.project_names) +@pytest.mark.parametrize("states", TestInvalidEventFilters.states) +@pytest.mark.parametrize("users", TestInvalidEventFilters.users) +@pytest.mark.parametrize("newer_than", TestInvalidEventFilters.newer_than) def test_get_events_invalid_data( topics, project_names, @@ -635,55 +361,11 @@ def test_get_events_invalid_data( or datetime.fromisoformat(newer_than) < datetime.now(timezone.utc) -@pytest.fixture -def event_id(): - """Fixture that retrieves the ID of a recent event created within - the last 5 days. - - Returns: - - The event ID of the most recent event within the last 5 days - if available. - - `None` if no recent events are found within this time frame. - - """ - recent_events = list(get_events( - newer_than=(datetime.now(timezone.utc) - timedelta(days=5)).isoformat() - )) - return recent_events[0]["id"] if recent_events else None - -test_update_sender = [ - ("test.server.api"), -] - -test_update_username = [ - ("testing_user"), -] - -test_update_status = [ - ("pending"), - ("in_progress"), - ("finished"), - ("failed"), - ("aborted"), - ("restarted") -] - -test_update_description = [ - ("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce vivera."), - ("Updated description test...") -] - -test_update_retries = [ - (1), - (0), - (10), -] - -@pytest.mark.parametrize("sender", test_update_sender) -@pytest.mark.parametrize("username", test_update_username) -@pytest.mark.parametrize("status", test_update_status) -@pytest.mark.parametrize("description", test_update_description) -@pytest.mark.parametrize("retries", test_update_retries) +@pytest.mark.parametrize("sender", TestUpdateEventData.update_sender) +@pytest.mark.parametrize("username", TestUpdateEventData.update_username) +@pytest.mark.parametrize("status", TestUpdateEventData.update_status) +@pytest.mark.parametrize("description", TestUpdateEventData.update_description) +@pytest.mark.parametrize("retries", TestUpdateEventData.update_retries) def test_update_event( event_id, sender, @@ -812,8 +494,8 @@ def clean_up_events(topics=[TEST_SOURCE_TOPIC, TEST_TARGET_TOPIC]): interfere with the test setup or outcomes by marking them as 'finished'. """ - events = list(get_events(topics=topics)) - for event in events: + pending_events = list(get_events(topics=topics)) + for event in pending_events: if event["status"] not in ["finished", "failed"]: update_event(event["id"], status="finished") @@ -834,8 +516,20 @@ def create_test_events(num_of_events=DEFAULT_NUMBER_OF_EVENTS): ] +@pytest.fixture +def delete_events(topics=[TEST_SOURCE_TOPIC, TEST_TARGET_TOPIC]): + """Cleans up events from the specified topics after the test completes. + """ + yield + + for event in list(get_events(topics=topics)): + delete_event(event["id"]) + + # clean_up should be below create_test to ensure it is called first # pytest probably does not guarantee the order of execution +# delete_events is disabled for now - until new sever version +# @pytest.mark.usefixtures("delete_events") @pytest.mark.usefixtures("create_test_events") @pytest.mark.usefixtures("clean_up_events") @pytest.mark.parametrize("sequential", test_sequential) @@ -889,6 +583,10 @@ def test_enroll_event_job(sequential): and job_1 != job_2 +# disabled for now - until new sever version +# delete_events is disabled for now - until new sever version +# @pytest.mark.usefixtures("delete_events") +@pytest.mark.usefixtures("create_test_events") @pytest.mark.usefixtures("clean_up_events") @pytest.mark.parametrize("sequential", test_sequential) def test_enroll_event_job_failed(sequential): @@ -931,6 +629,8 @@ def test_enroll_event_job_failed(sequential): assert sequential is not True or job_1 == job_2 +# delete_events is disabled for now - until new sever version +# @pytest.mark.usefixtures("delete_events") @pytest.mark.usefixtures("clean_up_events") @pytest.mark.parametrize("sequential", test_sequential) def test_enroll_event_job_same_sender(sequential): @@ -970,8 +670,11 @@ def test_enroll_event_job_same_sender(sequential): ("nonexisting_source_topic"), ] + +# delete_events is disabled for now - until new sever version +# @pytest.mark.usefixtures("delete_events") @pytest.mark.usefixtures("clean_up_events") -@pytest.mark.parametrize("topic", test_invalid_topics) +@pytest.mark.parametrize("topic", TestInvalidEventFilters.topics) @pytest.mark.parametrize("sequential", test_sequential) def test_enroll_event_job_invalid_topic(topic, sequential): """Tests `enroll_event_job` behavior when provided with invalid topics. @@ -1000,6 +703,8 @@ def test_enroll_event_job_invalid_topic(topic, sequential): # clean_up should be below create_test to ensure it is called first # pytest probably does not guarantee the order of execution +# delete_events is disabled for now - until new sever version +# @pytest.mark.usefixtures("delete_events") @pytest.mark.usefixtures("create_test_events") @pytest.mark.usefixtures("clean_up_events") def test_enroll_event_job_sequential_false(): @@ -1109,7 +814,7 @@ def test_addon_methods(): download_path = "tests/resources/tmp_downloads" private_file_path = os.path.join(download_path, "ayon-symbol.png") - delete(f"/addons/{addon_name}/{addon_version}") + delete_addon_version(addon_name, addon_version) assert all( addon_name != addon["name"] for addon in get_addons_info()["addons"] ) From 69f883287225705942a3b52afe4a5a0481e5e64a Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 27 Nov 2024 09:03:59 +0100 Subject: [PATCH 10/13] fix formatting --- tests/conftest.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index ecb3dd013..630ac542c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -103,7 +103,12 @@ class TestEventFilters: (["demo_Big_Feature"]), (["demo_Commercial"]), (["AY_Tests"]), - (["demo_Big_Episodic", "demo_Big_Feature", "demo_Commercial", "AY_Tests"]) + ([ + "demo_Big_Episodic", + "demo_Big_Feature", + "demo_Commercial", + "AY_Tests" + ]) ] topics = [ @@ -138,7 +143,14 @@ class TestEventFilters: states = [ (None), ([]), - (["pending", "in_progress", "finished", "failed", "aborted", "restarted"]), + ([ + "pending", + "in_progress", + "finished", + "failed", + "aborted", + "restarted" + ]), (["failed", "aborted"]), (["pending", "in_progress"]), (["finished", "failed", "restarted"]), @@ -239,7 +251,10 @@ class TestUpdateEventData: ] update_description = [ - ("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce vivera."), + ( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + " Fusce vivera." + ), ("Updated description test...") ] From 7ac9c31ea45fb2e3f86a704c2e299484c9562b8a Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 27 Nov 2024 09:07:53 +0100 Subject: [PATCH 11/13] fix formatting --- tests/test_server.py | 57 +++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/tests/test_server.py b/tests/test_server.py index 9e0b64e87..8ebb7d3e1 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -137,7 +137,9 @@ def test_get_events_timeout_has_children(has_children): assert has_children is False, ( f"{exc} even if has_children is {has_children}." ) - print("Warning: ServerError encountered, test skipped due to timeout.") + print( + "Warning: ServerError encountered, test skipped due to timeout." + ) pytest.skip("Skipping test due to server timeout.") @@ -146,8 +148,8 @@ def test_get_events_event_ids(event_ids): Verifies: - Each item returned has an ID in the `event_ids` list. - - The number of items returned matches the expected count when filtered - by each individual event ID. + - The number of items returned matches the expected count when + filtered by each individual event ID. """ res = list(get_events(event_ids=event_ids)) @@ -300,7 +302,9 @@ def test_get_events_timestamps(newer_than, older_than): @pytest.mark.parametrize("topics", TestInvalidEventFilters.topics) -@pytest.mark.parametrize("project_names", TestInvalidEventFilters.project_names) +@pytest.mark.parametrize( + "project_names", + TestInvalidEventFilters.project_names) @pytest.mark.parametrize("states", TestInvalidEventFilters.states) @pytest.mark.parametrize("users", TestInvalidEventFilters.users) @pytest.mark.parametrize("newer_than", TestInvalidEventFilters.newer_than) @@ -364,7 +368,9 @@ def test_get_events_invalid_data( @pytest.mark.parametrize("sender", TestUpdateEventData.update_sender) @pytest.mark.parametrize("username", TestUpdateEventData.update_username) @pytest.mark.parametrize("status", TestUpdateEventData.update_status) -@pytest.mark.parametrize("description", TestUpdateEventData.update_description) +@pytest.mark.parametrize( + "description", + TestUpdateEventData.update_description) @pytest.mark.parametrize("retries", TestUpdateEventData.update_retries) def test_update_event( event_id, @@ -539,12 +545,12 @@ def test_enroll_event_job(sequential): Verifies: - When `sequential` is set to `True`, only one job can be enrolled at - a time, preventing new enrollments until the first job is closed or - updated. + a time, preventing new enrollments until the first job is closed + or updated. - When `sequential` is `False` or `None`, multiple jobs can be enrolled concurrently without conflicts. - - The `update_event` function updates the `status` of a job to allowing - next sequential job processing. + - The `update_event` function updates the `status` of a job to + allowing next sequential job processing. Notes: - `update_event` is used to set `job_1`'s status to "failed" to test @@ -596,8 +602,8 @@ def test_enroll_event_job_failed(sequential): Verifies: - `enroll_event_job` creates a job (`job_1`) with specified parameters `(`source_topic`, `target_topic`, `sender`, and `sequential`). - - After `job_1` fails (status set to "failed"), a new job (`job_2`) can - be enrolled with the same parameters. + - After `job_1` fails (status set to "failed"), a new job (`job_2`) + can be enrolled with the same parameters. - When `sequential` is `True`, the test verifies that `job_1` and `job_2` are identical, as a failed sequential job should not allow a new job to be enrolled separately. @@ -641,8 +647,8 @@ def test_enroll_event_job_same_sender(sequential): - `enroll_event_job` creates a `job_1` and `job_2` with the same parameters (`source_topic`, `target_topic`, `sender`, and `sequential`). - - The test checks that `job_1` and `job_2` are identical, ensuring that - no duplicate jobs are created for the same sender. + - The test checks that `job_1` and `job_2` are identical, ensuring + that no duplicate jobs are created for the same sender. Notes: - TODO - delete events after test if possible @@ -716,9 +722,9 @@ def test_enroll_event_job_sequential_false(): - Each job has a unique `dependsOn` identifier Notes: - - The `depends_on_ids` set is used to track `dependsOn` identifiers and - verify that each job has a unique dependency state, as required for - concurrent processing. + - The `depends_on_ids` set is used to track `dependsOn` identifiers + and verify that each job has a unique dependency state, as + required for concurrent processing. - TODO - delete events after test if possible """ @@ -752,15 +758,16 @@ def test_thumbnail_operations( Verifies: - A thumbnail is created for the project and associated with a folder. - - The thumbnail associated with the folder is correctly retrieved, with - attributes matching the project name and thumbnail ID. + - The thumbnail associated with the folder is correctly retrieved, + with attributes matching the project name and thumbnail ID. - The content of the retrieved thumbnail matches the expected image bytes read from the specified `thumbnail_path`. Notes: - `delete_project` is called initially to remove any pre-existing project with the same name, ensuring no conflicts during testing. - - At the end of the test, the project is deleted to clean up resources. + - At the end of the test, the project is deleted to clean up + resources. """ if get_project(project_name): @@ -795,12 +802,12 @@ def test_addon_methods(): - An addon with the specified name and version does not exist at the start. - Uploads an addon package `.zip` file and triggers a server restart. - - Ensures the server restart completes, and verifies the uploaded addon - is available in the list of addons after the restart. + - Ensures the server restart completes, and verifies the uploaded + addon is available in the list of addons after the restart. - Downloads a private file associated with the addon, verifying its existence and correct download location. - - Cleans up downloaded files and directories after the test to maintain - a clean state. + - Cleans up downloaded files and directories after the test to + maintain a clean state. Notes: - `time.sleep()` is used to allow for a brief pause for the server @@ -863,8 +870,8 @@ def api_artist_user(): - Establishes a server API connection and retrieves the list of available access groups. - Configures a new user with limited permissions (`isAdmin` and - `isManager` set to `False`) and assigns all available access groups - as default and project-specific groups. + `isManager` set to `False`) and assigns all available access + groups as default and project-specific groups. - Creates a new API connection using the artist user's credentials (`username` and `password`) and logs in with it. From c03d682cc81ebae4758266ecbb028688a1980b96 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 27 Nov 2024 09:10:43 +0100 Subject: [PATCH 12/13] remove invalid arg from docstring --- ayon_api/entity_hub.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/ayon_api/entity_hub.py b/ayon_api/entity_hub.py index f3094824c..4cab8f282 100644 --- a/ayon_api/entity_hub.py +++ b/ayon_api/entity_hub.py @@ -3288,8 +3288,6 @@ class TaskEntity(BaseEntity): value is defined based on value of 'entity_id'. entity_hub (EntityHub): Object of entity hub which created object of the entity. - parent_id (Union[str, None]): DEPRECATED please use 'folder_id' - instead. """ _supports_name = True From f654b52f28ddcaa14388ec537c8da113ad27d1d0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 27 Nov 2024 09:11:24 +0100 Subject: [PATCH 13/13] remove unused import --- tests/conftest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 630ac542c..c1b1359ed 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,5 @@ from datetime import datetime, timedelta, timezone import pytest -from xml.dom.minidom import Entity from ayon_api import ( get_project,