def test_process_message_raises_error_generated_by_processor(
        subject, create_plate_processor):
    raised_error = TransientRabbitError("Test")
    create_plate_processor.return_value.process.side_effect = raised_error

    with pytest.raises(TransientRabbitError) as ex_info:
        subject.process_message(HEADERS, MESSAGE_BODY)

    assert ex_info.value == raised_error
Example #2
0
def test_process_when_transient_error_from_exporter(subject, mock_logger,
                                                    mock_exporter):
    transient_error = TransientRabbitError("Test transient error")
    mock_exporter.return_value.export_to_mongo.side_effect = transient_error

    with pytest.raises(TransientRabbitError) as ex_info:
        subject.process(MagicMock())

    mock_logger.error.assert_called_once()
    assert ex_info.value == transient_error
Example #3
0
    def centres(self):
        if self._centres is None:
            try:
                self._centres = get_centres_config(
                    self._config, CENTRE_DATA_SOURCE_RABBITMQ)
            except Exception:
                raise TransientRabbitError(
                    "Unable to reach MongoDB while getting centres config.")

        return self._centres
Example #4
0
def test_on_message_handles_transient_rabbit_error(subject):
    subject._process_message = Mock(side_effect=TransientRabbitError("Boom!"))
    channel = MagicMock()

    assert subject.had_transient_error is False

    with pytest.raises(TransientRabbitError):
        subject.on_message(channel, MagicMock(), MagicMock(), "")

    channel.basic_ack.assert_not_called()
    channel.basic_nack.assert_not_called()
    assert subject.had_transient_error is True
Example #5
0
    def _record_source_plate_in_mongo_db(
            self, session: ClientSession) -> ExportResult:
        """Find an existing plate in MongoDB or add a new one for the plate in the message."""
        try:
            plate_barcode = self._message.plate_barcode.value
            lab_id_field = self._message.lab_id

            session_database = get_mongo_db(self._config, session.client)
            source_plates_collection = get_mongo_collection(
                session_database, COLLECTION_SOURCE_PLATES)
            mongo_plate = source_plates_collection.find_one(
                filter={FIELD_BARCODE: plate_barcode}, session=session)

            if mongo_plate is not None:
                # There was a plate in Mongo DB for this field barcode so check that the lab ID matches then return.
                self._plate_uuid = mongo_plate[FIELD_LH_SOURCE_PLATE_UUID]

                if mongo_plate[FIELD_MONGO_LAB_ID] != lab_id_field.value:
                    return ExportResult(
                        success=False,
                        create_plate_errors=[
                            CreatePlateError(
                                type=ErrorType.ExportingPlateAlreadyExists,
                                origin=RABBITMQ_CREATE_FEEDBACK_ORIGIN_PLATE,
                                description=
                                (f"Plate barcode '{plate_barcode}' already exists "
                                 f"with a different lab ID: '{mongo_plate[FIELD_MONGO_LAB_ID]}'"
                                 ),
                                field=lab_id_field.name,
                            )
                        ],
                    )

                return ExportResult(success=True, create_plate_errors=[])

            # Create a new plate for this message.
            mongo_plate = create_source_plate_doc(plate_barcode,
                                                  lab_id_field.value)
            source_plates_collection.insert_one(mongo_plate, session=session)
            self._plate_uuid = mongo_plate[FIELD_LH_SOURCE_PLATE_UUID]

            return ExportResult(success=True, create_plate_errors=[])
        except Exception as ex:
            LOGGER.critical(
                f"Error accessing MongoDB during export of source plate '{plate_barcode}': {ex}"
            )
            LOGGER.exception(ex)

            raise TransientRabbitError(
                f"There was an error updating MongoDB while exporting plate with barcode '{plate_barcode}'."
            )
def test_process_message_handles_transient_error_from_schema_registry(
        subject, logger, rabbit_message):
    # We have mocked out the decode method.  The AvroEncoder speaks to the schema registry
    # which could raise this error type so we'll just mock it on the decode method.
    error_message = "Schema registry unreachable"
    rabbit_message.return_value.decode.side_effect = TransientRabbitError(
        error_message)

    with pytest.raises(TransientRabbitError):
        subject.process_message(HEADERS, MESSAGE_BODY)

    logger.error.assert_called_once()
    error_log = logger.error.call_args.args[0]
    assert "transient" in error_log.lower()
    assert error_message in error_log
Example #7
0
    def _record_samples_in_mongo_db(self,
                                    session: ClientSession) -> ExportResult:
        message_uuid = self._message.message_uuid.value
        LOGGER.debug(
            f"Attempting to insert {self._message.total_samples} "
            f"samples from message with UUID {message_uuid} into mongo...")

        try:
            try:
                session_database = get_mongo_db(self._config, session.client)
                samples_collection = get_mongo_collection(
                    session_database, COLLECTION_SAMPLES)
                result = samples_collection.insert_many(
                    documents=self._mongo_sample_docs,
                    ordered=False,
                    session=session)
            except BulkWriteError as ex:
                LOGGER.warning(
                    "BulkWriteError: will now establish whether this was because of duplicate samples."
                )

                duplication_errors = list(
                    filter(lambda x: x["code"] == 11000,
                           ex.details["writeErrors"])  # type: ignore
                )

                if len(duplication_errors) == 0:
                    # There weren't any duplication errors so this is not a problem with the message contents!
                    raise

                create_plate_errors = []
                for duplicate in [x["op"] for x in duplication_errors]:
                    create_plate_errors.append(
                        CreatePlateError(
                            type=ErrorType.ExportingSampleAlreadyExists,
                            origin=RABBITMQ_CREATE_FEEDBACK_ORIGIN_SAMPLE,
                            description=
                            (f"Sample with UUID '{duplicate[FIELD_LH_SAMPLE_UUID]}' was unable to be inserted "
                             "because another sample already exists with "
                             f"Lab ID = '{duplicate[FIELD_MONGO_LAB_ID]}'; "
                             f"Root Sample ID = '{duplicate[FIELD_MONGO_ROOT_SAMPLE_ID]}'; "
                             f"RNA ID = '{duplicate[FIELD_MONGO_RNA_ID]}'; "
                             f"Result = '{duplicate[FIELD_MONGO_RESULT]}'"),
                            sample_uuid=duplicate[FIELD_LH_SAMPLE_UUID],
                        ))

                return ExportResult(success=False,
                                    create_plate_errors=create_plate_errors)
        except Exception as ex:
            LOGGER.critical(
                f"Error accessing MongoDB during export of samples for message UUID '{message_uuid}': {ex}"
            )
            LOGGER.exception(ex)

            raise TransientRabbitError(
                f"There was an error updating MongoDB while exporting samples for message UUID '{message_uuid}'."
            )

        self._samples_inserted = len(result.inserted_ids)
        LOGGER.info(f"{self._samples_inserted} samples inserted into mongo.")

        return ExportResult(success=True, create_plate_errors=[])
Example #8
0
def get_json_from_url(url: str, api_key: str) -> dict:
    try:
        return (dict)(get(url, headers={"X-API-KEY": api_key}).json())
    except Exception:
        raise TransientRabbitError(
            f"Unable to connect to schema registry at {url}")