Skip to content

Commit 7694ad7

Browse files
labkey-chrisjlabkey-alanlabkey-tchad
authored
Fb integration tests (#40)
* Add integration marker and integration test suite * remove coverage from default pytest options it prevents us from being able to use the Python Debugger in Intellij/PyCharm * Fix typo * Add option to disable CSRF to ServerContext - Remove global DISABLE_CSRF_CHECK - Explicitly define args/kwargs for ServerContext, effectively making create_server_context useless. Will deprecate and remove at a later date. * Add dataset test fixture, create_dataset and create_dupicate_dataset tests * Add container.py Users can now create and delete containers with the python API * Upper case constants * Add study creation code to test_integration.py * add unit tests for endpoint testing of qc states * Clean up test_integration.py * Use python naming conventions in query_examples.py * Use python naming conventions in test_integration.py * use python naming conventions in sample files * Read properties file from TC during integration tests * Fix copy/paste error Co-authored-by: Alan Vezina <[email protected]> Co-authored-by: Trey Chadick <[email protected]>
1 parent a8ad6db commit 7694ad7

14 files changed

+327
-89
lines changed

MANIFEST.in

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
include *.txt
2-
recursive-include docs *.txt
2+
include pytest.ini
3+
recursive-include docs *.txt

README.md

+7
Original file line numberDiff line numberDiff line change
@@ -125,5 +125,12 @@ Then, the tests can be run with
125125
$ pytest .
126126
```
127127

128+
The integration tests do not run by default. If you want to run the integration tests make sure you have a live server
129+
running, a netrc file, and run the following command:
130+
131+
```bash
132+
$ pytest . -m "integration"
133+
```
134+
128135
### Maintainers
129136
Package maintainer's can reference the [Python Package Maintenance](https://docs.google.com/document/d/13nVxwyctH4YZ6gDhcrOu9Iz6qGFPAxicE1VHiVYpw9A/) document (requires permission) for updating releases.

labkey/container.py

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from labkey.utils import json_dumps, ServerContext
2+
3+
4+
def create(server_context, name, container_path=None, description=None, folderType=None, isWorkbook=None, title=None):
5+
# type: (ServerContext, str, str, str, str, bool, str) -> dict
6+
"""
7+
Create a container in LabKey.
8+
9+
:param server_context: A LabKey server context. See utils.create_server_context.
10+
:param name: The name of the container.
11+
:param container_path: the path of where you want to create the container.
12+
:param description: a description for the container.
13+
:param folderType: the desired folder type for the container.
14+
:param isWorkbook: sets whether the container is a workbook.
15+
:param title: the title for the container.
16+
:return:
17+
"""
18+
headers = {'Content-Type': 'application/json'}
19+
url = server_context.build_url('core', 'createContainer.api', container_path)
20+
payload = {
21+
'description': description,
22+
'folderType': folderType,
23+
'isWorkbook': isWorkbook,
24+
'name': name,
25+
'title': title,
26+
}
27+
return server_context.make_request(url, json_dumps(payload), headers=headers)
28+
29+
30+
def delete(server_context, container_path=None):
31+
# type: (ServerContext, str)
32+
"""
33+
Deletes a container at the given container_path, or at the server_context's container path
34+
:param server_context: A LabKey server context. See utils.create_server_context.
35+
:param container_path: The path of the container to delete.
36+
:return:
37+
"""
38+
headers = {'Content-Type': 'application/json'}
39+
url = server_context.build_url('core', 'deleteContainer.api', container_path)
40+
return server_context.make_request(url, None, headers=headers)

labkey/security.py

+10-10
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
#
1616
from __future__ import unicode_literals
1717

18-
security_controller = 'security'
19-
user_controller = 'user'
18+
SECURITY_CONTROLLER = 'security'
19+
USER_CONTROLLER = 'user'
2020

2121

2222
def activate_users(server_context, target_ids, container_path=None):
@@ -66,7 +66,7 @@ def create_user(server_context, email, container_path=None, send_email=False):
6666
:param send_email: true to send email notification to user
6767
:return:
6868
"""
69-
url = server_context.build_url(security_controller, 'createNewUser.api', container_path)
69+
url = server_context.build_url(SECURITY_CONTROLLER, 'createNewUser.api', container_path)
7070
payload = {
7171
'email': email,
7272
'sendEmail': send_email
@@ -116,7 +116,7 @@ def get_roles(server_context, container_path=None):
116116
:param container_path:
117117
:return:
118118
"""
119-
url = server_context.build_url(security_controller, 'getRoles.api', container_path=container_path)
119+
url = server_context.build_url(SECURITY_CONTROLLER, 'getRoles.api', container_path=container_path)
120120
return server_context.make_request(url, None)
121121

122122

@@ -127,7 +127,7 @@ def get_user_by_email(server_context, email):
127127
:param email:
128128
:return:
129129
"""
130-
url = server_context.build_url(user_controller, 'getUsers.api')
130+
url = server_context.build_url(USER_CONTROLLER, 'getUsers.api')
131131
payload = dict(includeDeactivatedAccounts=True)
132132
result = server_context.make_request(url, payload)
133133

@@ -142,7 +142,7 @@ def get_user_by_email(server_context, email):
142142

143143

144144
def list_groups(server_context, include_site_groups=False, container_path=None):
145-
url = server_context.build_url(security_controller, 'listProjectGroups.api', container_path)
145+
url = server_context.build_url(SECURITY_CONTROLLER, 'listProjectGroups.api', container_path)
146146

147147
return server_context.make_request(url, {
148148
'includeSiteGroups': include_site_groups
@@ -184,7 +184,7 @@ def reset_password(server_context, email, container_path=None):
184184
:param container_path:
185185
:return:
186186
"""
187-
url = server_context.build_url(security_controller, 'adminRotatePassword.api', container_path)
187+
url = server_context.build_url(SECURITY_CONTROLLER, 'adminRotatePassword.api', container_path)
188188

189189
return server_context.make_request(url, {
190190
'email': email
@@ -201,7 +201,7 @@ def __make_security_group_api_request(server_context, api, user_ids, group_id, c
201201
:param container_path: Additional container context path
202202
:return: Request json object
203203
"""
204-
url = server_context.build_url(security_controller, api, container_path)
204+
url = server_context.build_url(SECURITY_CONTROLLER, api, container_path)
205205

206206
# if user_ids is only a single scalar make it an array
207207
if not hasattr(user_ids, "__iter__"):
@@ -226,7 +226,7 @@ def __make_security_role_api_request(server_context, api, role, email=None, user
226226
if email is None and user_id is None:
227227
raise ValueError("Must supply either/both [email] or [user_id]")
228228

229-
url = server_context.build_url(security_controller, api, container_path)
229+
url = server_context.build_url(SECURITY_CONTROLLER, api, container_path)
230230

231231
return server_context.make_request(url, {
232232
'roleClassName': role['uniqueName'],
@@ -244,7 +244,7 @@ def __make_user_api_request(server_context, target_ids, api, container_path=None
244244
:param container_path: container context
245245
:return: response json
246246
"""
247-
url = server_context.build_url(user_controller, api, container_path)
247+
url = server_context.build_url(USER_CONTROLLER, api, container_path)
248248

249249
return server_context.make_request(url, {
250250
'userId': target_ids

labkey/utils.py

+18-20
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,18 @@
2727
__default_timeout = 60 * 5 # 5 minutes
2828
API_KEY_TOKEN = 'apikey'
2929
CSRF_TOKEN = 'X-LABKEY-CSRF'
30-
DISABLE_CSRF_CHECK = False # Used by tests to disable CSRF token check
3130

3231

3332
class ServerContext(object):
34-
35-
def __init__(self, **kwargs):
36-
self._container_path = kwargs.pop('container_path', None)
37-
self._context_path = kwargs.pop('context_path', None)
38-
self._domain = kwargs.pop('domain', None)
39-
self._use_ssl = kwargs.pop('use_ssl', True)
40-
self._verify_ssl = kwargs.pop('verify_ssl', True)
41-
self._api_key = kwargs.pop('api_key', None)
42-
33+
def __init__(self, domain, container_path, context_path=None, use_ssl=True, verify_ssl=True, api_key=None,
34+
disable_csrf=False):
35+
self._container_path = container_path
36+
self._context_path = context_path
37+
self._domain = domain
38+
self._use_ssl = use_ssl
39+
self._verify_ssl = verify_ssl
40+
self._api_key = api_key
41+
self._disable_csrf = disable_csrf
4342
self._session = requests.Session()
4443

4544
if self._use_ssl:
@@ -50,9 +49,7 @@ def __init__(self, **kwargs):
5049
self._scheme = 'http://'
5150

5251
def __repr__(self):
53-
return '<ServerContext [ {} | {} | {} ]>'.format(self._domain,
54-
self._context_path,
55-
self._container_path)
52+
return '<ServerContext [ {} | {} | {} ]>'.format(self._domain, self._context_path, self._container_path)
5653

5754
def build_url(self, controller, action, container_path=None):
5855
# type: (self, str, str, str) -> str
@@ -83,7 +80,7 @@ def make_request(self, url, payload, headers=None, timeout=300, method='POST',
8380
API_KEY_TOKEN: self._api_key
8481
})
8582

86-
if not DISABLE_CSRF_CHECK:
83+
if not self._disable_csrf:
8784
global CSRF_TOKEN
8885

8986
# CSRF check
@@ -111,8 +108,9 @@ def make_request(self, url, payload, headers=None, timeout=300, method='POST',
111108
handle_request_exception(e, server_context=self)
112109

113110

114-
def create_server_context(domain, container_path, context_path=None, use_ssl=True, verify_ssl=True, api_key=None):
115-
# type: (str, str, str, bool, bool, str) -> ServerContext
111+
def create_server_context(domain, container_path, context_path=None, use_ssl=True, verify_ssl=True, api_key=None,
112+
disable_csrf=False):
113+
# type: (str, str, str, bool, bool, str, bool) -> ServerContext
116114
"""
117115
Create a LabKey server context. This context is used to encapsulate properties
118116
about the LabKey server that is being requested against. This includes, but is not limited to,
@@ -123,19 +121,19 @@ def create_server_context(domain, container_path, context_path=None, use_ssl=Tru
123121
:param use_ssl:
124122
:param verify_ssl:
125123
:param api_key:
124+
:param disable_csrf:
126125
:return:
127126
"""
128-
config = dict(
127+
return ServerContext(
129128
domain=domain,
130129
container_path=container_path,
131130
context_path=context_path,
132131
use_ssl=use_ssl,
133132
verify_ssl=verify_ssl,
134-
api_key=api_key
133+
api_key=api_key,
134+
disable_csrf=disable_csrf,
135135
)
136136

137-
return ServerContext(**config)
138-
139137

140138
def build_url(server_context, controller, action, container_path=None):
141139
# type: (ServerContext, str, str, str) -> str

pytest.ini

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[pytest]
2+
addopts = -v -m "not integration"
3+
markers =
4+
integration: mark tests as integration, they will not be run by default (to run add '-m "integration"')

samples/experiment_example.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@
3030
###################
3131

3232
# Generate the Run object(s)
33-
runTest = Run()
34-
runTest.name = 'python upload'
35-
runTest.data_rows = [{
33+
run_test = Run()
34+
run_test.name = 'python upload'
35+
run_test.data_rows = [{
3636
# ColumnName: Value
3737
'SampleId': 'Monkey 1',
3838
'TimePoint': '2008/11/02 11:22:33',
@@ -49,11 +49,11 @@
4949
'DoubleData': 1.5,
5050
'HiddenData': 'jimbo'
5151
}]
52-
runTest.properties['RunFieldName'] = 'Run Field Value'
52+
run_test.properties['RunFieldName'] = 'Run Field Value'
5353

5454
# Generate the Batch object(s)
5555
batch = Batch()
56-
batch.runs = [runTest]
56+
batch.runs = [run_test]
5757
batch.name = 'python batch'
5858
batch.properties['PropertyName'] = 'Property Value'
5959

samples/experiment_platemetadata_example.py

+8-7
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@
3030
###################
3131

3232
# Generate the Run object(s)
33-
runTest = Run()
34-
runTest.name = 'python upload'
35-
runTest.data_rows = [{
33+
run_test = Run()
34+
run_test.name = 'python upload'
35+
run_test.data_rows = [{
3636
# ColumnName: Value
3737
'ParticipantId' : '1234',
3838
'VisitId' : 111,
@@ -47,11 +47,12 @@
4747
'WellLocation' : 'F12'
4848
}]
4949

50-
# Assays that are configured for plate support have a required run property for the plate template, this is the plate template lsid
51-
runTest.properties['PlateTemplate'] = 'urn:lsid:labkey.com:PlateTemplate.Folder-6:d8bbec7d-34cd-1038-bd67-b3bd777822f8'
50+
# Assays that are configured for plate support have a required run property for the plate template, this is the plate
51+
# template lsid
52+
run_test.properties['PlateTemplate'] = 'urn:lsid:labkey.com:PlateTemplate.Folder-6:d8bbec7d-34cd-1038-bd67-b3bd777822f8'
5253

5354
# The assay plate metadata is a specially formatted JSON object to map properties to the well groups
54-
runTest.plate_metadata = {
55+
run_test.plate_metadata = {
5556
'control' : {
5657
'positive' : {'dilution': 0.005},
5758
'negative' : {'dilution': 1.0}
@@ -66,7 +67,7 @@
6667

6768
# Generate the Batch object(s)
6869
batch = Batch()
69-
batch.runs = [runTest]
70+
batch.runs = [run_test]
7071
batch.name = 'python batch'
7172
batch.properties['PropertyName'] = 'Property Value'
7273

0 commit comments

Comments
 (0)