Skip to content

Commit 11d143f

Browse files
Conditional Format Integration Tests (#41)
* Move integration and unit tests to their own directories * Update sample code to be more "pythonic"
1 parent 7694ad7 commit 11d143f

14 files changed

+322
-62
lines changed

CHANGE.txt

+10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22
LabKey Python Client API News
33
+++++++++++
44

5+
What's New in the LabKey 1.4.1 package
6+
==============================
7+
8+
*Release date: 09/08/2020*
9+
10+
- Add integration tests
11+
- NOTE: The next planned release will be 2.0.0 and is expected to drop support for Python 2.x, we plan to introduce
12+
code that will only be compatible with Python 3.6 and beyond. Python 2.x is no longer supported by the PSF as of
13+
January 1st, 2020.
14+
515
What's New in the LabKey 1.4.0 package
616
==============================
717

labkey/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@
1616
from labkey import domain, query, experiment, security, utils
1717

1818
__title__ = 'labkey'
19-
__version__ = '1.4.0'
19+
__version__ = '1.4.1'
2020
__author__ = 'LabKey'
2121
__license__ = 'Apache License 2.0'
File renamed without changes.

test/integration/conftest.py

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import os
2+
from configparser import ConfigParser
3+
4+
import pytest
5+
6+
from labkey.utils import create_server_context
7+
from labkey import container
8+
from labkey.exceptions import QueryNotFoundError
9+
10+
DEFAULT_HOST = 'localhost'
11+
DEFAULT_PORT = '8080'
12+
DEFAULT_CONTEXT_PATH = 'labkey'
13+
PROJECT_NAME = 'PythonIntegrationTests'
14+
15+
16+
@pytest.fixture(scope='session')
17+
def server_context_vars():
18+
properties_file_path = os.getenv('TEAMCITY_BUILD_PROPERTIES_FILE')
19+
host = DEFAULT_HOST
20+
port = DEFAULT_PORT
21+
context_path = DEFAULT_CONTEXT_PATH
22+
23+
if properties_file_path is not None:
24+
with open(properties_file_path) as f:
25+
contents = f.read()
26+
# .properties files are ini files without any sections, so we need to inject one
27+
contents = '[config]\n' + contents
28+
parser = ConfigParser()
29+
parser.read_string(contents)
30+
parsed_config = parser['config']
31+
host = parsed_config.get('labkey.server', DEFAULT_HOST)
32+
port = parsed_config.get('tomcat.port', DEFAULT_PORT)
33+
context_path = parsed_config.get('labkey.contextpath', DEFAULT_CONTEXT_PATH)
34+
35+
if host.startswith('http://'):
36+
host = host.replace('http://', '')
37+
38+
if context_path.startswith('/'):
39+
context_path = context_path[1:]
40+
41+
return f'{host}:{port}', context_path
42+
43+
44+
@pytest.fixture(scope="session")
45+
def server_context(server_context_vars):
46+
"""
47+
Use this fixture by adding an argument called "server_context" to your test function. It assumes you have a server
48+
running at localhost:8080, a project name "PythonIntegrationTest", and a context path of "labkey". You will need
49+
a netrc file configured with a valid username and password in order for API requests to work.
50+
51+
:return: ServerContext
52+
"""
53+
server, context_path = server_context_vars
54+
return create_server_context(server, PROJECT_NAME, context_path, use_ssl=False)
55+
56+
57+
@pytest.fixture(autouse=True, scope="session")
58+
def project(server_context_vars):
59+
server, context_path = server_context_vars
60+
context = create_server_context(server, '', context_path, use_ssl=False)
61+
62+
try:
63+
container.delete(context, PROJECT_NAME)
64+
except QueryNotFoundError:
65+
# The project may not exist, and that is ok.
66+
pass
67+
68+
project_ = container.create(context, PROJECT_NAME, folderType='study')
69+
yield project_
70+
container.delete(context, PROJECT_NAME)

test/integration/test_domain.py

+238
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
import pytest
2+
3+
from labkey.query import QueryFilter
4+
5+
from labkey.domain import conditional_format, create, drop, get, save
6+
7+
pytestmark = pytest.mark.integration # Mark all tests in this module as integration tests
8+
LISTS_SCHEMA = 'lists'
9+
LIST_NAME = 'testlist'
10+
CONDITIONAL_FORMAT = [{
11+
'filter': 'format.column~gte=25',
12+
'textcolor': 'ff0000',
13+
'backgroundcolor': 'ffffff',
14+
'bold': True,
15+
'italic': False,
16+
'strikethrough': False
17+
}]
18+
SERIALIZED_QUERY_FILTER = QueryFilter('formatted', 35, QueryFilter.Types.LESS_THAN)
19+
SERIALIZED_CONDITIONAL_FORMAT = conditional_format(
20+
query_filter=SERIALIZED_QUERY_FILTER,
21+
bold=False, text_color="ffff00"
22+
).to_json()
23+
LIST_DEFINITION = {
24+
'kind': 'IntList',
25+
'domainDesign': {
26+
'name': LIST_NAME,
27+
'fields': [
28+
{
29+
'name': 'rowId',
30+
'rangeURI': 'int'
31+
},
32+
{
33+
'name': 'formatted',
34+
'rangeURI': 'int',
35+
'conditionalFormats': CONDITIONAL_FORMAT
36+
}
37+
]
38+
},
39+
'options': {
40+
'keyName': 'rowId',
41+
'keyType': 'AutoIncrementInteger'
42+
}
43+
}
44+
45+
46+
@pytest.fixture(scope="function")
47+
def list_fixture(server_context):
48+
create(server_context, LIST_DEFINITION)
49+
created_list = get(server_context, LISTS_SCHEMA, LIST_NAME)
50+
yield created_list
51+
# clean up
52+
drop(server_context, LISTS_SCHEMA, LIST_NAME)
53+
54+
55+
def test_add_conditional_format(server_context, list_fixture):
56+
new_conditional_format = conditional_format(
57+
query_filter='format.column~lte=7',
58+
text_color='ff0055',
59+
background_color='ffffff',
60+
bold=True,
61+
italic=False,
62+
strike_through=False
63+
)
64+
65+
for field in list_fixture.fields:
66+
if field.name == 'formatted':
67+
field.conditional_formats.append(new_conditional_format)
68+
69+
save(server_context, LISTS_SCHEMA, LIST_NAME, list_fixture)
70+
saved_domain = get(server_context, LISTS_SCHEMA, LIST_NAME)
71+
72+
for field in saved_domain.fields:
73+
if field.name == "formatted":
74+
assert len(field.conditional_formats) == 2
75+
76+
77+
def test_add_conditional_format_with_multiple_filters(server_context, list_fixture):
78+
new_conditional_formats = [
79+
conditional_format(
80+
query_filter=[
81+
QueryFilter(column='column', value=10, filter_type=QueryFilter.Types.LESS_THAN),
82+
QueryFilter(column='column', value=100, filter_type=QueryFilter.Types.GREATER_THAN)
83+
],
84+
text_color='ff0055',
85+
background_color='ffffff',
86+
bold=True,
87+
italic=False,
88+
strike_through=False
89+
)
90+
]
91+
92+
for field in list_fixture.fields:
93+
if field.name == 'formatted':
94+
field.conditional_formats = []
95+
field.conditional_formats = new_conditional_formats
96+
97+
save(server_context, LISTS_SCHEMA, LIST_NAME, list_fixture)
98+
saved_domain = get(server_context, LISTS_SCHEMA, LIST_NAME)
99+
100+
for field in saved_domain.fields:
101+
if field.name == "formatted":
102+
assert field.conditional_formats.__len__() == 1
103+
assert field.conditional_formats[0].filter == 'format.column~lt=10&format.column~gt=100'
104+
105+
106+
@pytest.mark.xfail # this reproduces https://www.labkey.org/home/Developer/issues/issues-details.view?issueId=41318
107+
def test_add_malformed_query_filter(server_context, list_fixture):
108+
new_conditional_format = conditional_format(
109+
query_filter='this-is-a-badly-formed-filter',
110+
text_color='ff0055',
111+
background_color='ffffff',
112+
bold=True,
113+
italic=False,
114+
strike_through=False
115+
)
116+
117+
for field in list_fixture.fields:
118+
if field.name == 'formatted':
119+
field.conditional_formats = []
120+
field.conditional_formats = [new_conditional_format]
121+
122+
save(server_context, LISTS_SCHEMA, LIST_NAME, list_fixture)
123+
saved_domain = get(server_context, LISTS_SCHEMA, LIST_NAME)
124+
125+
for field in saved_domain.fields:
126+
if field.name == "formatted":
127+
assert field.conditional_formats[0].filter != 'this-is-a-badly-formed-filter', \
128+
"api should discard meaningless filters"
129+
130+
131+
def test_add_conditional_format_with_missing_filter(server_context, list_fixture):
132+
missing_filter_type_filter = QueryFilter('formatted', 13)
133+
new_conditional_format = conditional_format(
134+
query_filter=missing_filter_type_filter,
135+
text_color='ff0055',
136+
background_color='ffffff',
137+
bold=True,
138+
italic=False,
139+
strike_through=False
140+
)
141+
142+
for field in list_fixture.fields:
143+
if field.name == 'formatted':
144+
field.conditional_formats = []
145+
field.conditional_formats = [new_conditional_format]
146+
147+
save(server_context, LISTS_SCHEMA, LIST_NAME, list_fixture)
148+
saved_domain = get(server_context, LISTS_SCHEMA, LIST_NAME)
149+
150+
for field in saved_domain.fields:
151+
if field.name == "formatted":
152+
assert field.conditional_formats[0].filter == 'format.column~eq=13'
153+
154+
155+
def test_remove_conditional_format(server_context, list_fixture):
156+
for field in list_fixture.fields:
157+
if field.name == 'formatted':
158+
field.conditional_formats = []
159+
160+
save(server_context, LISTS_SCHEMA, LIST_NAME, list_fixture)
161+
saved_domain = get(server_context, LISTS_SCHEMA, LIST_NAME)
162+
163+
for field in saved_domain.fields:
164+
if field.name == "formatted":
165+
assert len(field.conditional_formats) == 0
166+
167+
168+
def test_update_conditional_format_serialize_filter(server_context, list_fixture):
169+
from labkey.query import QueryFilter
170+
new_filter = QueryFilter('formatted', 15, QueryFilter.Types.GREATER_THAN_OR_EQUAL)
171+
cf = conditional_format(new_filter, text_color="ff00ff")
172+
173+
for field in list_fixture.fields:
174+
if field.name == 'formatted':
175+
field.conditional_formats[0] = cf
176+
177+
save(server_context, LISTS_SCHEMA, LIST_NAME, list_fixture)
178+
saved_domain = get(server_context, LISTS_SCHEMA, LIST_NAME)
179+
180+
for field in saved_domain.fields:
181+
if field.name == 'formatted':
182+
assert field.conditional_formats[0].filter == 'format.column~gte=15'
183+
184+
185+
def test_update_conditional_format_plain_text(server_context, list_fixture):
186+
new_filter = "formatted~gte=15"
187+
188+
for field in list_fixture.fields:
189+
if field.name == 'formatted':
190+
field.conditional_formats[0].filter = new_filter
191+
192+
save(server_context, LISTS_SCHEMA, LIST_NAME, list_fixture)
193+
saved_domain = get(server_context, LISTS_SCHEMA, LIST_NAME)
194+
195+
for field in saved_domain.fields:
196+
if field.name == 'formatted':
197+
assert field.conditional_formats[0].filter == new_filter
198+
199+
200+
def test_create_list_with_conditional_formatted_field(server_context):
201+
composed_list_definition = {
202+
'kind': 'IntList',
203+
'domainDesign': {
204+
'name': 'composed_list_name',
205+
'fields': [
206+
{
207+
'name': 'rowId',
208+
'rangeURI': 'int'
209+
},
210+
{
211+
'name': 'formatted',
212+
'rangeURI': 'int',
213+
'conditionalFormats': [
214+
SERIALIZED_CONDITIONAL_FORMAT,
215+
{
216+
'filter': 'format.column~gte=25',
217+
'textcolor': 'ff0000',
218+
'backgroundcolor': 'ffffff',
219+
'bold': True,
220+
'italic': False,
221+
'strikethrough': False
222+
}
223+
]
224+
}
225+
]
226+
},
227+
'options': {
228+
'keyName': 'rowId',
229+
'keyType': 'AutoIncrementInteger'
230+
}
231+
}
232+
create(server_context, composed_list_definition)
233+
created_list = get(server_context, LISTS_SCHEMA, 'composed_list_name')
234+
for field in created_list.fields:
235+
if field.name == 'formatted':
236+
assert len(field.conditional_formats) == 2
237+
238+
drop(server_context, LISTS_SCHEMA, 'composed_list_name')

0 commit comments

Comments
 (0)