From f977cba564c96fb6a6ecf99c009d75643688dc27 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 14 Jul 2021 14:58:36 +0200 Subject: [PATCH] #1784 - added wrapper class Added documentation --- tests/lib/README.md | 43 ++- tests/lib/testing_wrapper.py | 105 ++++++ .../sync_server/test_site_operations.py | 310 ++++++------------ 3 files changed, 256 insertions(+), 202 deletions(-) create mode 100644 tests/lib/testing_wrapper.py diff --git a/tests/lib/README.md b/tests/lib/README.md index 043dd3b8e9..56ff9749a2 100644 --- a/tests/lib/README.md +++ b/tests/lib/README.md @@ -1 +1,42 @@ -Folder for libs and tooling for automatic testing. \ No newline at end of file +Automatic testing +----------------- +Folder for libs and tooling for automatic testing. + +- db_handler.py - class for preparation of test DB + - dumps DB(s) to BSON (mongodump) + - loads dump(s) to new DB (mongorestore) + - loads sql file(s) to DB (mongoimport) + - deletes test DB + +- file_handler.py - class to download test data from GDrive + - downloads data from (list) of files from GDrive + - checks md5 if file ok + - unzips if zip + +- testing_wrapper.py - base class to use for testing + - all env var necessary for running (OPENPYPE_MONGO ...) + - implements reusable fixtures to: + - load test data (uses `file_handler`) + - prepare DB (uses `db_handler`) + - modify temporarily env vars for testing + + Should be used as a skeleton to create new test cases. + + +Test data +--------- +Each class implementing `TestCase` can provide test file(s) by adding them to +TEST_FILES ('GDRIVE_FILE_ID', 'ACTUAL_FILE_NAME', 'MD5HASH') + +GDRIVE_FILE_ID can be pulled from shareable link from Google Drive app. + +Currently it is expected that test file will be zip file with structure: +- expected - expected files (not implemented yet) +- input + - data - test data (workfiles, images etc) + - dumps - folder for BSOn dumps from (`mongodump`) + - env_vars + env_vars.json - dictionary with environment variables {key:value} + + - sql - sql files to load with `mongoimport` (human readable) + \ No newline at end of file diff --git a/tests/lib/testing_wrapper.py b/tests/lib/testing_wrapper.py new file mode 100644 index 0000000000..fd50abd18e --- /dev/null +++ b/tests/lib/testing_wrapper.py @@ -0,0 +1,105 @@ +import os +import sys +import six +import json +import pytest +import tempfile +import shutil +from bson.objectid import ObjectId + +from tests.lib.db_handler import DBHandler +from tests.lib.file_handler import RemoteFileHandler + + +class TestCase(): + + TEST_OPENPYPE_MONGO = "mongodb://localhost:27017" + TEST_DB_NAME = "test_db" + TEST_PROJECT_NAME = "test_project" + TEST_OPENPYPE_NAME = "test_openpype" + + REPRESENTATION_ID = "60e578d0c987036c6a7b741d" + + TEST_FILES = [ + ("1eCwPljuJeOI8A3aisfOIBKKjcmIycTEt", "test_site_operations.zip", "") + ] + + @pytest.fixture(scope='session') + def monkeypatch_session(self): + """Monkeypatch couldn't be used with module or session fixtures.""" + from _pytest.monkeypatch import MonkeyPatch + m = MonkeyPatch() + yield m + m.undo() + + + @pytest.fixture(scope="module") + def download_test_data(self): + tmpdir = tempfile.mkdtemp() + for test_file in self.TEST_FILES: + file_id, file_name, md5 = test_file + + f_name, ext = os.path.splitext(file_name) + + RemoteFileHandler.download_file_from_google_drive(file_id, + str(tmpdir), + file_name) + + if ext.lstrip('.') in RemoteFileHandler.IMPLEMENTED_ZIP_FORMATS: + RemoteFileHandler.unzip(os.path.join(tmpdir, file_name)) + + yield tmpdir + shutil.rmtree(tmpdir) + + + @pytest.fixture(scope="module") + def env_var(self, monkeypatch_session, download_test_data): + """Sets temporary env vars from json file.""" + env_url = os.path.join(download_test_data, "input", + "env_vars", "env_var.json") + if not os.path.exists(env_url): + raise ValueError("Env variable file {} doesn't exist".format(env_url)) + + env_dict = {} + try: + with open(env_url) as json_file: + env_dict = json.load(json_file) + except ValueError: + print("{} doesn't contain valid JSON") + six.reraise(*sys.exc_info()) + + for key, value in env_dict.items(): + all_vars = globals() + all_vars.update(vars(TestCase)) # TODO check + value = value.format(**all_vars) + print("Setting {}:{}".format(key, value)) + monkeypatch_session.setenv(key, value) + + @pytest.fixture(scope="module") + def db_setup(self, download_test_data, env_var, monkeypatch_session): + """Restore prepared MongoDB dumps into selected DB.""" + backup_dir = os.path.join(download_test_data, "input", "dumps") + + uri = os.environ.get("OPENPYPE_MONGO") or "mongodb://localhost:27017" + db_handler = DBHandler(uri) + db_handler.setup_from_dump(self.TEST_DB_NAME, backup_dir, True, + db_name_out=self.TEST_DB_NAME) + + db_handler.setup_from_dump("openpype", backup_dir, True, + db_name_out=self.TEST_OPENPYPE_NAME) + + yield db_handler + + db_handler.teardown(self.TEST_DB_NAME) + db_handler.teardown(self.TEST_OPENPYPE_NAME) + + + @pytest.fixture(scope="module") + def db(self, db_setup): + """Provide test database connection. + + Database prepared from dumps with 'db_setup' fixture. + """ + from avalon.api import AvalonMongoDB + db = AvalonMongoDB() + yield db diff --git a/tests/unit/openpype/modules/sync_server/test_site_operations.py b/tests/unit/openpype/modules/sync_server/test_site_operations.py index 280e4daafe..9c27da21c0 100644 --- a/tests/unit/openpype/modules/sync_server/test_site_operations.py +++ b/tests/unit/openpype/modules/sync_server/test_site_operations.py @@ -11,213 +11,121 @@ removes temporary folder removes temporary databases (?) """ -import os -import sys -import six -import json import pytest -import tempfile -import shutil + +from tests.lib.testing_wrapper import TestCase from bson.objectid import ObjectId -from tests.lib.db_handler import DBHandler -from tests.lib.file_handler import RemoteFileHandler -TEST_OPENPYPE_MONGO = "mongodb://localhost:27017" -TEST_DB_NAME = "test_db" -TEST_PROJECT_NAME = "test_project" -TEST_OPENPYPE_NAME = "test_openpype" -REPRESENTATION_ID = "60e578d0c987036c6a7b741d" +class TestSiteOperation(TestCase): -TEST_FILES = [ - ("1eCwPljuJeOI8A3aisfOIBKKjcmIycTEt", "test_site_operations.zip", "") -] - - -@pytest.fixture(scope='session') -def monkeypatch_session(): - """Monkeypatch couldn't be used with module or session fixtures.""" - from _pytest.monkeypatch import MonkeyPatch - m = MonkeyPatch() - yield m - m.undo() - - -@pytest.fixture(scope="module") -def download_test_data(): - tmpdir = tempfile.mkdtemp() - for test_file in TEST_FILES: - file_id, file_name, md5 = test_file - - f_name, ext = os.path.splitext(file_name) - - RemoteFileHandler.download_file_from_google_drive(file_id, - str(tmpdir), - file_name) - - if ext.lstrip('.') in RemoteFileHandler.IMPLEMENTED_ZIP_FORMATS: - RemoteFileHandler.unzip(os.path.join(tmpdir, file_name)) - - yield tmpdir - shutil.rmtree(tmpdir) - - -@pytest.fixture(scope="module") -def env_var(monkeypatch_session, download_test_data): - """Sets temporary env vars from json file.""" - env_url = os.path.join(download_test_data, "input", - "env_vars", "env_var.json") - if not os.path.exists(env_url): - raise ValueError("Env variable file {} doesn't exist".format(env_url)) - - env_dict = {} - try: - with open(env_url) as json_file: - env_dict = json.load(json_file) - except ValueError: - print("{} doesn't contain valid JSON") - six.reraise(*sys.exc_info()) - - for key, value in env_dict.items(): - value = value.format(**globals()) - print("Setting {}:{}".format(key, value)) - monkeypatch_session.setenv(key, value) - - -@pytest.fixture(scope="module") -def db_setup(download_test_data, env_var, monkeypatch_session): - """Restore prepared MongoDB dumps into selected DB.""" - backup_dir = os.path.join(download_test_data, "input", "dumps") - - uri = os.environ.get("OPENPYPE_MONGO") or "mongodb://localhost:27017" - db_handler = DBHandler(uri) - db_handler.setup_from_dump(TEST_DB_NAME, backup_dir, True, - db_name_out=TEST_DB_NAME) - - db_handler.setup_from_dump("openpype", backup_dir, True, - db_name_out=TEST_OPENPYPE_NAME) - - yield db_handler - - db_handler.teardown(TEST_DB_NAME) - db_handler.teardown(TEST_OPENPYPE_NAME) - - -@pytest.fixture(scope="module") -def db(db_setup): - """Provide test database connection. - - Database prepared from dumps with 'db_setup' fixture. - """ - from avalon.api import AvalonMongoDB - db = AvalonMongoDB() - yield db - - -@pytest.fixture(scope="module") -def setup_sync_server_module(db): - """Get sync_server_module from ModulesManager""" - from openpype.modules import ModulesManager - - manager = ModulesManager() - sync_server = manager.modules_by_name["sync_server"] - yield sync_server - - -@pytest.mark.usefixtures("db") -def test_project_created(db): - assert ['test_project'] == db.database.collection_names(False) - - -@pytest.mark.usefixtures("db") -def test_objects_imported(db): - count_obj = len(list(db.database[TEST_PROJECT_NAME].find({}))) - assert 15 == count_obj - - -@pytest.mark.usefixtures("setup_sync_server_module") -def test_add_site(db, setup_sync_server_module): - """Adds 'test_site', checks that added, checks that doesn't duplicate.""" - query = { - "_id": ObjectId(REPRESENTATION_ID) - } - - ret = db.database[TEST_PROJECT_NAME].find(query) - - assert 1 == len(list(ret)), \ - "Single {} must be in DB".format(REPRESENTATION_ID) - - setup_sync_server_module.add_site(TEST_PROJECT_NAME, REPRESENTATION_ID, - site_name='test_site') - - ret = list(db.database[TEST_PROJECT_NAME].find(query)) - - assert 1 == len(ret), \ - "Single {} must be in DB".format(REPRESENTATION_ID) - - ret = ret.pop() - site_names = [site["name"] for site in ret["files"][0]["sites"]] - assert 'test_site' in site_names, "Site name wasn't added" - - -@pytest.mark.usefixtures("setup_sync_server_module") -def test_add_site_again(db, setup_sync_server_module): - """Depends on test_add_site, must throw exception.""" - with pytest.raises(ValueError): - setup_sync_server_module.add_site(TEST_PROJECT_NAME, REPRESENTATION_ID, + @pytest.fixture(scope="module") + def setup_sync_server_module(self, db): + """Get sync_server_module from ModulesManager""" + from openpype.modules import ModulesManager + + manager = ModulesManager() + sync_server = manager.modules_by_name["sync_server"] + yield sync_server + + + @pytest.mark.usefixtures("db") + def test_project_created(self, db): + assert ['test_project'] == db.database.collection_names(False) + + + @pytest.mark.usefixtures("db") + def test_objects_imported(self, db): + count_obj = len(list(db.database[self.TEST_PROJECT_NAME].find({}))) + assert 15 == count_obj + + + @pytest.mark.usefixtures("setup_sync_server_module") + def test_add_site(self, db, setup_sync_server_module): + """Adds 'test_site', checks that added, checks that doesn't duplicate.""" + query = { + "_id": ObjectId(self.REPRESENTATION_ID) + } + + ret = db.database[self.TEST_PROJECT_NAME].find(query) + + assert 1 == len(list(ret)), \ + "Single {} must be in DB".format(self.REPRESENTATION_ID) + + setup_sync_server_module.add_site(self.TEST_PROJECT_NAME, self.REPRESENTATION_ID, site_name='test_site') - - -@pytest.mark.usefixtures("setup_sync_server_module") -def test_add_site_again_force(db, setup_sync_server_module): - """Depends on test_add_site, must not throw exception.""" - setup_sync_server_module.add_site(TEST_PROJECT_NAME, REPRESENTATION_ID, - site_name='test_site', force=True) - - query = { - "_id": ObjectId(REPRESENTATION_ID) - } - - ret = list(db.database[TEST_PROJECT_NAME].find(query)) - - assert 1 == len(ret), \ - "Single {} must be in DB".format(REPRESENTATION_ID) - - -@pytest.mark.usefixtures("setup_sync_server_module") -def test_remove_site(db, setup_sync_server_module): - """Depends on test_add_site, must remove 'test_site'.""" - setup_sync_server_module.remove_site(TEST_PROJECT_NAME, REPRESENTATION_ID, - site_name='test_site') - - query = { - "_id": ObjectId(REPRESENTATION_ID) - } - - ret = list(db.database[TEST_PROJECT_NAME].find(query)) - - assert 1 == len(ret), \ - "Single {} must be in DB".format(REPRESENTATION_ID) - - ret = ret.pop() - site_names = [site["name"] for site in ret["files"][0]["sites"]] - - assert 'test_site' not in site_names, "Site name wasn't removed" - - -@pytest.mark.usefixtures("setup_sync_server_module") -def test_remove_site_again(db, setup_sync_server_module): - """Depends on test_add_site, must trow exception""" - with pytest.raises(ValueError): - setup_sync_server_module.remove_site(TEST_PROJECT_NAME, - REPRESENTATION_ID, + + ret = list(db.database[self.TEST_PROJECT_NAME].find(query)) + + assert 1 == len(ret), \ + "Single {} must be in DB".format(self.REPRESENTATION_ID) + + ret = ret.pop() + site_names = [site["name"] for site in ret["files"][0]["sites"]] + assert 'test_site' in site_names, "Site name wasn't added" + + + @pytest.mark.usefixtures("setup_sync_server_module") + def test_add_site_again(self, db, setup_sync_server_module): + """Depends on test_add_site, must throw exception.""" + with pytest.raises(ValueError): + setup_sync_server_module.add_site(self.TEST_PROJECT_NAME, self.REPRESENTATION_ID, + site_name='test_site') + + + @pytest.mark.usefixtures("setup_sync_server_module") + def test_add_site_again_force(self, db, setup_sync_server_module): + """Depends on test_add_site, must not throw exception.""" + setup_sync_server_module.add_site(self.TEST_PROJECT_NAME, self.REPRESENTATION_ID, + site_name='test_site', force=True) + + query = { + "_id": ObjectId(self.REPRESENTATION_ID) + } + + ret = list(db.database[self.TEST_PROJECT_NAME].find(query)) + + assert 1 == len(ret), \ + "Single {} must be in DB".format(self.REPRESENTATION_ID) + + + @pytest.mark.usefixtures("setup_sync_server_module") + def test_remove_site(self, db, setup_sync_server_module): + """Depends on test_add_site, must remove 'test_site'.""" + setup_sync_server_module.remove_site(self.TEST_PROJECT_NAME, self.REPRESENTATION_ID, site_name='test_site') + + query = { + "_id": ObjectId(self.REPRESENTATION_ID) + } + + ret = list(db.database[self.TEST_PROJECT_NAME].find(query)) + + assert 1 == len(ret), \ + "Single {} must be in DB".format(self.REPRESENTATION_ID) + + ret = ret.pop() + site_names = [site["name"] for site in ret["files"][0]["sites"]] + + assert 'test_site' not in site_names, "Site name wasn't removed" + + + @pytest.mark.usefixtures("setup_sync_server_module") + def test_remove_site_again(self, db, setup_sync_server_module): + """Depends on test_add_site, must trow exception""" + with pytest.raises(ValueError): + setup_sync_server_module.remove_site(self.TEST_PROJECT_NAME, + self.REPRESENTATION_ID, + site_name='test_site') + + query = { + "_id": ObjectId(self.REPRESENTATION_ID) + } + + ret = list(db.database[self.TEST_PROJECT_NAME].find(query)) + + assert 1 == len(ret), \ + "Single {} must be in DB".format(self.REPRESENTATION_ID) - query = { - "_id": ObjectId(REPRESENTATION_ID) - } - ret = list(db.database[TEST_PROJECT_NAME].find(query)) - - assert 1 == len(ret), \ - "Single {} must be in DB".format(REPRESENTATION_ID) +test_case = TestSiteOperation() \ No newline at end of file