diff --git a/core/src/main/java/google/registry/reporting/billing/CopyDetailReportsAction.java b/core/src/main/java/google/registry/reporting/billing/CopyDetailReportsAction.java index 71ef70db9..2bfd4abf1 100644 --- a/core/src/main/java/google/registry/reporting/billing/CopyDetailReportsAction.java +++ b/core/src/main/java/google/registry/reporting/billing/CopyDetailReportsAction.java @@ -18,11 +18,12 @@ import static com.google.common.base.Throwables.getRootCause; import static google.registry.request.Action.Method.POST; import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; import static jakarta.servlet.http.HttpServletResponse.SC_OK; +import static java.util.stream.Collectors.joining; import com.google.cloud.storage.BlobId; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Iterables; import com.google.common.flogger.FluentLogger; import com.google.common.io.ByteStreams; @@ -41,7 +42,6 @@ import jakarta.inject.Inject; import java.io.IOException; import java.io.InputStream; import java.util.Optional; -import java.util.stream.Collectors; /** Copy all registrar detail reports in a given bucket's subdirectory from GCS to Drive. */ @Action( @@ -98,7 +98,8 @@ public final class CopyDetailReportsAction implements Runnable { response.setPayload(String.format("Failure, encountered %s", e.getMessage())); return; } - ImmutableMap.Builder copyErrorsBuilder = new ImmutableMap.Builder<>(); + ImmutableMultimap.Builder copyErrorsBuilder = + new ImmutableMultimap.Builder<>(); for (String detailReportName : detailReportObjectNames) { // The standard report format is "invoice_details_yyyy-MM_registrarId_tld.csv // TODO(larryruili): Determine a safer way of enforcing this. @@ -145,17 +146,18 @@ public final class CopyDetailReportsAction implements Runnable { response.setStatus(SC_OK); response.setContentType(MediaType.PLAIN_TEXT_UTF_8); StringBuilder payload = new StringBuilder().append("Copied detail reports.\n"); - ImmutableMap copyErrors = copyErrorsBuilder.build(); + ImmutableMultimap copyErrors = copyErrorsBuilder.build(); if (!copyErrors.isEmpty()) { payload.append("The following errors were encountered:\n"); - payload.append( - copyErrors.entrySet().stream() - .map( - entrySet -> - String.format( - "Registrar: %s\nError: %s\n", - entrySet.getKey(), entrySet.getValue().getMessage())) - .collect(Collectors.joining())); + for (var registrarId : copyErrors.keySet()) { + payload.append( + String.format( + "Registrar: %s\nError: %s\n", + registrarId, + copyErrors.get(registrarId).stream() + .map(Throwable::getMessage) + .collect(joining("\n\t")))); + } } response.setPayload(payload.toString()); emailUtils.sendAlertEmail(payload.toString()); diff --git a/core/src/test/java/google/registry/reporting/billing/CopyDetailReportsActionTest.java b/core/src/test/java/google/registry/reporting/billing/CopyDetailReportsActionTest.java index c4b9568e2..b0ca522d4 100644 --- a/core/src/test/java/google/registry/reporting/billing/CopyDetailReportsActionTest.java +++ b/core/src/test/java/google/registry/reporting/billing/CopyDetailReportsActionTest.java @@ -178,21 +178,56 @@ class CopyDetailReportsActionTest { verify(emailUtils) .sendAlertEmail( """ - Copied detail reports. - The following errors were encountered: - Registrar: TheRegistrar - Error: java.io.IOException: expected - """); + Copied detail reports. + The following errors were encountered: + Registrar: TheRegistrar + Error: java.io.IOException: expected + """); assertThat(response.getStatus()).isEqualTo(SC_OK); assertThat(response.getContentType()).isEqualTo(MediaType.PLAIN_TEXT_UTF_8); assertThat(response.getPayload()) .isEqualTo( """ - Copied detail reports. - The following errors were encountered: - Registrar: TheRegistrar - Error: java.io.IOException: expected - """); + Copied detail reports. + The following errors were encountered: + Registrar: TheRegistrar + Error: java.io.IOException: expected + """); + } + + @Test + void testFail_tooManyFailures_one_registrar_sendsAlertEmail_continues() throws IOException { + gcsUtils.createFromBytes( + BlobId.of("test-bucket", "results/invoice_details_2017-10_TheRegistrar_hello.csv"), + "hola,mundo\n3,4".getBytes(UTF_8)); + + gcsUtils.createFromBytes( + BlobId.of("test-bucket", "results/invoice_details_2017-10_TheRegistrar_test.csv"), + "hello,world\n1,2".getBytes(UTF_8)); + when(driveConnection.createOrUpdateFile(any(), any(), any(), any())) + .thenThrow(new IOException("expected")); + + action.run(); + verify(emailUtils) + .sendAlertEmail( + """ + Copied detail reports. + The following errors were encountered: + Registrar: TheRegistrar + Error: java.io.IOException: expected + \tjava.io.IOException: expected + """); + assertThat(response.getStatus()).isEqualTo(SC_OK); + assertThat(response.getContentType()).isEqualTo(MediaType.PLAIN_TEXT_UTF_8); + assertThat(response.getPayload()) + .isEqualTo( + """ + Copied detail reports. + The following errors were encountered: + Registrar: TheRegistrar + Error: java.io.IOException: expected + \tjava.io.IOException: expected + """); } @Test