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
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
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
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
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
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=[])
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}")