diff --git a/dataservice/__init__.py b/dataservice/__init__.py index 2cf354e2c..1e565fbbc 100644 --- a/dataservice/__init__.py +++ b/dataservice/__init__.py @@ -23,7 +23,6 @@ from config import config from sqlalchemy.exc import IntegrityError -from werkzeug.exceptions import HTTPException def create_app(config_name): @@ -119,10 +118,10 @@ def register_error_handlers(app): NB: Exceptions to be handled must be imported in the head of this module """ from dataservice.api import errors - app.register_error_handler(HTTPException, errors.http_error) app.register_error_handler(IntegrityError, errors.integrity_error) - app.register_error_handler(404, errors.http_error) - app.register_error_handler(400, errors.http_error) + from werkzeug.exceptions import default_exceptions + for ex in default_exceptions: + app.register_error_handler(ex, errors.http_error) def register_blueprints(app): diff --git a/dataservice/api/common/schemas.py b/dataservice/api/common/schemas.py index 1c3db9d01..40357cbc5 100644 --- a/dataservice/api/common/schemas.py +++ b/dataservice/api/common/schemas.py @@ -1,12 +1,12 @@ from dataservice.extensions import ma from marshmallow import ( - fields, - post_dump, - pre_dump, - validates_schema, - ValidationError + fields, + post_dump, + pre_dump, + validates_schema, + ValidationError ) -from flask import url_for, request +from flask import url_for from dataservice.api.common.pagination import Pagination from flask_marshmallow import Schema @@ -120,8 +120,8 @@ class StatusSchema(Schema): commit = fields.Str(description='API short commit hash', example='aef3b5a') branch = fields.Str(description='API branch name', example='master') tags = fields.List( - fields.String(description='Any tags associated with the version', - example=['rc', 'beta'])) + fields.String(description='Any tags associated with the version', + example=['rc', 'beta'])) @post_dump(pass_many=False) def wrap_envelope(self, data): diff --git a/dataservice/api/common/views.py b/dataservice/api/common/views.py index 593341082..5d0a1ffa4 100644 --- a/dataservice/api/common/views.py +++ b/dataservice/api/common/views.py @@ -1,7 +1,6 @@ import yaml import jinja2 from flask.views import MethodView -from flask import render_template from dataservice.api.common.schemas import ( response_generator, paginated_generator @@ -42,9 +41,9 @@ def register_spec(cls, spec): for name, schema in c.schemas.items(): spec.definition(name, schema=schema) ResponseSchema = response_generator(schema) - spec.definition(name+'Response', schema=ResponseSchema) + spec.definition(name + 'Response', schema=ResponseSchema) PaginatedSchema = paginated_generator(schema) - spec.definition(name+'Paginated', schema=PaginatedSchema) + spec.definition(name + 'Paginated', schema=PaginatedSchema) @staticmethod def register_views(app): @@ -80,7 +79,7 @@ def _format_docstring(func): # No yaml section if yaml_start == -1: return - yaml_spec = yaml.safe_load(func.__doc__[yaml_start+3:]) + yaml_spec = yaml.safe_load(func.__doc__[yaml_start + 3:]) # No template to insert if 'template' not in yaml_spec: @@ -95,7 +94,7 @@ def _format_docstring(func): templated_spec.update(yaml_spec) # Dump the deserialized spec back to yaml in the docstring - func.__doc__ = func.__doc__[:yaml_start+4] + func.__doc__ = func.__doc__[:yaml_start + 4] func.__doc__ += yaml.dump(templated_spec, default_flow_style=False) # The docstring is now ready for further processing by apispec diff --git a/dataservice/api/demographic/resources.py b/dataservice/api/demographic/resources.py index 268f1274b..3f9f727da 100644 --- a/dataservice/api/demographic/resources.py +++ b/dataservice/api/demographic/resources.py @@ -1,5 +1,4 @@ from flask import abort, request -from sqlalchemy.orm.exc import NoResultFound from marshmallow import ValidationError from dataservice.extensions import db @@ -83,26 +82,16 @@ def get(self, kf_id): resource: Demographic """ + dm = Demographic.query.get(kf_id) + if dm is None: + abort(404, 'could not find {} `{}`' + .format('demographic', kf_id)) - # Get all - if kf_id is None: - d = Demographic.query.all() - return DemographicSchema(many=True).jsonify(d) - # Get one - else: - try: - d = Demographic.query.filter_by(kf_id=kf_id).one() - # Not found in database - except NoResultFound: - abort(404, 'could not find {} `{}`' - .format('demographic', kf_id)) - return DemographicSchema().jsonify(d) - - def put(self, kf_id): - """ - Update existing demographic + return DemographicSchema().jsonify(dm) - Update an existing demographic given a Kids First id + def patch(self, kf_id): + """ + Update an existing demographic. Allows partial update of resource --- template: path: @@ -111,34 +100,25 @@ def put(self, kf_id): resource: Demographic """ - body = request.json - - # Check if demographic exists - try: - d1 = Demographic.query.filter_by(kf_id=kf_id).one() - # Not found in database - except NoResultFound: - abort(404, 'could not find {} `{}`'.format('demographic', kf_id)) + dm = Demographic.query.get(kf_id) + if dm is None: + abort(404, 'could not find {} `{}`' + .format('demographic', kf_id)) - # Validation only + # Partial update - validate but allow missing required fields + body = request.json or {} try: - d = DemographicSchema(strict=True).load(body).data - # Request body not valid - except ValidationError as e: - abort(400, 'could not update demographic: {}'.format(e.messages)) + dm = DemographicSchema(strict=True).load(body, instance=dm, + partial=True).data + except ValidationError as err: + abort(400, 'could not update demographic: {}'.format(err.messages)) - # Deserialize - d1.external_id = body.get('external_id') - d1.race = body.get('race') - d1.gender = body.get('gender') - d1.ethnicity = body.get('ethnicity') - d1.participant_id = body.get('participant_id') - - # Save to database + db.session.add(dm) db.session.commit() - return DemographicSchema(200, 'demographic {} updated' - .format(d1.kf_id)).jsonify(d1), 200 + return DemographicSchema( + 200, 'demographic {} updated'.format(dm.kf_id) + ).jsonify(dm), 200 def delete(self, kf_id): """ @@ -154,15 +134,14 @@ def delete(self, kf_id): Demographic """ # Check if demographic exists - try: - d = Demographic.query.filter_by(kf_id=kf_id).one() - # Not found in database - except NoResultFound: - abort(404, 'could not find {} `{}`'.format('demographic', kf_id)) + dm = Demographic.query.get(kf_id) + if dm is None: + abort(404, 'could not find {} `{}`' + .format('demographic', kf_id)) # Save in database - db.session.delete(d) + db.session.delete(dm) db.session.commit() return DemographicSchema(200, 'demographic {} deleted' - .format(d.kf_id)).jsonify(d), 200 + .format(dm.kf_id)).jsonify(dm), 200 diff --git a/dataservice/api/demographic/schemas.py b/dataservice/api/demographic/schemas.py index fc5d4b3d0..5e1dd938a 100644 --- a/dataservice/api/demographic/schemas.py +++ b/dataservice/api/demographic/schemas.py @@ -5,11 +5,6 @@ class DemographicSchema(BaseSchema): - # Should not have to do this, since participant_id is part of the - # Demographic model and should be dumped. However it looks like this is - # still a bug in marshmallow_sqlalchemy. The bug is that ma sets - # dump_only=True for foreign keys by default. See link below - # https://github.com/marshmallow-code/marshmallow-sqlalchemy/issues/20 participant_id = field_for(Demographic, 'participant_id', required=True, load_only=True) diff --git a/dataservice/api/diagnosis/resources.py b/dataservice/api/diagnosis/resources.py index 4d08312ee..9b8edd3ae 100644 --- a/dataservice/api/diagnosis/resources.py +++ b/dataservice/api/diagnosis/resources.py @@ -1,5 +1,4 @@ from flask import abort, request -from sqlalchemy.orm.exc import NoResultFound from marshmallow import ValidationError from dataservice.extensions import db @@ -84,19 +83,15 @@ def get(self, kf_id): Diagnosis """ # Get one - try: - d = Diagnosis.query.filter_by(kf_id=kf_id).one() - # Not found in database - except NoResultFound: + dg = Diagnosis.query.get(kf_id) + if dg is None: abort(404, 'could not find {} `{}`' .format('diagnosis', kf_id)) - return DiagnosisSchema().jsonify(d) + return DiagnosisSchema().jsonify(dg) - def put(self, kf_id): + def patch(self, kf_id): """ - Update existing diagnosis - - Update an existing diagnosis given a Kids First id + Update an existing diagnosis. Allows partial update of resource --- template: path: @@ -105,34 +100,25 @@ def put(self, kf_id): resource: Diagnosis """ - body = request.json - try: - # Check if diagnosis exists - d1 = Diagnosis.query.filter_by(kf_id=kf_id).one() - # Not found in database - except NoResultFound: - abort(404, 'could not find {} `{}`'.format('diagnosis', kf_id)) + dg = Diagnosis.query.get(kf_id) + if dg is None: + abort(404, 'could not find {} `{}`' + .format('diagnosis', kf_id)) - # Validation only + # Partial update - validate but allow missing required fields + body = request.json or {} try: - d = DiagnosisSchema(strict=True).load(body).data - # Request body not valid - except ValidationError as e: - abort(400, 'could not update diagnosis: {}'.format(e.messages)) + dg = DiagnosisSchema(strict=True).load(body, instance=dg, + partial=True).data + except ValidationError as err: + abort(400, 'could not update diagnosis: {}'.format(err.messages)) - # Deserialize - d1.external_id = body.get('external_id') - d1.diagnosis = body.get('diagnosis') - d1.diagnosis_category = body.get('diagnosis_category') - d1.tumor_location = body.get('tumor_location') - d1.age_at_event_days = body.get('age_at_event_days') - d1.participant_id = body.get('participant_id') - - # Save to database + db.session.add(dg) db.session.commit() - return DiagnosisSchema(200, 'diagnosis {} updated' - .format(d1.kf_id)).jsonify(d1), 200 + return DiagnosisSchema( + 200, 'diagnosis {} updated'.format(dg.kf_id) + ).jsonify(dg), 200 def delete(self, kf_id): """ @@ -149,15 +135,14 @@ def delete(self, kf_id): """ # Check if diagnosis exists - try: - d = Diagnosis.query.filter_by(kf_id=kf_id).one() - # Not found in database - except NoResultFound: - abort(404, 'could not find {} `{}`'.format('diagnosis', kf_id)) + dg = Diagnosis.query.get(kf_id) + if dg is None: + abort(404, 'could not find {} `{}`' + .format('diagnosis', kf_id)) # Save in database - db.session.delete(d) + db.session.delete(dg) db.session.commit() return DiagnosisSchema(200, 'diagnosis {} deleted' - .format(d.kf_id)).jsonify(d), 200 + .format(dg.kf_id)).jsonify(dg), 200 diff --git a/dataservice/api/diagnosis/schemas.py b/dataservice/api/diagnosis/schemas.py index d2835f7d5..13d1de897 100644 --- a/dataservice/api/diagnosis/schemas.py +++ b/dataservice/api/diagnosis/schemas.py @@ -1,5 +1,4 @@ from marshmallow_sqlalchemy import field_for -from marshmallow import validates, ValidationError from dataservice.api.diagnosis.models import Diagnosis from dataservice.api.common.schemas import BaseSchema @@ -8,11 +7,6 @@ class DiagnosisSchema(BaseSchema): - # Should not have to do this, since participant_id is part of the - # Diagnosis model and should be dumped. However it looks like this is - # still a bug in marshmallow_sqlalchemy. The bug is that ma sets - # dump_only=True for foreign keys by default. See link below - # https://github.com/marshmallow-code/marshmallow-sqlalchemy/issues/20 participant_id = field_for(Diagnosis, 'participant_id', required=True, load_only=True, example='DZB048J5') age_at_event_days = field_for(Diagnosis, 'age_at_event_days', diff --git a/dataservice/api/docs/resources.py b/dataservice/api/docs/resources.py index 6043a987e..273276cd7 100644 --- a/dataservice/api/docs/resources.py +++ b/dataservice/api/docs/resources.py @@ -1,6 +1,4 @@ -import os import glob -import subprocess from flask.views import View from flask import jsonify, current_app, render_template diff --git a/dataservice/api/participant/resources.py b/dataservice/api/participant/resources.py index 4b58d32aa..205f3f564 100644 --- a/dataservice/api/participant/resources.py +++ b/dataservice/api/participant/resources.py @@ -1,6 +1,4 @@ -from flask import abort, request, current_app -from flask.views import MethodView -from sqlalchemy.orm.exc import NoResultFound +from flask import abort, request from sqlalchemy.orm import joinedload from marshmallow import ValidationError @@ -83,16 +81,16 @@ def get(self, kf_id): resource: Participant """ - try: - participant = Participant.query.filter_by(kf_id=kf_id).one() - except NoResultFound: + p = Participant.query.get(kf_id) + if p is None: abort(404, 'could not find {} `{}`' - .format('Participant', kf_id)) - return ParticipantSchema().jsonify(participant) + .format('participant', kf_id)) + + return ParticipantSchema().jsonify(p) - def put(self, kf_id): + def patch(self, kf_id): """ - Update an existing participant + Update an existing participant. Allows partial update --- template: path: @@ -101,23 +99,25 @@ def put(self, kf_id): resource: Participant """ - body = request.json - try: - p = Participant.query.filter_by(kf_id=kf_id).one() - except NoResultFound: + p = Participant.query.get(kf_id) + if p is None: abort(404, 'could not find {} `{}`' - .format('Participant', kf_id)) + .format('participant', kf_id)) + + # Partial update - validate but allow missing required fields + body = request.json or {} + try: + p = ParticipantSchema(strict=True).load(body, instance=p, + partial=True).data + except ValidationError as err: + abort(400, 'could not update participant: {}'.format(err.messages)) - p.external_id = body.get('external_id') - p.family_id = body.get('family_id') - p.is_proband = body.get('is_proband') - p.consent_type = body.get('consent_type') - p.study_id = body.get('study_id') + db.session.add(p) db.session.commit() return ParticipantSchema( - 201, 'participant {} updated'.format(p.kf_id) - ).jsonify(p), 201 + 200, 'participant {} updated'.format(p.kf_id) + ).jsonify(p), 200 def delete(self, kf_id): """ @@ -130,10 +130,10 @@ def delete(self, kf_id): resource: Participant """ - try: - p = Participant.query.filter_by(kf_id=kf_id).one() - except NoResultFound: - abort(404, 'could not find {} `{}`'.format('Participant', kf_id)) + p = Participant.query.get(kf_id) + if p is None: + abort(404, 'could not find {} `{}`' + .format('participant', kf_id)) db.session.delete(p) db.session.commit() diff --git a/dataservice/api/sample/resources.py b/dataservice/api/sample/resources.py index d6c2d448c..21853701b 100644 --- a/dataservice/api/sample/resources.py +++ b/dataservice/api/sample/resources.py @@ -1,5 +1,4 @@ from flask import abort, request -from sqlalchemy.orm.exc import NoResultFound from marshmallow import ValidationError from dataservice.extensions import db @@ -83,19 +82,15 @@ def get(self, kf_id): resource: Sample """ - try: - s = Sample.query.filter_by(kf_id=kf_id).one() - # Not found in database - except NoResultFound: + sa = Sample.query.get(kf_id) + if sa is None: abort(404, 'could not find {} `{}`' .format('sample', kf_id)) - return SampleSchema().jsonify(s) + return SampleSchema().jsonify(sa) - def put(self, kf_id): + def patch(self, kf_id): """ - Update existing sample - - Update an existing sample given a Kids First id + Update an existing sample. Allows partial update of resource --- template: path: @@ -104,37 +99,25 @@ def put(self, kf_id): resource: Sample """ - body = request.json + sa = Sample.query.get(kf_id) + if sa is None: + abort(404, 'could not find {} `{}`' + .format('sample', kf_id)) - # Check if sample exists + # Partial update - validate but allow missing required fields + body = request.json or {} try: - s1 = Sample.query.filter_by(kf_id=kf_id).one() - # Not found in database - except NoResultFound: - abort(404, 'could not find {} `{}`'.format('sample', kf_id)) + sa = SampleSchema(strict=True).load(body, instance=sa, + partial=True).data + except ValidationError as err: + abort(400, 'could not update sample: {}'.format(err.messages)) - # Validation only - try: - s = SampleSchema(strict=True).load(body).data - # Request body not valid - except ValidationError as e: - abort(400, 'could not update sample: {}'.format(e.messages)) - - # Deserialize - s1.external_id = body.get('external_id') - s1.tissue_type = body.get('tissue_type') - s1.composition = body.get('composition') - s1.anatomical_site = body.get('anatomical_site') - s1.tumor_descriptor = body.get('tumor_descriptor') - s1.aliquots = body.get('aliquots', []) - s1.age_at_event_days = body.get('age_at_event_days') - s1.participant_id = body.get('participant_id') - - # Save to database + db.session.add(sa) db.session.commit() - return SampleSchema(200, 'sample {} updated' - .format(s1.kf_id)).jsonify(s1), 200 + return SampleSchema( + 200, 'sample {} updated'.format(sa.kf_id) + ).jsonify(sa), 200 def delete(self, kf_id): """ @@ -151,15 +134,14 @@ def delete(self, kf_id): """ # Check if sample exists - try: - s = Sample.query.filter_by(kf_id=kf_id).one() - # Not found in database - except NoResultFound: - abort(404, 'could not find {} `{}`'.format('sample', kf_id)) + sa = Sample.query.get(kf_id) + if sa is None: + abort(404, 'could not find {} `{}`' + .format('sample', kf_id)) # Save in database - db.session.delete(s) + db.session.delete(sa) db.session.commit() return SampleSchema(200, 'sample {} deleted' - .format(s.kf_id)).jsonify(s), 200 + .format(sa.kf_id)).jsonify(sa), 200 diff --git a/dataservice/api/sample/schemas.py b/dataservice/api/sample/schemas.py index 9ee22d8dc..d776a181d 100644 --- a/dataservice/api/sample/schemas.py +++ b/dataservice/api/sample/schemas.py @@ -7,11 +7,6 @@ class SampleSchema(BaseSchema): - # Should not have to do this, since participant_id is part of the - # Sample model and should be dumped. However it looks like this is - # still a bug in marshmallow_sqlalchemy. The bug is that ma sets - # dump_only=True for foreign keys by default. See link below - # https://github.com/marshmallow-code/marshmallow-sqlalchemy/issues/20 participant_id = field_for(Sample, 'participant_id', required=True, load_only=True) age_at_event_days = field_for(Sample, 'age_at_event_days', diff --git a/dataservice/api/templates/update_by_id.yml b/dataservice/api/templates/update_by_id.yml index 1c2a46e28..71c532002 100644 --- a/dataservice/api/templates/update_by_id.yml +++ b/dataservice/api/templates/update_by_id.yml @@ -1,4 +1,4 @@ -description: Update a {{ resource.lower() }} +description: Partial update of a {{ resource.lower() }} tags: - {{ resource }} parameters: diff --git a/dataservice/extensions.py b/dataservice/extensions.py index 10fdfea5b..2e8e96b68 100644 --- a/dataservice/extensions.py +++ b/dataservice/extensions.py @@ -1,5 +1,5 @@ from flask_migrate import Migrate -from flask_sqlalchemy import SQLAlchemy, Model +from flask_sqlalchemy import SQLAlchemy from flask_marshmallow import Marshmallow db = SQLAlchemy() diff --git a/tests/conftest.py b/tests/conftest.py index 381a129eb..b729fc995 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,15 +10,11 @@ from dataservice.api.sample.models import Sample -@pytest.yield_fixture(scope='module') -def app(): - return create_app('testing') - - @pytest.yield_fixture(scope='session') def app(): yield create_app('testing') + @pytest.yield_fixture(scope='module') def client(app): app_context = app.app_context() @@ -82,14 +78,18 @@ def entities(client): # Create and save entities to db study = Study(**inputs['/studies']) - p = Participant(**inputs['/participants'], study=study) + p = Participant(**inputs['/participants']) demo = Demographic(**inputs['/demographics'], participant_id=p.kf_id) sample = Sample(**inputs['/samples'], participant_id=p.kf_id) diagnosis = Diagnosis(**inputs['/diagnoses'], participant_id=p.kf_id) p.demographic = demo p.samples = [sample] p.diagnoses = [diagnosis] - db.session.add(p) + + # Add participants to study + study.participants.extend([p]) + + db.session.add(study) db.session.commit() # Add foreign keys @@ -101,5 +101,8 @@ def entities(client): # Add kf_ids inputs['kf_ids'] = {'/participants': p.kf_id} + inputs['kf_ids'].update({'/demographics': p.demographic.kf_id}) + inputs['kf_ids'].update({'/diagnoses': diagnosis.kf_id}) + inputs['kf_ids'].update({'/samples': sample.kf_id}) return inputs diff --git a/tests/demographic/test_demographic_resources.py b/tests/demographic/test_demographic_resources.py index 374512a5d..221b49d64 100644 --- a/tests/demographic/test_demographic_resources.py +++ b/tests/demographic/test_demographic_resources.py @@ -1,5 +1,7 @@ import json from flask import url_for +from datetime import datetime +from dateutil import parser, tz from urllib.parse import urlparse from dataservice.extensions import db @@ -26,7 +28,7 @@ def test_post(self): db.session.add(study) db.session.commit() - # Create a participant + # Create a demographic p = Participant(external_id='Test subject 0', is_proband=True, study_id=study.kf_id) db.session.add(p) @@ -47,13 +49,15 @@ def test_post(self): # Check response status status_code self.assertEqual(response.status_code, 201) + # Check response content response = json.loads(response.data.decode('utf-8')) demographic = response['results'] - self.assertEqual(kwargs['external_id'], demographic['external_id']) - self.assertEqual(kwargs['race'], demographic['race']) - self.assertEqual(kwargs['ethnicity'], demographic['ethnicity']) - self.assertEqual(kwargs['gender'], demographic['gender']) + dm = Demographic.query.get(demographic.get('kf_id')) + for k, v in kwargs.items(): + if k == 'participant_id': + continue + self.assertEqual(demographic[k], getattr(dm, k)) def test_post_missing_req_params(self): """ @@ -190,117 +194,77 @@ def test_get_all(self): content = response.get('results') self.assertEqual(len(content), 1) - def test_put(self): + def test_patch(self): """ Test update existing demographic """ + # Send patch request kwargs = self._create_save_to_db() - - # Send put request + kf_id = kwargs.get('kf_id') body = { 'race': 'black or african', 'gender': 'male', 'participant_id': kwargs['participant_id'] } - response = self.client.put(url_for(DEMOGRAPHICS_URL, - kf_id=kwargs['kf_id']), - headers=self._api_headers(), - data=json.dumps(body)) + response = self.client.patch(url_for(DEMOGRAPHICS_URL, + kf_id=kf_id), + headers=self._api_headers(), + data=json.dumps(body)) # Check status code self.assertEqual(response.status_code, 200) - # Check field values got updated - response = json.loads(response.data.decode('utf-8')) - demographic = response['results'] - self.assertEqual(kwargs['kf_id'], demographic['kf_id']) - # Fields that should be None since they were not in put request body - self.assertIs(None, demographic['external_id']) - self.assertIs(None, demographic['ethnicity']) - # Fields that should be updated w values - self.assertEqual(body['race'], demographic['race']) - self.assertEqual(body['gender'], demographic['gender']) - - def test_put_not_found(self): - """ - Test update non-existent demographic - """ - # Send put request - kf_id = 'non-existent' - body = { - 'race': 'black or african', - 'gender': 'male', - 'participant_id': id_service.kf_id_generator('PT')() - } - response = self.client.put(url_for(DEMOGRAPHICS_URL, - kf_id=kf_id), - headers=self._api_headers(), - data=json.dumps(body)) - # Check status code - self.assertEqual(response.status_code, 404) - # Check response body - response = json.loads(response.data.decode("utf-8")) - # Check error message - message = 'could not find demographic' - self.assertIn(message, response['_status']['message']) - # Check database - c = Demographic.query.filter_by(kf_id=kf_id).count() - self.assertEqual(c, 0) - def test_put_bad_input(self): + # Message + resp = json.loads(response.data.decode("utf-8")) + self.assertIn('demographic', resp['_status']['message']) + self.assertIn('updated', resp['_status']['message']) + + # Content - check only patched fields are updated + demographic = resp['results'] + dm = Demographic.query.get(kf_id) + for k, v in body.items(): + self.assertEqual(v, getattr(dm, k)) + + # Content - Check remaining fields are unchanged + unchanged_keys = (set(demographic.keys()) - + set(body.keys())) + for k in unchanged_keys: + val = getattr(dm, k) + if isinstance(val, datetime): + d = val.replace(tzinfo=tz.tzutc()) + self.assertEqual(str(parser.parse(demographic[k])), str(d)) + else: + self.assertEqual(demographic[k], val) + + self.assertEqual(1, Demographic.query.count()) + + def test_patch_bad_input(self): """ - Test update existing demographic with bad input - - Participant with participant_id does not exist + Test updating an existing participant with invalid input """ - # Create and save demographic to db kwargs = self._create_save_to_db() - - # Send put request + kf_id = kwargs.get('kf_id') body = { 'race': 'black or african', 'gender': 'male', 'participant_id': 'AAAA1111' } - response = self.client.put(url_for(DEMOGRAPHICS_URL, - kf_id=kwargs['kf_id']), - headers=self._api_headers(), - data=json.dumps(body)) + response = self.client.patch(url_for(DEMOGRAPHICS_URL, + kf_id=kf_id), + headers=self._api_headers(), + data=json.dumps(body)) # Check status code self.assertEqual(response.status_code, 400) # Check response body response = json.loads(response.data.decode("utf-8")) # Check error message - message = '"AAAA1111" does not exist' + message = 'participant "AAAA1111" does not exist' self.assertIn(message, response['_status']['message']) - # Check field values - d = Demographic.query.first() - self.assertEqual(d.race, kwargs.get('race')) - self.assertEqual(d.gender, kwargs.get('gender')) - - def test_put_missing_req_params(self): - """ - Test create demographic that is missing required parameters in body - """ - # Create and save demographic to db - kwargs = self._create_save_to_db() - # Create demographic data - body = { - 'gender': 'male' - } - # Send put request - response = self.client.put(url_for(DEMOGRAPHICS_URL, - kf_id=kwargs['kf_id']), - headers=self._api_headers(), - data=json.dumps(body)) - # Check status code - self.assertEqual(response.status_code, 400) - # Check response body - response = json.loads(response.data.decode("utf-8")) - # Check error message - message = 'could not update demographic' - self.assertIn(message, response['_status']['message']) - # Check field values + # Check that properties are unchanged d = Demographic.query.first() - self.assertEqual(d.gender, kwargs['gender']) + for k, v in kwargs.items(): + if k == 'participant_id': + continue + self.assertEqual(v, getattr(d, k)) def test_delete(self): """ diff --git a/tests/diagnosis/test_diagnosis_resources.py b/tests/diagnosis/test_diagnosis_resources.py index ee02082ee..7d54fb939 100644 --- a/tests/diagnosis/test_diagnosis_resources.py +++ b/tests/diagnosis/test_diagnosis_resources.py @@ -1,6 +1,8 @@ import json from flask import url_for from urllib.parse import urlparse +from datetime import datetime +from dateutil import parser, tz from dataservice.extensions import db from dataservice.api.common import id_service @@ -22,14 +24,7 @@ def test_post(self): """ Test create a new diagnosis """ - # Create study - study = Study(external_id='phs001') - - # Create a participant - p = Participant(external_id='Test subject 0', is_proband=True, - study=study) - db.session.add(p) - db.session.commit() + kwargs = self._create_save_to_db() # Create diagnosis data kwargs = { @@ -38,7 +33,7 @@ def test_post(self): 'age_at_event_days': 365, 'diagnosis_category': 'cancer', 'tumor_location': 'Brain', - 'participant_id': p.kf_id + 'participant_id': kwargs.get('participant_id') } # Send get request response = self.client.post(url_for(DIAGNOSES_LIST_URL), @@ -47,16 +42,16 @@ def test_post(self): # Check response status status_code self.assertEqual(response.status_code, 201) + # Check response content response = json.loads(response.data.decode('utf-8')) diagnosis = response['results'] - self.assertEqual(kwargs['external_id'], diagnosis['external_id']) - self.assertEqual(kwargs['diagnosis'], diagnosis['diagnosis']) - self.assertEqual(kwargs['age_at_event_days'], - diagnosis['age_at_event_days']) - self.assertEqual(kwargs['diagnosis_category'], - diagnosis['diagnosis_category']) - self.assertEqual(kwargs['tumor_location'], diagnosis['tumor_location']) + dg = Diagnosis.query.get(diagnosis.get('kf_id')) + for k, v in kwargs.items(): + if k == 'participant_id': + continue + self.assertEqual(diagnosis[k], getattr(dg, k)) + self.assertEqual(2, Diagnosis.query.count()) def test_post_missing_req_params(self): """ @@ -190,30 +185,12 @@ def test_get(self): diagnosis = response['results'] participant_link = response['_links']['participant'] participant_id = urlparse(participant_link).path.split('/')[-1] - self.assertEqual(diagnosis['kf_id'], kwargs['kf_id']) - self.assertEqual(participant_id, - kwargs['participant_id']) - self.assertEqual(diagnosis['external_id'], kwargs['external_id']) - self.assertEqual(diagnosis['diagnosis'], kwargs['diagnosis']) - self.assertEqual(kwargs['diagnosis_category'], - diagnosis['diagnosis_category']) - self.assertEqual(kwargs['tumor_location'], diagnosis['tumor_location']) - self.assertEqual(diagnosis['age_at_event_days'], - kwargs['age_at_event_days']) - self.assertEqual(participant_id, kwargs['participant_id']) - - def test_get_not_found(self): - """ - Test get diagnosis that does not exist - """ - # Create diagnosis - kf_id = 'non_existent' - response = self.client.get(url_for(DIAGNOSES_URL, kf_id=kf_id), - headers=self._api_headers()) - self.assertEqual(response.status_code, 404) - response = json.loads(response.data.decode("utf-8")) - message = "could not find diagnosis `{}`".format(kf_id) - self.assertIn(message, response['_status']['message']) + for k, v in kwargs.items(): + if k == 'participant_id': + self.assertEqual(participant_id, + kwargs['participant_id']) + else: + self.assertEqual(diagnosis[k], diagnosis[k]) def test_get_all(self): """ @@ -228,76 +205,62 @@ def test_get_all(self): content = response.get('results') self.assertEqual(len(content), 1) - def test_put(self): + def test_patch(self): """ - Test update existing diagnosis + Test updating an existing diagnosis """ kwargs = self._create_save_to_db() + kf_id = kwargs.get('kf_id') - # Send put request + # Update existing diagnosis body = { 'diagnosis': 'hangry', 'diagnosis_category': 'birth defect', 'participant_id': kwargs['participant_id'] } - response = self.client.put(url_for(DIAGNOSES_URL, - kf_id=kwargs['kf_id']), - headers=self._api_headers(), - data=json.dumps(body)) - # Check status code + response = self.client.patch(url_for(DIAGNOSES_URL, + kf_id=kf_id), + headers=self._api_headers(), + data=json.dumps(body)) + # Status code self.assertEqual(response.status_code, 200) - # Check field values got updated - response = json.loads(response.data.decode('utf-8')) - diagnosis = response['results'] - self.assertEqual(kwargs['kf_id'], diagnosis['kf_id']) - # Fields that should be None since they were not in put request body - self.assertIs(None, diagnosis['external_id']) - self.assertIs(None, diagnosis['age_at_event_days']) - self.assertIs(None, diagnosis['tumor_location']) - # Fields that should be updated w values - self.assertEqual(body['diagnosis'], diagnosis['diagnosis']) - self.assertEqual(body['diagnosis_category'], - diagnosis['diagnosis_category']) - - def test_put_not_found(self): - """ - Test update non-existent diagnosis - """ - # Send put request - kf_id = 'non-existent' - body = {} - response = self.client.put(url_for(DIAGNOSES_URL, - kf_id=kf_id), - headers=self._api_headers(), - data=json.dumps(body)) - # Check status code - self.assertEqual(response.status_code, 404) - # Check response body - response = json.loads(response.data.decode("utf-8")) - # Check error message - message = 'could not find diagnosis' - self.assertIn(message, response['_status']['message']) - # Check database - c = Diagnosis.query.filter_by(kf_id=kf_id).count() - self.assertEqual(c, 0) - def test_put_bad_input(self): + # Message + resp = json.loads(response.data.decode("utf-8")) + self.assertIn('diagnosis', resp['_status']['message']) + self.assertIn('updated', resp['_status']['message']) + + # Content - check only patched fields are updated + diagnosis = resp['results'] + dg = Diagnosis.query.get(kf_id) + for k, v in body.items(): + self.assertEqual(v, getattr(dg, k)) + # Content - Check remaining fields are unchanged + unchanged_keys = (set(diagnosis.keys()) - + set(body.keys())) + for k in unchanged_keys: + val = getattr(dg, k) + if isinstance(val, datetime): + d = val.replace(tzinfo=tz.tzutc()) + self.assertEqual(str(parser.parse(diagnosis[k])), str(d)) + else: + self.assertEqual(diagnosis[k], val) + + self.assertEqual(1, Diagnosis.query.count()) + + def test_patch_bad_input(self): """ - Test update existing diagnosis with bad input - - Participant with participant_id does not exist + Test updating an existing participant with invalid input """ - # Create and save diagnosis to db kwargs = self._create_save_to_db() - - # Send put request + kf_id = kwargs.get('kf_id') body = { 'participant_id': 'AAAA1111' } - response = self.client.put(url_for(DIAGNOSES_URL, - kf_id=kwargs['kf_id']), - headers=self._api_headers(), - data=json.dumps(body)) + response = self.client.patch(url_for(DIAGNOSES_URL, + kf_id=kf_id), + headers=self._api_headers(), + data=json.dumps(body)) # Check status code self.assertEqual(response.status_code, 400) # Check response body @@ -305,36 +268,37 @@ def test_put_bad_input(self): # Check error message message = 'participant "AAAA1111" does not exist' self.assertIn(message, response['_status']['message']) - # Check field values - d = Diagnosis.query.first() - self.assertEqual(d.diagnosis, kwargs.get('diagnosis')) - self.assertEqual(d.age_at_event_days, kwargs.get('age_at_event_days')) - - def test_put_missing_req_params(self): + # Check that properties are unchanged + dg = Diagnosis.query.first() + for k, v in kwargs.items(): + if k == 'participant_id': + continue + self.assertEqual(v, getattr(dg, k)) + + def test_patch_missing_req_params(self): """ Test create diagnosis that is missing required parameters in body """ # Create and save diagnosis to db kwargs = self._create_save_to_db() + kf_id = kwargs.get('kf_id') # Create diagnosis data body = { 'diagnosis': 'hangry and flu' } # Send put request - response = self.client.put(url_for(DIAGNOSES_URL, - kf_id=kwargs['kf_id']), - headers=self._api_headers(), - data=json.dumps(body)) + response = self.client.patch(url_for(DIAGNOSES_URL, + kf_id=kwargs['kf_id']), + headers=self._api_headers(), + data=json.dumps(body)) # Check status code - self.assertEqual(response.status_code, 400) + self.assertEqual(response.status_code, 200) # Check response body response = json.loads(response.data.decode("utf-8")) - # Check error message - message = 'could not update diagnosis' - self.assertIn(message, response['_status']['message']) # Check field values - d = Diagnosis.query.first() - self.assertEqual(d.diagnosis, kwargs['diagnosis']) + dg = Diagnosis.query.get(kf_id) + for k, v in body.items(): + self.assertEqual(v, getattr(dg, k)) def test_delete(self): """ @@ -353,23 +317,6 @@ def test_delete(self): d = Diagnosis.query.first() self.assertIs(d, None) - def test_delete_not_found(self): - """ - Test delete diagnosis that does not exist - """ - kf_id = 'non-existent' - # Send get request - response = self.client.delete(url_for(DIAGNOSES_URL, - kf_id=kf_id), - headers=self._api_headers()) - # Check status code - self.assertEqual(response.status_code, 404) - # Check response body - response = json.loads(response.data.decode("utf-8")) - # Check database - d = Diagnosis.query.first() - self.assertIs(d, None) - def _create_save_to_db(self): """ Create and save diagnosis diff --git a/tests/participant/test_participant_resources.py b/tests/participant/test_participant_resources.py index 1d6b2469b..bb9d1349a 100644 --- a/tests/participant/test_participant_resources.py +++ b/tests/participant/test_participant_resources.py @@ -1,4 +1,6 @@ import json +from datetime import datetime +from dateutil import parser, tz from flask import url_for @@ -24,7 +26,6 @@ def test_post_participant(self): resp = json.loads(response.data.decode("utf-8")) self.assertEqual(response.status_code, 201) - self._test_response_content(resp, 201) self.assertIn('participant', resp['_status']['message']) self.assertIn('created', resp['_status']['message']) @@ -38,17 +39,29 @@ def test_post_participant(self): self.assertEqual(p.family_id, participant['family_id']) self.assertEqual(p.is_proband, participant['is_proband']) - def test_get_not_found(self): + def test_post_missing_req_params(self): """ - Test get participant that does not exist + Test create participant that is missing required parameters in body """ - kf_id = 'non_existent' - response = self.client.get(url_for(PARTICIPANT_URL, kf_id=kf_id), - headers=self._api_headers()) - resp = json.loads(response.data.decode("utf-8")) - self.assertEqual(response.status_code, 404) - message = "could not find Participant `{}`".format(kf_id) - self.assertIn(message, resp['_status']['message']) + # Create participant data + body = { + 'external_id': 'p1' + } + # Send post request + response = self.client.post(url_for(PARTICIPANT_LIST_URL), + headers=self._api_headers(), + data=json.dumps(body)) + + # Check status code + self.assertEqual(response.status_code, 400) + # Check response body + response = json.loads(response.data.decode("utf-8")) + # Check error message + message = 'could not create participant' + self.assertIn(message, response['_status']['message']) + # Check field values + p = Participant.query.first() + self.assertIs(p, None) def test_get_participant(self): """ @@ -63,7 +76,6 @@ def test_get_participant(self): headers=self._api_headers()) resp = json.loads(response.data.decode("utf-8")) self.assertEqual(response.status_code, 200) - self._test_response_content(resp, 200) participant = resp['results'] p = Participant.query.first() @@ -86,7 +98,7 @@ def test_get_all_participants(self): self.assertIs(type(content), list) self.assertEqual(len(content), 1) - def test_put_participant(self): + def test_patch_participant(self): """ Test updating an existing participant """ @@ -94,35 +106,75 @@ def test_put_participant(self): resp = json.loads(response.data.decode("utf-8")) participant = resp['results'] kf_id = participant.get('kf_id') - external_id = participant.get('external_id') - - # Create new study, add participant to it - s = Study(external_id='phs002') - db.session.add(s) - db.session.commit() - + # Update existing participant body = { - 'external_id': 'Updated-{}'.format(external_id), - 'consent_type': 'GRU-PUB', - 'is_proband': True, - 'study_id': s.kf_id + 'external_id': 'participant 0', + 'consent_type': 'something', + 'kf_id': kf_id } - response = self.client.put(url_for(PARTICIPANT_URL, - kf_id=kf_id), - headers=self._api_headers(), - data=json.dumps(body)) - self.assertEqual(response.status_code, 201) + response = self.client.patch(url_for(PARTICIPANT_URL, + kf_id=kf_id), + headers=self._api_headers(), + data=json.dumps(body)) + # Status code + self.assertEqual(response.status_code, 200) + # Message resp = json.loads(response.data.decode("utf-8")) - self._test_response_content(resp, 201) self.assertIn('participant', resp['_status']['message']) self.assertIn('updated', resp['_status']['message']) + # Content - check only patched fields are updated + p = Participant.query.get(kf_id) + for k, v in body.items(): + self.assertEqual(v, getattr(p, k)) + # Content - Check remaining fields are unchanged + unchanged_keys = (set(participant.keys()) - + set(body.keys())) + for k in unchanged_keys: + val = getattr(p, k) + if isinstance(val, datetime): + d = val.replace(tzinfo=tz.tzutc()) + self.assertEqual(str(parser.parse(participant[k])), str(d)) + else: + self.assertEqual(participant[k], val) + + self.assertEqual(1, Participant.query.count()) + + def test_patch_bad_input(self): + """ + Test updating an existing participant with invalid input + """ + response = self._make_participant(external_id="TEST") + resp = json.loads(response.data.decode("utf-8")) participant = resp['results'] - self.assertEqual(participant['kf_id'], kf_id) - self.assertEqual(participant['external_id'], body['external_id']) - self.assertEqual(participant['consent_type'], body['consent_type']) - self.assertEqual(None,participant['family_id']) + kf_id = participant.get('kf_id') + # Update existing participant + body = { + 'external_id': 'participant 0', + 'is_proband': 'should be a boolean', + 'kf_id': kf_id + } + response = self.client.patch(url_for(PARTICIPANT_URL, + kf_id=kf_id), + headers=self._api_headers(), + data=json.dumps(body)) + # Check status code + self.assertEqual(response.status_code, 400) + # Check response body + response = json.loads(response.data.decode("utf-8")) + # Check error message + message = 'could not update participant' + self.assertIn(message, response['_status']['message']) + # Check field values + p = Participant.query.first() + for k, v in participant.items(): + val = getattr(p, k) + if isinstance(val, datetime): + d = val.replace(tzinfo=tz.tzutc()) + self.assertEqual(str(parser.parse(v)), str(d)) + else: + self.assertEqual(v, val) def test_delete_participant(self): """ @@ -159,19 +211,10 @@ def _make_participant(self, external_id="TEST-0001"): 'external_id': external_id, 'family_id': 'Test_Family_id_0', 'is_proband': True, - 'consent_type': 'GRU-IRB', + 'consent_type': 'GRU-IRB', 'study_id': s.kf_id } response = self.client.post(url_for(PARTICIPANT_LIST_URL), headers=self._api_headers(), data=json.dumps(body)) return response - - def _test_response_content(self, resp, status_code): - """ - Test that response body has expected fields - """ - self.assertIn('results', resp) - self.assertIn('_status', resp) - self.assertIn('message', resp['_status']) - self.assertEqual(resp['_status']['code'], status_code) diff --git a/tests/sample/test_sample_resources.py b/tests/sample/test_sample_resources.py index ba0bccf3c..a779f7149 100644 --- a/tests/sample/test_sample_resources.py +++ b/tests/sample/test_sample_resources.py @@ -1,5 +1,7 @@ import json from urllib.parse import urlparse +from datetime import datetime +from dateutil import parser, tz from flask import url_for @@ -23,15 +25,7 @@ def test_post(self): """ Test create a new sample """ - study = Study(external_id='phs001') - db.session.add(study) - db.session.commit() - - # Create a participant - p = Participant(external_id='Test subject 0', - is_proband=True, study_id=study.kf_id) - db.session.add(p) - db.session.commit() + kwargs = self._create_save_to_db() # Create sample data kwargs = { @@ -41,7 +35,7 @@ def test_post(self): 'anatomical_site': 'Brain', 'age_at_event_days': 365, 'tumor_descriptor': 'Metastatic', - 'participant_id': p.kf_id + 'participant_id': kwargs.get('participant_id') } # Send post request response = self.client.post(url_for(SAMPLES_LIST_URL), @@ -57,6 +51,7 @@ def test_post(self): for k, v in kwargs.items(): if k is not 'participant_id': self.assertEqual(sample.get(k), v) + self.assertEqual(2, Sample.query.count()) def test_post_missing_req_params(self): """ @@ -188,19 +183,6 @@ def test_get(self): self.assertEqual(participant_id, kwargs['participant_id']) - def test_get_not_found(self): - """ - Test get sample that does not exist - """ - # Create sample - kf_id = 'non_existent' - response = self.client.get(url_for(SAMPLES_URL, kf_id=kf_id), - headers=self._api_headers()) - self.assertEqual(response.status_code, 404) - response = json.loads(response.data.decode("utf-8")) - message = "could not find sample `{}`".format(kf_id) - self.assertIn(message, response['_status']['message']) - def test_get_all(self): """ Test retrieving all samples @@ -214,113 +196,99 @@ def test_get_all(self): content = response.get('results') self.assertEqual(len(content), 1) - def test_put(self): + def test_patch(self): """ - Test update existing sample + Test updating an existing sample """ kwargs = self._create_save_to_db() + kf_id = kwargs.get('kf_id') - # Send put request + # Update existing sample body = { - 'tissue_type': 'abnormal', + 'tissue_type': 'saliva', 'participant_id': kwargs['participant_id'] } - response = self.client.put(url_for(SAMPLES_URL, - kf_id=kwargs['kf_id']), - headers=self._api_headers(), - data=json.dumps(body)) - # Check status code + response = self.client.patch(url_for(SAMPLES_URL, + kf_id=kf_id), + headers=self._api_headers(), + data=json.dumps(body)) + # Status code self.assertEqual(response.status_code, 200) - # Check field values got updated - response = json.loads(response.data.decode('utf-8')) - sample = response['results'] - self.assertEqual(kwargs['kf_id'], sample['kf_id']) - # Fields that should be None since they were not in put request body - self.assertIs(None, sample['external_id']) - self.assertIs(None, sample['composition']) - self.assertIs(None, sample['anatomical_site']) - self.assertIs(None, sample['tumor_descriptor']) - self.assertEqual([], sample['aliquots']) - # Fields that should be updated w values - self.assertEqual(body['tissue_type'], sample['tissue_type']) - - def test_put_not_found(self): - """ - Test update non-existent sample - """ - # Send put request - kf_id = 'non-existent' - body = {} - response = self.client.put(url_for(SAMPLES_URL, - kf_id=kf_id), - headers=self._api_headers(), - data=json.dumps(body)) - # Check status code - self.assertEqual(response.status_code, 404) - # Check response body - response = json.loads(response.data.decode("utf-8")) - # Check error message - message = 'could not find sample' - self.assertIn(message, response['_status']['message']) - # Check database - c = Sample.query.filter_by(kf_id=kf_id).count() - self.assertEqual(c, 0) - def test_put_bad_input(self): + # Message + resp = json.loads(response.data.decode("utf-8")) + self.assertIn('sample', resp['_status']['message']) + self.assertIn('updated', resp['_status']['message']) + + # Content - check only patched fields are updated + sample = resp['results'] + sa = Sample.query.get(kf_id) + for k, v in body.items(): + self.assertEqual(v, getattr(sa, k)) + # Content - Check remaining fields are unchanged + unchanged_keys = (set(sample.keys()) - + set(body.keys())) + for k in unchanged_keys: + val = getattr(sa, k) + if isinstance(val, datetime): + d = val.replace(tzinfo=tz.tzutc()) + self.assertEqual(str(parser.parse(sample[k])), str(d)) + else: + self.assertEqual(sample[k], val) + + self.assertEqual(1, Sample.query.count()) + + def test_patch_bad_input(self): """ - Test update existing sample with bad input - - Participant with participant_id does not exist + Test updating an existing participant with invalid input """ - # Create and save sample to db kwargs = self._create_save_to_db() - - # Send put request + kf_id = kwargs.get('kf_id') body = { 'participant_id': 'AAAA1111' } - response = self.client.put(url_for(SAMPLES_URL, - kf_id=kwargs['kf_id']), - headers=self._api_headers(), - data=json.dumps(body)) + response = self.client.patch(url_for(SAMPLES_URL, + kf_id=kf_id), + headers=self._api_headers(), + data=json.dumps(body)) # Check status code self.assertEqual(response.status_code, 400) # Check response body response = json.loads(response.data.decode("utf-8")) # Check error message - message = 'Cannot update sample without an existing' message = 'participant "AAAA1111" does not exist' self.assertIn(message, response['_status']['message']) - # Check field values - d = Sample.query.first() - self.assertEqual(d.tissue_type, kwargs.get('tissue_type')) - self.assertEqual(d.age_at_event_days, kwargs.get('age_at_event_days')) + # Check that properties are unchanged + sa = Sample.query.first() + for k, v in kwargs.items(): + if k == 'participant_id': + continue + self.assertEqual(v, getattr(sa, k)) - def test_put_missing_req_params(self): + def test_patch_missing_req_params(self): """ Test create sample that is missing required parameters in body """ - # Create and save sample to db + # Create and save diagnosis to db kwargs = self._create_save_to_db() - # Create sample data + kf_id = kwargs.get('kf_id') + # Create diagnosis data body = { - 'tissue_type': 'abnormal' + 'tissue_type': 'blood' } # Send put request - response = self.client.put(url_for(SAMPLES_URL, - kf_id=kwargs['kf_id']), - headers=self._api_headers(), - data=json.dumps(body)) + response = self.client.patch(url_for(SAMPLES_URL, + kf_id=kwargs['kf_id']), + headers=self._api_headers(), + data=json.dumps(body)) # Check status code - self.assertEqual(response.status_code, 400) + self.assertEqual(response.status_code, 200) # Check response body response = json.loads(response.data.decode("utf-8")) - # Check error message - message = 'could not update sample' - self.assertIn(message, response['_status']['message']) # Check field values - d = Sample.query.first() - self.assertEqual(d.tissue_type, kwargs['tissue_type']) + sa = Sample.query.get(kf_id) + for k, v in body.items(): + self.assertEqual(v, getattr(sa, k)) def test_delete(self): """ diff --git a/tests/test_api.py b/tests/test_api.py index f326c771b..1a49158fe 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -15,6 +15,8 @@ class TestAPI: ('/samples/123', 'GET', 404), ('/diagnoses', 'GET', 200), ('/diagnoses/123', 'GET', 404), + ('/demographics', 'GET', 200), + ('/demographics/123', 'GET', 404), ('/participants', 'GET', 200), ('/persons', 'GET', 404), ('/participants/123', 'GET', 404) @@ -32,16 +34,20 @@ def test_status_codes(self, client, endpoint, method, status_code): ('/persons', 'GET', 'not found'), ('/samples', 'GET', 'success'), ('/samples/123', 'GET', 'could not find sample `123`'), - ('/samples/123', 'PUT', 'could not find sample `123`'), + ('/samples/123', 'PATCH', 'could not find sample `123`'), ('/samples/123', 'DELETE', 'could not find sample `123`'), ('/diagnoses', 'GET', 'success'), ('/diagnoses/123', 'GET', 'could not find diagnosis `123`'), - ('/diagnoses/123', 'PUT', 'could not find diagnosis `123`'), + ('/diagnoses/123', 'PATCH', 'could not find diagnosis `123`'), ('/diagnoses/123', 'DELETE', 'could not find diagnosis `123`'), + ('/demographics', 'GET', 'success'), + ('/demographics/123', 'GET', 'could not find demographic `123`'), + ('/demographics/123', 'PATCH', 'could not find demographic `123`'), + ('/demographics/123', 'DELETE', 'could not find demographic `123`'), ('/participants', 'GET', 'success'), - ('/participants/123', 'GET', 'could not find Participant `123`'), - ('/participants/123', 'PUT', 'could not find Participant `123`'), - ('/participants/123', 'DELETE', 'could not find Participant `123`') + ('/participants/123', 'GET', 'could not find participant `123`'), + ('/participants/123', 'PATCH', 'could not find participant `123`'), + ('/participants/123', 'DELETE', 'could not find participant `123`') ]) def test_status_messages(self, client, endpoint, method, status_message): """ @@ -56,7 +62,8 @@ def test_status_messages(self, client, endpoint, method, status_message): @pytest.mark.parametrize('endpoint,method', [ ('/participants', 'GET'), ('/samples', 'GET'), - ('/diagnoses', 'GET') + ('/diagnoses', 'GET'), + ('/demographics', 'GET') ]) def test_status_format(self, client, endpoint, method): """ Test that the _response field is consistent """ @@ -68,40 +75,57 @@ def test_status_format(self, client, endpoint, method): assert 'code' in body['_status'] assert type(body['_status']['code']) is int - @pytest.mark.parametrize('endpoint,field', [ - ('/participants', 'created_at'), - ('/participants', 'modified_at') + @pytest.mark.parametrize('endpoint, method, fields', [ + ('/participants', 'POST', ['created_at', 'modified_at']), + ('/participants', 'PATCH', ['created_at', 'modified_at']), + ('/demographics', 'PATCH', ['created_at', 'modified_at']), + ('/diagnoses', 'PATCH', ['created_at', 'modified_at']), + ('/samples', 'PATCH', ['created_at', 'modified_at']) ]) - def test_read_only(self, client, entities, endpoint, field): + def test_read_only(self, client, entities, endpoint, method, fields): """ Test that given fields can not be written or modified """ inputs = entities[endpoint] - inputs.update({field: 'test'}) - resp = client.post(endpoint, - data=json.dumps(inputs), - headers={'Content-Type': 'application/json'}) + method_name = method.lower() + [inputs.update({field: 'test'}) for field in fields] + call_func = getattr(client, method_name) + kwargs = {'data': json.dumps(inputs), + 'headers': {'Content-Type': 'application/json'}} + if method_name in {'put', 'patch'}: + kf_id = entities.get('kf_ids').get(endpoint) + endpoint = '{}/{}'.format(endpoint, kf_id) + resp = call_func(endpoint, **kwargs) body = json.loads(resp.data.decode('utf-8')) - assert (field not in body['results'] - or body['results'][field] != 'test') + for field in fields: + assert (field not in body['results'] + or body['results'][field] != 'test') - @pytest.mark.parametrize('endpoint,field', [ - ('/participants', 'blah'), - ('/samples', 'blah') - ]) - def test_unknown_field(self, client, entities, endpoint, field): + @pytest.mark.parametrize('method', ['POST', 'PATCH']) + @pytest.mark.parametrize('endpoint', ['/participants', + '/demographics', + '/diagnoses', + '/samples']) + def test_unknown_field(self, client, entities, endpoint, method): """ Test that unknown fields are rejected when trying to create """ inputs = entities[endpoint] - inputs.update({field: 'test'}) - resp = client.post(endpoint, - data=json.dumps(inputs), - headers={'Content-Type': 'application/json'}) + inputs.update({'blah': 'test'}) + action = 'create' + if method.lower() in {'put', 'patch'}: + action = 'update' + kf_id = entities.get('kf_ids').get(endpoint) + endpoint = '{}/{}'.format(endpoint, kf_id) + call_func = getattr(client, method.lower()) + resp = call_func(endpoint, data=json.dumps(inputs), + headers={'Content-Type': 'application/json'}) + body = json.loads(resp.data.decode('utf-8')) assert body['_status']['code'] == 400 - assert 'could not create ' in body['_status']['message'] + assert 'could not {} '.format(action) in body['_status']['message'] assert 'Unknown field' in body['_status']['message'] @pytest.mark.parametrize('resource,field', [ ('/participants', 'demographic'), - ('/participants', 'diagnoses') + ('/participants', 'diagnoses'), + ('/participants', 'samples'), ]) def test_relations(self, client, entities, resource, field): """ Checks that references to other resources have correct ID """