From 68536a48a1d56e4146699a4350bc20defcb264f3 Mon Sep 17 00:00:00 2001 From: Natasha Singh Date: Mon, 19 Mar 2018 16:51:49 -0400 Subject: [PATCH 1/3] :sparkles: Add family_relationship schema --- .../api/family_relationship/schemas.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 dataservice/api/family_relationship/schemas.py diff --git a/dataservice/api/family_relationship/schemas.py b/dataservice/api/family_relationship/schemas.py new file mode 100644 index 000000000..3866e919c --- /dev/null +++ b/dataservice/api/family_relationship/schemas.py @@ -0,0 +1,27 @@ +from marshmallow_sqlalchemy import field_for + +from dataservice.api.family_relationship.models import FamilyRelationship +from dataservice.api.common.schemas import BaseSchema +from dataservice.extensions import ma + + +class FamilyRelationshipSchema(BaseSchema): + participant_id = field_for(FamilyRelationship, 'participant_id', + required=True, + load_only=True, example='PT_B048J5') + relative_id = field_for(FamilyRelationship, 'relative_id', + required=True, + load_only=True, example='PT_B048J6') + + class Meta(BaseSchema.Meta): + model = FamilyRelationship + resource_url = 'api.family_relationships' + collection_url = 'api.family_relationships_list' + exclude = ('relative', 'participant') + + _links = ma.Hyperlinks({ + 'self': ma.URLFor(Meta.resource_url, kf_id=''), + 'collection': ma.URLFor(Meta.collection_url), + 'participant': ma.URLFor('api.participants', kf_id=''), + 'relative': ma.URLFor('api.participants', kf_id='') + }) From 03e9235a1744e31eb9c8d359a49988dbe58e40ea Mon Sep 17 00:00:00 2001 From: Natasha Singh Date: Mon, 19 Mar 2018 16:53:00 -0400 Subject: [PATCH 2/3] :sparkles: Add family relationship crud resource/API --- dataservice/api/__init__.py | 2 + .../api/family_relationship/__init__.py | 6 + .../api/family_relationship/resources.py | 154 ++++++++++++++++++ 3 files changed, 162 insertions(+) create mode 100644 dataservice/api/family_relationship/resources.py diff --git a/dataservice/api/__init__.py b/dataservice/api/__init__.py index 898043496..83cc0770e 100644 --- a/dataservice/api/__init__.py +++ b/dataservice/api/__init__.py @@ -10,6 +10,8 @@ from dataservice.api.investigator import InvestigatorListAPI from dataservice.api.participant import ParticipantAPI from dataservice.api.participant import ParticipantListAPI +from dataservice.api.family_relationship import FamilyRelationshipAPI +from dataservice.api.family_relationship import FamilyRelationshipListAPI from dataservice.api.demographic import DemographicAPI from dataservice.api.demographic import DemographicListAPI from dataservice.api.diagnosis import DiagnosisAPI diff --git a/dataservice/api/family_relationship/__init__.py b/dataservice/api/family_relationship/__init__.py index e69de29bb..1ae3c47ec 100644 --- a/dataservice/api/family_relationship/__init__.py +++ b/dataservice/api/family_relationship/__init__.py @@ -0,0 +1,6 @@ +from dataservice.api.family_relationship.resources import ( + FamilyRelationshipAPI +) +from dataservice.api.family_relationship.resources import ( + FamilyRelationshipListAPI +) diff --git a/dataservice/api/family_relationship/resources.py b/dataservice/api/family_relationship/resources.py new file mode 100644 index 000000000..d9f95cc34 --- /dev/null +++ b/dataservice/api/family_relationship/resources.py @@ -0,0 +1,154 @@ +from flask import abort, request +from marshmallow import ValidationError + +from dataservice.extensions import db +from dataservice.api.common.pagination import paginated, Pagination +from dataservice.api.family_relationship.models import FamilyRelationship +from dataservice.api.family_relationship.schemas import ( + FamilyRelationshipSchema +) +from dataservice.api.common.views import CRUDView + + +class FamilyRelationshipListAPI(CRUDView): + """ + FamilyRelationship REST API + """ + endpoint = 'family_relationships_list' + rule = '/family-relationships' + schemas = {'FamilyRelationship': FamilyRelationshipSchema} + + @paginated + def get(self, after, limit): + """ + Get all family_relationships + --- + description: Get all family_relationships + template: + path: + get_list.yml + properties: + resource: + FamilyRelationship + """ + q = FamilyRelationship.query + + return (FamilyRelationshipSchema(many=True) + .jsonify(Pagination(q, after, limit))) + + def post(self): + """ + Create a new family_relationship + --- + template: + path: + new_resource.yml + properties: + resource: + FamilyRelationship + """ + + body = request.json + + # Deserialize + try: + fr = FamilyRelationshipSchema(strict=True).load(body).data + # Request body not valid + except ValidationError as e: + abort(400, 'could not create family_relationship: {}' + .format(e.messages)) + + # Add to and save in database + db.session.add(fr) + db.session.commit() + + return FamilyRelationshipSchema(201, 'family_relationship {} created' + .format(fr.kf_id)).jsonify(fr), 201 + + +class FamilyRelationshipAPI(CRUDView): + """ + FamilyRelationship REST API + """ + endpoint = 'family_relationships' + rule = '/family-relationships/' + schemas = {'FamilyRelationship': FamilyRelationshipSchema} + + def get(self, kf_id): + """ + Get a family_relationship by id + --- + template: + path: + get_by_id.yml + properties: + resource: + FamilyRelationship + """ + # Get one + fr = FamilyRelationship.query.get(kf_id) + if fr is None: + abort(404, 'could not find {} `{}`' + .format('family_relationship', kf_id)) + return FamilyRelationshipSchema().jsonify(fr) + + def patch(self, kf_id): + """ + Update an existing family_relationship. + + Allows partial update of resource + --- + template: + path: + update_by_id.yml + properties: + resource: + FamilyRelationship + """ + fr = FamilyRelationship.query.get(kf_id) + if fr is None: + abort(404, 'could not find {} `{}`' + .format('family_relationship', kf_id)) + + # Partial update - validate but allow missing required fields + body = request.json or {} + try: + fr = FamilyRelationshipSchema(strict=True).load(body, instance=fr, + partial=True).data + except ValidationError as err: + abort(400, 'could not update family_relationship: {}' + .format(err.messages)) + + db.session.add(fr) + db.session.commit() + + return FamilyRelationshipSchema( + 200, 'family_relationship {} updated'.format(fr.kf_id) + ).jsonify(fr), 200 + + def delete(self, kf_id): + """ + Delete family_relationship by id + + Deletes a family_relationship given a Kids First id + --- + template: + path: + delete_by_id.yml + properties: + resource: + FamilyRelationship + """ + + # Check if family_relationship exists + fr = FamilyRelationship.query.get(kf_id) + if fr is None: + abort(404, 'could not find {} `{}`' + .format('family_relationship', kf_id)) + + # Save in database + db.session.delete(fr) + db.session.commit() + + return FamilyRelationshipSchema(200, 'family_relationship {} deleted' + .format(fr.kf_id)).jsonify(fr), 200 From 4157b1059ac729af15e61cb0024a79e771c82284 Mon Sep 17 00:00:00 2001 From: Natasha Singh Date: Mon, 19 Mar 2018 16:54:30 -0400 Subject: [PATCH 3/3] :white_check_mark: Add fam relationship unit tests --- dataservice/utils.py | 10 + tests/conftest.py | 28 ++- .../test_family_relationship_resources.py | 186 ++++++++++++++++++ tests/test_api.py | 117 ++++++----- tests/test_pagination.py | 57 ++++-- 5 files changed, 323 insertions(+), 75 deletions(-) create mode 100644 tests/family_relationship/test_family_relationship_resources.py diff --git a/dataservice/utils.py b/dataservice/utils.py index 0f97a3906..9d790ef8d 100644 --- a/dataservice/utils.py +++ b/dataservice/utils.py @@ -1,5 +1,15 @@ import pkg_resources +from itertools import tee def _get_version(): return pkg_resources.get_distribution("kf-api-dataservice").version + + +def iterate_pairwise(iterable): + """ + Iterate over an iterable in consecutive pairs + """ + a, b = tee(iterable) + next(b, None) + return zip(a, b) diff --git a/tests/conftest.py b/tests/conftest.py index b767c90f7..417597b51 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,6 +7,7 @@ from dataservice.api.investigator.models import Investigator from dataservice.api.study.models import Study from dataservice.api.participant.models import Participant +from dataservice.api.family_relationship.models import FamilyRelationship from dataservice.api.demographic.models import Demographic from dataservice.api.diagnosis.models import Diagnosis from dataservice.api.sample.models import Sample @@ -107,13 +108,28 @@ def entities(client): 'phenotype': 'test phenotype 1', 'hpo_id': 'HP:0000118', 'age_at_event_days': 120 + }, + '/family-relationships': { + 'participant_to_relative_relation': 'mother' } } # Create and save entities to db + # Study, investigator investigator = Investigator(**inputs['/investigators']) study = Study(**inputs['/studies']) + study.investigator = investigator + + # Add participants to study p = Participant(**inputs['/participants']) + p1 = Participant(**inputs['/participants']) + p2 = Participant(**inputs['/participants']) + + study.participants.extend([p, p1, p2]) + db.session.add(study) + db.session.commit() + + # Add entities to participant outcome = Outcome(**inputs['/outcomes'], participant_id=p.kf_id) phenotype = Phenotype(**inputs['/phenotypes'], participant_id=p.kf_id) demo = Demographic(**inputs['/demographics'], participant_id=p.kf_id) @@ -131,11 +147,12 @@ def entities(client): p.outcomes = [outcome] p.phenotypes = [phenotype] - # Add participants to study - study.investigator = investigator - study.participants.append(p) - db.session.add(study) - db.session.add(p) + # Family relationship + inputs['/family-relationships']['participant_id'] = p1.kf_id + inputs['/family-relationships']['relative_id'] = p2.kf_id + fr = FamilyRelationship(**inputs['/family-relationships']) + + db.session.add(fr) db.session.commit() # Add foreign keys @@ -165,5 +182,6 @@ def entities(client): inputs['kf_ids'].update({'/samples': sample.kf_id}) inputs['kf_ids'].update({'/aliquots': aliquot.kf_id}) inputs['kf_ids'].update({'/sequencing-experiments': seq_exp.kf_id}) + inputs['kf_ids'].update({'/family-relationships': fr.kf_id}) return inputs diff --git a/tests/family_relationship/test_family_relationship_resources.py b/tests/family_relationship/test_family_relationship_resources.py new file mode 100644 index 000000000..0af1eeac2 --- /dev/null +++ b/tests/family_relationship/test_family_relationship_resources.py @@ -0,0 +1,186 @@ +import json +from flask import url_for +from urllib.parse import urlparse +from sqlalchemy import or_ + +from dataservice.extensions import db +from dataservice.api.family_relationship.models import FamilyRelationship +from dataservice.api.participant.models import Participant +from dataservice.api.study.models import Study +from tests.utils import FlaskTestCase + +FAMILY_RELATIONSHIPS_URL = 'api.family_relationships' +FAMILY_RELATIONSHIPS_LIST_URL = 'api.family_relationships_list' + + +class FamilyRelationshipTest(FlaskTestCase): + """ + Test family_relationship api + """ + + def test_post(self): + """ + Test create a new family_relationship + """ + kwargs = self._create_save_to_db() + + # Create new family relationship + results = Participant.query.filter( + or_ + (Participant.external_id == 'Pebbles', + Participant.external_id == 'Dino')) + kwargs = { + 'participant_id': results[0].kf_id, + 'relative_id': results[1].kf_id, + 'participant_to_relative_relation': 'father' + } + # Send get request + response = self.client.post(url_for(FAMILY_RELATIONSHIPS_LIST_URL), + data=json.dumps(kwargs), + headers=self._api_headers()) + + # Check response status status_code + self.assertEqual(response.status_code, 201) + + # Check response content + response = json.loads(response.data.decode('utf-8')) + family_relationship = response['results'] + fr = FamilyRelationship.query.get(family_relationship.get('kf_id')) + for k, v in kwargs.items(): + if k == 'participant_id': + self.assertEqual(v, kwargs.get('participant_id')) + elif k == 'relative_id': + self.assertEqual(v, kwargs.get('relative_id')) + else: + self.assertEqual(family_relationship[k], getattr(fr, k)) + self.assertEqual(2, FamilyRelationship.query.count()) + + def test_get(self): + # Create and save family_relationship to db + kwargs = self._create_save_to_db() + # Send get request + response = self.client.get(url_for(FAMILY_RELATIONSHIPS_URL, + kf_id=kwargs['kf_id']), + headers=self._api_headers()) + + # Check response status code + self.assertEqual(response.status_code, 200) + # Check response content + response = json.loads(response.data.decode('utf-8')) + family_relationship = response['results'] + participant_link = response['_links']['participant'] + participant_id = urlparse(participant_link).path.split('/')[-1] + relative_link = response['_links']['relative'] + relative_id = urlparse(relative_link).path.split('/')[-1] + for k, v in kwargs.items(): + if k == 'participant_id': + self.assertEqual(participant_id, + kwargs['participant_id']) + elif k == 'relative_id': + self.assertEqual(relative_id, + kwargs['relative_id']) + else: + self.assertEqual(family_relationship[k], + family_relationship[k]) + + def test_get_all(self): + """ + Test retrieving all family_relationships + """ + self._create_save_to_db() + self._create_save_to_db() + + response = self.client.get(url_for(FAMILY_RELATIONSHIPS_LIST_URL), + headers=self._api_headers()) + self.assertEqual(response.status_code, 200) + response = json.loads(response.data.decode("utf-8")) + content = response.get('results') + self.assertEqual(len(content), 2) + + def test_patch(self): + """ + Test updating an existing family_relationship + """ + kwargs = self._create_save_to_db() + kf_id = kwargs.get('kf_id') + + # Update existing family_relationship + body = { + 'participant_to_relative_relation': 'mother' + } + response = self.client.patch(url_for(FAMILY_RELATIONSHIPS_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.assertIn('family_relationship', resp['_status']['message']) + self.assertIn('updated', resp['_status']['message']) + + # Content - check only patched fields are updated + family_relationship = resp['results'] + fr = FamilyRelationship.query.get(kf_id) + for k, v in body.items(): + self.assertEqual(v, getattr(fr, k)) + # Unchanged + self.assertEqual(kwargs.get('relative_to_participant_relation'), + family_relationship.get( + 'relative_to_participant_relation')) + self.assertEqual(1, FamilyRelationship.query.count()) + + def test_delete(self): + """ + Test delete an existing family_relationship + """ + kwargs = self._create_save_to_db() + # Send get request + response = self.client.delete(url_for(FAMILY_RELATIONSHIPS_URL, + kf_id=kwargs['kf_id']), + headers=self._api_headers()) + # Check status code + self.assertEqual(response.status_code, 200) + # Check response body + response = json.loads(response.data.decode("utf-8")) + # Check database + fr = FamilyRelationship.query.first() + self.assertIs(fr, None) + + def _create_save_to_db(self): + """ + Create and save family_relationship + + Requires creating a participant + Create a family_relationship and add it to participant as kwarg + Save participant + """ + # Create study + study = Study(external_id='phs001') + + # Create participants + p1 = Participant(external_id='Fred', is_proband=False) + p2 = Participant(external_id='Pebbles', is_proband=True) + p3 = Participant(external_id='Pebbles', is_proband=True) + p4 = Participant(external_id='Dino', is_proband=False) + + study.participants.extend([p1, p2, p3, p4]) + db.session.add(study) + db.session.commit() + + # Create family_relationship + kwargs = { + 'participant_id': p1.kf_id, + 'relative_id': p2.kf_id, + 'participant_to_relative_relation': 'father' + } + fr = FamilyRelationship(**kwargs) + + db.session.add(fr) + db.session.commit() + kwargs['kf_id'] = fr.kf_id + kwargs['relative_to_participant_relation'] = \ + fr.relative_to_participant_relation + + return kwargs diff --git a/tests/test_api.py b/tests/test_api.py index 37f3d865c..4e39b9f6d 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -32,7 +32,9 @@ class TestAPI: ('/studies/123', 'GET', 404), ('/investigators', 'GET', 200), ('/outcomes', 'GET', 200), - ('/outcomes/123', 'GET', 404) + ('/outcomes/123', 'GET', 404), + ('/family-relationships', 'GET', 200), + ('/family-relationship/123', 'GET', 404) ]) def test_status_codes(self, client, endpoint, method, status_code): """ Test endpoint response codes """ @@ -45,37 +47,6 @@ def test_status_codes(self, client, endpoint, method, status_code): @pytest.mark.parametrize('endpoint,method,status_message', [ ('/status', 'GET', 'Welcome to'), ('/persons', 'GET', 'not found'), - ('/sequencing-experiments', 'GET', 'success'), - ('/sequencing-experiments/123', 'GET', - 'could not find sequencing_experiment `123`'), - ('/sequencing-experiments/123', 'PATCH', - 'could not find sequencing_experiment `123`'), - ('/sequencing-experiments/123', 'DELETE', - 'could not find sequencing_experiment `123`'), - ('/aliquots', 'GET', 'success'), - ('/aliquots/123', 'GET', 'could not find aliquot `123`'), - ('/aliquots/123', 'PATCH', 'could not find aliquot `123`'), - ('/aliquots/123', 'DELETE', 'could not find aliquot `123`'), - ('/samples', 'GET', 'success'), - ('/samples/123', 'GET', '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', '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`'), - ('/phenotypes', 'GET', 'success'), - ('/phenotypes/123', 'GET', 'could not find phenotype `123`'), - ('/phenotypes/123', 'PATCH', 'could not find phenotype `123`'), - ('/phenotypes/123', 'DELETE', 'could not find phenotype `123`'), - ('/participants', 'GET', 'success'), - ('/participants/123', 'GET', 'could not find participant `123`'), - ('/participants/123', 'PATCH', 'could not find participant `123`'), - ('/participants/123', 'DELETE', 'could not find participant `123`'), ('/studies', 'GET', 'success'), ('/studies/123', 'GET', 'could not find study `123`'), ('/studies/123', 'PATCH', 'could not find study `123`'), @@ -84,10 +55,48 @@ def test_status_codes(self, client, endpoint, method, status_code): ('/investigators/123', 'GET', 'could not find investigator `123`'), ('/investigators/123', 'PATCH', 'could not find investigator `123`'), ('/investigators/123', 'DELETE', 'could not find investigator `123`'), + ('/participants', 'GET', 'success'), + ('/participants/123', 'GET', 'could not find participant `123`'), + ('/participants/123', 'PATCH', 'could not find participant `123`'), + ('/participants/123', 'DELETE', 'could not find participant `123`'), ('/outcomes', 'GET', 'success'), ('/outcomes/123', 'GET', 'could not find outcome `123`'), ('/outcomes/123', 'PATCH', 'could not find outcome `123`'), - ('/outcomes/123', 'DELETE', 'could not find outcome `123`') + ('/outcomes/123', 'DELETE', 'could not find outcome `123`'), + ('/phenotypes', 'GET', 'success'), + ('/phenotypes/123', 'GET', 'could not find phenotype `123`'), + ('/phenotypes/123', 'PATCH', 'could not find phenotype `123`'), + ('/phenotypes/123', 'DELETE', 'could not find phenotype `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`'), + ('/diagnoses', 'GET', 'success'), + ('/diagnoses/123', 'GET', 'could not find diagnosis `123`'), + ('/diagnoses/123', 'PATCH', 'could not find diagnosis `123`'), + ('/diagnoses/123', 'DELETE', 'could not find diagnosis `123`'), + ('/samples', 'GET', 'success'), + ('/samples/123', 'GET', 'could not find sample `123`'), + ('/samples/123', 'PATCH', 'could not find sample `123`'), + ('/samples/123', 'DELETE', 'could not find sample `123`'), + ('/aliquots', 'GET', 'success'), + ('/aliquots/123', 'GET', 'could not find aliquot `123`'), + ('/aliquots/123', 'PATCH', 'could not find aliquot `123`'), + ('/aliquots/123', 'DELETE', 'could not find aliquot `123`'), + ('/sequencing-experiments', 'GET', 'success'), + ('/sequencing-experiments/123', 'GET', + 'could not find sequencing_experiment `123`'), + ('/sequencing-experiments/123', 'PATCH', + 'could not find sequencing_experiment `123`'), + ('/sequencing-experiments/123', 'DELETE', + 'could not find sequencing_experiment `123`'), + ('/family-relationships', 'GET', 'success'), + ('/family-relationships/123', 'GET', + 'could not find family_relationship `123`'), + ('/family-relationships/123', 'PATCH', + 'could not find family_relationship `123`'), + ('/family-relationships/123', 'DELETE', + 'could not find family_relationship `123`') ]) def test_status_messages(self, client, endpoint, method, status_message): """ @@ -100,18 +109,17 @@ def test_status_messages(self, client, endpoint, method, status_message): assert status_message in resp['_status']['message'] @pytest.mark.parametrize('endpoint,method', [ - ('/participants', 'GET'), - ('/sequencing-experiments', 'GET'), - ('/aliquots', 'GET'), - ('/samples', 'GET'), - ('/diagnoses', 'GET'), - ('/demographics', 'GET'), ('/studies', 'GET'), ('/investigators', 'GET'), ('/participants', 'GET'), ('/outcomes', 'GET'), - ('/studies', 'GET'), - ('/phenotypes', 'GET') + ('/phenotypes', 'GET'), + ('/demographics', 'GET'), + ('/diagnoses', 'GET'), + ('/samples', 'GET'), + ('/aliquots', 'GET'), + ('/sequencing-experiments', 'GET'), + ('/family-relationships', 'GET') ]) def test_status_format(self, client, endpoint, method): """ Test that the _response field is consistent """ @@ -142,7 +150,8 @@ def test_status_format(self, client, endpoint, method): ('/aliquots', 'POST', ['created_at', 'modified_at']), ('/aliquots', 'PATCH', ['created_at', 'modified_at']), ('/sequencing-experiments', 'POST', ['created_at', 'modified_at']), - ('/sequencing-experiments', 'PATCH', ['created_at', 'modified_at']) + ('/sequencing-experiments', 'PATCH', ['created_at', 'modified_at']), + ('/family-relationships', 'PATCH', ['created_at', 'modified_at']) ]) def test_read_only(self, client, entities, endpoint, method, fields): """ Test that given fields can not be written or modified """ @@ -162,16 +171,17 @@ def test_read_only(self, client, entities, endpoint, method, fields): or body['results'][field] != 'test') @pytest.mark.parametrize('method', ['POST', 'PATCH']) - @pytest.mark.parametrize('endpoint', ['/participants', + @pytest.mark.parametrize('endpoint', ['/studies', + '/investigators', + '/participants', + '/outcomes', + '/phenotypes', '/demographics', '/diagnoses', '/samples', + '/aliquots', '/sequencing-experiments', - '/studies', - '/investigators', - '/outcomes', - '/phenotypes', - '/aliquots']) + '/family-relationships']) def test_unknown_field(self, client, entities, endpoint, method): """ Test that unknown fields are rejected when trying to create """ inputs = entities[endpoint] @@ -194,9 +204,9 @@ def test_unknown_field(self, client, entities, endpoint, method): ('/participants', 'demographic'), ('/participants', 'diagnoses'), ('/participants', 'samples'), + ('/samples', 'aliquots'), ('/participants', 'outcomes'), ('/participants', 'phenotypes'), - ('/samples', 'aliquots'), ('/aliquots', 'sequencing_experiments') ]) def test_relations(self, client, entities, resource, field): @@ -254,7 +264,10 @@ def test_bad_input(self, client, entities, endpoint, method, field, value): @pytest.mark.parametrize('endpoint, field', [('/aliquots', 'sample_id'), ('/aliquots', 'analyte_type'), - ('/sequencing-experiments', 'aliquot_id')]) + ('/sequencing-experiments', 'aliquot_id'), + ('/family-relationships', 'participant_id'), + ('/family-relationships', 'relative_id') + ]) def test_missing_required_params(self, client, entities, endpoint, method, field): """ Tests missing required parameters """ @@ -278,7 +291,9 @@ def test_missing_required_params(self, client, entities, endpoint, [('/aliquots', 'sample_id'), ('/outcomes', 'participant_id'), ('/phenotypes', 'participant_id'), - ('/sequencing-experiments', 'aliquot_id')]) + ('/sequencing-experiments', 'aliquot_id'), + ('/family-relationships', 'participant_id'), + ('/family-relationships', 'relative_id')]) def test_bad_foreign_key(self, client, entities, endpoint, method, field): """ Test bad foreign key diff --git a/tests/test_pagination.py b/tests/test_pagination.py index 01c1b9dc7..a9af3bf08 100644 --- a/tests/test_pagination.py +++ b/tests/test_pagination.py @@ -13,6 +13,8 @@ from dataservice.api.sample.models import Sample from dataservice.api.aliquot.models import Aliquot from dataservice.api.sequencing_experiment.models import SequencingExperiment +from dataservice.api.family_relationship.models import FamilyRelationship +from dataservice.utils import iterate_pairwise class TestPagination: @@ -39,6 +41,7 @@ def participants(client): s.investigator = inv db.session.add(s) db.session.flush() + participants = [] for i in range(102): p = Participant(external_id="test", study_id=s.kf_id, @@ -63,29 +66,42 @@ def participants(client): p.outcomes = [outcome] phen = Phenotype() p.phenotypes = [phen] + participants.append(p) db.session.add(p) db.session.commit() - @pytest.mark.parametrize('endpoint', [ - ('/studies'), - ('/investigators'), - ('/participants'), - ('/outcomes'), - ('/phenotypes'), - ('/demographics'), - ('/diagnoses'), - ('/samples'), - ('/aliquots'), - ('/sequencing-experiments') + # Family relationships + for participant, relative in iterate_pairwise(participants): + gender = participant.demographic.gender + rel = 'mother' + if gender == 'male': + rel = 'father' + r = FamilyRelationship(participant=participant, relative=relative, + participant_to_relative_relation=rel) + db.session.add(r) + db.session.commit() + + @pytest.mark.parametrize('endpoint, expected_total', [ + ('/studies', 102), + ('/investigators', 102), + ('/participants', 102), + ('/outcomes', 102), + ('/phenotypes', 102), + ('/demographics', 102), + ('/diagnoses', 102), + ('/samples', 102), + ('/aliquots', 102), + ('/sequencing-experiments', 102), + ('/family-relationships', 101) ]) - def test_pagination(self, client, participants, endpoint): + def test_pagination(self, client, participants, endpoint, expected_total): """ Test pagination of resource """ resp = client.get(endpoint) resp = json.loads(resp.data.decode('utf-8')) assert len(resp['results']) == 10 assert resp['limit'] == 10 - assert resp['total'] == 102 + assert resp['total'] == expected_total ids_seen = [] # Iterate through via the `next` link @@ -113,7 +129,8 @@ def test_pagination(self, client, participants, endpoint): ('/diagnoses'), ('/samples'), ('/aliquots'), - ('/sequencing-experiments') + ('/sequencing-experiments'), + ('/family-relationships') ]) def test_limit(self, client, participants, endpoint): # Check that limit param operates correctly @@ -142,7 +159,8 @@ def test_limit(self, client, participants, endpoint): ('/diagnoses'), ('/samples'), ('/aliquots'), - ('/sequencing-experiments') + ('/sequencing-experiments'), + ('/family-relationships') ]) def test_after(self, client, participants, endpoint): """ Test `after` offeset paramater """ @@ -175,7 +193,8 @@ def test_after(self, client, participants, endpoint): ('/diagnoses'), ('/samples'), ('/aliquots'), - ('/sequencing-experiments') + ('/sequencing-experiments'), + ('/family-relationships') ]) def test_self(self, client, participants, endpoint): """ Test that the self link gives the same page """ @@ -193,13 +212,13 @@ def test_self(self, client, participants, endpoint): @pytest.mark.parametrize('endpoint', [ ('/participants'), - ('/demographics'), - ('/samples'), ('/outcomes'), + ('/demographics'), ('/diagnoses'), ('/samples'), ('/aliquots'), - ('/sequencing-experiments') + ('/sequencing-experiments'), + ('/family-relationships') ]) def test_individual_links(self, client, participants, endpoint): """ Test that each individual result has properly formatted _links """