def setUp(self):
     super(QuestionnaireDaoTest, self).setUp(with_data=False)
     self.dao = QuestionnaireDao()
     self.questionnaire_history_dao = QuestionnaireHistoryDao()
     self.questionnaire_concept_dao = QuestionnaireConceptDao()
     self.questionnaire_question_dao = QuestionnaireQuestionDao()
     self.code_dao = CodeDao()
     self.CODE_1 = Code(codeId=1,
                        system='a',
                        value='b',
                        display=u'c',
                        topic=u'd',
                        codeType=CodeType.MODULE,
                        mapped=True)
     self.CODE_2 = Code(codeId=2,
                        system='a',
                        value='x',
                        display=u'y',
                        codeType=CodeType.MODULE,
                        mapped=False)
     self.CODE_3 = Code(codeId=3,
                        system='a',
                        value='z',
                        display=u'y',
                        codeType=CodeType.MODULE,
                        mapped=False)
     self.CODE_4 = Code(codeId=4,
                        system='a',
                        value='c',
                        codeType=CodeType.QUESTION,
                        mapped=True,
                        parentId=1)
     self.CODE_5 = Code(codeId=5,
                        system='a',
                        value='d',
                        codeType=CodeType.QUESTION,
                        mapped=True,
                        parentId=2)
     self.CODE_6 = Code(codeId=6,
                        system='a',
                        value='e',
                        codeType=CodeType.QUESTION,
                        mapped=True,
                        parentId=2)
     self.CONCEPT_1 = QuestionnaireConcept(codeId=1)
     self.CONCEPT_2 = QuestionnaireConcept(codeId=2)
     self.QUESTION_1 = QuestionnaireQuestion(linkId='a',
                                             codeId=4,
                                             repeats=False)
     self.QUESTION_2 = QuestionnaireQuestion(linkId='d',
                                             codeId=5,
                                             repeats=True)
     self.insert_codes()
 def _get_questionnaire(questionnaire, resource_json):
   """Retrieves the questionnaire referenced by this response; mutates the resource JSON to include
   the version if it doesn't already."""
   if not questionnaire.reference.startswith(_QUESTIONNAIRE_PREFIX):
     raise BadRequest('Questionnaire reference %s is invalid' % questionnaire.reference)
   questionnaire_reference = questionnaire.reference[len(_QUESTIONNAIRE_PREFIX):]
   # If the questionnaire response specifies the version of the questionnaire it's for, use it.
   if _QUESTIONNAIRE_HISTORY_SEGMENT in questionnaire_reference:
     questionnaire_ref_parts = questionnaire_reference.split(_QUESTIONNAIRE_HISTORY_SEGMENT)
     if len(questionnaire_ref_parts) != 2:
       raise BadRequest('Questionnaire id %s is invalid' % questionnaire_reference)
     try:
       questionnaire_id = int(questionnaire_ref_parts[0])
       version = int(questionnaire_ref_parts[1])
       q = QuestionnaireHistoryDao().get_with_children((questionnaire_id, version))
       if not q:
         raise BadRequest('Questionnaire with id %d, version %d is not found' %
                          (questionnaire_id, version))
       return q
     except ValueError:
       raise BadRequest('Questionnaire id %s is invalid' % questionnaire_reference)
   else:
     try:
       questionnaire_id = int(questionnaire_reference)
       from dao.questionnaire_dao import QuestionnaireDao
       q = QuestionnaireDao().get_with_children(questionnaire_id)
       if not q:
         raise BadRequest('Questionnaire with id %d is not found' % questionnaire_id)
       # Mutate the questionnaire reference to include the version.
       questionnaire_reference = _QUESTIONNAIRE_REFERENCE_FORMAT % (questionnaire_id, q.version)
       resource_json["questionnaire"]["reference"] = questionnaire_reference
       return q
     except ValueError:
       raise BadRequest('Questionnaire id %s is invalid' % questionnaire_reference)
  def insert_with_session(self, session, questionnaire_response):
    questionnaire_history = (
        QuestionnaireHistoryDao().
        get_with_children_with_session(session, [questionnaire_response.questionnaireId,
                                                 questionnaire_response.questionnaireVersion]))
    if not questionnaire_history:
      raise BadRequest('Questionnaire with ID %s, version %s is not found' %
                       (questionnaire_response.questionnaireId,
                        questionnaire_response.questionnaireVersion))
    q_question_ids = set([
        question.questionnaireQuestionId for question in questionnaire_history.questions])
    for answer in questionnaire_response.answers:
      if answer.questionId not in q_question_ids:
        raise BadRequest('Questionnaire response contains question ID %s not in questionnaire.' %
                         answer.questionId)

    questionnaire_response.created = clock.CLOCK.now()

    # Put the ID into the resource.
    resource_json = json.loads(questionnaire_response.resource)
    resource_json['id'] = str(questionnaire_response.questionnaireResponseId)
    questionnaire_response.resource = json.dumps(resource_json)

    question_ids = [answer.questionId for answer in questionnaire_response.answers]
    questions = QuestionnaireQuestionDao().get_all_with_session(session, question_ids)
    code_ids = [question.codeId for question in questions]
    current_answers = (QuestionnaireResponseAnswerDao().
        get_current_answers_for_concepts(session, questionnaire_response.participantId, code_ids))

    # IMPORTANT: update the participant summary first to grab an exclusive lock on the participant
    # row. If you insetad do this after the insert of the questionnaire response, MySQL will get a
    # shared lock on the participant row due the foreign key, and potentially deadlock later trying
    # to get the exclusive lock if another thread is updating the participant. See DA-269.
    # (We need to lock both participant and participant summary because the summary row may not
    # exist yet.)
    self._update_participant_summary(
        session, questionnaire_response, code_ids, questions, questionnaire_history)

    super(QuestionnaireResponseDao, self).insert_with_session(session, questionnaire_response)
    # Mark existing answers for the questions in this response given previously by this participant
    # as ended.
    for answer in current_answers:
      answer.endTime = questionnaire_response.created
      session.merge(answer)

    return questionnaire_response
