|
|
@@ -1,6 +1,5 @@
|
|
|
package cn.yunzhixue.ability.center.examsprint.application.report;
|
|
|
|
|
|
-import cn.yunzhixue.ability.center.examsprint.contracts.report.CreateExamSprintReportRequest;
|
|
|
import cn.yunzhixue.ability.center.examsprint.contracts.report.AchievementExamSprintReportPayload;
|
|
|
import cn.yunzhixue.ability.center.examsprint.contracts.report.ExamSprintReportGenerationStatus;
|
|
|
import cn.yunzhixue.ability.center.examsprint.contracts.report.ExamSprintReportType;
|
|
|
@@ -11,6 +10,7 @@ import cn.yunzhixue.ability.center.examsprint.domain.report.ExamSprintReportRepo
|
|
|
import cn.yunzhixue.ability.center.examsprint.domain.report.ExamSprintReportStorage;
|
|
|
import cn.yunzhixue.ability.center.kernel.BusinessException;
|
|
|
import cn.yunzhixue.ability.center.kernel.ErrorCode;
|
|
|
+import com.fasterxml.jackson.databind.JsonNode;
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
|
|
import jakarta.validation.Validation;
|
|
|
@@ -43,16 +43,12 @@ class ExamSprintReportApplicationServiceTest {
|
|
|
private static final Validator VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator();
|
|
|
|
|
|
@Test
|
|
|
- void createReportStoresOutlookTypeAndReturnsReportId() {
|
|
|
+ void createOutlookReportStoresOutlookTypeAndReturnsReportId() {
|
|
|
TestRepository repository = new TestRepository();
|
|
|
TestStorage storage = new TestStorage();
|
|
|
DefaultExamSprintReportApplicationService service = service(repository, reportId -> { }, storage);
|
|
|
|
|
|
- CreateExamSprintReportRequest request = new CreateExamSprintReportRequest(
|
|
|
- ExamSprintReportType.OUTLOOK,
|
|
|
- validOutlookPayload());
|
|
|
-
|
|
|
- var response = service.createReport(request);
|
|
|
+ var response = service.createOutlookReport(validOutlookPayload());
|
|
|
|
|
|
assertThat(response.reportId()).isNotBlank();
|
|
|
ExamSprintReport saved = repository.findById(response.reportId()).orElseThrow();
|
|
|
@@ -61,16 +57,12 @@ class ExamSprintReportApplicationServiceTest {
|
|
|
}
|
|
|
|
|
|
@Test
|
|
|
- void createReportStoresAchievementTypeAndReturnsReportId() {
|
|
|
+ void createAchievementReportStoresAchievementTypeAndReturnsReportId() {
|
|
|
TestRepository repository = new TestRepository();
|
|
|
TestStorage storage = new TestStorage();
|
|
|
DefaultExamSprintReportApplicationService service = service(repository, reportId -> { }, storage);
|
|
|
|
|
|
- CreateExamSprintReportRequest request = new CreateExamSprintReportRequest(
|
|
|
- ExamSprintReportType.ACHIEVEMENT,
|
|
|
- validAchievementPayload());
|
|
|
-
|
|
|
- var response = service.createReport(request);
|
|
|
+ var response = service.createAchievementReport(validAchievementPayload());
|
|
|
|
|
|
assertThat(response.reportId()).isNotBlank();
|
|
|
ExamSprintReport saved = repository.findById(response.reportId()).orElseThrow();
|
|
|
@@ -80,70 +72,42 @@ class ExamSprintReportApplicationServiceTest {
|
|
|
}
|
|
|
|
|
|
@Test
|
|
|
- void createReportRejectsInvalidAchievementPayloadBeforeSaving() {
|
|
|
- TestRepository repository = new TestRepository();
|
|
|
- boolean[] dispatched = {false};
|
|
|
- DefaultExamSprintReportApplicationService service = service(
|
|
|
- repository,
|
|
|
- reportId -> dispatched[0] = true,
|
|
|
- new TestStorage());
|
|
|
+ void createAchievementReportRejectsInvalidAchievementPayloadBeforeSaving() {
|
|
|
ObjectNode invalidPayload = validAchievementPayload().deepCopy();
|
|
|
invalidPayload.remove("reportTitle");
|
|
|
|
|
|
- assertThatThrownBy(() -> service.createReport(new CreateExamSprintReportRequest(
|
|
|
- ExamSprintReportType.ACHIEVEMENT,
|
|
|
- invalidPayload)))
|
|
|
- .isInstanceOf(BusinessException.class)
|
|
|
- .extracting(exception -> ((BusinessException) exception).getErrorCode())
|
|
|
- .isEqualTo(ErrorCode.VALIDATION_ERROR);
|
|
|
-
|
|
|
- assertThat(repository.storage).isEmpty();
|
|
|
- assertThat(dispatched[0]).isFalse();
|
|
|
+ assertCreateAchievementReportRejectsInvalidPayload(invalidPayload);
|
|
|
}
|
|
|
|
|
|
@Test
|
|
|
- void createReportRejectsNullOrNonObjectPayloadBeforeSaving() {
|
|
|
- assertCreateReportRejectsInvalidPayload(ExamSprintReportType.OUTLOOK, OBJECT_MAPPER.nullNode());
|
|
|
- assertCreateReportRejectsInvalidPayload(ExamSprintReportType.ACHIEVEMENT, OBJECT_MAPPER.nullNode());
|
|
|
- assertCreateReportRejectsInvalidPayload(ExamSprintReportType.OUTLOOK, OBJECT_MAPPER.getNodeFactory().textNode("not-an-object"));
|
|
|
- assertCreateReportRejectsInvalidPayload(ExamSprintReportType.ACHIEVEMENT, OBJECT_MAPPER.getNodeFactory().textNode("not-an-object"));
|
|
|
+ void createReportsRejectNullOrNonObjectPayloadBeforeSaving() {
|
|
|
+ assertCreateOutlookReportRejectsInvalidPayload(OBJECT_MAPPER.nullNode());
|
|
|
+ assertCreateAchievementReportRejectsInvalidPayload(OBJECT_MAPPER.nullNode());
|
|
|
+ assertCreateOutlookReportRejectsInvalidPayload(OBJECT_MAPPER.getNodeFactory().textNode("not-an-object"));
|
|
|
+ assertCreateAchievementReportRejectsInvalidPayload(OBJECT_MAPPER.getNodeFactory().textNode("not-an-object"));
|
|
|
}
|
|
|
|
|
|
@Test
|
|
|
- void createReportRejectsMissingAchievementBeforeValueBeforeSaving() {
|
|
|
- TestRepository repository = new TestRepository();
|
|
|
- boolean[] dispatched = {false};
|
|
|
- DefaultExamSprintReportApplicationService service = service(
|
|
|
- repository,
|
|
|
- reportId -> dispatched[0] = true,
|
|
|
- new TestStorage());
|
|
|
+ void createAchievementReportRejectsMissingAchievementBeforeValueBeforeSaving() {
|
|
|
ObjectNode invalidPayload = validAchievementPayload().deepCopy();
|
|
|
invalidPayload.withObject("vocabularyComparison").remove("beforeValue");
|
|
|
|
|
|
- assertThatThrownBy(() -> service.createReport(new CreateExamSprintReportRequest(
|
|
|
- ExamSprintReportType.ACHIEVEMENT,
|
|
|
- invalidPayload)))
|
|
|
- .isInstanceOf(BusinessException.class)
|
|
|
- .extracting(exception -> ((BusinessException) exception).getErrorCode())
|
|
|
- .isEqualTo(ErrorCode.VALIDATION_ERROR);
|
|
|
-
|
|
|
- assertThat(repository.storage).isEmpty();
|
|
|
- assertThat(dispatched[0]).isFalse();
|
|
|
+ assertCreateAchievementReportRejectsInvalidPayload(invalidPayload);
|
|
|
}
|
|
|
|
|
|
@ParameterizedTest(name = "{0}")
|
|
|
@MethodSource("invalidAchievementPayloadJsonTypes")
|
|
|
- void createReportRejectsAchievementPayloadWithInvalidJsonTypes(
|
|
|
+ void createAchievementReportRejectsAchievementPayloadWithInvalidJsonTypes(
|
|
|
String caseName,
|
|
|
Consumer<ObjectNode> mutatePayload) {
|
|
|
ObjectNode invalidPayload = validAchievementPayload().deepCopy();
|
|
|
mutatePayload.accept(invalidPayload);
|
|
|
|
|
|
- assertCreateReportRejectsInvalidPayload(ExamSprintReportType.ACHIEVEMENT, invalidPayload);
|
|
|
+ assertCreateAchievementReportRejectsInvalidPayload(invalidPayload);
|
|
|
}
|
|
|
|
|
|
@Test
|
|
|
- void createReportReturnsFailedStatusWhenDispatchFails() {
|
|
|
+ void createOutlookReportReturnsFailedStatusWhenDispatchFails() {
|
|
|
TestRepository repository = new TestRepository();
|
|
|
DefaultExamSprintReportApplicationService service = service(
|
|
|
repository,
|
|
|
@@ -152,11 +116,7 @@ class ExamSprintReportApplicationServiceTest {
|
|
|
},
|
|
|
new TestStorage());
|
|
|
|
|
|
- CreateExamSprintReportRequest request = new CreateExamSprintReportRequest(
|
|
|
- ExamSprintReportType.OUTLOOK,
|
|
|
- validOutlookPayload());
|
|
|
-
|
|
|
- var response = service.createReport(request);
|
|
|
+ var response = service.createOutlookReport(validOutlookPayload());
|
|
|
|
|
|
assertThat(response.reportId()).isNotBlank();
|
|
|
assertThat(response.generationStatus()).isEqualTo(ExamSprintReportGenerationStatus.FAILED);
|
|
|
@@ -169,12 +129,12 @@ class ExamSprintReportApplicationServiceTest {
|
|
|
}
|
|
|
|
|
|
@Test
|
|
|
- void createReportCopiesPayloadBeforeSaving() {
|
|
|
+ void createOutlookReportCopiesPayloadBeforeSaving() {
|
|
|
TestRepository repository = new TestRepository();
|
|
|
DefaultExamSprintReportApplicationService service = service(repository, reportId -> { }, new TestStorage());
|
|
|
ObjectNode payload = validOutlookPayload().deepCopy();
|
|
|
|
|
|
- var response = service.createReport(new CreateExamSprintReportRequest(ExamSprintReportType.OUTLOOK, payload));
|
|
|
+ var response = service.createOutlookReport(payload);
|
|
|
|
|
|
payload.withObject("reportMetadata").put("learnerName", "王同学");
|
|
|
|
|
|
@@ -194,7 +154,7 @@ class ExamSprintReportApplicationServiceTest {
|
|
|
FIXED_CLOCK.instant().plusSeconds(3600))
|
|
|
.success(
|
|
|
FIXED_CLOCK.instant().minusSeconds(30),
|
|
|
- "exam-sprint-reports/achievement/report-success/exam-sprint-achievement-report-report-success.pdf",
|
|
|
+ "exam-sprint-achievement-report-report-success.pdf",
|
|
|
"exam-sprint-achievement-report-report-success.pdf");
|
|
|
repository.save(report);
|
|
|
DefaultExamSprintReportApplicationService service = service(repository, reportId -> { }, storage);
|
|
|
@@ -205,7 +165,7 @@ class ExamSprintReportApplicationServiceTest {
|
|
|
assertThat(response.downloadUrl()).isEqualTo("/api/exam-sprint/reports/report-success/download");
|
|
|
assertThat(response.previewHtmlUrl()).isEqualTo("/api/exam-sprint/reports/report-success/preview/html");
|
|
|
assertThat(storage.generatedKeys)
|
|
|
- .containsExactly("exam-sprint-reports/achievement/report-success/exam-sprint-achievement-report-report-success.pdf");
|
|
|
+ .containsExactly("exam-sprint-achievement-report-report-success.pdf");
|
|
|
}
|
|
|
|
|
|
@Test
|
|
|
@@ -219,7 +179,7 @@ class ExamSprintReportApplicationServiceTest {
|
|
|
FIXED_CLOCK.instant().plusSeconds(3600))
|
|
|
.success(
|
|
|
FIXED_CLOCK.instant().minusSeconds(30),
|
|
|
- "exam-sprint-reports/achievement/report-preview/exam-sprint-achievement-report-report-preview.pdf",
|
|
|
+ "exam-sprint-achievement-report-report-preview.pdf",
|
|
|
"exam-sprint-achievement-report-report-preview.pdf"));
|
|
|
DefaultExamSprintReportApplicationService service = service(repository, reportId -> { }, new TestStorage());
|
|
|
|
|
|
@@ -257,7 +217,7 @@ class ExamSprintReportApplicationServiceTest {
|
|
|
FIXED_CLOCK.instant().minusSeconds(600),
|
|
|
FIXED_CLOCK.instant().minusSeconds(1)).success(
|
|
|
FIXED_CLOCK.instant().minusSeconds(300),
|
|
|
- "exam-sprint-reports/outlook/report-expired/exam-sprint-outlook-report-report-expired.pdf",
|
|
|
+ "exam-sprint-outlook-report-report-expired.pdf",
|
|
|
"exam-sprint-outlook-report-report-expired.pdf"));
|
|
|
DefaultExamSprintReportApplicationService service = service(repository, reportId -> { }, storage);
|
|
|
|
|
|
@@ -296,7 +256,7 @@ class ExamSprintReportApplicationServiceTest {
|
|
|
FIXED_CLOCK.instant().minusSeconds(600),
|
|
|
FIXED_CLOCK.instant().minusSeconds(1)).success(
|
|
|
FIXED_CLOCK.instant().minusSeconds(300),
|
|
|
- "exam-sprint-reports/outlook/report-delete-fails/first.pdf",
|
|
|
+ "first.pdf",
|
|
|
"first.pdf"));
|
|
|
repository.save(ExamSprintReport.pending(
|
|
|
"report-delete-succeeds",
|
|
|
@@ -305,9 +265,9 @@ class ExamSprintReportApplicationServiceTest {
|
|
|
FIXED_CLOCK.instant().minusSeconds(600),
|
|
|
FIXED_CLOCK.instant().minusSeconds(1)).success(
|
|
|
FIXED_CLOCK.instant().minusSeconds(300),
|
|
|
- "exam-sprint-reports/outlook/report-delete-succeeds/second.pdf",
|
|
|
+ "second.pdf",
|
|
|
"second.pdf"));
|
|
|
- storage.failDeleteFor("exam-sprint-reports/outlook/report-delete-fails/first.pdf");
|
|
|
+ storage.failDeleteFor("first.pdf");
|
|
|
DefaultExamSprintReportApplicationService service = service(repository, reportId -> { }, storage);
|
|
|
|
|
|
service.cleanupExpiredReports();
|
|
|
@@ -315,12 +275,12 @@ class ExamSprintReportApplicationServiceTest {
|
|
|
ExamSprintReport failedDeleteReport = repository.findById("report-delete-fails").orElseThrow();
|
|
|
ExamSprintReport successfulDeleteReport = repository.findById("report-delete-succeeds").orElseThrow();
|
|
|
assertThat(failedDeleteReport.storageObjectKey())
|
|
|
- .isEqualTo("exam-sprint-reports/outlook/report-delete-fails/first.pdf");
|
|
|
+ .isEqualTo("first.pdf");
|
|
|
assertThat(successfulDeleteReport.generationStatus()).isEqualTo(ExamSprintReportGenerationStatus.EXPIRED);
|
|
|
assertThat(successfulDeleteReport.storageObjectKey()).isNull();
|
|
|
assertThat(storage.deletedKeys)
|
|
|
- .contains("exam-sprint-reports/outlook/report-delete-fails/first.pdf")
|
|
|
- .contains("exam-sprint-reports/outlook/report-delete-succeeds/second.pdf");
|
|
|
+ .contains("first.pdf")
|
|
|
+ .contains("second.pdf");
|
|
|
}
|
|
|
|
|
|
private DefaultExamSprintReportApplicationService service(
|
|
|
@@ -443,7 +403,7 @@ class ExamSprintReportApplicationServiceTest {
|
|
|
(Consumer<ObjectNode>) payload -> payload.withObject("examUnknownWordsHitStatus").putArray("hitWords").add(123)));
|
|
|
}
|
|
|
|
|
|
- private void assertCreateReportRejectsInvalidPayload(ExamSprintReportType reportType, com.fasterxml.jackson.databind.JsonNode payload) {
|
|
|
+ private void assertCreateOutlookReportRejectsInvalidPayload(JsonNode payload) {
|
|
|
TestRepository repository = new TestRepository();
|
|
|
boolean[] dispatched = {false};
|
|
|
DefaultExamSprintReportApplicationService service = service(
|
|
|
@@ -451,7 +411,24 @@ class ExamSprintReportApplicationServiceTest {
|
|
|
reportId -> dispatched[0] = true,
|
|
|
new TestStorage());
|
|
|
|
|
|
- assertThatThrownBy(() -> service.createReport(new CreateExamSprintReportRequest(reportType, payload)))
|
|
|
+ assertThatThrownBy(() -> service.createOutlookReport(payload))
|
|
|
+ .isInstanceOf(BusinessException.class)
|
|
|
+ .extracting(exception -> ((BusinessException) exception).getErrorCode())
|
|
|
+ .isEqualTo(ErrorCode.VALIDATION_ERROR);
|
|
|
+
|
|
|
+ assertThat(repository.storage).isEmpty();
|
|
|
+ assertThat(dispatched[0]).isFalse();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void assertCreateAchievementReportRejectsInvalidPayload(JsonNode payload) {
|
|
|
+ TestRepository repository = new TestRepository();
|
|
|
+ boolean[] dispatched = {false};
|
|
|
+ DefaultExamSprintReportApplicationService service = service(
|
|
|
+ repository,
|
|
|
+ reportId -> dispatched[0] = true,
|
|
|
+ new TestStorage());
|
|
|
+
|
|
|
+ assertThatThrownBy(() -> service.createAchievementReport(payload))
|
|
|
.isInstanceOf(BusinessException.class)
|
|
|
.extracting(exception -> ((BusinessException) exception).getErrorCode())
|
|
|
.isEqualTo(ErrorCode.VALIDATION_ERROR);
|
|
|
@@ -496,13 +473,13 @@ class ExamSprintReportApplicationServiceTest {
|
|
|
String fileName,
|
|
|
byte[] pdfBytes,
|
|
|
Instant expiresAt) {
|
|
|
- return new StoredExamSprintReportFile("blob/" + reportId + "/" + fileName, fileName);
|
|
|
+ return new StoredExamSprintReportFile(fileName, fileName);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public URI generateDownloadUrl(String storageObjectKey, Duration ttl) {
|
|
|
generatedKeys.add(storageObjectKey);
|
|
|
- return URI.create("/api/exam-sprint/reports/" + storageObjectKey.split("/")[2] + "/download");
|
|
|
+ return URI.create("/api/exam-sprint/reports/" + reportIdFromStorageObjectKey(storageObjectKey) + "/download");
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
@@ -521,6 +498,19 @@ class ExamSprintReportApplicationServiceTest {
|
|
|
void failDeleteFor(String storageObjectKey) {
|
|
|
deleteFailures.add(storageObjectKey);
|
|
|
}
|
|
|
+
|
|
|
+ private String reportIdFromStorageObjectKey(String storageObjectKey) {
|
|
|
+ String fileName = storageObjectKey.substring(storageObjectKey.lastIndexOf('/') + 1);
|
|
|
+ String outlookPrefix = "exam-sprint-outlook-report-";
|
|
|
+ String achievementPrefix = "exam-sprint-achievement-report-";
|
|
|
+ if (fileName.startsWith(outlookPrefix)) {
|
|
|
+ return fileName.substring(outlookPrefix.length(), fileName.length() - ".pdf".length());
|
|
|
+ }
|
|
|
+ if (fileName.startsWith(achievementPrefix)) {
|
|
|
+ return fileName.substring(achievementPrefix.length(), fileName.length() - ".pdf".length());
|
|
|
+ }
|
|
|
+ throw new IllegalStateException("Unexpected storage object key: " + storageObjectKey);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private static class PreviewTestRenderer implements ExamSprintReportRenderer {
|