Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion charts/appreg-api/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ appVersion: "1.1"
description: A Helm chart for AppReg API
name: appreg-api
home: https://github.com/hmcts/appreg-api
version: 0.0.8
version: 0.0.9
maintainers:
- name: HMCTS AppReg Team
dependencies:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* Represents a validation or processing failure for a specific row and location in a bulk upload
* file.
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class BulkUploadError {
private int rowNumber;
private String location;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -148,10 +149,7 @@ public void validating(AsyncJobLifecycleEvent<BulkUploadRow> event) throws IOExc

rowErrors.addAll(validator.validateRow(rowNumber, row));
rowErrors.addAll(validateMappedDto(rowNumber, dto));

if (rowErrors.isEmpty()) {
Comment thread
mustafaahmeddev marked this conversation as resolved.
rowErrors.addAll(validateBusinessRules(rowNumber, dto));
}
rowErrors.addAll(validateBusinessRules(rowNumber, dto));

allErrors.addAll(rowErrors);
rowNumber++;
Expand Down Expand Up @@ -498,7 +496,23 @@ private void writeErrorCSVLine(
errors.stream().filter(e -> e.getRowNumber() == finalRowCount).toList();
builder.append(line);
for (BulkUploadError error : rowErrors) {
builder.append("|").append(error.getMessage());
if (Objects.nonNull(error.getRejectedValue())
&& !error.getRejectedValue().isBlank()) {
builder.append("|")
.append(
"%s - %s: %s"
.formatted(
error.getLocation(),
error.getRejectedValue(),
"Field has been rejected"));

} else {
builder.append("|")
.append(
"%s: %s"
.formatted(
error.getLocation(), error.getMessage()));
}
}
builder.append("\n");
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ private static List<TemplateSubstitution> trimAndKeyWordingFields(
}

private static String normalise(String value) {
return StringUtils.lowerCase(value, Locale.ROOT);
return StringUtils.lowerCase(value, Locale.ROOT).trim();
}

/** Job-scoped validation state. A session is confined to one async lifecycle. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,10 @@ public void canValueBeSubstituted(String value) {
if (value.length() > this.getDetail().getConstraint().getLength()) {
throw new AppRegistryException(
CommonAppError.WORDING_LENGTH_FAILURE,
"Invalid length type in template",
"Invalid length type in template: expected %d but got %d"
.formatted(
this.getDetail().getConstraint().getLength(),
value.length()),
Map.of(this.getDetail().getKey(), value));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,61 @@ void givenCodeAwareValidationFailure_whenValidating_thenLogsRowFailure() throws
"DATA_ERROR"))));
}

@Test
void givenMissingRequiredFields_whenValidatingBusinessRules_thenLogsRowFailure()
throws IOException {
BulkUploadRow row = validOrganisationRow();
row.setApplicationCode(null);
row.setApplicantCode(null);
JobContext context = new JobContext();
AsyncJobLifecycleEvent<BulkUploadRow> event = event(row, context);

lifecycle.setCSVFile(csvFile);

AppRegistryException exception =
assertThrows(AppRegistryException.class, () -> lifecycle.validating(event));

verify(persistenceService, times(1)).writeClob(any(), any());

assertThat(exception.getCode())
.isEqualTo(AppListEntryError.BULK_UPLOAD_ROW_VALIDATION_FAILED);

ObjectMapper mapper = new ObjectMapper();
BulkUploadError[] errors =
mapper.readValue(
context.getValidationFailureMessages().getFirst(), BulkUploadError[].class);
assertThat(errors).hasSize(3);

assertThat(context.getValidationFailureMessages())
.containsExactly(
createErrorDescription(
List.of(
new BulkUploadError(
2,
"APPLICANT_CODE",
null,
"Applicant code is required",
row.getRespondentAddressLine1(),
row.getRespondentOrganisationName(),
"DATA_ERROR"),
new BulkUploadError(
2,
"APPLICATION_CODE",
null,
"Application code is required",
row.getRespondentAddressLine1(),
row.getRespondentOrganisationName(),
"DATA_ERROR"),
new BulkUploadError(
2,
"applicationCode",
null,
"must not be null",
row.getRespondentAddressLine1(),
row.getRespondentOrganisationName(),
"DATA_ERROR"))));
}

@Test
void givenExistingValidationFailures_whenValidating_thenPrependsHeaderErrors()
throws IOException {
Expand Down Expand Up @@ -698,7 +753,54 @@ void whenValidating_csvFileIsSet_thenWritesClobSuccessfully() throws IOException

assertThat(writtenCsv.toString())
.contains("HEADER|")
.contains("row-two|must match")
.contains("row-two|")
.contains("respondent.organisation.contactDetails.postcode")
.contains(row.getRespondentPostcode())
.contains("Field has been rejected")
.contains("row-three|");

verify(persistenceService, times(1)).writeClob(any(), any());
}

@Test
void whenValidating_csvFileIsSet_thenWritesClobSuccessfully_multipleRowErrorsForSingleRow()
throws IOException {
when(csvFile.getBytes()).thenReturn("HEADER\nrow-two\nrow-three\n".getBytes());

StringBuilder writtenCsv = new StringBuilder();
doAnswer(
invocation -> {
ByteArrayInputStream inputStream = invocation.getArgument(1);
writtenCsv.append(new String(inputStream.readAllBytes()));
return null;
})
.when(persistenceService)
.writeClob(any(), any());

BulkUploadRow row = validOrganisationRow();
row.setRespondentPostcode("invalid");
row.setRespondentEmail("testtest.com");

JobContext context = new JobContext();
AsyncJobLifecycleEvent<BulkUploadRow> event = event(row, context);

lifecycle.setCSVFile(csvFile);

AppRegistryException exception =
assertThrows(AppRegistryException.class, () -> lifecycle.validating(event));

assertThat(exception.getCode())
.isEqualTo(AppListEntryError.BULK_UPLOAD_ROW_VALIDATION_FAILED);

assertThat(writtenCsv.toString())
.contains("HEADER|")
.contains("row-two|")
.contains("respondent.organisation.contactDetails.email")
.contains("testtest.com")
.contains("Field has been rejected|")
.contains("respondent.organisation.contactDetails.postcode")
.contains(row.getRespondentPostcode())
.contains("Field has been rejected")
.contains("row-three|");

verify(persistenceService, times(1)).writeClob(any(), any());
Expand Down Expand Up @@ -737,7 +839,8 @@ void whenValidating_csvFileIsSet_containsHeaderErrors_thenWritesClobSuccessfully

assertThat(writtenCsv.toString())
.contains("HEADER|HEADER_ERROR:4|HEADER_ERROR:3|HEADER_ERROR:2|HEADER_ERROR:1")
.contains("row-two|must match")
.contains(
"row-two|respondent.organisation.contactDetails.postcode - invalid: Field has been rejected")
.contains("row-three|");

verify(persistenceService, times(1)).writeClob(any(), any());
Expand Down