def test_log_metric_invalid_unit(invalid_input, expected): # GIVEN invalid units are provided # WHEN log_metric is called # THEN ValueError exception should be raised with pytest.raises(expected): log_metric(name="test_metric", **invalid_input)
def test_log_metric(capsys): # GIVEN a service, unit and value have been provided # WHEN log_metric is called # THEN custom metric line should be match given values log_metric(service="payment", name="test_metric", unit=MetricUnit.Seconds, value=60) expected = "MONITORING|60|Seconds|test_metric|ServerlessAirline|service=payment\n" captured = capsys.readouterr() assert captured.out == expected
def test_log_metric_partially_correct_args(capsys, invalid_input, expected): # GIVEN invalid arguments are provided such as empty dimension values and metric units in strings # WHEN log_metric is called # THEN default values should be used such as "Count" as a unit, invalid dimensions not included # and no exception raised log_metric(name="test_metric", **invalid_input) captured = capsys.readouterr() assert captured.out == expected
def test_log_metric_multiple_dimensions(capsys): # GIVEN multiple optional dimensions are provided # WHEN log_metric is called # THEN dimensions should appear as dimenion=value log_metric( name="test_metric", unit=MetricUnit.Seconds, value=60, customer="abc", charge_id="123" ) expected = "MONITORING|60|Seconds|test_metric|ServerlessAirline|service=service_undefined,customer=abc,charge_id=123\n" captured = capsys.readouterr() assert captured.out == expected
def test_log_metric_env_var(monkeypatch, capsys): # GIVEN a service, unit and value have been provided # WHEN log_metric is called # THEN custom metric line should be match given values service_name = "payment" monkeypatch.setenv("POWERTOOLS_SERVICE_NAME", service_name) log_metric(name="test_metric", unit=MetricUnit.Seconds, value=60) expected = "MONITORING|60|Seconds|test_metric|ServerlessAirline|service=payment\n" captured = capsys.readouterr() assert captured.out == expected
def lambda_handler(event, context): """AWS Lambda Function entrypoint to cancel booking Parameters ---------- event: dict, required Step Functions State Machine event chargeId: string pre-authorization charge ID context: object, required Lambda Context runtime methods and attributes Context doc: https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html Returns ------- boolean Raises ------ BookingCancellationException Booking Cancellation Exception including error message upon failure """ global _cold_start if _cold_start: log_metric(name="ColdStart", unit=MetricUnit.Count, value=1, function_name=context.function_name) _cold_start = False booking_id = event.get("bookingId") if not booking_id: log_metric(name="InvalidBookingRequest", unit=MetricUnit.Count, value=1, operation="cancel_booking") logger.error({"operation": "invalid_event", "details": event}) raise ValueError("Invalid booking ID") try: logger.debug(f"Cancelling booking - {booking_id}") ret = cancel_booking(booking_id) log_metric(name="SuccessfulCancellation", unit=MetricUnit.Count, value=1) logger.debug("Adding Booking Status annotation") tracer.put_annotation("BookingStatus", "CANCELLED") return ret except BookingCancellationException as err: log_metric(name="FailedCancellation", unit=MetricUnit.Count, value=1) logger.debug("Adding Booking Status annotation before raising error") tracer.put_annotation("BookingStatus", "ERROR") logger.error({"operation": "cancel_booking", "details": err}) raise BookingCancellationException(details=err)
def lambda_handler(event, context): """AWS Lambda Function entrypoint to reserve a booking Parameters ---------- event: dict, required Step Functions State Machine event chargeId: string pre-authorization charge ID stateExecutionId: string Step Functions Process Booking Execution ID chargeId: string Pre-authorization payment token customerId: string Customer unique identifier bookingOutboundFlightId: string Outbound flight unique identifier context: object, required Lambda Context runtime methods and attributes Context doc: https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html Returns ------- bookingId: string booking ID generated Raises ------ BookingReservationException Booking Reservation Exception including error message upon failure """ global _cold_start if _cold_start: log_metric(name="ColdStart", unit=MetricUnit.Count, value=1, function_name=context.function_name) _cold_start = False if not is_booking_request_valid(event): log_metric( name="InvalidBookingRequest", unit=MetricUnit.Count, value=1, operation="reserve_booking", ) logger.error({"operation": "invalid_event", "details": event}) raise ValueError("Invalid booking request") try: logger.debug(f"Reserving booking for customer {event['customerId']}") ret = reserve_booking(event) log_metric(name="SuccessfulReservation", unit=MetricUnit.Count, value=1) logger.debug("Adding Booking Reservation annotation") tracer.put_annotation("Booking", ret["bookingId"]) tracer.put_annotation("BookingStatus", "RESERVED") # Step Functions use the return to append `bookingId` key into the overall output return ret["bookingId"] except BookingReservationException as err: log_metric(name="FailedReservation", unit=MetricUnit.Count, value=1) logger.debug( "Adding Booking Reservation annotation before raising error") tracer.put_annotation("BookingStatus", "ERROR") logger.error({"operation": "reserve_booking", "details": err}) raise BookingReservationException(details=err)
def lambda_handler(event, context): """AWS Lambda Function entrypoint to confirm booking Parameters ---------- event: dict, required Step Functions State Machine event bookingId: string Unique Booking ID of an unconfirmed booking context: object, required Lambda Context runtime methods and attributes Context doc: https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html Returns ------- string bookingReference generated Raises ------ BookingConfirmationException Booking Confirmation Exception including error message upon failure """ global _cold_start if _cold_start: log_metric(name="ColdStart", unit=MetricUnit.Count, value=1, function_name=context.function_name) _cold_start = False booking_id = event.get("bookingId") if not booking_id: log_metric( name="InvalidBookingRequest", unit=MetricUnit.Count, value=1, operation="confirm_booking", ) logger.error({"operation": "invalid_event", "details": event}) raise ValueError("Invalid booking ID") try: logger.debug(f"Confirming booking - {booking_id}") ret = confirm_booking(booking_id) log_metric(name="SuccessfulBooking", unit=MetricUnit.Count, value=1) logger.debug("Adding Booking Status annotation") tracer.put_annotation("BookingReference", ret["bookingReference"]) tracer.put_annotation("BookingStatus", "CONFIRMED") # Step Functions use the return to append `bookingReference` key into the overall output return ret["bookingReference"] except BookingConfirmationException as err: log_metric(name="FailedBooking", unit=MetricUnit.Count, value=1) logger.debug("Adding Booking Status annotation before raising error") tracer.put_annotation("BookingStatus", "ERROR") logger.error({"operation": "confirm_booking", "details": err}) raise BookingConfirmationException(details=err)
def lambda_handler(event, context): """AWS Lambda Function entrypoint to refund payment Parameters ---------- event: dict, required Step Functions State Machine event chargeId: string pre-authorization charge ID context: object, required Lambda Context runtime methods and attributes Context doc: https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html Returns ------- string JSON Stringified data containing refundId ID Raises ------ RefundException Refund Exception including error message upon failure """ global _cold_start if _cold_start: log_metric(name="ColdStart", unit=MetricUnit.Count, value=1, function_name=context.function_name) _cold_start = False payment_token = event.get("chargeId") customer_id = event.get("customerId") if not payment_token: log_metric(name="InvalidPaymentRequest", unit=MetricUnit.Count, value=1, operation="refund_payment") logger.error({"operation": "invalid_event", "details": event}) raise ValueError("Invalid Charge ID") try: logger.debug( f"Refunding payment from customer {customer_id} using {payment_token} token" ) ret = refund_payment(payment_token) log_metric(name="SuccessfulRefund", unit=MetricUnit.Count, value=1) logger.debug("Adding Payment Refund Status annotation") tracer.put_annotation("Refund", ret["refundId"]) tracer.put_annotation("PaymentStatus", "REFUNDED") return ret except RefundException as err: log_metric(name="FailedRefund", unit=MetricUnit.Count, value=1) logger.debug( "Adding Payment Refund Status annotation before raising error") tracer.put_annotation("RefundStatus", "FAILED") logger.error({"operation": "refund_payment", "details": err}) raise RefundException(details=err)
def lambda_handler(event, context): """AWS Lambda Function entrypoint to collect payment Parameters ---------- event: dict, required Step Functions State Machine event chargeId: string pre-authorization charge ID context: object, required Lambda Context runtime methods and attributes Context doc: https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html Returns ------- dict receiptUrl: string receipt URL of charge collected price: int amount collected Raises ------ BookingConfirmationException Booking Confirmation Exception including error message upon failure """ global _cold_start if _cold_start: log_metric( name="ColdStart", unit=MetricUnit.Count, value=1, function_name=context.function_name ) _cold_start = False pre_authorization_token = event.get("chargeId") customer_id = event.get("customerId") if not pre_authorization_token: log_metric( name="InvalidPaymentRequest", unit=MetricUnit.Count, value=1, operation="collect_payment", ) logger.error({"operation": "invalid_event", "details": event}) raise ValueError("Invalid Charge ID") try: logger.debug( f"Collecting payment from customer {customer_id} using {pre_authorization_token} token" ) ret = collect_payment(pre_authorization_token) log_metric(name="SuccessfulPayment", unit=MetricUnit.Count, value=1) logger.debug("Adding Payment Status annotation") tracer.put_annotation("PaymentStatus", "SUCCESS") # Step Functions can append multiple values if you return a single dict return ret except PaymentException as err: log_metric(name="FailedPayment", unit=MetricUnit.Count, value=1) logger.debug("Adding Payment Status annotation before raising error") tracer.put_annotation("PaymentStatus", "FAILED") logger.error({"operation": "collect_payment", "details": err}) raise PaymentException(details=err)
def lambda_handler(event, context): """AWS Lambda Function entrypoint to notify booking Parameters ---------- event: dict, required Step Functions State Machine event customer_id: string Unique Customer ID price: string Flight price bookingReference: string Confirmed booking reference context: object, required Lambda Context runtime methods and attributes Context doc: https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html Returns ------- string notificationId Unique ID confirming notification delivery Raises ------ BookingNotificationException Booking Notification Exception including error message upon failure """ global _cold_start if _cold_start: log_metric(name="ColdStart", unit=MetricUnit.Count, value=1, function_name=context.function_name) _cold_start = False customer_id = event.get("customerId", False) payment = event.get("payment", {}) price = payment.get("price", False) booking_reference = event.get("bookingReference", False) if not customer_id and not price: log_metric(name="InvalidBookingRequest", unit=MetricUnit.Count, value=1, operation="notify_booking") logger.error({"operation": "invalid_event", "details": event}) raise ValueError("Invalid customer and price") try: payload = {"customerId": customer_id, "price": price} ret = notify_booking(payload, booking_reference) log_metric(name="SuccessfulNotification", unit=MetricUnit.Count, value=1) logger.debug("Adding Booking Notification annotation") tracer.put_annotation("BookingNotification", ret["notificationId"]) tracer.put_annotation("BookingNotificationStatus", "SUCCESS") # Step Functions use the return to append `notificationId` key into the overall output return ret["notificationId"] except BookingNotificationException as err: log_metric(name="FailedNotification", unit=MetricUnit.Count, value=1) logger.debug( "Adding Booking Notification annotation before raising error") tracer.put_annotation("BookingNotificationStatus", "FAILED") logger.error({"operation": "notify_booking", "details": err}) raise BookingNotificationException(details=err)