class QuestionnaireDaoTest(SqlTestBase):
    def setUp(self):
        super(QuestionnaireDaoTest, self).setUp(with_data=False)
        self.dao = QuestionnaireDao()
        self.questionnaire_history_dao = QuestionnaireHistoryDao()
        self.questionnaire_concept_dao = QuestionnaireConceptDao()
        self.questionnaire_question_dao = QuestionnaireQuestionDao()
        self.code_dao = CodeDao()
        self.CODE_1 = Code(codeId=1,
                           system='a',
                           value='b',
                           display=u'c',
                           topic=u'd',
                           codeType=CodeType.MODULE,
                           mapped=True)
        self.CODE_2 = Code(codeId=2,
                           system='a',
                           value='x',
                           display=u'y',
                           codeType=CodeType.MODULE,
                           mapped=False)
        self.CODE_3 = Code(codeId=3,
                           system='a',
                           value='z',
                           display=u'y',
                           codeType=CodeType.MODULE,
                           mapped=False)
        self.CODE_4 = Code(codeId=4,
                           system='a',
                           value='c',
                           codeType=CodeType.QUESTION,
                           mapped=True,
                           parentId=1)
        self.CODE_5 = Code(codeId=5,
                           system='a',
                           value='d',
                           codeType=CodeType.QUESTION,
                           mapped=True,
                           parentId=2)
        self.CODE_6 = Code(codeId=6,
                           system='a',
                           value='e',
                           codeType=CodeType.QUESTION,
                           mapped=True,
                           parentId=2)
        self.CONCEPT_1 = QuestionnaireConcept(codeId=1)
        self.CONCEPT_2 = QuestionnaireConcept(codeId=2)
        self.QUESTION_1 = QuestionnaireQuestion(linkId='a',
                                                codeId=4,
                                                repeats=False)
        self.QUESTION_2 = QuestionnaireQuestion(linkId='d',
                                                codeId=5,
                                                repeats=True)
        self.insert_codes()

    def insert_codes(self):
        self.code_dao.insert(self.CODE_1)
        self.code_dao.insert(self.CODE_2)
        self.code_dao.insert(self.CODE_3)
        self.code_dao.insert(self.CODE_4)
        self.code_dao.insert(self.CODE_5)
        self.code_dao.insert(self.CODE_6)

    def test_get_before_insert(self):
        self.assertIsNone(self.dao.get(1))
        self.assertIsNone(self.dao.get_with_children(1))
        self.assertIsNone(
            self.dao.get_latest_questionnaire_with_concept(self.CODE_1.codeId))
        self.assertIsNone(self.questionnaire_history_dao.get([1, 1]))
        self.assertIsNone(
            self.questionnaire_history_dao.get_with_children([1, 1]))
        self.assertIsNone(self.questionnaire_concept_dao.get(1))
        self.assertIsNone(self.questionnaire_question_dao.get(1))

    def check_history(self):
        expected_history = QuestionnaireHistory(questionnaireId=1,
                                                version=1,
                                                created=TIME,
                                                lastModified=TIME,
                                                resource=RESOURCE_1_WITH_ID)
        questionnaire_history = self.questionnaire_history_dao.get([1, 1])
        self.assertEquals(expected_history.asdict(),
                          questionnaire_history.asdict())

        questionnaire_history = self.questionnaire_history_dao.get_with_children(
            [1, 1])
        expected_history.concepts.append(EXPECTED_CONCEPT_1)
        expected_history.concepts.append(EXPECTED_CONCEPT_2)
        expected_history.questions.append(EXPECTED_QUESTION_1)
        expected_history.questions.append(EXPECTED_QUESTION_2)

        self.assertEquals(EXPECTED_CONCEPT_1.asdict(),
                          self.questionnaire_concept_dao.get(1).asdict())
        self.assertEquals(EXPECTED_CONCEPT_2.asdict(),
                          self.questionnaire_concept_dao.get(2).asdict())
        self.assertEquals(EXPECTED_QUESTION_1.asdict(),
                          self.questionnaire_question_dao.get(1).asdict())
        self.assertEquals(EXPECTED_QUESTION_2.asdict(),
                          self.questionnaire_question_dao.get(2).asdict())

    def test_insert(self):
        q = Questionnaire(resource=RESOURCE_1)
        q.concepts.append(self.CONCEPT_1)
        q.concepts.append(self.CONCEPT_2)
        q.questions.append(self.QUESTION_1)
        q.questions.append(self.QUESTION_2)

        with FakeClock(TIME):
            self.dao.insert(q)

        # Creating a questionnaire creates a history entry with children
        self.check_history()

        expected_questionnaire = Questionnaire(questionnaireId=1,
                                               version=1,
                                               created=TIME,
                                               lastModified=TIME,
                                               resource=RESOURCE_1_WITH_ID)
        questionnaire = self.dao.get(1)
        self.assertEquals(expected_questionnaire.asdict(),
                          questionnaire.asdict())

        expected_questionnaire.concepts.append(EXPECTED_CONCEPT_1)
        expected_questionnaire.concepts.append(EXPECTED_CONCEPT_2)
        expected_questionnaire.questions.append(EXPECTED_QUESTION_1)
        expected_questionnaire.questions.append(EXPECTED_QUESTION_2)

        questionnaire = self.dao.get_with_children(1)

        self.assertEquals(
            sort_lists(expected_questionnaire.asdict_with_children()),
            sort_lists(questionnaire.asdict_with_children()))
        self.assertEquals(
            questionnaire.asdict(),
            self.dao.get_latest_questionnaire_with_concept(
                self.CODE_1.codeId).asdict())

    def test_insert_duplicate(self):
        q = Questionnaire(questionnaireId=1, resource=RESOURCE_1)
        self.dao.insert(q)
        try:
            self.dao.insert(q)
            self.fail("IntegrityError expected")
        except IntegrityError:
            pass

    def test_update_right_expected_version(self):
        q = Questionnaire(resource=RESOURCE_1)
        with FakeClock(TIME):
            self.dao.insert(q)

        q = Questionnaire(questionnaireId=1, version=1, resource=RESOURCE_2)
        with FakeClock(TIME_2):
            self.dao.update(q)

        expected_questionnaire = Questionnaire(questionnaireId=1,
                                               version=2,
                                               created=TIME,
                                               lastModified=TIME_2,
                                               resource=RESOURCE_2_WITH_ID)
        questionnaire = self.dao.get(1)
        self.assertEquals(expected_questionnaire.asdict(),
                          questionnaire.asdict())

    def test_update_wrong_expected_version(self):
        q = Questionnaire(resource=RESOURCE_1)
        with FakeClock(TIME):
            self.dao.insert(q)

        q = Questionnaire(questionnaireId=1, version=2, resource=RESOURCE_2)
        with FakeClock(TIME_2):
            try:
                self.dao.update(q)
                self.fail("PreconditionFailed expected")
            except PreconditionFailed:
                pass

    def test_update_not_exists(self):
        q = Questionnaire(questionnaireId=1, resource=RESOURCE_1)
        try:
            self.dao.update(q)
            self.fail("NotFound expected")
        except NotFound:
            pass

    def test_insert_multiple_questionnaires_same_concept(self):
        q = Questionnaire(resource=RESOURCE_1)
        q.concepts.append(self.CONCEPT_1)
        q.concepts.append(self.CONCEPT_2)
        with FakeClock(TIME):
            self.dao.insert(q)

        q2 = Questionnaire(resource=RESOURCE_2)
        q2.concepts.append(self.CONCEPT_1)
        with FakeClock(TIME_2):
            self.dao.insert(q2)

        self.assertEquals(
            2,
            self.dao.get_latest_questionnaire_with_concept(
                self.CODE_1.codeId).questionnaireId)
        self.assertEquals(
            1,
            self.dao.get_latest_questionnaire_with_concept(
                self.CODE_2.codeId).questionnaireId)
    def insert_with_session(self, session, questionnaire_response):

        # Look for a questionnaire that matches any of the questionnaire history records.
        questionnaire_history = (
            QuestionnaireHistoryDao().get_with_children_with_session(
                session, [
                    questionnaire_response.questionnaireId,
                    questionnaire_response.questionnaireVersion
                ]))

        if not questionnaire_history:
            raise BadRequest(
                'Questionnaire with ID %s, version %s is not found' %
                (questionnaire_response.questionnaireId,
                 questionnaire_response.questionnaireVersion))

        # Get the questions from the questionnaire history record.
        q_question_ids = set([
            question.questionnaireQuestionId
            for question in questionnaire_history.questions
        ])
        for answer in questionnaire_response.answers:
            if answer.questionId not in q_question_ids:
                raise BadRequest(
                    'Questionnaire response contains question ID %s not in questionnaire.'
                    % answer.questionId)

        questionnaire_response.created = clock.CLOCK.now()
        if not questionnaire_response.authored:
            questionnaire_response.authored = questionnaire_response.created

        # Put the ID into the resource.
        resource_json = json.loads(questionnaire_response.resource)
        resource_json['id'] = str(
            questionnaire_response.questionnaireResponseId)
        questionnaire_response.resource = json.dumps(resource_json)

        # Gather the question ids and records that match the questions in the response
        question_ids = [
            answer.questionId for answer in questionnaire_response.answers
        ]
        questions = QuestionnaireQuestionDao().get_all_with_session(
            session, question_ids)

        # DA-623: raise error when response link ids do not match our question link ids.
        # Gather the valid link ids for this question
        link_ids = [question.linkId for question in questions]
        # look through the response and verify each link id is valid for each question.
        self._validate_link_ids_from_resource_json_group(
            resource_json, link_ids)

        code_ids = [question.codeId for question in questions]
        current_answers = (
            QuestionnaireResponseAnswerDao().get_current_answers_for_concepts(
                session, questionnaire_response.participantId, code_ids))

        # IMPORTANT: update the participant summary first to grab an exclusive lock on the participant
        # row. If you insetad do this after the insert of the questionnaire response, MySQL will get a
        # shared lock on the participant row due the foreign key, and potentially deadlock later trying
        # to get the exclusive lock if another thread is updating the participant. See DA-269.
        # (We need to lock both participant and participant summary because the summary row may not
        # exist yet.)
        with self.session() as new_session:
            self._update_participant_summary(new_session,
                                             questionnaire_response, code_ids,
                                             questions, questionnaire_history,
                                             resource_json)

        super(QuestionnaireResponseDao,
              self).insert_with_session(session, questionnaire_response)
        # Mark existing answers for the questions in this response given previously by this participant
        # as ended.
        for answer in current_answers:
            answer.endTime = questionnaire_response.created
            session.merge(answer)

        return questionnaire_response