diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 6b38c5e7..6fa27331 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -6,6 +6,7 @@ on: - main tags: - "v*" + pull_request: jobs: @@ -16,24 +17,16 @@ jobs: matrix: python-version: ["2.7", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11"] runs-on: ubuntu-latest - container: python:${{ matrix.python-version }} - services: - ftrack: - image: ftrackdocker/test-server:latest - credentials: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - ports: - - 80:80 steps: - uses: actions/checkout@v3 - name: Run pytest run: | python setup.py test env: - FTRACK_SERVER: http://ftrack:80 + FTRACK_SERVER: ${{ secrets.FTRACK_SERVER }} FTRACK_API_USER: ${{ secrets.FTRACK_API_USER }} FTRACK_API_KEY: ${{ secrets.FTRACK_API_KEY_UNITTEST }} + build: runs-on: ubuntu-latest name: Build package distribution diff --git a/source/ftrack_api/entity/user.py b/source/ftrack_api/entity/user.py index 49318f86..c0da4569 100644 --- a/source/ftrack_api/entity/user.py +++ b/source/ftrack_api/entity/user.py @@ -46,7 +46,7 @@ def start_timer(self, context=None, comment='', name=None, force=False): try: self.session.commit() except ftrack_api.exception.ServerError as error: - if 'IntegrityError' in str(error): + if 'DuplicateEntryError' in str(error): raise ftrack_api.exception.NotUniqueError( ('Failed to start a timelog for user with id: {0}, it is ' 'likely that a timer is already running. Either use ' diff --git a/test/fixture/media/image-resized-10.png b/test/fixture/media/image-resized-10.png index da6ec772..c7774076 100644 Binary files a/test/fixture/media/image-resized-10.png and b/test/fixture/media/image-resized-10.png differ diff --git a/test/unit/conftest.py b/test/unit/conftest.py index 24f644e3..e2130bec 100644 --- a/test/unit/conftest.py +++ b/test/unit/conftest.py @@ -384,15 +384,64 @@ def cleanup(): @pytest.fixture() -def new_asset_version(request, session): +def new_asset_version_with_component(request, session, new_task, unique_name): + '''Return a new asset version with one component attached.''' + asset_parent = new_task['parent'] + asset_type = session.query('AssetType').first() + + asset = session.create('Asset', { + 'name': unique_name, + 'type': asset_type, + 'parent': asset_parent + }) + asset_version = session.create('AssetVersion', { + 'asset_id': asset['id'], + 'asset': asset, + 'task': new_task + }) + component = session.create('Component', { + 'name': unique_name, + 'version_id': asset_version['id'], + }) + session.commit() + + def cleanup(): + '''Remove created entities.''' + session.delete(component) + session.delete(asset_version) + session.delete(asset) + session.commit() + + request.addfinalizer(cleanup) + + return asset_version + + +@pytest.fixture() +def new_asset_version(request, session, new_task, unique_name): '''Return a new asset version.''' + asset_parent = new_task['parent'] + asset_type = session.query('AssetType').first() + + asset = session.create('Asset', { + 'name': unique_name, + 'type': asset_type, + 'parent': asset_parent + }) asset_version = session.create('AssetVersion', { - 'asset_id': 'dd9a7e2e-c5eb-11e1-9885-f23c91df25eb' + 'asset_id': asset['id'], + 'asset': asset, + 'task': new_task }) session.commit() - # Do not cleanup the version as that will sometimes result in a deadlock - # database error. + def cleanup(): + '''Remove created entities.''' + session.delete(asset_version) + session.delete(asset) + session.commit() + + request.addfinalizer(cleanup) return asset_version diff --git a/test/unit/entity/test_component.py b/test/unit/entity/test_component.py index 347c74a5..acb74ad0 100644 --- a/test/unit/entity/test_component.py +++ b/test/unit/entity/test_component.py @@ -55,11 +55,11 @@ def image_path(): return image_path -def test_create_task_thumbnail(task, image_path): +def test_create_task_thumbnail(new_task, image_path): '''Successfully create thumbnail component and set as task thumbnail.''' - component = task.create_thumbnail(image_path) + component = new_task.create_thumbnail(image_path) component.session.commit() - assert component['id'] == task['thumbnail_id'] + assert component['id'] == new_task['thumbnail_id'] def test_create_thumbnail_with_data(task, image_path, unique_name): diff --git a/test/unit/entity/test_location.py b/test/unit/entity/test_location.py index b943b2ee..a9820a69 100644 --- a/test/unit/entity/test_location.py +++ b/test/unit/entity/test_location.py @@ -53,10 +53,8 @@ def decode(self, resource_identifier, context=None): def new_location(request, session, unique_name, temporary_directory): '''Return new managed location.''' - from builtins import str - - location = session.create(str('Location'), { - str('name'): str('test-location-{}'.format(unique_name)) + location = session.create('Location', { + 'name': 'test-location-{}'.format(unique_name) }) @@ -72,8 +70,15 @@ def new_location(request, session, unique_name, temporary_directory): def cleanup(): '''Remove created entity.''' + + location_components = session.query( + 'ComponentLocation where location_id is {0}'.format( + location['id'] + ) + ).all() + # First auto-remove all components in location. - for location_component in location['location_components']: + for location_component in location_components: session.delete(location_component) # At present, need this intermediate commit otherwise server errors @@ -108,8 +113,14 @@ def new_unmanaged_location(request, session, unique_name): def cleanup(): '''Remove created entity.''' + location_components = session.query( + 'ComponentLocation where location_id is {0}'.format( + location['id'] + ) + ).all() + # First auto-remove all components in location. - for location_component in location['location_components']: + for location_component in location_components: session.delete(location_component) # At present, need this intermediate commit otherwise server errors @@ -547,4 +558,5 @@ def test_transfer_component_from_server( assert ( multi_location.get_component_availability(server_image_component) == 100.0 - ) \ No newline at end of file + ) + diff --git a/test/unit/event/event_hub_server_heartbeat.py b/test/unit/event/event_hub_server_heartbeat.py index 001b6842..446b6ed0 100644 --- a/test/unit/event/event_hub_server_heartbeat.py +++ b/test/unit/event/event_hub_server_heartbeat.py @@ -10,7 +10,6 @@ from ftrack_api.event.base import Event -TOPIC = 'test_event_hub_server_heartbeat' RECEIVED = [] @@ -25,6 +24,7 @@ def main(arguments=None): '''Publish and receive heartbeat test.''' parser = argparse.ArgumentParser() parser.add_argument('mode', choices=['publish', 'subscribe']) + parser.add_argument('topic') namespace = parser.parse_args(arguments) logging.basicConfig(level=logging.INFO) @@ -56,7 +56,7 @@ def main(arguments=None): for counter in range(1, message_count + 1): session.event_hub.publish( - Event(topic=TOPIC, data=dict(counter=counter)) + Event(topic=namespace.topic, data=dict(counter=counter)) ) print('Sent message {0}'.format(counter)) @@ -64,7 +64,7 @@ def main(arguments=None): time.sleep(sleep_time_per_message) elif namespace.mode == 'subscribe': - session.event_hub.subscribe('topic={0}'.format(TOPIC), callback) + session.event_hub.subscribe('topic={0}'.format(namespace.topic), callback) session.event_hub.wait( duration=( ((message_count - 1) * sleep_time_per_message) + 15 diff --git a/test/unit/event/test_hub.py b/test/unit/event/test_hub.py index 5cafb6ee..df0fecef 100644 --- a/test/unit/event/test_hub.py +++ b/test/unit/event/test_hub.py @@ -9,6 +9,7 @@ import sys import requests import logging +import uuid import pytest from flaky import flaky @@ -676,14 +677,15 @@ def replier(event): '''Replier.''' return 'Replied' - event_hub.subscribe('topic=test', replier) + topic_name = 'test_{0}'.format(uuid.uuid4()) + event_hub.subscribe('topic={0}'.format(topic_name), replier) called = {'callback': None} def on_reply(event): called['callback'] = event['data'] - event_hub.publish(Event(topic='test'), on_reply=on_reply) + event_hub.publish(Event(topic=topic_name), on_reply=on_reply) event_hub.wait(2) assert called['callback'] == 'Replied' @@ -700,15 +702,16 @@ def replier_two(event): '''Replier.''' return 'Two' - event_hub.subscribe('topic=test', replier_one) - event_hub.subscribe('topic=test', replier_two) + topic_name = 'test_{0}'.format(uuid.uuid4()) + event_hub.subscribe('topic={0}'.format(topic_name), replier_one) + event_hub.subscribe('topic={0}'.format(topic_name), replier_two) called = {'callback': []} def on_reply(event): called['callback'].append(event['data']) - event_hub.publish(Event(topic='test'), on_reply=on_reply) + event_hub.publish(Event(topic=topic_name), on_reply=on_reply) event_hub.wait(2) assert sorted(called['callback']) == ['One', 'Two'] @@ -719,15 +722,17 @@ def test_server_heartbeat_response(): test_script = os.path.join( os.path.dirname(__file__), 'event_hub_server_heartbeat.py' ) + # set the topic name to something unique + topic = 'test_event_hub_server_heartbeat_{0}'.format(uuid.uuid4()) # Start subscriber that will listen for all three messages. - subscriber = subprocess.Popen([sys.executable, test_script, 'subscribe']) + subscriber = subprocess.Popen([sys.executable, test_script, 'subscribe', topic]) # Give subscriber time to connect to server. time.sleep(10) # Start publisher to publish three messages. - publisher = subprocess.Popen([sys.executable, test_script, 'publish']) + publisher = subprocess.Popen([sys.executable, test_script, 'publish', topic]) publisher.wait() subscriber.wait() diff --git a/test/unit/test_session.py b/test/unit/test_session.py index 4e6d2661..6ea5d9f0 100644 --- a/test/unit/test_session.py +++ b/test/unit/test_session.py @@ -185,6 +185,7 @@ def test_ensure_entity_with_non_string_data_types(session, mocker): datetime = arrow.get() task = session.query('Task').first() + # if session.api_user contained '@', we'd need to work some more on queries. user = session.query( 'User where username is {}'.format(session.api_user) ).first() @@ -1328,7 +1329,7 @@ def get_versions(sessions): ) -def test_query_nested(session): +def test_query_nested(session, new_asset_version_with_component): '''Query components nested and update a value and query again. This test will query components via 2 relations, then update the @@ -1343,19 +1344,13 @@ def test_query_nested(session): query = ( 'select versions.components.name from Asset where id is ' - '"12939d0c-6766-11e1-8104-f23c91df25eb"' + '{0}'.format(new_asset_version_with_component['asset_id']) ) def get_version(session): '''Return the test version from *session*.''' asset = session.query(query).first() - asset_version = None - for version in asset['versions']: - if version['version'] == 8: - asset_version = version - break - - return asset_version + return asset['versions'][0] asset_version = get_version(session_one) asset_version2 = get_version(session_two) @@ -1400,7 +1395,7 @@ def test_merge_iterations(session, mocker, project): pytest.param(lambda component, asset_version, asset: asset['versions'], id='from_asset') ] ) -def test_query_nested2(session, get_versions): +def test_query_nested2(session, new_asset_version_with_component, get_versions): '''Query version.asset.versions from component and then add new version. This test will query versions via multiple relations and ensure a new @@ -1413,15 +1408,10 @@ def test_query_nested2(session, get_versions): auto_connect_event_hub=False ) - # Get a random component that is linked to a version and asset. - component_id = session_two.query( - 'FileComponent where version.asset_id != None' - ).first()['id'] - + # Get a component that is linked to a version and asset. + component_id = new_asset_version_with_component['components'][0]['id'] query = ( - 'select version.asset.versions from Component where id is "{}"'.format( - component_id - ) + 'select version.asset.versions from Component where id is "{}"'.format(component_id) ) component = session_one.query(query).one()