From 82a51a8773abdbf2ea89561fb2b1e60ba931b955 Mon Sep 17 00:00:00 2001 From: Chris Joosse Date: Mon, 31 Aug 2020 12:17:09 -0700 Subject: [PATCH 01/13] add unit tests for endpoint testing of conditional formats --- pytest.ini | 2 +- test/test_integration.py | 60 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/pytest.ini b/pytest.ini index a7d43cd..0dd2473 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,4 @@ [pytest] -addopts = -v -m "not integration" +#addopts = -v -m "not integration" markers = integration: mark tests as integration, they will not be run by default (to run add '-m "integration"') diff --git a/test/test_integration.py b/test/test_integration.py index b816d8a..5fd8aca 100644 --- a/test/test_integration.py +++ b/test/test_integration.py @@ -3,6 +3,7 @@ import pytest +from labkey.domain import conditional_format from labkey.exceptions import ServerContextError from labkey.utils import create_server_context from labkey.query import delete_rows, insert_rows, select_rows, update_rows @@ -31,6 +32,35 @@ {'label': 'approved', 'publicData': True}, ] +LISTS_SCHEMA = 'lists' +LIST_NAME = 'testlist' +CONDITIONAL_FORMAT = [{ + 'filter': 'format.column~gte=25', + 'textcolor': 'ff0000', + 'backgroundcolor': 'ffffff', + 'bold' : True, + 'italic' : False, + 'strikethrough' : False + }] +LIST_DEFINITION ={ + 'kind': 'IntList', + 'domainDesign': { + 'name': LIST_NAME, + 'fields': [{ + 'name': 'rowId', + 'rangeURI': 'int' + }, { + 'name': 'formatted', + 'rangeURI': 'int', + 'conditionalFormats': CONDITIONAL_FORMAT + }] + }, + 'options': { + 'keyName': 'rowId', + 'keyType': 'AutoIncrementInteger' + } +} + @pytest.fixture(scope='session') def server_context_vars(): @@ -64,6 +94,7 @@ def server_context_vars(): def project(server_context_vars): server, context_path = server_context_vars context = create_server_context(server, '', context_path, use_ssl=False) + container.delete(context, PROJECT_NAME) project_ = container.create(context, PROJECT_NAME, folderType='study') yield project_ container.delete(context, PROJECT_NAME) @@ -127,6 +158,15 @@ def qc_states(server_context, study): delete_rows(server_context, 'core', 'qcstate', cleanup_qc_states) +@pytest.fixture(scope="function") +def create_list(server_context, project): + domain.create(server_context, LIST_DEFINITION) + created_list = domain.get(server_context, LISTS_SCHEMA, LIST_NAME) + yield created_list + #clean up + domain.drop(server_context, LISTS_SCHEMA, LIST_NAME) + + def test_select_rows(server_context): resp = select_rows(server_context, 'core', 'Users') assert resp['schemaName'] == 'core' @@ -194,3 +234,23 @@ def test_cannot_delete_qc_state_in_use(server_context, qc_states, study, dataset # now clean up/stop using it dataset_row_to_remove = [{'lsid': inserted_lsid}] delete_rows(server_context, SCHEMA_NAME, QUERY_NAME, dataset_row_to_remove) + + +def test_add_conditional_format(server_context, project, create_list): + new_conditional_format = conditional_format( + query_filter='format.column~lte=7', + text_color='ff0055', + background_color='ffffff', + bold=True, + italic=False, + strike_through=False + ) + for field in create_list.fields: + if field.name == 'formatted': + field.conditional_formats.append(new_conditional_format) + domain.save(server_context, LISTS_SCHEMA, LIST_NAME, create_list) + saved_domain = domain.get(server_context, LISTS_SCHEMA, LIST_NAME) + + for field in saved_domain.fields: + if field.name == "formatted": + assert field.conditional_formats.__len__() == 2 \ No newline at end of file From 5249c273d4728a4a133b53a3f97a0b01c6fd1d43 Mon Sep 17 00:00:00 2001 From: Chris Joosse Date: Mon, 31 Aug 2020 12:29:38 -0700 Subject: [PATCH 02/13] add unit tests for endpoint testing ,remove conditional formats --- test/test_integration.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/test/test_integration.py b/test/test_integration.py index 5fd8aca..43f8ff3 100644 --- a/test/test_integration.py +++ b/test/test_integration.py @@ -94,7 +94,7 @@ def server_context_vars(): def project(server_context_vars): server, context_path = server_context_vars context = create_server_context(server, '', context_path, use_ssl=False) - container.delete(context, PROJECT_NAME) + #container.delete(context, PROJECT_NAME) project_ = container.create(context, PROJECT_NAME, folderType='study') yield project_ container.delete(context, PROJECT_NAME) @@ -253,4 +253,16 @@ def test_add_conditional_format(server_context, project, create_list): for field in saved_domain.fields: if field.name == "formatted": - assert field.conditional_formats.__len__() == 2 \ No newline at end of file + assert field.conditional_formats.__len__() == 2 + + +def test_remove_conditional_format(server_context, project, create_list): + for field in create_list.fields: + if field.name == 'formatted': + field.conditional_formats = [] + domain.save(server_context, LISTS_SCHEMA, LIST_NAME, create_list) + saved_domain = domain.get(server_context, LISTS_SCHEMA, LIST_NAME) + + for field in saved_domain.fields: + if field.name == "formatted": + assert field.conditional_formats.__len__() == 0 From ed295e350da07517bcf7383087bc87af3e6f8338 Mon Sep 17 00:00:00 2001 From: Alan Vezina Date: Mon, 31 Aug 2020 14:51:13 -0500 Subject: [PATCH 03/13] Move integration tests to integration folder - Moved re-usable fixtures to conftest.py - Renamed test_integration.py to test_query.py --- test/conftest.py | 0 test/integration/conftest.py | 71 +++++++++ test/integration/test_domain.py | 74 ++++++++++ .../test_query.py} | 135 +----------------- 4 files changed, 148 insertions(+), 132 deletions(-) delete mode 100644 test/conftest.py create mode 100644 test/integration/conftest.py create mode 100644 test/integration/test_domain.py rename test/{test_integration.py => integration/test_query.py} (53%) diff --git a/test/conftest.py b/test/conftest.py deleted file mode 100644 index e69de29..0000000 diff --git a/test/integration/conftest.py b/test/integration/conftest.py new file mode 100644 index 0000000..e858b8a --- /dev/null +++ b/test/integration/conftest.py @@ -0,0 +1,71 @@ +import os +from configparser import ConfigParser + +import pytest + +from labkey.utils import create_server_context +from labkey import container +from labkey.exceptions import QueryNotFoundError + +DEFAULT_HOST = 'localhost' +DEFAULT_PORT = '8080' +DEFAULT_CONTEXT_PATH = 'labkey' +PROJECT_NAME = 'PythonIntegrationTests' + + +@pytest.fixture(scope='session') +def server_context_vars(): + properties_file_path = os.getenv('TEAMCITY_BUILD_PROPERTIES_FILE') + host = DEFAULT_HOST + port = DEFAULT_PORT + context_path = DEFAULT_CONTEXT_PATH + + if properties_file_path is not None: + with open(properties_file_path) as f: + contents = f.read() + # .properties files are ini files without any sections, so we need to inject one + contents = '[config]\n' + contents + parser = ConfigParser() + parser.read_string(contents) + parsed_config = parser['config'] + host = parsed_config.get('labkey.server', DEFAULT_HOST) + port = parsed_config.get('tomcat.port', DEFAULT_PORT) + context_path = parsed_config.get('labkey.contextpath', DEFAULT_CONTEXT_PATH) + + if host.startswith('http://'): + host = host.replace('http://', '') + + if context_path.startswith('/'): + context_path = context_path[1:] + + return f'{host}:{port}', context_path + + +@pytest.fixture(scope="session") +def server_context(server_context_vars): + """ + Use this fixture by adding an argument called "server_context" to your test function. It assumes you have a server + running at localhost:8080, a project name "PythonIntegrationTest", and a context path of "labkey". You will need + a netrc file configured with a valid username and password in order for API requests to work. + + :return: ServerContext + """ + server, context_path = server_context_vars + return create_server_context(server, PROJECT_NAME, context_path, use_ssl=False) + + +@pytest.fixture(autouse=True, scope="session") +def project(server_context_vars): + server, context_path = server_context_vars + context = create_server_context(server, '', context_path, use_ssl=False) + + try: + + container.delete(context, PROJECT_NAME) + except QueryNotFoundError: + # The project may not exist, and that is ok. + pass + + project_ = container.create(context, PROJECT_NAME, folderType='study') + yield project_ + container.delete(context, PROJECT_NAME) diff --git a/test/integration/test_domain.py b/test/integration/test_domain.py new file mode 100644 index 0000000..92f6a84 --- /dev/null +++ b/test/integration/test_domain.py @@ -0,0 +1,74 @@ +import pytest + +from labkey.domain import conditional_format, create, drop, get, save + +pytestmark = pytest.mark.integration # Mark all tests in this module as integration tests +LISTS_SCHEMA = 'lists' +LIST_NAME = 'testlist' +CONDITIONAL_FORMAT = [{ + 'filter': 'format.column~gte=25', + 'textcolor': 'ff0000', + 'backgroundcolor': 'ffffff', + 'bold' : True, + 'italic' : False, + 'strikethrough' : False +}] +LIST_DEFINITION ={ + 'kind': 'IntList', + 'domainDesign': { + 'name': LIST_NAME, + 'fields': [{ + 'name': 'rowId', + 'rangeURI': 'int' + }, { + 'name': 'formatted', + 'rangeURI': 'int', + 'conditionalFormats': CONDITIONAL_FORMAT + }] + }, + 'options': { + 'keyName': 'rowId', + 'keyType': 'AutoIncrementInteger' + } +} + + +@pytest.fixture(scope="function") +def list_fixture(server_context): + create(server_context, LIST_DEFINITION) + created_list = get(server_context, LISTS_SCHEMA, LIST_NAME) + yield created_list + # clean up + drop(server_context, LISTS_SCHEMA, LIST_NAME) + + +def test_add_conditional_format(server_context, list_fixture): + new_conditional_format = conditional_format( + query_filter='format.column~lte=7', + text_color='ff0055', + background_color='ffffff', + bold=True, + italic=False, + strike_through=False + ) + for field in list_fixture.fields: + if field.name == 'formatted': + field.conditional_formats.append(new_conditional_format) + save(server_context, LISTS_SCHEMA, LIST_NAME, list_fixture) + saved_domain = get(server_context, LISTS_SCHEMA, LIST_NAME) + + for field in saved_domain.fields: + if field.name == "formatted": + assert field.conditional_formats.__len__() == 2 + + +def test_remove_conditional_format(server_context, list_fixture): + for field in list_fixture.fields: + if field.name == 'formatted': + field.conditional_formats = [] + save(server_context, LISTS_SCHEMA, LIST_NAME, list_fixture) + saved_domain = get(server_context, LISTS_SCHEMA, LIST_NAME) + + for field in saved_domain.fields: + if field.name == "formatted": + assert field.conditional_formats.__len__() == 0 diff --git a/test/test_integration.py b/test/integration/test_query.py similarity index 53% rename from test/test_integration.py rename to test/integration/test_query.py index 43f8ff3..570e7ea 100644 --- a/test/test_integration.py +++ b/test/integration/test_query.py @@ -1,19 +1,11 @@ -import os -from configparser import ConfigParser - import pytest -from labkey.domain import conditional_format from labkey.exceptions import ServerContextError -from labkey.utils import create_server_context from labkey.query import delete_rows, insert_rows, select_rows, update_rows -from labkey import domain, container +from labkey import domain + pytestmark = pytest.mark.integration # Mark all tests in this module as integration tests -DEFAULT_HOST = 'localhost' -DEFAULT_PORT = '8080' -DEFAULT_CONTEXT_PATH = 'labkey' -PROJECT_NAME = 'PythonIntegrationTests' STUDY_NAME = 'TestStudy' SCHEMA_NAME = 'study' QUERY_NAME = 'KrankenLevel' @@ -32,86 +24,6 @@ {'label': 'approved', 'publicData': True}, ] -LISTS_SCHEMA = 'lists' -LIST_NAME = 'testlist' -CONDITIONAL_FORMAT = [{ - 'filter': 'format.column~gte=25', - 'textcolor': 'ff0000', - 'backgroundcolor': 'ffffff', - 'bold' : True, - 'italic' : False, - 'strikethrough' : False - }] -LIST_DEFINITION ={ - 'kind': 'IntList', - 'domainDesign': { - 'name': LIST_NAME, - 'fields': [{ - 'name': 'rowId', - 'rangeURI': 'int' - }, { - 'name': 'formatted', - 'rangeURI': 'int', - 'conditionalFormats': CONDITIONAL_FORMAT - }] - }, - 'options': { - 'keyName': 'rowId', - 'keyType': 'AutoIncrementInteger' - } -} - - -@pytest.fixture(scope='session') -def server_context_vars(): - properties_file_path = os.getenv('TEAMCITY_BUILD_PROPERTIES_FILE') - host = DEFAULT_HOST - port = DEFAULT_PORT - context_path = DEFAULT_CONTEXT_PATH - - if properties_file_path is not None: - with open(properties_file_path) as f: - contents = f.read() - # .properties files are ini files without any sections, so we need to inject one - contents = '[config]\n' + contents - parser = ConfigParser() - parser.read_string(contents) - parsed_config = parser['config'] - host = parsed_config.get('labkey.server', DEFAULT_HOST) - port = parsed_config.get('tomcat.port', DEFAULT_PORT) - context_path = parsed_config.get('labkey.contextpath', DEFAULT_CONTEXT_PATH) - - if host.startswith('http://'): - host = host.replace('http://', '') - - if context_path.startswith('/'): - context_path = context_path[1:] - - return f'{host}:{port}', context_path - - -@pytest.fixture(autouse=True, scope="session") -def project(server_context_vars): - server, context_path = server_context_vars - context = create_server_context(server, '', context_path, use_ssl=False) - #container.delete(context, PROJECT_NAME) - project_ = container.create(context, PROJECT_NAME, folderType='study') - yield project_ - container.delete(context, PROJECT_NAME) - - -@pytest.fixture(scope="session") -def server_context(server_context_vars): - """ - Use this fixture by adding an argument called "server_context" to your test function. It assumes you have a server - running at localhost:8080, a project name "PythonIntegrationTest", and a context path of "labkey". You will need - a netrc file configured with a valid username and password in order for API requests to work. - - :return: ServerContext - """ - server, context_path = server_context_vars - return create_server_context(server, PROJECT_NAME, context_path, use_ssl=False) - @pytest.fixture(scope="session") def study(server_context): @@ -158,15 +70,6 @@ def qc_states(server_context, study): delete_rows(server_context, 'core', 'qcstate', cleanup_qc_states) -@pytest.fixture(scope="function") -def create_list(server_context, project): - domain.create(server_context, LIST_DEFINITION) - created_list = domain.get(server_context, LISTS_SCHEMA, LIST_NAME) - yield created_list - #clean up - domain.drop(server_context, LISTS_SCHEMA, LIST_NAME) - - def test_select_rows(server_context): resp = select_rows(server_context, 'core', 'Users') assert resp['schemaName'] == 'core' @@ -209,7 +112,7 @@ def test_insert_duplicate_labeled_qc_state_produces_error(server_context, qc_sta with pytest.raises(ServerContextError) as e: dupe_qc_state = [{'label': 'needs verification', 'publicData': 'false'}] insert_rows(server_context, 'core', 'qcstate', dupe_qc_state) - + assert "500: ERROR: duplicate key value violates unique constraint" in e.value.message @@ -234,35 +137,3 @@ def test_cannot_delete_qc_state_in_use(server_context, qc_states, study, dataset # now clean up/stop using it dataset_row_to_remove = [{'lsid': inserted_lsid}] delete_rows(server_context, SCHEMA_NAME, QUERY_NAME, dataset_row_to_remove) - - -def test_add_conditional_format(server_context, project, create_list): - new_conditional_format = conditional_format( - query_filter='format.column~lte=7', - text_color='ff0055', - background_color='ffffff', - bold=True, - italic=False, - strike_through=False - ) - for field in create_list.fields: - if field.name == 'formatted': - field.conditional_formats.append(new_conditional_format) - domain.save(server_context, LISTS_SCHEMA, LIST_NAME, create_list) - saved_domain = domain.get(server_context, LISTS_SCHEMA, LIST_NAME) - - for field in saved_domain.fields: - if field.name == "formatted": - assert field.conditional_formats.__len__() == 2 - - -def test_remove_conditional_format(server_context, project, create_list): - for field in create_list.fields: - if field.name == 'formatted': - field.conditional_formats = [] - domain.save(server_context, LISTS_SCHEMA, LIST_NAME, create_list) - saved_domain = domain.get(server_context, LISTS_SCHEMA, LIST_NAME) - - for field in saved_domain.fields: - if field.name == "formatted": - assert field.conditional_formats.__len__() == 0 From 965395eac6b9ec76e5b8cda5c7f4fd70515f3807 Mon Sep 17 00:00:00 2001 From: Chris Joosse Date: Mon, 31 Aug 2020 14:33:05 -0700 Subject: [PATCH 04/13] add unit tests for endpoint testing , update conditional format using api components --- test/integration/test_domain.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/test/integration/test_domain.py b/test/integration/test_domain.py index 92f6a84..4679f1e 100644 --- a/test/integration/test_domain.py +++ b/test/integration/test_domain.py @@ -9,11 +9,11 @@ 'filter': 'format.column~gte=25', 'textcolor': 'ff0000', 'backgroundcolor': 'ffffff', - 'bold' : True, - 'italic' : False, - 'strikethrough' : False + 'bold': True, + 'italic': False, + 'strikethrough': False }] -LIST_DEFINITION ={ +LIST_DEFINITION = { 'kind': 'IntList', 'domainDesign': { 'name': LIST_NAME, @@ -72,3 +72,18 @@ def test_remove_conditional_format(server_context, list_fixture): for field in saved_domain.fields: if field.name == "formatted": assert field.conditional_formats.__len__() == 0 + + +def test_update_conditional_format(server_context, list_fixture): + from labkey.query import QueryFilter + new_filter = QueryFilter('formatted', 15, QueryFilter.Types.GREATER_THAN_OR_EQUAL) + for field in list_fixture.fields: + if field.name == 'formatted': + field.conditional_formats[0].filter = new_filter + + save(server_context, LISTS_SCHEMA, LIST_NAME, list_fixture) + saved_domain = get(server_context, LISTS_SCHEMA, LIST_NAME) + + for field in list_fixture.fields: + if field.name == 'formatted': + assert field.conditional_formats[0].filter == new_filter \ No newline at end of file From 997e391802b09fce3cb0a8fa770d377f37b6f5dd Mon Sep 17 00:00:00 2001 From: Chris Joosse Date: Mon, 31 Aug 2020 14:53:04 -0700 Subject: [PATCH 05/13] add unit tests for endpoint testing , update conditional format using pre-serialized text --- test/integration/test_domain.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/test/integration/test_domain.py b/test/integration/test_domain.py index 4679f1e..39304b9 100644 --- a/test/integration/test_domain.py +++ b/test/integration/test_domain.py @@ -74,7 +74,7 @@ def test_remove_conditional_format(server_context, list_fixture): assert field.conditional_formats.__len__() == 0 -def test_update_conditional_format(server_context, list_fixture): +def test_update_conditional_format_serialize_filter(server_context, list_fixture): from labkey.query import QueryFilter new_filter = QueryFilter('formatted', 15, QueryFilter.Types.GREATER_THAN_OR_EQUAL) for field in list_fixture.fields: @@ -84,6 +84,20 @@ def test_update_conditional_format(server_context, list_fixture): save(server_context, LISTS_SCHEMA, LIST_NAME, list_fixture) saved_domain = get(server_context, LISTS_SCHEMA, LIST_NAME) + for field in saved_domain.fields: + if field.name == 'formatted': + assert field.conditional_formats[0].filter == new_filter + + +def test_update_conditional_format_plain_text(server_context, list_fixture): + new_filter = "formatted~gte=15" for field in list_fixture.fields: + if field.name == 'formatted': + field.conditional_formats[0].filter = new_filter + + save(server_context, LISTS_SCHEMA, LIST_NAME, list_fixture) + saved_domain = get(server_context, LISTS_SCHEMA, LIST_NAME) + + for field in saved_domain.fields: if field.name == 'formatted': assert field.conditional_formats[0].filter == new_filter \ No newline at end of file From 4b0aa5c1e3926ef732c19171d10dfd65a771a493 Mon Sep 17 00:00:00 2001 From: Chris Joosse Date: Tue, 1 Sep 2020 13:52:31 -0700 Subject: [PATCH 06/13] test create pathway for list definition featuring conditional formats composed of serializable objects --- test/integration/test_domain.py | 44 ++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/test/integration/test_domain.py b/test/integration/test_domain.py index 39304b9..3889821 100644 --- a/test/integration/test_domain.py +++ b/test/integration/test_domain.py @@ -1,4 +1,5 @@ import pytest +from labkey.query import QueryFilter from labkey.domain import conditional_format, create, drop, get, save @@ -13,6 +14,11 @@ 'italic': False, 'strikethrough': False }] +SERIALIZED_QUERY_FILTER = QueryFilter('formatted', 35, QueryFilter.Types.LESS_THAN) +SERIALIZED_CONDITIONAL_FORMAT = conditional_format( + query_filter=SERIALIZED_QUERY_FILTER, + bold=False, text_color="ffff00" +) LIST_DEFINITION = { 'kind': 'IntList', 'domainDesign': { @@ -74,6 +80,7 @@ def test_remove_conditional_format(server_context, list_fixture): assert field.conditional_formats.__len__() == 0 +@pytest.mark.xfail def test_update_conditional_format_serialize_filter(server_context, list_fixture): from labkey.query import QueryFilter new_filter = QueryFilter('formatted', 15, QueryFilter.Types.GREATER_THAN_OR_EQUAL) @@ -100,4 +107,39 @@ def test_update_conditional_format_plain_text(server_context, list_fixture): for field in saved_domain.fields: if field.name == 'formatted': - assert field.conditional_formats[0].filter == new_filter \ No newline at end of file + assert field.conditional_formats[0].filter == new_filter + + +def test_create_list_with_conditionalFormattedField(server_context): + composed_list_definition = { + 'kind': 'IntList', + 'domainDesign': { + 'name': 'composed_list_name', + 'fields': [{ + 'name': 'rowId', + 'rangeURI': 'int' + }, { + 'name': 'formatted', + 'rangeURI': 'int', + 'conditionalFormats': [SERIALIZED_CONDITIONAL_FORMAT, + {'filter': 'format.column~gte=25', + 'textcolor': 'ff0000', + 'backgroundcolor': 'ffffff', + 'bold': True, + 'italic': False, + 'strikethrough': False + }] + }] + }, + 'options': { + 'keyName': 'rowId', + 'keyType': 'AutoIncrementInteger' + } + } + create(server_context, composed_list_definition) + created_list = get(server_context, LISTS_SCHEMA, 'composed_list_name') + for field in created_list.fields: + if field.name == 'formatted': + assert field.conditional_formats.__len__() == 2 + + drop(server_context, LISTS_SCHEMA, 'composed_list_name') From 2bfedf0845c1609d005c596b1630060cfedd4caa Mon Sep 17 00:00:00 2001 From: Chris Joosse Date: Tue, 1 Sep 2020 16:26:22 -0700 Subject: [PATCH 07/13] add tests for error handling --- test/integration/test_domain.py | 78 +++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 4 deletions(-) diff --git a/test/integration/test_domain.py b/test/integration/test_domain.py index 3889821..4db7ae2 100644 --- a/test/integration/test_domain.py +++ b/test/integration/test_domain.py @@ -1,4 +1,6 @@ import pytest +from labkey.exceptions import ServerContextError + from labkey.query import QueryFilter from labkey.domain import conditional_format, create, drop, get, save @@ -18,7 +20,7 @@ SERIALIZED_CONDITIONAL_FORMAT = conditional_format( query_filter=SERIALIZED_QUERY_FILTER, bold=False, text_color="ffff00" -) +).to_json() LIST_DEFINITION = { 'kind': 'IntList', 'domainDesign': { @@ -68,6 +70,74 @@ def test_add_conditional_format(server_context, list_fixture): assert field.conditional_formats.__len__() == 2 +def test_add_conditional_format_with_multiple_filters(server_context, list_fixture): + new_conditional_formats = [conditional_format(query_filter=[ + QueryFilter(column='column', value=10, filter_type=QueryFilter.Types.LESS_THAN), + QueryFilter(column='column', value=100, filter_type=QueryFilter.Types.GREATER_THAN) + ], + text_color='ff0055', + background_color='ffffff', + bold=True, + italic=False, + strike_through=False)] + for field in list_fixture.fields: + if field.name == 'formatted': + field.conditional_formats = [] + field.conditional_formats = new_conditional_formats + save(server_context, LISTS_SCHEMA, LIST_NAME, list_fixture) + saved_domain = get(server_context, LISTS_SCHEMA, LIST_NAME) + + for field in saved_domain.fields: + if field.name == "formatted": + assert field.conditional_formats.__len__() == 1 + assert field.conditional_formats[0].filter == 'format.column~lt=10&format.column~gt=100' + + +@pytest.mark.xfail # this reproes https://www.labkey.org/home/Developer/issues/issues-details.view?issueId=41318 +def test_add_malformed_query_filter(server_context, list_fixture): + new_conditional_format = conditional_format( + query_filter='this-is-a-badly-formed-filter', + text_color='ff0055', + background_color='ffffff', + bold=True, + italic=False, + strike_through=False + ) + for field in list_fixture.fields: + if field.name == 'formatted': + field.conditional_formats = [] + field.conditional_formats = [new_conditional_format] + + save(server_context, LISTS_SCHEMA, LIST_NAME, list_fixture) + saved_domain = get(server_context, LISTS_SCHEMA, LIST_NAME) + + for field in saved_domain.fields: + if field.name == "formatted": + assert field.conditional_formats[0].filter != 'this-is-a-badly-formed-filter', "api should discard meaningless filters" + + +def test_add_conditional_format_with_missing_filter(server_context, list_fixture): + missing_filter_type_filter = QueryFilter('formatted', 13) + new_conditional_format = conditional_format(query_filter=missing_filter_type_filter, + text_color='ff0055', + background_color='ffffff', + bold=True, + italic=False, + strike_through=False + ) + for field in list_fixture.fields: + if field.name == 'formatted': + field.conditional_formats = [] + field.conditional_formats = [new_conditional_format] + + save(server_context, LISTS_SCHEMA, LIST_NAME, list_fixture) + saved_domain = get(server_context, LISTS_SCHEMA, LIST_NAME) + + for field in saved_domain.fields: + if field.name == "formatted": + assert field.conditional_formats[0].filter == 'format.column~eq=13' + + def test_remove_conditional_format(server_context, list_fixture): for field in list_fixture.fields: if field.name == 'formatted': @@ -80,20 +150,20 @@ def test_remove_conditional_format(server_context, list_fixture): assert field.conditional_formats.__len__() == 0 -@pytest.mark.xfail def test_update_conditional_format_serialize_filter(server_context, list_fixture): from labkey.query import QueryFilter new_filter = QueryFilter('formatted', 15, QueryFilter.Types.GREATER_THAN_OR_EQUAL) + cf = conditional_format(new_filter, text_color="ff00ff") for field in list_fixture.fields: if field.name == 'formatted': - field.conditional_formats[0].filter = new_filter + field.conditional_formats[0] = cf save(server_context, LISTS_SCHEMA, LIST_NAME, list_fixture) saved_domain = get(server_context, LISTS_SCHEMA, LIST_NAME) for field in saved_domain.fields: if field.name == 'formatted': - assert field.conditional_formats[0].filter == new_filter + assert field.conditional_formats[0].filter == 'format.column~gte=15' def test_update_conditional_format_plain_text(server_context, list_fixture): From 697a41512a0cc57448dc6297a74eea3ee1438169 Mon Sep 17 00:00:00 2001 From: Chris Joosse Date: Wed, 2 Sep 2020 12:45:35 -0700 Subject: [PATCH 08/13] cr cleanup, casing and indent fixes --- test/integration/test_domain.py | 43 ++++++++++++++++----------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/test/integration/test_domain.py b/test/integration/test_domain.py index 4db7ae2..415afd7 100644 --- a/test/integration/test_domain.py +++ b/test/integration/test_domain.py @@ -1,5 +1,4 @@ import pytest -from labkey.exceptions import ServerContextError from labkey.query import QueryFilter @@ -113,18 +112,18 @@ def test_add_malformed_query_filter(server_context, list_fixture): for field in saved_domain.fields: if field.name == "formatted": - assert field.conditional_formats[0].filter != 'this-is-a-badly-formed-filter', "api should discard meaningless filters" + assert field.conditional_formats[0].filter != 'this-is-a-badly-formed-filter', \ + "api should discard meaningless filters" def test_add_conditional_format_with_missing_filter(server_context, list_fixture): missing_filter_type_filter = QueryFilter('formatted', 13) new_conditional_format = conditional_format(query_filter=missing_filter_type_filter, - text_color='ff0055', - background_color='ffffff', - bold=True, - italic=False, - strike_through=False - ) + text_color='ff0055', + background_color='ffffff', + bold=True, + italic=False, + strike_through=False) for field in list_fixture.fields: if field.name == 'formatted': field.conditional_formats = [] @@ -180,7 +179,7 @@ def test_update_conditional_format_plain_text(server_context, list_fixture): assert field.conditional_formats[0].filter == new_filter -def test_create_list_with_conditionalFormattedField(server_context): +def test_create_list_with_conditional_formatted_field(server_context): composed_list_definition = { 'kind': 'IntList', 'domainDesign': { @@ -192,20 +191,20 @@ def test_create_list_with_conditionalFormattedField(server_context): 'name': 'formatted', 'rangeURI': 'int', 'conditionalFormats': [SERIALIZED_CONDITIONAL_FORMAT, - {'filter': 'format.column~gte=25', - 'textcolor': 'ff0000', - 'backgroundcolor': 'ffffff', - 'bold': True, - 'italic': False, - 'strikethrough': False - }] - }] - }, - 'options': { - 'keyName': 'rowId', - 'keyType': 'AutoIncrementInteger' - } + {'filter': 'format.column~gte=25', + 'textcolor': 'ff0000', + 'backgroundcolor': 'ffffff', + 'bold': True, + 'italic': False, + 'strikethrough': False + }] + }] + }, + 'options': { + 'keyName': 'rowId', + 'keyType': 'AutoIncrementInteger' } + } create(server_context, composed_list_definition) created_list = get(server_context, LISTS_SCHEMA, 'composed_list_name') for field in created_list.fields: From ed3dbb048b016360a012525023f9bea98cb672d0 Mon Sep 17 00:00:00 2001 From: Chris Joosse Date: Thu, 3 Sep 2020 09:46:11 -0700 Subject: [PATCH 09/13] revert mistaken commit of addopts configuration --- pytest.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest.ini b/pytest.ini index 0dd2473..a7d43cd 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,4 @@ [pytest] -#addopts = -v -m "not integration" +addopts = -v -m "not integration" markers = integration: mark tests as integration, they will not be run by default (to run add '-m "integration"') From be56c600c7f35ae39ebb8f370c0b870f0aa5b4ff Mon Sep 17 00:00:00 2001 From: Alan Vezina Date: Thu, 3 Sep 2020 17:10:58 -0500 Subject: [PATCH 10/13] Move unit tests to unit folder --- test/{ => integration}/__init__.py | 0 test/unit/__init__.py | 0 test/{ => unit}/test_domain.py | 0 test/{ => unit}/test_experiment_api.py | 0 test/{ => unit}/test_query_api.py | 0 test/{ => unit}/test_security.py | 0 test/{ => unit}/test_unsupported.py | 0 test/{ => unit}/test_utils.py | 0 test/{ => unit}/utilities.py | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename test/{ => integration}/__init__.py (100%) create mode 100644 test/unit/__init__.py rename test/{ => unit}/test_domain.py (100%) rename test/{ => unit}/test_experiment_api.py (100%) rename test/{ => unit}/test_query_api.py (100%) rename test/{ => unit}/test_security.py (100%) rename test/{ => unit}/test_unsupported.py (100%) rename test/{ => unit}/test_utils.py (100%) rename test/{ => unit}/utilities.py (100%) diff --git a/test/__init__.py b/test/integration/__init__.py similarity index 100% rename from test/__init__.py rename to test/integration/__init__.py diff --git a/test/unit/__init__.py b/test/unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/test_domain.py b/test/unit/test_domain.py similarity index 100% rename from test/test_domain.py rename to test/unit/test_domain.py diff --git a/test/test_experiment_api.py b/test/unit/test_experiment_api.py similarity index 100% rename from test/test_experiment_api.py rename to test/unit/test_experiment_api.py diff --git a/test/test_query_api.py b/test/unit/test_query_api.py similarity index 100% rename from test/test_query_api.py rename to test/unit/test_query_api.py diff --git a/test/test_security.py b/test/unit/test_security.py similarity index 100% rename from test/test_security.py rename to test/unit/test_security.py diff --git a/test/test_unsupported.py b/test/unit/test_unsupported.py similarity index 100% rename from test/test_unsupported.py rename to test/unit/test_unsupported.py diff --git a/test/test_utils.py b/test/unit/test_utils.py similarity index 100% rename from test/test_utils.py rename to test/unit/test_utils.py diff --git a/test/utilities.py b/test/unit/utilities.py similarity index 100% rename from test/utilities.py rename to test/unit/utilities.py From 8cedc30b6bfd431ca427d81dc884dfc97ce24b6d Mon Sep 17 00:00:00 2001 From: Alan Vezina Date: Thu, 3 Sep 2020 17:15:13 -0500 Subject: [PATCH 11/13] Bump version to 1.4.1 --- CHANGE.txt | 10 ++++++++++ labkey/__init__.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGE.txt b/CHANGE.txt index dc786b0..35d2bdc 100644 --- a/CHANGE.txt +++ b/CHANGE.txt @@ -2,6 +2,16 @@ LabKey Python Client API News +++++++++++ +What's New in the LabKey 1.4.1 package +============================== + +*Release date: 09/03/2020* + +- Add integration tests +- NOTE: The next planned release will be 2.0.0 and is expected to drop support for Python 2.x, we plan to introduce +code that will only be compatible with Python 3.6 and beyond. Python 2.x is no longer supported by the PSF as of +January 1st, 2020. + What's New in the LabKey 1.4.0 package ============================== diff --git a/labkey/__init__.py b/labkey/__init__.py index 57f3235..5984df4 100644 --- a/labkey/__init__.py +++ b/labkey/__init__.py @@ -16,6 +16,6 @@ from labkey import domain, query, experiment, security, utils __title__ = 'labkey' -__version__ = '1.4.0' +__version__ = '1.4.1' __author__ = 'LabKey' __license__ = 'Apache License 2.0' From bf0d8675e7a16f20d0d39adb664b39f36c5810d1 Mon Sep 17 00:00:00 2001 From: Alan Vezina Date: Thu, 3 Sep 2020 17:27:33 -0500 Subject: [PATCH 12/13] Consistently format code --- test/integration/conftest.py | 1 - test/integration/test_domain.py | 108 +++++++++++++++++++------------- test/integration/test_query.py | 1 - 3 files changed, 66 insertions(+), 44 deletions(-) diff --git a/test/integration/conftest.py b/test/integration/conftest.py index e858b8a..b88e50f 100644 --- a/test/integration/conftest.py +++ b/test/integration/conftest.py @@ -60,7 +60,6 @@ def project(server_context_vars): context = create_server_context(server, '', context_path, use_ssl=False) try: - container.delete(context, PROJECT_NAME) except QueryNotFoundError: # The project may not exist, and that is ok. diff --git a/test/integration/test_domain.py b/test/integration/test_domain.py index 415afd7..5d936f7 100644 --- a/test/integration/test_domain.py +++ b/test/integration/test_domain.py @@ -24,14 +24,17 @@ 'kind': 'IntList', 'domainDesign': { 'name': LIST_NAME, - 'fields': [{ - 'name': 'rowId', - 'rangeURI': 'int' - }, { - 'name': 'formatted', - 'rangeURI': 'int', - 'conditionalFormats': CONDITIONAL_FORMAT - }] + 'fields': [ + { + 'name': 'rowId', + 'rangeURI': 'int' + }, + { + 'name': 'formatted', + 'rangeURI': 'int', + 'conditionalFormats': CONDITIONAL_FORMAT + } + ] }, 'options': { 'keyName': 'rowId', @@ -58,31 +61,39 @@ def test_add_conditional_format(server_context, list_fixture): italic=False, strike_through=False ) + for field in list_fixture.fields: if field.name == 'formatted': field.conditional_formats.append(new_conditional_format) + save(server_context, LISTS_SCHEMA, LIST_NAME, list_fixture) saved_domain = get(server_context, LISTS_SCHEMA, LIST_NAME) for field in saved_domain.fields: if field.name == "formatted": - assert field.conditional_formats.__len__() == 2 + assert len(field.conditional_formats) == 2 def test_add_conditional_format_with_multiple_filters(server_context, list_fixture): - new_conditional_formats = [conditional_format(query_filter=[ - QueryFilter(column='column', value=10, filter_type=QueryFilter.Types.LESS_THAN), - QueryFilter(column='column', value=100, filter_type=QueryFilter.Types.GREATER_THAN) - ], - text_color='ff0055', - background_color='ffffff', - bold=True, - italic=False, - strike_through=False)] + new_conditional_formats = [ + conditional_format( + query_filter=[ + QueryFilter(column='column', value=10, filter_type=QueryFilter.Types.LESS_THAN), + QueryFilter(column='column', value=100, filter_type=QueryFilter.Types.GREATER_THAN) + ], + text_color='ff0055', + background_color='ffffff', + bold=True, + italic=False, + strike_through=False + ) + ] + for field in list_fixture.fields: if field.name == 'formatted': field.conditional_formats = [] field.conditional_formats = new_conditional_formats + save(server_context, LISTS_SCHEMA, LIST_NAME, list_fixture) saved_domain = get(server_context, LISTS_SCHEMA, LIST_NAME) @@ -92,7 +103,7 @@ def test_add_conditional_format_with_multiple_filters(server_context, list_fixtu assert field.conditional_formats[0].filter == 'format.column~lt=10&format.column~gt=100' -@pytest.mark.xfail # this reproes https://www.labkey.org/home/Developer/issues/issues-details.view?issueId=41318 +@pytest.mark.xfail # this reproduces https://www.labkey.org/home/Developer/issues/issues-details.view?issueId=41318 def test_add_malformed_query_filter(server_context, list_fixture): new_conditional_format = conditional_format( query_filter='this-is-a-badly-formed-filter', @@ -102,6 +113,7 @@ def test_add_malformed_query_filter(server_context, list_fixture): italic=False, strike_through=False ) + for field in list_fixture.fields: if field.name == 'formatted': field.conditional_formats = [] @@ -118,12 +130,15 @@ def test_add_malformed_query_filter(server_context, list_fixture): def test_add_conditional_format_with_missing_filter(server_context, list_fixture): missing_filter_type_filter = QueryFilter('formatted', 13) - new_conditional_format = conditional_format(query_filter=missing_filter_type_filter, - text_color='ff0055', - background_color='ffffff', - bold=True, - italic=False, - strike_through=False) + new_conditional_format = conditional_format( + query_filter=missing_filter_type_filter, + text_color='ff0055', + background_color='ffffff', + bold=True, + italic=False, + strike_through=False + ) + for field in list_fixture.fields: if field.name == 'formatted': field.conditional_formats = [] @@ -141,18 +156,20 @@ def test_remove_conditional_format(server_context, list_fixture): for field in list_fixture.fields: if field.name == 'formatted': field.conditional_formats = [] + save(server_context, LISTS_SCHEMA, LIST_NAME, list_fixture) saved_domain = get(server_context, LISTS_SCHEMA, LIST_NAME) for field in saved_domain.fields: if field.name == "formatted": - assert field.conditional_formats.__len__() == 0 + assert len(field.conditional_formats) == 0 def test_update_conditional_format_serialize_filter(server_context, list_fixture): from labkey.query import QueryFilter new_filter = QueryFilter('formatted', 15, QueryFilter.Types.GREATER_THAN_OR_EQUAL) cf = conditional_format(new_filter, text_color="ff00ff") + for field in list_fixture.fields: if field.name == 'formatted': field.conditional_formats[0] = cf @@ -167,6 +184,7 @@ def test_update_conditional_format_serialize_filter(server_context, list_fixture def test_update_conditional_format_plain_text(server_context, list_fixture): new_filter = "formatted~gte=15" + for field in list_fixture.fields: if field.name == 'formatted': field.conditional_formats[0].filter = new_filter @@ -184,21 +202,27 @@ def test_create_list_with_conditional_formatted_field(server_context): 'kind': 'IntList', 'domainDesign': { 'name': 'composed_list_name', - 'fields': [{ - 'name': 'rowId', - 'rangeURI': 'int' - }, { - 'name': 'formatted', - 'rangeURI': 'int', - 'conditionalFormats': [SERIALIZED_CONDITIONAL_FORMAT, - {'filter': 'format.column~gte=25', - 'textcolor': 'ff0000', - 'backgroundcolor': 'ffffff', - 'bold': True, - 'italic': False, - 'strikethrough': False - }] - }] + 'fields': [ + { + 'name': 'rowId', + 'rangeURI': 'int' + }, + { + 'name': 'formatted', + 'rangeURI': 'int', + 'conditionalFormats': [ + SERIALIZED_CONDITIONAL_FORMAT, + { + 'filter': 'format.column~gte=25', + 'textcolor': 'ff0000', + 'backgroundcolor': 'ffffff', + 'bold': True, + 'italic': False, + 'strikethrough': False + } + ] + } + ] }, 'options': { 'keyName': 'rowId', @@ -209,6 +233,6 @@ def test_create_list_with_conditional_formatted_field(server_context): created_list = get(server_context, LISTS_SCHEMA, 'composed_list_name') for field in created_list.fields: if field.name == 'formatted': - assert field.conditional_formats.__len__() == 2 + assert len(field.conditional_formats) == 2 drop(server_context, LISTS_SCHEMA, 'composed_list_name') diff --git a/test/integration/test_query.py b/test/integration/test_query.py index 570e7ea..5c867f4 100644 --- a/test/integration/test_query.py +++ b/test/integration/test_query.py @@ -85,7 +85,6 @@ def test_create_dataset(dataset): def test_create_duplicate_dataset(server_context, dataset): # Dataset fixture is not used directly here, but it is an argument so it gets created and cleaned up when this test # runs - with pytest.raises(ServerContextError) as e: domain.create(server_context, DATASET_DOMAIN) From e5c77b04acd3a4676c12b866d11e438245417771 Mon Sep 17 00:00:00 2001 From: Alan Vezina Date: Tue, 8 Sep 2020 12:38:41 -0500 Subject: [PATCH 13/13] Bump release date again. --- CHANGE.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGE.txt b/CHANGE.txt index 35d2bdc..cd95a98 100644 --- a/CHANGE.txt +++ b/CHANGE.txt @@ -5,7 +5,7 @@ LabKey Python Client API News What's New in the LabKey 1.4.1 package ============================== -*Release date: 09/03/2020* +*Release date: 09/08/2020* - Add integration tests - NOTE: The next planned release will be 2.0.0 and is expected to drop support for Python 2.x, we plan to introduce