class ParticipantSummaryDaoTest(NdbTestBase): def setUp(self): super(ParticipantSummaryDaoTest, self).setUp(use_mysql=True) self.dao = ParticipantSummaryDao() self.order_dao = BiobankOrderDao() self.measurement_dao = PhysicalMeasurementsDao() self.participant_dao = ParticipantDao() self.no_filter_query = Query([], None, 2, None) self.one_filter_query = Query([FieldFilter("participantId", Operator.EQUALS, 1)], None, 2, None) self.two_filter_query = Query([FieldFilter("participantId", Operator.EQUALS, 1), FieldFilter("hpoId", Operator.EQUALS, PITT_HPO_ID)], None, 2, None) self.ascending_biobank_id_query = Query([], OrderBy("biobankId", True), 2, None) self.descending_biobank_id_query = Query([], OrderBy("biobankId", False), 2, None) self.enrollment_status_order_query = Query([], OrderBy("enrollmentStatus", True), 2, None) self.hpo_id_order_query = Query([], OrderBy("hpoId", True), 2, None) self.first_name_order_query = Query([], OrderBy("firstName", True), 2, None) def assert_no_results(self, query): results = self.dao.query(query) self.assertEquals([], results.items) self.assertIsNone(results.pagination_token) def assert_results(self, query, items, pagination_token=None): results = self.dao.query(query) self.assertListAsDictEquals(items, results.items) self.assertEquals(pagination_token, results.pagination_token, "Pagination tokens don't match; decoded = %s, %s" % (_decode_token(pagination_token), _decode_token(results.pagination_token))) def test_query_with_total(self): num_participants = 5 query = Query([], None, 10, None, include_total=True) results = self.dao.query(query) self.assertEqual(results.total, 0) for i in range(num_participants): participant = Participant(participantId=i, biobankId=i) self._insert(participant) results = self.dao.query(query) self.assertEqual(results.total, num_participants) def testQuery_noSummaries(self): self.assert_no_results(self.no_filter_query) self.assert_no_results(self.one_filter_query) self.assert_no_results(self.two_filter_query) self.assert_no_results(self.ascending_biobank_id_query) self.assert_no_results(self.descending_biobank_id_query) def _insert(self, participant, first_name=None, last_name=None): self.participant_dao.insert(participant) summary = self.participant_summary(participant) if first_name: summary.firstName = first_name if last_name: summary.lastName = last_name self.dao.insert(summary) return participant def testQuery_oneSummary(self): participant = Participant(participantId=1, biobankId=2) self._insert(participant) summary = self.dao.get(1) self.assert_results(self.no_filter_query, [summary]) self.assert_results(self.one_filter_query, [summary]) self.assert_no_results(self.two_filter_query) self.assert_results(self.ascending_biobank_id_query, [summary]) self.assert_results(self.descending_biobank_id_query, [summary]) def testUnicodeNameRoundTrip(self): name = self.fake.first_name() with self.assertRaises(UnicodeEncodeError): str(name) # sanity check that the name contains non-ASCII participant = self._insert(Participant(participantId=1, biobankId=2)) summary = self.dao.get(participant.participantId) summary.firstName = name self.dao.update(summary) fetched_summary = self.dao.get(participant.participantId) self.assertEquals(name, fetched_summary.firstName) def testQuery_twoSummaries(self): participant_1 = Participant(participantId=1, biobankId=2) self._insert(participant_1, 'Alice', 'Smith') participant_2 = Participant(participantId=2, biobankId=1) self._insert(participant_2, 'Zed', 'Zebra') ps_1 = self.dao.get(1) ps_2 = self.dao.get(2) self.assert_results(self.no_filter_query, [ps_1, ps_2]) self.assert_results(self.one_filter_query, [ps_1]) self.assert_no_results(self.two_filter_query) self.assert_results(self.ascending_biobank_id_query, [ps_2, ps_1]) self.assert_results(self.descending_biobank_id_query, [ps_1, ps_2]) def testQuery_threeSummaries_paginate(self): participant_1 = Participant(participantId=1, biobankId=4) self._insert(participant_1, 'Alice', 'Aardvark') participant_2 = Participant(participantId=2, biobankId=1) self._insert(participant_2, 'Bob', 'Builder') participant_3 = Participant(participantId=3, biobankId=3) self._insert(participant_3, 'Chad', 'Caterpillar') ps_1 = self.dao.get(1) ps_2 = self.dao.get(2) ps_3 = self.dao.get(3) self.assert_results(self.no_filter_query, [ps_1, ps_2], _make_pagination_token(['Builder', 'Bob', None, 2])) self.assert_results(self.one_filter_query, [ps_1]) self.assert_no_results(self.two_filter_query) self.assert_results(self.ascending_biobank_id_query, [ps_2, ps_3], _make_pagination_token([3, 'Caterpillar', 'Chad', None, 3])) self.assert_results(self.descending_biobank_id_query, [ps_1, ps_3], _make_pagination_token([3, 'Caterpillar', 'Chad', None, 3])) self.assert_results(_with_token(self.no_filter_query, _make_pagination_token(['Builder', 'Bob', None, 2])), [ps_3]) self.assert_results(_with_token(self.ascending_biobank_id_query, _make_pagination_token([3, 'Caterpillar', 'Chad', None, 3])), [ps_1]) self.assert_results(_with_token(self.descending_biobank_id_query, _make_pagination_token([3, 'Caterpillar', 'Chad', None, 3])), [ps_2]) def testQuery_fourFullSummaries_paginate(self): participant_1 = Participant(participantId=1, biobankId=4) self._insert(participant_1, 'Bob', 'Jones') participant_2 = Participant(participantId=2, biobankId=1) self._insert(participant_2, 'Bob', 'Jones') participant_3 = Participant(participantId=3, biobankId=3) self._insert(participant_3, 'Bob', 'Jones') participant_4 = Participant(participantId=4, biobankId=2) self._insert(participant_4, 'Bob', 'Jones') ps_1 = self.dao.get(1) ps_2 = self.dao.get(2) ps_3 = self.dao.get(3) ps_4 = self.dao.get(4) ps_1.lastName = 'Jones' ps_1.firstName = 'Bob' ps_1.dateOfBirth = datetime.date(1978, 10, 9) ps_1.hpoId = PITT_HPO_ID self.dao.update(ps_1) ps_2.lastName = 'Aardvark' ps_2.firstName = 'Bob' ps_2.dateOfBirth = datetime.date(1978, 10, 10) ps_2.enrollmentStatus = EnrollmentStatus.MEMBER self.dao.update(ps_2) ps_3.lastName = 'Jones' ps_3.firstName = 'Bob' ps_3.dateOfBirth = datetime.date(1978, 10, 10) ps_3.hpoId = PITT_HPO_ID ps_3.enrollmentStatus = EnrollmentStatus.MEMBER self.dao.update(ps_3) ps_4.lastName = 'Jones' ps_4.enrollmentStatus = EnrollmentStatus.FULL_PARTICIPANT self.dao.update(ps_4) self.assert_results(self.no_filter_query, [ps_2, ps_4], _make_pagination_token(['Jones', 'Bob', None, 4])) self.assert_results(self.one_filter_query, [ps_1]) self.assert_results(self.two_filter_query, [ps_1]) self.assert_results(self.ascending_biobank_id_query, [ps_2, ps_4], _make_pagination_token([2, 'Jones', 'Bob', None, 4])) self.assert_results(self.descending_biobank_id_query, [ps_1, ps_3], _make_pagination_token([3, 'Jones', 'Bob', datetime.date(1978, 10, 10), 3])) self.assert_results(self.hpo_id_order_query, [ps_2, ps_4], _make_pagination_token([0, 'Jones', 'Bob', None, 4])) self.assert_results(self.enrollment_status_order_query, [ps_1, ps_2], _make_pagination_token(['MEMBER', 'Aardvark', 'Bob', datetime.date(1978, 10, 10), 2])) self.assert_results(_with_token(self.no_filter_query, _make_pagination_token(['Jones', 'Bob', None, 4])), [ps_1, ps_3]) self.assert_results(_with_token(self.ascending_biobank_id_query, _make_pagination_token([2, 'Jones', 'Bob', None, 4])), [ps_3, ps_1]) self.assert_results(_with_token(self.descending_biobank_id_query, _make_pagination_token([3, 'Jones', 'Bob', datetime.date(1978, 10, 10), 3])), [ps_4, ps_2]) self.assert_results(_with_token(self.hpo_id_order_query, _make_pagination_token([0, 'Jones', 'Bob', None, 4])), [ps_1, ps_3]) self.assert_results(_with_token(self.enrollment_status_order_query, _make_pagination_token(['MEMBER', 'Aardvark', 'Bob', datetime.date(1978, 10, 10), 2])), [ps_3, ps_4]) def test_update_from_samples(self): # baseline_tests = ['BASELINE1', 'BASELINE2'] baseline_tests = ["1PST8", "2PST8"] config.override_setting(config.BASELINE_SAMPLE_TEST_CODES, baseline_tests) self.dao.update_from_biobank_stored_samples() # safe noop p_baseline_samples = self._insert(Participant(participantId=1, biobankId=11)) p_mixed_samples = self._insert(Participant(participantId=2, biobankId=22)) p_no_samples = self._insert(Participant(participantId=3, biobankId=33)) p_unconfirmed = self._insert(Participant(participantId=4, biobankId=44)) self.assertEquals(self.dao.get(p_baseline_samples.participantId).numBaselineSamplesArrived, 0) def get_p_baseline_last_modified(): return self.dao.get(p_baseline_samples.participantId).lastModified p_baseline_last_modified1 = get_p_baseline_last_modified() sample_dao = BiobankStoredSampleDao() def add_sample(participant, test_code, sample_id): TIME = datetime.datetime(2018, 3, 2) sample_dao.insert(BiobankStoredSample( biobankStoredSampleId=sample_id, biobankId=participant.biobankId, biobankOrderIdentifier='KIT', test=test_code, confirmed=TIME)) add_sample(p_baseline_samples, baseline_tests[0], '11111') add_sample(p_baseline_samples, baseline_tests[1], '22223') add_sample(p_mixed_samples, baseline_tests[0], '11112') add_sample(p_mixed_samples, 'NOT1', '44441') # add unconfirmed sample sample_dao.insert(BiobankStoredSample(biobankStoredSampleId=55555, biobankId=p_unconfirmed.biobankId, biobankOrderIdentifier='KIT', test=baseline_tests[1], confirmed=None)) # sleep 1 sec to make lastModified different time.sleep(1) self.dao.update_from_biobank_stored_samples() p_baseline_last_modified2 = get_p_baseline_last_modified() self.assertNotEquals(p_baseline_last_modified2, p_baseline_last_modified1) self.assertEquals(self.dao.get(p_baseline_samples.participantId).numBaselineSamplesArrived, 2) self.assertEquals(self.dao.get(p_mixed_samples.participantId).numBaselineSamplesArrived, 1) self.assertEquals(self.dao.get(p_no_samples.participantId).numBaselineSamplesArrived, 0) self.assertEquals(self.dao.get(p_unconfirmed.participantId).numBaselineSamplesArrived, 0) M_baseline_samples = self._insert(Participant(participantId=9, biobankId=99)) add_sample(M_baseline_samples, baseline_tests[0], '999') M_first_update = self.dao.get(M_baseline_samples.participantId) # sleep 1 sec to make lastModified different time.sleep(1) self.dao.update_from_biobank_stored_samples() add_sample(M_baseline_samples, baseline_tests[1], '9999') M_second_update = self.dao.get(M_baseline_samples.participantId) # sleep 1 sec to make lastModified different time.sleep(1) self.dao.update_from_biobank_stored_samples() self.assertNotEqual(M_first_update.lastModified, M_second_update.lastModified) self.assertEquals(get_p_baseline_last_modified(), p_baseline_last_modified2) def test_update_from_samples_changed_tests(self): baseline_tests = ["1PST8", "2PST8"] config.override_setting(config.BASELINE_SAMPLE_TEST_CODES, baseline_tests) self.dao.update_from_biobank_stored_samples() # safe noop participant = self._insert(Participant(participantId=1, biobankId=11)) self.assertEquals(self.dao.get(participant.participantId).numBaselineSamplesArrived, 0) sample_dao = BiobankStoredSampleDao() def add_sample(test_code, sample_id): TIME = datetime.datetime(2018, 3, 2) sample_dao.insert(BiobankStoredSample( biobankStoredSampleId=sample_id, biobankId=participant.biobankId, biobankOrderIdentifier='KIT', test=test_code, confirmed=TIME)) add_sample(baseline_tests[0], '11111') add_sample(baseline_tests[1], '22223') self.dao.update_from_biobank_stored_samples() summary = self.dao.get(participant.participantId) init_last_modified = summary.lastModified self.assertEquals(summary.numBaselineSamplesArrived, 2) # sleep 1 sec to make lastModified different time.sleep(1) # Simulate removal of one of the baseline tests from config.json. baseline_tests.pop() config.override_setting(config.BASELINE_SAMPLE_TEST_CODES, baseline_tests) self.dao.update_from_biobank_stored_samples() summary = self.dao.get(participant.participantId) self.assertEquals(summary.numBaselineSamplesArrived, 1) self.assertNotEqual(init_last_modified, summary.lastModified) def test_only_update_dna_sample(self): dna_tests = ["1ED10", "1SAL2"] config.override_setting(config.DNA_SAMPLE_TEST_CODES, dna_tests) self.dao.update_from_biobank_stored_samples() # safe noop p_dna_samples = self._insert(Participant(participantId=1, biobankId=11)) self.assertEquals(self.dao.get(p_dna_samples.participantId).samplesToIsolateDNA, None) self.assertEquals( self.dao.get(p_dna_samples.participantId).enrollmentStatusCoreStoredSampleTime, None) self.assertEquals( self.dao.get(p_dna_samples.participantId).enrollmentStatusCoreOrderedSampleTime, None) sample_dao = BiobankStoredSampleDao() def add_sample(participant, test_code, sample_id, confirmed_time): sample_dao.insert(BiobankStoredSample( biobankStoredSampleId=sample_id, biobankId=participant.biobankId, biobankOrderIdentifier='KIT', test=test_code, confirmed=confirmed_time)) confirmed_time_0 = datetime.datetime(2018, 3, 1) add_sample(p_dna_samples, dna_tests[0], '11111', confirmed_time_0) self.dao.update_from_biobank_stored_samples() self.assertEquals(self.dao.get(p_dna_samples.participantId).samplesToIsolateDNA, SampleStatus.RECEIVED) # only update dna sample will not update enrollmentStatusCoreStoredSampleTime self.assertEquals( self.dao.get(p_dna_samples.participantId).enrollmentStatusCoreStoredSampleTime, None) self.assertEquals( self.dao.get(p_dna_samples.participantId).enrollmentStatusCoreOrderedSampleTime, None) def test_calculate_enrollment_status(self): self.assertEquals(EnrollmentStatus.FULL_PARTICIPANT, self.dao.calculate_enrollment_status(True, NUM_BASELINE_PPI_MODULES, PhysicalMeasurementsStatus.COMPLETED, SampleStatus.RECEIVED)) self.assertEquals(EnrollmentStatus.MEMBER, self.dao.calculate_enrollment_status(True, NUM_BASELINE_PPI_MODULES - 1, PhysicalMeasurementsStatus.COMPLETED, SampleStatus.RECEIVED)) self.assertEquals(EnrollmentStatus.MEMBER, self.dao.calculate_enrollment_status(True, NUM_BASELINE_PPI_MODULES, PhysicalMeasurementsStatus.UNSET, SampleStatus.RECEIVED)) self.assertEquals(EnrollmentStatus.MEMBER, self.dao.calculate_enrollment_status(True, NUM_BASELINE_PPI_MODULES, PhysicalMeasurementsStatus.COMPLETED, SampleStatus.UNSET)) self.assertEquals(EnrollmentStatus.INTERESTED, self.dao.calculate_enrollment_status(False, NUM_BASELINE_PPI_MODULES, PhysicalMeasurementsStatus.COMPLETED, SampleStatus.RECEIVED)) def testUpdateEnrollmentStatus(self): ehr_consent_time = datetime.datetime(2018, 3, 1) summary = ParticipantSummary( participantId=1, biobankId=2, consentForStudyEnrollment=QuestionnaireStatus.SUBMITTED, consentForElectronicHealthRecords=QuestionnaireStatus.SUBMITTED, consentForElectronicHealthRecordsTime=ehr_consent_time, enrollmentStatus=EnrollmentStatus.INTERESTED) self.dao.update_enrollment_status(summary) self.assertEquals(EnrollmentStatus.MEMBER, summary.enrollmentStatus) self.assertEquals(ehr_consent_time, summary.enrollmentStatusMemberTime) def testUpdateEnrollmentStatusLastModified(self): """DA-631: enrollment_status update should update last_modified.""" participant = self._insert(Participant(participantId=6, biobankId=66)) # collect current modified and enrollment status summary = self.dao.get(participant.participantId) test_dt = datetime.datetime(2018, 11, 1) def reset_summary(): # change summary so enrollment status will be changed from INTERESTED to MEMBER. summary.enrollmentStatus = EnrollmentStatus.INTERESTED summary.lastModified = test_dt summary.consentForStudyEnrollment = QuestionnaireStatus.SUBMITTED summary.consentForElectronicHealthRecords = QuestionnaireStatus.SUBMITTED summary.physicalMeasurementsStatus = PhysicalMeasurementsStatus.COMPLETED summary.samplesToIsolateDNA = SampleStatus.RECEIVED self.dao.update(summary) ## Test Step 1: Validate update_from_biobank_stored_samples() changes lastModified. reset_summary() # Update and reload summary record self.dao.update_from_biobank_stored_samples(participant_id=participant.participantId) summary = self.dao.get(participant.participantId) # Test that status has changed and lastModified is also different self.assertEquals(EnrollmentStatus.MEMBER, summary.enrollmentStatus) self.assertNotEqual(test_dt, summary.lastModified) ## Test Step 2: Validate that update_enrollment_status() changes the lastModified property. reset_summary() summary = self.dao.get(participant.participantId) self.assertEqual(test_dt, summary.lastModified) # update_enrollment_status() does not touch the db, it only modifies object properties. self.dao.update_enrollment_status(summary) self.assertEquals(EnrollmentStatus.MEMBER, summary.enrollmentStatus) self.assertNotEqual(test_dt, summary.lastModified) def testNumberDistinctVisitsCounts(self): self.participant = self._insert(Participant(participantId=7, biobankId=77)) # insert biobank order order = self.order_dao.insert(self._make_biobank_order()) summary = self.dao.get(self.participant.participantId) self.assertEquals(summary.numberDistinctVisits, 1) cancel_request = cancel_biobank_order() # cancel biobank order self.order_dao.update_with_patch(order.biobankOrderId, cancel_request, order.version) summary = self.dao.get(self.participant.participantId) # distinct count should be 0 self.assertEquals(summary.numberDistinctVisits, 0) self.measurement_json = json.dumps(load_measurement_json(self.participant.participantId, TIME_1.isoformat())) # insert physical measurement measurement = self.measurement_dao.insert(self._make_physical_measurements()) summary = self.dao.get(self.participant.participantId) # count should be 1 self.assertEquals(summary.numberDistinctVisits, 1) # cancel the measurement cancel_measurement = get_restore_or_cancel_info() with self.measurement_dao.session() as session: self.measurement_dao.update_with_patch(measurement.physicalMeasurementsId, session, cancel_measurement) summary = self.dao.get(self.participant.participantId) # count should be 0 self.assertEquals(summary.numberDistinctVisits, 0) with clock.FakeClock(TIME_1): self.order_dao.insert(self._make_biobank_order(biobankOrderId='2', identifiers=[ BiobankOrderIdentifier(system='b', value='d')], samples=[BiobankOrderedSample( biobankOrderId='2', test=BIOBANK_TESTS[0], description='description', processingRequired=True)])) with clock.FakeClock(TIME_2): self.measurement_dao.insert(self._make_physical_measurements( physicalMeasurementsId=2)) summary = self.dao.get(self.participant.participantId) # A PM on another day should add a new distinct count. self.assertEquals(summary.numberDistinctVisits, 2) with clock.FakeClock(TIME_3): self.order_dao.insert(self._make_biobank_order(biobankOrderId='3', identifiers=[ BiobankOrderIdentifier(system='s', value='s')], samples=[BiobankOrderedSample( biobankOrderId ='3', finalized=TIME_3, test=BIOBANK_TESTS[1], description='another description', processingRequired=False)])) # a physical measurement on same day as biobank order does not add distinct visit. self.measurement_dao.insert(self._make_physical_measurements(physicalMeasurementsId=6)) # another biobank order on the same day should also not add a distinct visit self.order_dao.insert(self._make_biobank_order(biobankOrderId='7', identifiers=[ BiobankOrderIdentifier(system='x', value='x')], samples=[BiobankOrderedSample( biobankOrderId ='7', finalized=TIME_3, test=BIOBANK_TESTS[1], description='another description', processingRequired=False)])) summary = self.dao.get(self.participant.participantId) # 1 from each of TIME_1 TIME_2 TIME_3 self.assertEquals(summary.numberDistinctVisits, 3) def test_qa_scenarios_for_pmb_visits(self): """ PDR at https://docs.google.com/document/d/1sL54f-I91RvhjIprrdbwD8TlR9Jq91MX2ELf1EtJdxc/edit#heading=h.bqo8kt3igsrw<Paste> """ self.participant = self._insert(Participant(participantId=6, biobankId=66)) # test scenario 1 with clock.FakeClock(TIME_4): self.measurement_json = json.dumps(load_measurement_json(self.participant.participantId, TIME_4.isoformat())) self.measurement_dao.insert(self._make_physical_measurements(physicalMeasurementsId=666, participantId=self.participant.participantId, finalized=TIME_4)) summary = self.dao.get(self.participant.participantId) self.assertEquals(summary.numberDistinctVisits, 1) with clock.FakeClock(TIME_5): self.measurement_json = json.dumps(load_measurement_json(self.participant.participantId, TIME_5.isoformat())) self.measurement_dao.insert(self._make_physical_measurements(physicalMeasurementsId=669, finalized=TIME_5)) summary = self.dao.get(self.participant.participantId) self.assertEquals(summary.numberDistinctVisits, 2) # test scenario 2 with clock.FakeClock(TIME_6): self.participant = self._insert(Participant(participantId=9, biobankId=13)) self.measurement_json = json.dumps(load_measurement_json(self.participant.participantId, TIME_6.isoformat())) self.measurement_dao.insert(self._make_physical_measurements(physicalMeasurementsId=8, finalized=TIME_6)) self.order_dao.insert(self._make_biobank_order(biobankOrderId='2', identifiers=[ BiobankOrderIdentifier(system='b', value='d')], samples=[BiobankOrderedSample( biobankOrderId='2', finalized=TIME_7, test=BIOBANK_TESTS[0], description='description', processingRequired=True)])) summary = self.dao.get(self.participant.participantId) # distinct count should be 2 self.assertEquals(summary.numberDistinctVisits, 2) # test scenario 3 with clock.FakeClock(TIME_6): self.participant = self._insert(Participant(participantId=66, biobankId=42)) self.measurement_json = json.dumps(load_measurement_json(self.participant.participantId, TIME_6.isoformat())) self.measurement_dao.insert(self._make_physical_measurements(physicalMeasurementsId=12, createdSiteId=2, finalized=TIME_6)) self.order_dao.insert(self._make_biobank_order(biobankOrderId='18', finalizedSiteId=1, identifiers=[ BiobankOrderIdentifier(system='x', value='y')], samples=[BiobankOrderedSample( biobankOrderId='18', finalized=TIME_6, test=BIOBANK_TESTS[0], description='description', processingRequired=True)])) summary = self.dao.get(self.participant.participantId) # distinct count should be 1 self.assertEquals(summary.numberDistinctVisits, 1) # test scenario 4 with clock.FakeClock(TIME_8): self.participant = self._insert(Participant(participantId=6613, biobankId=142)) self.measurement_json = json.dumps(load_measurement_json(self.participant.participantId, TIME_8.isoformat())) self.measurement_dao.insert(self._make_physical_measurements(physicalMeasurementsId=129, finalized=TIME_8)) order = self.order_dao.insert(self._make_biobank_order(biobankOrderId='999', identifiers=[ BiobankOrderIdentifier(system='s', value='s')], samples=[BiobankOrderedSample( biobankOrderId='999', finalized=TIME_8, test=BIOBANK_TESTS[1], description='description', processingRequired=True)])) summary = self.dao.get(self.participant.participantId) # distinct count should be 1 self.assertEquals(summary.numberDistinctVisits, 1) # change finalized time, recalculating count with self.order_dao.session() as session: existing_order = copy.deepcopy(order) order.samples[0].finalized = TIME_9 self.order_dao._do_update(session, order, existing_order) summary = self.dao.get(self.participant.participantId) self.assertEquals(summary.numberDistinctVisits, 1) # change test, should not change count. with self.order_dao.session() as session: existing_order = copy.deepcopy(order) order.samples[0].test = BIOBANK_TESTS[0] self.order_dao._do_update(session, order, existing_order) summary = self.dao.get(self.participant.participantId) self.assertEquals(summary.numberDistinctVisits, 1) # test scenario 5 with clock.FakeClock(TIME_12): self.participant = self._insert(Participant(participantId=3000, biobankId=2019)) self.order_dao.insert(self._make_biobank_order(biobankOrderId='700', identifiers=[ BiobankOrderIdentifier(system='n', value='s')], samples=[BiobankOrderedSample( biobankOrderId='700', finalized=TIME_10, test=BIOBANK_TESTS[1], description='description', processingRequired=True)])) summary = self.dao.get(self.participant.participantId) # distinct count should be 1 self.assertEquals(summary.numberDistinctVisits, 1) other_order = self.order_dao.insert(self._make_biobank_order(biobankOrderId='701', identifiers=[ BiobankOrderIdentifier(system='n', value='t')], samples=[BiobankOrderedSample( biobankOrderId='701', finalized=TIME_11, test=BIOBANK_TESTS[1], description='description', processingRequired=True)])) summary = self.dao.get(self.participant.participantId) # distinct count should be 2 self.assertEquals(summary.numberDistinctVisits, 2) order = self.order_dao.insert(self._make_biobank_order(biobankOrderId='702', identifiers=[ BiobankOrderIdentifier(system='n', value='u')], samples=[BiobankOrderedSample( biobankOrderId='702', finalized=TIME_12, test=BIOBANK_TESTS[1], description='description', processingRequired=True)])) summary = self.dao.get(self.participant.participantId) # distinct count should be 3 self.assertEquals(summary.numberDistinctVisits, 3) self.measurement_json = json.dumps(load_measurement_json(self.participant.participantId, TIME_12.isoformat())) self.measurement_dao.insert(self._make_physical_measurements(physicalMeasurementsId=120, finalized=TIME_12)) summary = self.dao.get(self.participant.participantId) # distinct count should be 3 self.assertEquals(summary.numberDistinctVisits, 3) cancel_request = cancel_biobank_order() # cancel biobank order with PM on same day self.order_dao.update_with_patch(order.biobankOrderId, cancel_request, order.version) summary = self.dao.get(self.participant.participantId) # distinct count should be 3 (the PM on same day still counts) self.assertEquals(summary.numberDistinctVisits, 3) self.measurement_json = json.dumps(load_measurement_json(self.participant.participantId, TIME_1.isoformat())) self.measurement_dao.insert(self._make_physical_measurements(physicalMeasurementsId=150, finalized=TIME_1)) summary = self.dao.get(self.participant.participantId) # distinct count should be 4 self.assertEquals(summary.numberDistinctVisits, 4) # cancel order with pm on different day self.order_dao.update_with_patch(other_order.biobankOrderId, cancel_request, order.version) summary = self.dao.get(self.participant.participantId) # distinct count should be 3 self.assertEquals(summary.numberDistinctVisits, 3) def test_pm_restore_cancel_biobank_restore_cancel(self): self.participant = self._insert(Participant(participantId=9, biobankId=13)) self.measurement_json = json.dumps(load_measurement_json(self.participant.participantId, TIME_4.isoformat())) measurement = self.measurement_dao.insert(self._make_physical_measurements(physicalMeasurementsId=669, finalized=TIME_4)) summary = self.dao.get(self.participant.participantId) self.assertEquals(summary.numberDistinctVisits, 1) with clock.FakeClock(TIME_5): order = self.order_dao.insert(self._make_biobank_order(biobankOrderId='2', identifiers=[ BiobankOrderIdentifier(system='b', value='d')], samples=[BiobankOrderedSample( biobankOrderId='2', finalized=TIME_5, test=BIOBANK_TESTS[0], description='description', processingRequired=True)])) with clock.FakeClock(TIME_7): summary = self.dao.get(self.participant.participantId) # distinct count should be 2 self.assertEquals(summary.numberDistinctVisits, 2) # cancel the measurement cancel_measurement = get_restore_or_cancel_info() with self.measurement_dao.session() as session: self.measurement_dao.update_with_patch(measurement.physicalMeasurementsId, session, cancel_measurement) summary = self.dao.get(self.participant.participantId) self.assertEquals(summary.numberDistinctVisits, 1) with clock.FakeClock(TIME_7): restore_measurement = get_restore_or_cancel_info(status='restored') with self.measurement_dao.session() as session: self.measurement_dao.update_with_patch(measurement.physicalMeasurementsId, session, restore_measurement) summary = self.dao.get(self.participant.participantId) self.assertEquals(summary.numberDistinctVisits, 2) cancel_request = cancel_biobank_order() order = self.order_dao.update_with_patch(order.biobankOrderId, cancel_request, order.version) summary = self.dao.get(self.participant.participantId) self.assertEquals(summary.numberDistinctVisits, 1) restore_order = get_restore_or_cancel_info(status='restored') restore_order['amendedReason'] = 'some reason' self.order_dao.update_with_patch(order.biobankOrderId, restore_order, order.version) summary = self.dao.get(self.participant.participantId) self.assertEquals(summary.numberDistinctVisits, 2) def test_amending_biobank_order_distinct_visit_count(self): self.participant = self._insert(Participant(participantId=9, biobankId=13)) with clock.FakeClock(TIME_5): order = self.order_dao.insert(self._make_biobank_order(biobankOrderId='2', identifiers=[ BiobankOrderIdentifier(system='b', value='d')], samples=[BiobankOrderedSample( biobankOrderId='2', finalized=TIME_5, test=BIOBANK_TESTS[0], description='description', processingRequired=True)])) summary = self.dao.get(self.participant.participantId) self.assertEquals(summary.numberDistinctVisits, 1) with clock.FakeClock(TIME_7): amend_order = self._get_amended_info(order) with self.order_dao.session() as session: self.order_dao._do_update(session, amend_order, order) # Shouldn't change on a simple amendment (unless finalized time on samples change) summary = self.dao.get(self.participant.participantId) self.assertEquals(summary.numberDistinctVisits, 1) with clock.FakeClock(TIME_7_5): cancel_request = cancel_biobank_order() order = self.order_dao.update_with_patch(order.biobankOrderId, cancel_request, order.version) # A cancelled order (even after amending) should reduce count (unless some other valid order on same day) summary = self.dao.get(self.participant.participantId) self.assertEquals(summary.numberDistinctVisits, 0) @staticmethod def _get_amended_info(order): amendment = dict(amendedReason='I had to change something', amendedInfo={ "author": { "system": "https://www.pmi-ops.org/healthpro-username", "value": "*****@*****.**" }, "site": { "system": "https://www.pmi-ops.org/site-id", "value": "hpo-site-monroeville" } }) order.amendedReason = amendment['amendedReason'] order.amendedInfo = amendment['amendedInfo'] return order def _make_biobank_order(self, **kwargs): """Makes a new BiobankOrder (same values every time) with valid/complete defaults. Kwargs pass through to BiobankOrder constructor, overriding defaults. """ for k, default_value in ( ('biobankOrderId', '1'), ('created', clock.CLOCK.now()), ('participantId', self.participant.participantId), ('sourceSiteId', 1), ('sourceUsername', '*****@*****.**'), ('collectedSiteId', 1), ('collectedUsername', '*****@*****.**'), ('processedSiteId', 1), ('processedUsername', '*****@*****.**'), ('finalizedSiteId', 2), ('finalizedUsername', '*****@*****.**'), ('identifiers', [BiobankOrderIdentifier(system='a', value='c')]), ('samples', [BiobankOrderedSample( biobankOrderId='1', test=BIOBANK_TESTS[0], description='description', finalized=TIME_1, processingRequired=True)])): if k not in kwargs: kwargs[k] = default_value return BiobankOrder(**kwargs) def _make_physical_measurements(self, **kwargs): """Makes a new PhysicalMeasurements (same values every time) with valid/complete defaults. Kwargs pass through to PM constructor, overriding defaults. """ for k, default_value in ( ('physicalMeasurementsId', 1), ('participantId', self.participant.participantId), ('resource', self.measurement_json), ('createdSiteId', 1), ('finalized', TIME_3), ('finalizedSiteId', 2)): if k not in kwargs: kwargs[k] = default_value return PhysicalMeasurements(**kwargs)
class PhysicalMeasurementsDaoTest(SqlTestBase): def setUp(self): super(PhysicalMeasurementsDaoTest, self).setUp(use_mysql=True) self.participant = Participant(participantId=1, biobankId=2) ParticipantDao().insert(self.participant) self.dao = PhysicalMeasurementsDao() self.participant_summary_dao = ParticipantSummaryDao() self.measurement_json = json.dumps( load_measurement_json(self.participant.participantId, TIME_1.isoformat())) self.biobank = BiobankOrderDao() def test_from_client_json(self): measurement = PhysicalMeasurementsDao.from_client_json( json.loads(self.measurement_json)) self.assertIsNotNone(measurement.createdSiteId) self.assertIsNotNone(measurement.finalizedSiteId) def _make_physical_measurements(self, **kwargs): """Makes a new PhysicalMeasurements (same values every time) with valid/complete defaults. Kwargs pass through to PM constructor, overriding defaults. """ for k, default_value in (('physicalMeasurementsId', 1), ('participantId', self.participant.participantId), ('resource', self.measurement_json), ('createdSiteId', 1), ('finalizedSiteId', 2)): if k not in kwargs: kwargs[k] = default_value return PhysicalMeasurements(**kwargs) def testInsert_noParticipantId(self): with self.assertRaises(BadRequest): self.dao.insert( self._make_physical_measurements(participantId=None)) def testInsert_wrongParticipantId(self): with self.assertRaises(BadRequest): self.dao.insert(self._make_physical_measurements(participantId=2)) def _with_id(self, resource, id_): measurements_json = json.loads(resource) measurements_json['id'] = id_ return json.dumps(measurements_json) def testInsert_noSummary(self): with self.assertRaises(BadRequest): self.dao.insert(self._make_physical_measurements()) def _make_summary(self): self.participant_summary_dao.insert( self.participant_summary(self.participant)) def testInsert_rightParticipantId(self): self._make_summary() summary = ParticipantSummaryDao().get(self.participant.participantId) self.assertIsNone(summary.physicalMeasurementsStatus) with FakeClock(TIME_2): measurements = self.dao.insert(self._make_physical_measurements()) expected_measurements = PhysicalMeasurements( physicalMeasurementsId=1, participantId=self.participant.participantId, resource=self._with_id(self.measurement_json, '1'), created=TIME_2, finalized=TIME_1, final=True, logPositionId=1, createdSiteId=1, finalizedSiteId=2) self.assertEquals(expected_measurements.asdict(), measurements.asdict()) measurements = self.dao.get(measurements.physicalMeasurementsId) self.assertEquals(expected_measurements.asdict(), measurements.asdict()) # Completing physical measurements changes the participant summary status summary = ParticipantSummaryDao().get(self.participant.participantId) self.assertEquals(PhysicalMeasurementsStatus.COMPLETED, summary.physicalMeasurementsStatus) self.assertEquals(TIME_2, summary.physicalMeasurementsTime) self.assertEquals(TIME_2, summary.lastModified) def test_backfill_is_noop(self): self._make_summary() measurements_id = self.dao.insert( self._make_physical_measurements()).physicalMeasurementsId orig_measurements = self.dao.get_with_children( measurements_id).asdict() self.dao.backfill_measurements() backfilled_measurements = self.dao.get_with_children( measurements_id).asdict() # Formatting of resource gets changed, so test it separately as parsed JSON. self.assertEquals(json.loads(orig_measurements['resource']), json.loads(backfilled_measurements['resource'])) del orig_measurements['resource'] del backfilled_measurements['resource'] self.assertEquals(orig_measurements, backfilled_measurements) def testInsert_withdrawnParticipantFails(self): self.participant.withdrawalStatus = WithdrawalStatus.NO_USE ParticipantDao().update(self.participant) self._make_summary() summary = ParticipantSummaryDao().get(self.participant.participantId) self.assertIsNone(summary.physicalMeasurementsStatus) with self.assertRaises(Forbidden): self.dao.insert(self._make_physical_measurements()) def testInsert_getFailsForWithdrawnParticipant(self): self._make_summary() self.dao.insert(self._make_physical_measurements()) self.participant.version += 1 self.participant.withdrawalStatus = WithdrawalStatus.NO_USE ParticipantDao().update(self.participant) with self.assertRaises(Forbidden): self.dao.get(1) with self.assertRaises(Forbidden): self.dao.query( Query([ FieldFilter('participantId', Operator.EQUALS, self.participant.participantId) ], None, 10, None)) def testInsert_duplicate(self): self._make_summary() with FakeClock(TIME_2): measurements = self.dao.insert(self._make_physical_measurements()) with FakeClock(TIME_3): measurements_2 = self.dao.insert( self._make_physical_measurements()) self.assertEquals(measurements.asdict(), measurements_2.asdict()) def testInsert_amend(self): self._make_summary() with FakeClock(TIME_2): measurements = self.dao.insert( self._make_physical_measurements(physicalMeasurementsId=1)) amendment_json = load_measurement_json_amendment( self.participant.participantId, measurements.physicalMeasurementsId, TIME_2) with FakeClock(TIME_3): new_measurements = self.dao.insert( self._make_physical_measurements( physicalMeasurementsId=2, resource=json.dumps(amendment_json))) measurements = self.dao.get(measurements.physicalMeasurementsId) amended_json = json.loads(measurements.resource) self.assertEquals('amended', amended_json['entry'][0]['resource']['status']) self.assertEquals('1', amended_json['id']) amendment_json = json.loads(new_measurements.resource) self.assertEquals('2', amendment_json['id']) self.assertTrue(new_measurements.final) self.assertEquals(TIME_3, new_measurements.created) ps_dao = ParticipantSummaryDao().get(self.participant.participantId) # An amendment should not add a distinct visit count to summary self.assertEqual(ps_dao.numberDistinctVisits, 1) def test_update_with_patch_cancel(self): self._make_summary() summary = ParticipantSummaryDao().get(self.participant.participantId) self.assertIsNone(summary.physicalMeasurementsStatus) with FakeClock(TIME_2): measurements = self.dao.insert(self._make_physical_measurements()) cancel = get_restore_or_cancel_info() with FakeClock(TIME_3): with PhysicalMeasurementsDao().session() as session: update = self.dao.update_with_patch( measurements.physicalMeasurementsId, session, cancel) self.assertEqual(update.status, PhysicalMeasurementsStatus.CANCELLED) self.assertEqual(update.reason, cancel['reason']) self.assertEqual(update.cancelledSiteId, 1) self.assertEqual(update.cancelledTime, TIME_3) self.assertEqual(update.cancelledUsername, cancel['cancelledInfo']['author']['value']) summary = ParticipantSummaryDao().get(self.participant.participantId) self.assertEqual(summary.physicalMeasurementsStatus, PhysicalMeasurementsStatus.CANCELLED) self.assertEqual(summary.physicalMeasurementsTime, None) self.assertEqual(summary.physicalMeasurementsFinalizedTime, TIME_1) self.assertEqual(summary.physicalMeasurementsCreatedSiteId, 1) self.assertEqual(summary.physicalMeasurementsFinalizedSiteId, None) with FakeClock(TIME_3): measurements = self.dao.insert( self._make_physical_measurements(physicalMeasurementsId=2)) with FakeClock(TIME_4): self.dao.insert( self._make_physical_measurements(physicalMeasurementsId=3)) summary = ParticipantSummaryDao().get(self.participant.participantId) self.assertEqual(summary.physicalMeasurementsStatus, PhysicalMeasurementsStatus.COMPLETED) self.assertEqual(summary.physicalMeasurementsTime, TIME_4) self.assertEqual(summary.physicalMeasurementsFinalizedTime, TIME_1) self.assertEqual(summary.physicalMeasurementsCreatedSiteId, 1) self.assertEqual(summary.physicalMeasurementsFinalizedSiteId, 2)