def test_generate_incomplete_data(self) -> None:
     """Test that upload_string_to_storage is not called and a KeyError is raised if the recipient data is missing
     a key needed for the HTML template."""
     with self.assertRaises(KeyError):
         self.recipient_data.pop('officer_given_name')
         report_context = PoMonthlyReportContext(self.state_code,
                                                 self.recipient_data)
         generate(report_context)
         self.mock_upload_string_to_storage.assert_not_called()
    def test_generate(self) -> None:
        """Test that the prepared html is added to Google Cloud Storage with the correct bucket name, filepath,
        and prepared html template for the report context."""
        prepared_html = self.report_context.render_html()
        generate(self.report_context)

        bucket_name = "recidiviz-test-report-html"
        bucket_filepath = f"{self.state_code.value}/{self.mock_batch_id}/html/{self.recipient.email_address}.html"
        path = GcsfsFilePath.from_absolute_path(f"gs://{bucket_name}/{bucket_filepath}")
        self.assertEqual(self.gcs_file_system.download_as_string(path), prepared_html)
 def test_generate(self) -> None:
     """Test that upload_string_to_storage is called with the correct bucket name, filepath, and prepared
     html template for the report context."""
     template_path = self.report_context.get_html_template_filepath()
     with open(template_path) as html_file:
         prepared_data = self.report_context.get_prepared_data()
         html_template = Template(html_file.read())
         prepared_html = html_template.substitute(prepared_data)
     generate(self.report_context)
     bucket_name = 'recidiviz-test-report-html'
     bucket_filepath = f'{self.mock_batch_id}/{self.recipient_data["email_address"]}.html'
     self.mock_upload_string_to_storage.assert_called_with(
         bucket_name, bucket_filepath, prepared_html, 'text/html')
Example #4
0
 def test_generate(self) -> None:
     """Test that the prepared html is added to Google Cloud Storage with the correct bucket name, filepath,
     and prepared html template for the report context."""
     template_path = self.report_context.get_html_template_filepath()
     with open(template_path) as html_file:
         prepared_data = self.report_context.get_prepared_data()
         html_template = Template(html_file.read())
         prepared_html = html_template.substitute(prepared_data)
     generate(self.report_context)
     bucket_name = "recidiviz-test-report-html"
     bucket_filepath = (
         f"{self.mock_batch_id}/html/{self.recipient.email_address}.html"
     )
     path = GcsfsFilePath.from_absolute_path(f"gs://{bucket_name}/{bucket_filepath}")
     self.assertEqual(self.gcs_file_system.download_as_string(path), prepared_html)
    def test_generate_incomplete_data(self) -> None:
        """Test that no files are added to Google Cloud Storage and a KeyError is raised
        if the recipient data is missing a key needed for the HTML template."""

        with self.assertRaises(KeyError):
            recipient = Recipient.from_report_json(
                {
                    "email_address": "*****@*****.**",
                    "state_code": "US_ID",
                    "district": "DISTRICT OFFICE 3",
                }
            )

            report_context = self.report_context_type(self.state_code, recipient)
            generate(report_context)

        self.assertEqual(self.gcs_file_system.all_paths, [])
Example #6
0
def start(state_code: str,
          report_type: str,
          test_address: Optional[str] = None) -> str:
    """Begins data retrieval for a new batch of email reports.

    Start with collection of data from the calculation pipelines.

    If a test_address is provided, it overrides the recipient's email_address with a formatted test_address
    and uses the formatted test_address to save the generated emails.

    Args:
        state_code: The state for which to generate reports
        report_type: The type of report to send
        test_address: Optional email address for which to generate all emails

    Returns: The batch id for the newly started batch
    """
    batch_id = utils.generate_batch_id()
    logging.info("New batch started for %s and %s. Batch id = %s", state_code,
                 report_type, batch_id)

    recipient_data = retrieve_data(state_code, report_type, batch_id)

    for recipient in recipient_data:
        if test_address:
            recipient_email_address = recipient[utils.KEY_EMAIL_ADDRESS]
            formatted_test_address = utils.format_test_address(
                test_address, recipient_email_address)
            # Override the recipient's address with the test address
            recipient[utils.KEY_EMAIL_ADDRESS] = formatted_test_address
            logging.info("Generating email for [%s] with test address: %s",
                         recipient_email_address, formatted_test_address)

        recipient[utils.KEY_BATCH_ID] = batch_id
        report_context = get_report_context(state_code, report_type, recipient)
        email_generation.generate(report_context)

    return batch_id
