diff --git a/tests/conftest.py b/tests/conftest.py index c1b1359ed..bac4f9c03 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,6 +6,7 @@ create_project, update_project, delete_project, + get_events, get_folders, get_products, get_tasks @@ -70,33 +71,76 @@ def project_entity_fixture(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 + hub.fetch_hierarchy_entities() + + folder_ids = { + folder["id"] + for folder in get_folders(project_name_fixture, fields={"id"}) + } + task_ids = { + task["id"] for task in get_tasks( - project_name_fixture, - folder_ids=[folder["id"]] - ): - hub.delete_entity(hub.get_task_by_id(task["id"])) + project_name_fixture, folder_ids=folder_ids, fields={"id"} + ) + } + product_ids = { + product["id"] + for product in get_products( + project_name_fixture, folder_ids=folder_ids, fields={"id"} + ) + } + for product_id in product_ids: + product = hub.get_product_by_id(product_id) + if product is not None: + hub.delete_entity(product) + + for task_id in task_ids: + task = hub.get_task_by_id(task_id) + if task is not None: + hub.delete_entity(task) + + hub.commit_changes() - # 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) + for folder_id in folder_ids: + folder = hub.get_folder_by_id(folder_id) + if folder is not None: + hub.delete_entity(folder) - entity = hub.get_folder_by_id(folder["id"]) - hub.delete_entity(entity) + hub.commit_changes() - hub.commit_changes() + +@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]] + + +@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 class TestEventFilters: project_names = [ - (None), ([]), (["demo_Big_Episodic"]), (["demo_Big_Feature"]), @@ -111,7 +155,6 @@ class TestEventFilters: ] topics = [ - (None), ([]), (["entity.folder.attrib_changed"]), (["entity.task.created", "entity.project.created"]), @@ -262,3 +305,17 @@ class TestUpdateEventData: (0), (10), ] + + +class TestProductData: + names = [ + ("test_name"), + ("test_123"), + ] + + product_types = [ + ("animation"), + ("camera"), + ("render"), + ("workfile"), + ] diff --git a/tests/test_entity_hub.py b/tests/test_entity_hub.py index dcc8d959c..28a3765d0 100644 --- a/tests/test_entity_hub.py +++ b/tests/test_entity_hub.py @@ -9,7 +9,7 @@ import ayon_api from ayon_api.entity_hub import EntityHub, UNKNOWN_VALUE -from .conftest import project_entity_fixture +from .conftest import project_entity_fixture, TestProductData def test_rename_status(project_entity_fixture): @@ -41,15 +41,14 @@ def test_rename_status(project_entity_fixture): @pytest.mark.parametrize( - "folder_name, subfolder_name, folders_count", + "folder_name, folders_count", [ - ("entity_hub_simple_test", "subfolder", 3), + ("entity_hub_simple_test", 3), ] ) def test_simple_operations( project_entity_fixture, folder_name, - subfolder_name, folders_count ): """Test of simple operations with folders - create, move, delete. @@ -506,7 +505,7 @@ def test_create_delete_with_duplicated_names( for folder_number in range(num_of_subfolders): subfolder = hub.add_new_folder( folder_type="Folder", - parent_id=folder1["id"], + parent_id=folder1.id, name=f"{subfolder_name}{folder_number:03}" ) subfolders.append(subfolder) @@ -515,18 +514,18 @@ def test_create_delete_with_duplicated_names( # create and delete folder with same name subfolder = hub.add_new_folder( folder_type="Folder", - parent_id=folder1["id"], + parent_id=folder1.id, name=f"{subfolder_name}{folder_number:03}" ) hub.delete_entity(subfolder) hub.commit_changes() - assert hub.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 hub.get_folder_by_id( project_name, - subfolder["id"]) is not None + subfolder.id) is not None # clean up hub.delete_entity(folder1) @@ -758,142 +757,217 @@ def test_create_delete_with_duplicated_names( # statuses = project_entity_fixture.get_statuses() # pass -test_names = [ - ("test_name"), - ("test_123"), -] - -test_product_types = [ - ("animation"), - ("camera"), - ("render"), - ("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) +@pytest.mark.parametrize("folder_name", TestProductData.names) +@pytest.mark.parametrize("product_name", TestProductData.names) +@pytest.mark.parametrize("product_type", TestProductData.product_types) def test_create_delete_products( project_entity_fixture, folder_name, product_name, product_type ): + """ + Test the creation and deletion of products within a project. + + Verifies: + - the product is created and can be retrieved by its ID + - the product name, type, and folder ID are set correctly + - the product is deleted and cannot be retrieved by its ID + """ 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"])) + for num, folder_type in enumerate(project_entity_fixture["folderTypes"]): + assert list(ayon_api.get_folders( + project_name=project_name, folder_names=[folder_name] + )) == [] + folder = hub.add_new_folder( + name=f"{folder_name}{num:02}", + folder_type=folder_type["name"], + ) - # 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) + hub.commit_changes() - entity = hub.get_folder_by_id(folder["id"]) - hub.delete_entity(entity) + product = hub.add_new_product( + name=product_name, + product_type=product_type, + folder_id=folder.id + ) hub.commit_changes() - folder = hub.add_new_folder( - folder_type=folder_type, - name=folder_name, - ) + 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 - product = hub.add_new_product( - name=product_name, - product_type=product_type, - folder_id=folder["id"] - ) + hub.delete_entity(product) + hub.commit_changes() - hub.commit_changes() + assert hub.get_product_by_id(product.id) is None + assert ayon_api.get_product_by_id(project_name, product.id) is None - 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 +@pytest.mark.usefixtures("clean_project") +@pytest.mark.parametrize("folder_name", TestProductData.names) +@pytest.mark.parametrize("product_name", TestProductData.names) +@pytest.mark.parametrize("product_type", TestProductData.product_types) +def test_create_delete_products_bonus( + project_entity_fixture, + folder_name, + product_name, + product_type +): + """ + Test the creation and deletion of products within a project. + + Verifies: + - the product is created and can be retrieved by its ID + - the product name, type, and folder ID are set correctly + - the product is deleted with a new EntityHub and cannot be retrieved + by its ID + """ + project_name = project_entity_fixture["name"] 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"] + + products = [] + for num, folder_type in enumerate(project_entity_fixture["folderTypes"]): + assert list(ayon_api.get_folders( + project_name=project_name, folder_names=[folder_name] + )) == [] + folder = hub.add_new_folder( + name=f"{folder_name}{num:02}", + folder_type=folder_type["name"], + ) + + hub.commit_changes() + + 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 + + products.append(product) + + # create new entity hub for same project and validate the changes + # are propagated + new_hub = EntityHub(project_name) + + for product in products: + new_product = new_hub.get_product_by_id(product.id) + assert new_product is not None + assert new_product.get_name() == product_name + assert new_product.get_product_type() == product_type + + new_hub.delete_entity(new_product) + new_hub.commit_changes() + + assert ayon_api.get_product_by_id( + project_name, new_product.id, fields={"id"} + ) is None + assert new_hub.get_product_by_id(new_product.id) is None @pytest.mark.usefixtures("clean_project") -@pytest.mark.parametrize("name", test_names) +@pytest.mark.parametrize("name", TestProductData.names) def test_create_delete_folders(project_entity_fixture, name): + """Tests the creation and deletion of folders within a project. + + Verifies: + - A folder can be successfully created for each folder type specified + in the project. + - The created folder exists both locally (in `hub`) and remotely (via + `ayon_api`) after committing changes. + - The folder can be deleted, and its deletion is confirmed locally + and remotely. + + """ 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, - ) + for folder_type in project_entity_fixture["folderTypes"]: + folder = hub.add_new_folder( + folder_type=folder_type["name"], + name=name, + ) - hub.commit_changes() + hub.commit_changes() - assert ayon_api.get_folders( - project_name, - folder_names=[name], - folder_types=folder_types[0:1], - folder_ids=[folder["id"]] - ) + assert hub.get_folder_by_id(folder.id) + assert ayon_api.get_folder_by_id( + project_name, 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"])) + hub.delete_entity(folder) + hub.commit_changes() - entity = hub.get_folder_by_id(folder["id"]) + assert hub.get_folder_by_id(folder.id) is None + assert ayon_api.get_folder_by_id( + project_name, folder.id + ) is None - for id in entity.children_ids: - hub.delete_entity(hub.get_entity_by_id(id)) - hub.delete_entity(entity) +@pytest.mark.usefixtures("clean_project") +@pytest.mark.parametrize("name", TestProductData.names) +def test_create_delete_folders_bonus(project_entity_fixture, name): + """Tests the creation, persistence, and deletion of multiple folders within + a project. + + Verifies: + - After creation, folders are accessible locally (via `hub`) and + remotely (via `ayon_api`). + - Folder persistence is confirmed using a new `EntityHub` instance to + simulate a fresh session. + - Folders can be deleted, and their deletion is reflected both locally + and remotely. + + """ + project_name = project_entity_fixture["name"] + + hub = EntityHub(project_name) + + folders = [] + for num, folder_type in enumerate(project_entity_fixture["folderTypes"]): + folder = hub.add_new_folder( + folder_type=folder_type["name"], + name=f"{name}{num:02}", + ) hub.commit_changes() - # new folder - folder = hub.add_new_folder( - folder_type=folder_types[1], - name=name, - ) + assert hub.get_folder_by_id(folder.id) + assert ayon_api.get_folder_by_id( + project_name, folder.id + ) + folders.append(folder) - hub.commit_changes() + new_hub = EntityHub(project_name) - assert ayon_api.get_folders( - project_name, - folder_names=[name], - folder_types=folder_types[1:2], - folder_ids=[folder["id"]] - ) + for folder in folders: + assert new_hub.get_folder_by_id(folder.id) + assert ayon_api.get_folder_by_id( + project_name, folder.id + ) + + new_hub.delete_entity(folder) + new_hub.commit_changes() + + assert new_hub.get_folder_by_id(folder.id) is None + assert ayon_api.get_folder_by_id( + project_name, folder.id + ) is None test_version_numbers = [ @@ -905,11 +979,23 @@ def test_create_delete_folders(project_entity_fixture, name): @pytest.mark.usefixtures("clean_project") @pytest.mark.parametrize("version_numbers", test_version_numbers) def test_create_delete_versions(project_entity_fixture, version_numbers): + """Tests the creation and deletion of versions within a product hierarchy. + + Verifies: + - A folder and product can be created as a prerequisite hierarchy. + - Versions can be added to a product, with their IDs correctly + reflected in the product's children. + - Versions exist in the local `hub` after creation. + - Versions can be successfully deleted, and their removal is confirmed + both in the `hub` and in the product's children. + + """ + project_name = project_entity_fixture["name"] # prepare hierarchy folder_types = [ type["name"] for type in project_entity_fixture["folderTypes"] ] - hub = EntityHub(project_entity_fixture["name"]) + hub = EntityHub(project_name) folder = hub.add_new_folder( folder_type=folder_types[0], @@ -919,7 +1005,7 @@ def test_create_delete_versions(project_entity_fixture, version_numbers): product = hub.add_new_product( name="test_product", product_type="animation", - folder_id=folder["id"] + folder_id=folder.id ) assert product.get_children_ids() == set() @@ -930,7 +1016,7 @@ def test_create_delete_versions(project_entity_fixture, version_numbers): versions.append( hub.add_new_version( version, - product["id"] + product.id ) ) @@ -940,16 +1026,15 @@ def test_create_delete_versions(project_entity_fixture, version_numbers): assert len(versions) == len(res) for version in versions: - assert version - assert hub.get_version_by_id(version["id"]) - assert version["id"] in res + 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.delete_entity(version) hub.commit_changes() - assert hub.get_version_by_id(version["id"]) is None - # assert + assert hub.get_version_by_id(version.id) is None + assert ayon_api.get_version_by_id(project_name, version.id) is None test_invalid_version_number = [ @@ -1131,8 +1216,8 @@ def test_set_tag_on_version(project_entity_fixture, tags): assert tag in version.get_tags() -def test_set_invalid_tag_on_version(): - raise NotImplementedError() +# def test_set_invalid_tag_on_version(): +# raise NotImplementedError() test_statuses = [ @@ -1147,34 +1232,58 @@ def test_set_invalid_tag_on_version(): ("done_outline"), ] +test_color = [ + ("#ff0000"), + ("#00ff00"), + ("#0000ff"), +] + @pytest.mark.parametrize("status_name", test_statuses) @pytest.mark.parametrize("icon_name", test_icon) +@pytest.mark.parametrize("color", test_color) def test_status_definition_on_project( project_entity_fixture, status_name, - icon_name + icon_name, + color ): hub = EntityHub(project_entity_fixture["name"]) + statuses = hub.project_entity.get_statuses() - project = hub.project_entity - project.get_statuses().create( - status_name, - icon_name + # create status + statuses.create( + name=status_name, + icon=icon_name, + color=color ) - 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) + assert status_name == statuses.get(status_name).get_name() + assert icon_name == statuses.get(status_name).get_icon() + assert color == statuses.get(status_name).get_color() - # project.set_status() - # project_status_obj = hub.project_entity.get_statuses() - # project_status_obj.set_state() - # print(type(project_status_obj), project_status_obj) + # delete status + statuses.remove_by_name(status_name) + assert statuses.get(status_name) is None -# definice status na projects -# zmena statusu a tagu na entitach - verzich -# vytvareni a mazani produktu a verzi +def test_status_definition_on_project_with_invalid_values( + project_entity_fixture +): + hub = EntityHub(project_entity_fixture["name"]) + statuses = hub.project_entity.get_statuses() + # invalid color + with pytest.raises(ValueError): + statuses.create( + name="status2", + icon="arrow_forward", + color="invalid_color" + ) + # invalid name + with pytest.raises(ValueError): + statuses.create( + name="&_invalid_name", + icon="invalid_icon", + color="invalid_color" + ) diff --git a/tests/test_get_events.py b/tests/test_get_events.py index dd315fe97..27451fcda 100644 --- a/tests/test_get_events.py +++ b/tests/test_get_events.py @@ -63,7 +63,7 @@ def test_get_events_all_filter_combinations( topics=topics, event_ids=event_ids, project_names=project_names, - states=states, + statuses=states, users=users, include_logs=include_logs, has_children=has_children, @@ -97,7 +97,7 @@ def test_get_events_all_filter_combinations( list(get_events( topics=[topic], project_names=project_names, - states=states, + statuses=states, users=users, include_logs=include_logs, has_children=has_children, @@ -111,7 +111,7 @@ def test_get_events_all_filter_combinations( list(get_events( topics=topics, project_names=[project_name], - states=states, + statuses=states, users=users, include_logs=include_logs, has_children=has_children, @@ -125,7 +125,7 @@ def test_get_events_all_filter_combinations( list(get_events( topics=topics, project_names=project_names, - states=[state], + statuses=[state], users=users, include_logs=include_logs, has_children=has_children, @@ -139,7 +139,7 @@ def test_get_events_all_filter_combinations( list(get_events( topics=topics, project_names=project_names, - states=states, + statuses=states, users=[user], include_logs=include_logs, has_children=has_children, diff --git a/tests/test_server.py b/tests/test_server.py index 8ebb7d3e1..9f2ae2abb 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -7,9 +7,12 @@ from datetime import datetime, timedelta, timezone import os -import pytest +import sys +import subprocess import time +import pytest + from ayon_api import ( close_connection, create_folder, @@ -180,7 +183,7 @@ def test_get_events_project_name(project_names): assert item.get("project") in project_names # test if the legths are equal - assert project_names is None or len(res) == sum(len( + assert len(res) == sum(len( list(get_events( project_names=[project_name] )) or [] @@ -292,12 +295,12 @@ def test_get_events_timestamps(newer_than, older_than): for item in res: assert (newer_than is None) or ( - datetime.fromisoformat(item.get("createdAt") - > datetime.fromisoformat(newer_than)) + datetime.fromisoformat(item.get("createdAt")) + > datetime.fromisoformat(newer_than) ) assert (older_than is None) or ( - datetime.fromisoformat(item.get("createdAt") - < datetime.fromisoformat(older_than)) + datetime.fromisoformat(item.get("createdAt")) + < datetime.fromisoformat(older_than) ) @@ -340,7 +343,7 @@ def test_get_events_invalid_data( res = list(get_events( topics=topics, project_names=project_names, - states=states, + statuses=states, users=users, newer_than=newer_than )) @@ -820,25 +823,32 @@ def test_addon_methods(): addon_version = "1.0.0" download_path = "tests/resources/tmp_downloads" private_file_path = os.path.join(download_path, "ayon-symbol.png") + for addon in get_addons_info()["addons"]: + if addon["name"] == addon_name and addon["version"] == addon_version: + delete_addon_version(addon_name, addon_version) + break - delete_addon_version(addon_name, addon_version) assert all( addon_name != addon["name"] for addon in get_addons_info()["addons"] ) + subprocess.run([sys.executable, "tests/resources/addon/create_package.py"]) try: _ = upload_addon_zip("tests/resources/addon/package/tests-1.0.0.zip") trigger_server_restart() # need to wait at least 0.1 sec. to restart server + last_check = time.time() time.sleep(0.5) while True: try: addons = get_addons_info()["addons"] break except exceptions.ServerError as exc: - assert "Connection timed out" in str(exc) + if time.time() - last_check > 60: + raise AssertionError(f"Server restart failed {exc}") + time.sleep(0.5) assert any(addon_name == addon["name"] for addon in addons)