|
@@ -2,9 +2,17 @@ package cn.yunzhixue.ability.center.examsprint.infrastructure.report.storage;
|
|
|
|
|
|
|
|
import cn.yunzhixue.ability.center.examsprint.domain.report.ExamSprintReportStorage;
|
|
import cn.yunzhixue.ability.center.examsprint.domain.report.ExamSprintReportStorage;
|
|
|
import cn.yunzhixue.ability.center.examsprint.domain.report.ReportType;
|
|
import cn.yunzhixue.ability.center.examsprint.domain.report.ReportType;
|
|
|
|
|
+import com.azure.core.http.HttpHeaderName;
|
|
|
|
|
+import com.azure.core.http.HttpHeaders;
|
|
|
|
|
+import com.azure.core.http.HttpMethod;
|
|
|
|
|
+import com.azure.core.http.HttpRequest;
|
|
|
|
|
+import com.azure.core.http.HttpResponse;
|
|
|
|
|
+import com.azure.core.http.rest.SimpleResponse;
|
|
|
import com.azure.core.util.Context;
|
|
import com.azure.core.util.Context;
|
|
|
import com.azure.storage.blob.BlobClient;
|
|
import com.azure.storage.blob.BlobClient;
|
|
|
import com.azure.storage.blob.BlobContainerClient;
|
|
import com.azure.storage.blob.BlobContainerClient;
|
|
|
|
|
+import com.azure.storage.blob.models.BlobStorageException;
|
|
|
|
|
+import com.azure.storage.blob.models.BlockBlobItem;
|
|
|
import com.azure.storage.blob.options.BlobParallelUploadOptions;
|
|
import com.azure.storage.blob.options.BlobParallelUploadOptions;
|
|
|
import org.junit.jupiter.api.Test;
|
|
import org.junit.jupiter.api.Test;
|
|
|
import org.junit.jupiter.api.extension.ExtendWith;
|
|
import org.junit.jupiter.api.extension.ExtendWith;
|
|
@@ -14,9 +22,11 @@ import org.springframework.boot.test.system.OutputCaptureExtension;
|
|
|
|
|
|
|
|
import java.lang.reflect.Constructor;
|
|
import java.lang.reflect.Constructor;
|
|
|
import java.net.URI;
|
|
import java.net.URI;
|
|
|
|
|
+import java.net.URL;
|
|
|
import java.time.Clock;
|
|
import java.time.Clock;
|
|
|
import java.time.Duration;
|
|
import java.time.Duration;
|
|
|
import java.time.Instant;
|
|
import java.time.Instant;
|
|
|
|
|
+import java.time.OffsetDateTime;
|
|
|
import java.time.ZoneOffset;
|
|
import java.time.ZoneOffset;
|
|
|
|
|
|
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
|
@@ -202,6 +212,8 @@ class AzureBlobExamSprintReportStorageTest {
|
|
|
BlobContainerClient containerClient = mock(BlobContainerClient.class);
|
|
BlobContainerClient containerClient = mock(BlobContainerClient.class);
|
|
|
BlobClient blobClient = mock(BlobClient.class);
|
|
BlobClient blobClient = mock(BlobClient.class);
|
|
|
when(containerClient.getBlobClient(anyString())).thenReturn(blobClient);
|
|
when(containerClient.getBlobClient(anyString())).thenReturn(blobClient);
|
|
|
|
|
+ when(blobClient.uploadWithResponse(argThat(options -> true), eq(Context.NONE)))
|
|
|
|
|
+ .thenReturn(successfulUploadResponse());
|
|
|
AzureBlobExamSprintReportStorage storage = new AzureBlobExamSprintReportStorage(
|
|
AzureBlobExamSprintReportStorage storage = new AzureBlobExamSprintReportStorage(
|
|
|
containerClient,
|
|
containerClient,
|
|
|
"report",
|
|
"report",
|
|
@@ -227,6 +239,51 @@ class AzureBlobExamSprintReportStorageTest {
|
|
|
.contains("fileName=file.pdf")
|
|
.contains("fileName=file.pdf")
|
|
|
.contains("pdfByteLength=3")
|
|
.contains("pdfByteLength=3")
|
|
|
.contains("durationMs=")
|
|
.contains("durationMs=")
|
|
|
|
|
+ .contains("azureStatusCode=201")
|
|
|
|
|
+ .contains("azureRequestId=azure-request-123")
|
|
|
|
|
+ .contains("azureClientRequestId=client-request-456")
|
|
|
|
|
+ .contains("azureETag=etag-789")
|
|
|
|
|
+ .contains("azureLastModified=2026-01-03T08:01Z")
|
|
|
|
|
+ .doesNotContain("connection-string")
|
|
|
|
|
+ .doesNotContain("accountKey")
|
|
|
|
|
+ .doesNotContain("account-key");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void uploadLogsAzureDiagnosticsAndRethrowsWhenUploadRequestFails(CapturedOutput output) throws Exception {
|
|
|
|
|
+ BlobContainerClient containerClient = mock(BlobContainerClient.class);
|
|
|
|
|
+ BlobClient blobClient = mock(BlobClient.class);
|
|
|
|
|
+ when(containerClient.getBlobClient(anyString())).thenReturn(blobClient);
|
|
|
|
|
+ when(blobClient.uploadWithResponse(argThat(options -> true), eq(Context.NONE)))
|
|
|
|
|
+ .thenThrow(blobStorageException());
|
|
|
|
|
+ AzureBlobExamSprintReportStorage storage = new AzureBlobExamSprintReportStorage(
|
|
|
|
|
+ containerClient,
|
|
|
|
|
+ "report",
|
|
|
|
|
+ "https://dcjxbtest.blob.core.chinacloudapi.cn",
|
|
|
|
|
+ FIXED_CLOCK);
|
|
|
|
|
+
|
|
|
|
|
+ assertThatThrownBy(() -> storage.upload(
|
|
|
|
|
+ "report-123",
|
|
|
|
|
+ ReportType.OUTLOOK,
|
|
|
|
|
+ "file.pdf",
|
|
|
|
|
+ new byte[]{1, 2, 3},
|
|
|
|
|
+ Instant.parse("2026-01-10T00:00:00Z")))
|
|
|
|
|
+ .isInstanceOf(BlobStorageException.class);
|
|
|
|
|
+
|
|
|
|
|
+ assertThat(output.getAll())
|
|
|
|
|
+ .contains("exam_sprint_report_azure_storage_upload_failed")
|
|
|
|
|
+ .contains("reportId=report-123")
|
|
|
|
|
+ .contains("reportType=OUTLOOK")
|
|
|
|
|
+ .contains("storageObjectKey=reports/outlook/report-123/file.pdf")
|
|
|
|
|
+ .contains("blobName=reports/outlook/report-123/file.pdf")
|
|
|
|
|
+ .contains("fileName=file.pdf")
|
|
|
|
|
+ .contains("pdfByteLength=3")
|
|
|
|
|
+ .contains("durationMs=")
|
|
|
|
|
+ .contains("exceptionType=BlobStorageException")
|
|
|
|
|
+ .contains("azureStatusCode=503")
|
|
|
|
|
+ .contains("azureRequestId=azure-request-failed")
|
|
|
|
|
+ .contains("azureClientRequestId=client-request-failed")
|
|
|
|
|
+ .contains("azureErrorCode=ServerBusy")
|
|
|
.doesNotContain("connection-string")
|
|
.doesNotContain("connection-string")
|
|
|
.doesNotContain("accountKey")
|
|
.doesNotContain("accountKey")
|
|
|
.doesNotContain("account-key");
|
|
.doesNotContain("account-key");
|
|
@@ -262,4 +319,34 @@ class AzureBlobExamSprintReportStorageTest {
|
|
|
assertThat(firstStoredFile.fileName()).isEqualTo("same-display-name.pdf");
|
|
assertThat(firstStoredFile.fileName()).isEqualTo("same-display-name.pdf");
|
|
|
assertThat(secondStoredFile.fileName()).isEqualTo("same-display-name.pdf");
|
|
assertThat(secondStoredFile.fileName()).isEqualTo("same-display-name.pdf");
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ private SimpleResponse<BlockBlobItem> successfulUploadResponse() {
|
|
|
|
|
+ HttpHeaders headers = new HttpHeaders()
|
|
|
|
|
+ .set(HttpHeaderName.X_MS_REQUEST_ID, "azure-request-123")
|
|
|
|
|
+ .set(HttpHeaderName.X_MS_CLIENT_REQUEST_ID, "client-request-456");
|
|
|
|
|
+ BlockBlobItem blockBlobItem = new BlockBlobItem(
|
|
|
|
|
+ "etag-789",
|
|
|
|
|
+ OffsetDateTime.parse("2026-01-03T08:01:00Z"),
|
|
|
|
|
+ null,
|
|
|
|
|
+ true,
|
|
|
|
|
+ null);
|
|
|
|
|
+ return new SimpleResponse<>(
|
|
|
|
|
+ new HttpRequest(HttpMethod.PUT, "https://example.test/report/file.pdf"),
|
|
|
|
|
+ 201,
|
|
|
|
|
+ headers,
|
|
|
|
|
+ blockBlobItem);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private BlobStorageException blobStorageException() throws Exception {
|
|
|
|
|
+ HttpResponse response = mock(HttpResponse.class);
|
|
|
|
|
+ when(response.getStatusCode()).thenReturn(503);
|
|
|
|
|
+ when(response.getHeaders()).thenReturn(new HttpHeaders()
|
|
|
|
|
+ .set(HttpHeaderName.X_MS_REQUEST_ID, "azure-request-failed")
|
|
|
|
|
+ .set(HttpHeaderName.X_MS_CLIENT_REQUEST_ID, "client-request-failed")
|
|
|
|
|
+ .set("x-ms-error-code", "ServerBusy"));
|
|
|
|
|
+ when(response.getHeaderValue(HttpHeaderName.X_MS_REQUEST_ID)).thenReturn("azure-request-failed");
|
|
|
|
|
+ when(response.getHeaderValue(HttpHeaderName.X_MS_CLIENT_REQUEST_ID)).thenReturn("client-request-failed");
|
|
|
|
|
+ when(response.getRequest()).thenReturn(new HttpRequest(HttpMethod.PUT, new URL("https://example.test/report/file.pdf")));
|
|
|
|
|
+ return new BlobStorageException("ServerBusy", response, null);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|