Example #7
0
def start(
    state_code: str,
    report_type: str,
    batch_id: Optional[str] = None,
    test_address: Optional[str] = None,
    region_code: Optional[str] = None,
    email_allowlist: Optional[List[str]] = None,
    message_body: Optional[str] = None,
) -> Tuple[int, int]:
    """Begins data retrieval for a new batch of email reports.

    Start with collection of data from the calculation pipelines.

    If a test_address is provided, it overrides the recipient's email_address with a formatted test_address
    and uses the formatted test_address to save the generated emails.

    Args:
        state_code: The state for which to generate reports
        report_type: The type of report to send
        batch_id: The batch id to save the newly started batch to
        test_address: Optional email address for which to generate all emails
        region_code: Optional region code which specifies the sub-region of the state in which to
            generate reports. If empty, this generates reports for all regions.
        email_allowlist: Optional list of email_addresses to generate for; all other recipients are skipped
        recipient_emails: Optional list of email_addresses to generate for; all other recipients are skipped
        message_body: Optional override for the message body in the email.

    Returns: Tuple containing:
        - Number of failed email generations
        - Number of successful email generations
    """
    if batch_id is None:
        batch_id = utils.generate_batch_id()

    logging.info(
        "New batch started for %s (region: %s) and %s. Batch id = %s",
        state_code,
        region_code,
        report_type,
        batch_id,
    )

    recipients: List[Recipient] = retrieve_data(state_code, report_type, batch_id)
    recipients = filter_recipients(recipients, region_code, email_allowlist)

    if test_address:
        logging.info("Overriding batch emails with test address: %s", test_address)
        recipients = [
            recipient.create_derived_recipient(
                {
                    "email_address": utils.format_test_address(
                        test_address, recipient.email_address
                    ),
                }
            )
            for recipient in recipients
        ]

    if message_body is not None:
        logging.info(
            "Overriding default message body in batch emails (batch id = %s)", batch_id
        )
        recipients = [
            recipient.create_derived_recipient({"message_body": message_body})
            for recipient in recipients
        ]

    failure_count = 0
    success_count = 0

    for recipient in recipients:
        try:
            report_context = get_report_context(state_code, report_type, recipient)
            email_generation.generate(report_context)
        except Exception as e:
            failure_count += 1
            logging.error("Failed to generate report email for %s %s", recipient, e)
        else:
            success_count += 1

    return failure_count, success_count
Example #8
0
def start(
    state_code: StateCode,
    report_type: ReportType,
    batch_id: Optional[str] = None,
    test_address: Optional[str] = None,
    region_code: Optional[str] = None,
    email_allowlist: Optional[List[str]] = None,
    message_body_override: Optional[str] = None,
) -> MultiRequestResult[str, str]:
    """Begins data retrieval for a new batch of email reports.

    Start with collection of data from the calculation pipelines.

    If a test_address is provided, it overrides the recipient's email_address with a formatted test_address
    and uses the formatted test_address to save the generated emails.

    Args:
        state_code: The state for which to generate reports
        report_type: The type of report to send
        batch_id: The batch id to save the newly started batch to
        test_address: Optional email address for which to generate all emails
        region_code: Optional region code which specifies the sub-region of the state in which to
            generate reports. If empty, this generates reports for all regions.
        email_allowlist: Optional list of email_addresses to generate for; all other recipients are skipped
        recipient_emails: Optional list of email_addresses to generate for; all other recipients are skipped
        message_body_override: Optional override for the message body in the email.

    Returns: A MultiRequestResult containing the email addresses for which reports were successfully generated for
            and failed to generate for
    """
    if batch_id is None:
        batch_id = utils.generate_batch_id()

    logging.info(
        "New batch started for %s (region: %s) and %s. Batch id = %s",
        state_code,
        region_code,
        report_type,
        batch_id,
    )

    recipients: List[Recipient] = retrieve_data(state_code, report_type, batch_id)
    recipients = filter_recipients(recipients, region_code, email_allowlist)

    if test_address:
        logging.info("Overriding batch emails with test address: %s", test_address)
        recipients = [
            recipient.create_derived_recipient(
                {
                    "email_address": utils.format_test_address(
                        test_address, recipient.email_address
                    ),
                }
            )
            for recipient in recipients
        ]

    if message_body_override is not None:
        logging.info(
            "Overriding default message body in batch emails (batch id = %s)", batch_id
        )
        recipients = [
            recipient.create_derived_recipient(
                {"message_body_override": message_body_override}
            )
            for recipient in recipients
        ]

    failed_email_addresses: List[str] = []
    succeeded_email_addresses: List[str] = []

    # Currently this is only used to pass review month & year, but when we need to add
    # more, the way that we do this should likely be changed/refactored.
    metadata: Dict[str, str] = {}

    for recipient in recipients:
        try:
            report_context = get_report_context(state_code, report_type, recipient)
            email_generation.generate(report_context)
        except Exception as e:
            failed_email_addresses.append(recipient.email_address)
            logging.error("Failed to generate report email for %s %s", recipient, e)
        else:
            succeeded_email_addresses.append(recipient.email_address)
            if report_type == ReportType.POMonthlyReport and len(metadata) == 0:
                metadata["review_year"] = recipient.data["review_year"]
                metadata["review_month"] = recipient.data["review_month"]

    _write_batch_metadata(
        batch_id=batch_id,
        state_code=state_code,
        report_type=report_type,
        **metadata,
    )
    return MultiRequestResult(
        successes=succeeded_email_addresses, failures=failed_email_addresses
    )