RED-7049 - Wrong error code when adding entity with already existing rank - backport

- add a check for duplicate ranks before cloning or exporting a dossier template
- added junit tests

Signed-off-by: Corina Olariu <corina.olariu.ext@knecon.com>
This commit is contained in:
Corina Olariu 2024-03-11 11:01:56 +02:00
parent f5f33978ed
commit f8645ef50c
8 changed files with 235 additions and 45 deletions

View File

@ -39,7 +39,6 @@ import com.iqser.red.storage.commons.service.StorageService;
import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter;
import com.knecon.fforesight.tenantcommons.TenantContext;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@ -77,6 +76,7 @@ public class DossierTemplateCloneService {
dossierTemplateRepository.findById(dossierTemplateId).ifPresentOrElse(dossierTemplate -> {
dossierTemplatePersistenceService.validateDossierTemplateForDuplicateRanks(dossierTemplateId);
OffsetDateTime now = OffsetDateTime.now().truncatedTo(ChronoUnit.MILLIS);
clonedDossierTemplate.setId(UUID.randomUUID().toString());
clonedDossierTemplate.setName(cloneDossierTemplateRequest.getName());

View File

@ -1,5 +1,20 @@
package com.iqser.red.service.persistence.management.v1.processor.service.export;
import static com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter.convert;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
@ -13,7 +28,16 @@ import com.iqser.red.service.persistence.management.v1.processor.model.DownloadJ
import com.iqser.red.service.persistence.management.v1.processor.service.ColorsService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileManagementStorageService;
import com.iqser.red.service.persistence.management.v1.processor.service.WatermarkService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.*;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DictionaryPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierAttributeConfigPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierStatusPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierTemplatePersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DownloadStatusPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.EntryPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.FileAttributeConfigPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.LegalBasisMappingPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.ReportTemplatePersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.RulesPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.utils.FileSystemBackedArchiver;
import com.iqser.red.service.persistence.management.v1.processor.utils.StorageIdUtils;
import com.iqser.red.service.persistence.service.v1.api.shared.model.RuleFileType;
@ -37,17 +61,6 @@ import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import static com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter.convert;
@Slf4j
@Service
@RequiredArgsConstructor
@ -79,6 +92,7 @@ public class DossierTemplateExportService {
var mimeType = "application/zip";
dossierTemplatePersistenceService.validateDossierTemplateForDuplicateRanks(request.getDossierTemplateId());
String downloadFilename = request.getDossierTemplateId() + ".zip";
String storageId = StorageIdUtils.getStorageId(request.getUserId(), request.getDossierTemplateId());

View File

@ -11,7 +11,6 @@ import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.TypeEntity;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.ConflictException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.DossierRepository;
@ -19,6 +18,7 @@ import com.iqser.red.service.persistence.management.v1.processor.service.persist
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.TypeRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.dictionaryentry.EntryRepository;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.DictionarySummaryResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.TypeRankSummary;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
@ -285,4 +285,11 @@ public class DictionaryPersistenceService {
return typeRepository.unSoftDeleteTypeById(typeId);
}
public List<TypeRankSummary> getTypeRankSummaryList(String dossierTemplateId) {
return typeRepository.findTypeRankSummaryList(dossierTemplateId);
}
}

View File

@ -5,9 +5,9 @@ import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@ -38,6 +38,7 @@ public class DossierTemplatePersistenceService {
private final DossierTemplateRepository dossierTemplateRepository;
private final LegalBasisMappingPersistenceService legalBasisMappingPersistenceService;
private final RulesPersistenceService rulesPersistenceService;
private final DictionaryPersistenceService dictionaryPersistenceService;
private final TypeRepository typeRepository;
@ -227,4 +228,19 @@ public class DossierTemplatePersistenceService {
dossierTemplateRepository.saveAndFlush(dossierTemplate);
}
public void validateDossierTemplateForDuplicateRanks(String dossierTemplateId) {
var duplicateRanks = dictionaryPersistenceService.getTypeRankSummaryList(dossierTemplateId)
.stream()
.filter(t -> t.getTypesCount() > 1)
.collect(Collectors.toList());
if (!duplicateRanks.isEmpty()) {
String errorMessage = "Duplicate ranks found in dossier template " + dossierTemplateId + "\n" + duplicateRanks.stream()
.map(t -> String.format(" Rank %d has %d entries", t.getRank(), t.getTypesCount()))
.collect(Collectors.joining("\n"));
throw new BadRequestException(errorMessage);
}
}
}

View File

@ -11,6 +11,7 @@ import org.springframework.data.repository.query.Param;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.TypeEntity;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.DictionarySummaryResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.TypeRankSummary;
public interface TypeRepository extends JpaRepository<TypeEntity, String> {
@ -69,6 +70,14 @@ public interface TypeRepository extends JpaRepository<TypeEntity, String> {
List<DictionarySummaryResponse> findDictionarySummaryList(@Param("dossierTemplateIds") Set<String> dossierTemplateIds);
@Query("""
select new com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.TypeRankSummary(t.dossierTemplateId, t.dossierId, t.rank, count(t))
from TypeEntity t where t.dossierTemplateId = :dossierTemplateId and t.softDeletedTime is null
group by t.dossierTemplateId, t.dossierId, t.rank
order by t.rank""")
List<TypeRankSummary> findTypeRankSummaryList(@Param("dossierTemplateId") String dossierTemplateId);
@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("Update TypeEntity t set t.softDeletedTime = offset_datetime where t.id = :typeId")
void softDeleteTypeById(@Param("typeId") String typeId);

View File

@ -1,18 +1,65 @@
package com.iqser.red.service.peristence.v1.server.integration.tests;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockMultipartFile;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Sets;
import com.iqser.red.service.peristence.v1.server.integration.client.*;
import com.iqser.red.service.peristence.v1.server.integration.client.DictionaryClient;
import com.iqser.red.service.peristence.v1.server.integration.client.DossierAttributeConfigClient;
import com.iqser.red.service.peristence.v1.server.integration.client.DossierClient;
import com.iqser.red.service.peristence.v1.server.integration.client.DossierStatusClient;
import com.iqser.red.service.peristence.v1.server.integration.client.DossierTemplateClient;
import com.iqser.red.service.peristence.v1.server.integration.client.DownloadClient;
import com.iqser.red.service.peristence.v1.server.integration.client.FileAttributeConfigClient;
import com.iqser.red.service.peristence.v1.server.integration.client.LegalBasisClient;
import com.iqser.red.service.peristence.v1.server.integration.client.ReportTemplateClient;
import com.iqser.red.service.peristence.v1.server.integration.client.WatermarkClient;
import com.iqser.red.service.peristence.v1.server.integration.service.DossierTemplateTesterAndProvider;
import com.iqser.red.service.peristence.v1.server.integration.service.DossierTesterAndProvider;
import com.iqser.red.service.peristence.v1.server.integration.utils.AbstractPersistenceServerServiceTest;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.model.DownloadJob;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierTemplateImportService;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierTemplateManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileManagementStorageService;
import com.iqser.red.service.persistence.management.v1.processor.service.export.ExportDownloadMessageReceiver;
import com.iqser.red.service.persistence.service.v1.api.shared.model.*;
import com.iqser.red.service.persistence.service.v1.api.shared.model.CreateTypeValue;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierAttributesConfig;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierStatusRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierTemplateModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileAttributesConfig;
import com.iqser.red.service.persistence.service.v1.api.shared.model.TypeValue;
import com.iqser.red.service.persistence.service.v1.api.shared.model.WatermarkModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.CloneDossierTemplateRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.DossierAttributeConfig;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.DossierTemplateStatus;
@ -31,32 +78,6 @@ import com.knecon.fforesight.tenantcommons.TenantContext;
import feign.FeignException;
import lombok.SneakyThrows;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockMultipartFile;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@SuppressWarnings("PMD")
public class DossierTemplateTest extends AbstractPersistenceServerServiceTest {
@ -189,6 +210,7 @@ public class DossierTemplateTest extends AbstractPersistenceServerServiceTest {
public void testCloneDossierTemplate() {
var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
var dossier = dossierTesterAndProvider.provideTestDossier(dossierTemplate);
var type = CreateTypeValue.builder()
.type("t")
@ -227,6 +249,21 @@ public class DossierTemplateTest extends AbstractPersistenceServerServiceTest {
var loadedType1 = dictionaryClient.getDictionaryForType(createdType1.getType(), createdType1.getDossierTemplateId(), null);
var loadedType2 = dictionaryClient.getDictionaryForType(createdType2.getType(), createdType2.getDossierTemplateId(), null);
// force types on dossier level
dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), dossier.getId());
dictionaryClient.getDictionaryForType(type2.getType(), type2.getDossierTemplateId(), dossier.getId());
var allTypes = dictionaryClient.getAllTypes(dossierTemplate.getId(), dossier.getId(), false).getTypes();
assertThat(allTypes.size()).isEqualTo(4);
var typesWithRankOfType1 = allTypes.stream()
.filter(t -> t.getRank() == type.getRank())
.collect(Collectors.toList());
assertThat(typesWithRankOfType1.size()).isEqualTo(2);
var typesWithRankOfType2 = allTypes.stream()
.filter(t -> t.getRank() == type2.getRank())
.collect(Collectors.toList());
assertThat(typesWithRankOfType2.size()).isEqualTo(2);
dictionaryClient.addEntry(createdType1.getType(), createdType1.getDossierTemplateId(), List.of("entry1", "entry2"), false, null, DictionaryEntryType.ENTRY);
dictionaryClient.addEntry(createdType1.getType(), createdType1.getDossierTemplateId(), List.of("entry3", "entry4"), false, null, DictionaryEntryType.FALSE_POSITIVE);

View File

@ -0,0 +1,88 @@
package com.iqser.red.service.peristence.v1.server.integration.tests;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import com.iqser.red.service.peristence.v1.server.integration.client.DossierTemplateClient;
import com.iqser.red.service.peristence.v1.server.integration.service.DossierTemplateTesterAndProvider;
import com.iqser.red.service.peristence.v1.server.integration.service.DossierTesterAndProvider;
import com.iqser.red.service.peristence.v1.server.integration.utils.AbstractPersistenceServerServiceTest;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DictionaryPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierTemplateModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.CloneDossierTemplateRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.Dossier;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.TypeRankSummary;
import feign.FeignException;
import lombok.SneakyThrows;
public class DossierTemplateWithDuplicateRanksTest extends AbstractPersistenceServerServiceTest {
@Autowired
private DossierTesterAndProvider dossierTesterAndProvider;
@Autowired
private DossierTemplateTesterAndProvider dossierTemplateTesterAndProvider;
@Autowired
private DossierTemplateClient dossierTemplateClient;
@MockBean
private DictionaryPersistenceService dictionaryPersistenceService;
private DossierTemplateModel dossierTemplate;
private Dossier dossier;
@BeforeEach
public void setupData() {
dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
dossier = dossierTesterAndProvider.provideTestDossier(dossierTemplate);
List<TypeRankSummary> typesValues = new ArrayList<>();
TypeRankSummary typeRank1 = new TypeRankSummary(dossierTemplate.getId(), dossier.getId(), 50, 2);
TypeRankSummary typeRank2 = new TypeRankSummary(dossierTemplate.getId(), dossier.getId(), 100, 1);
typesValues.add(typeRank1);
typesValues.add(typeRank2);
when(dictionaryPersistenceService.getTypeRankSummaryList(dossierTemplate.getId())).thenReturn(typesValues);
}
@Test
public void testDownloadWithDuplicateRanks() {
// test the export of dossier template
Assertions.assertThrows(FeignException.BadRequest.class, () -> dossierTemplateClient.prepareExportDownload(dossierTemplate.getId()));
}
@Test
@SneakyThrows
public void testCloneDossierTemplateWithDuplicateRanks() {
var allTemplates = dossierTemplateClient.getAllDossierTemplates();
assertThat(allTemplates.size()).isEqualTo(1);
assertThat(allTemplates.get(0)).isEqualTo(dossierTemplate);
CloneDossierTemplateRequest cdtr = CloneDossierTemplateRequest.builder()
.name("Clone of " + dossierTemplate.getName())
.cloningUserId("user")
.ocrByDefault(true)
.removeWatermark(false)
.build();
Assertions.assertThrows(FeignException.BadRequest.class, () -> dossierTemplateClient.cloneDossierTemplate(dossierTemplate.getId(), cdtr));
}
}

View File

@ -0,0 +1,19 @@
package com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TypeRankSummary {
private String dossierTemplateId;
private String dossierId;
private int rank;
private long typesCount; // number of types with the same rank
}