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
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package fr.insee.genesis.domain.model.surveyunit;

import com.fasterxml.jackson.annotation.JsonFormat;
import fr.insee.modelefiliere.RawResponseDto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
Expand Down Expand Up @@ -38,6 +39,7 @@ public class SurveyUnitModel {
private DataState state;
private Mode mode;
private Boolean isCapturedIndirectly;
private RawResponseDto.QuestionnaireStateEnum questionnaireState;
private LocalDateTime validationDate;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'hh:mm")
private LocalDateTime recordDate;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@
import fr.insee.genesis.exceptions.GenesisError;
import fr.insee.genesis.exceptions.GenesisException;
import fr.insee.genesis.infrastructure.utils.FileUtils;
import fr.insee.modelefiliere.RawResponseDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
Expand All @@ -40,7 +42,7 @@

@Service
@Slf4j
public class RawResponseService implements RawResponseApiPort {
public class RawResponseService implements RawResponseApiPort {

private final ControllerUtils controllerUtils;
private final QuestionnaireMetadataService metadataService;
Expand Down Expand Up @@ -210,6 +212,13 @@ public List<SurveyUnitModel> convertRawResponse(List<RawResponse> rawResponses,
for (RawResponse rawResponse : rawResponses) {
//Get optional fields
Boolean isCapturedIndirectly = getIsCapturedIndirectly(rawResponse);
String questionnaireStateString = getStringFieldInPayload(rawResponse, "questionnaireState");
RawResponseDto.QuestionnaireStateEnum questionnaireStateEnum = null;
try{
questionnaireStateEnum = RawResponseDto.QuestionnaireStateEnum.valueOf(questionnaireStateString);
} catch (IllegalArgumentException iae){
log.warn("'{}' is not a valid questionnaire state according to filiere model", questionnaireStateString);
}
LocalDateTime validationDate = getValidationDate(rawResponse);
String usualSurveyUnitId = getStringFieldInPayload(rawResponse,"usualSurveyUnitId");
String majorModelVersion = getStringFieldInPayload(rawResponse, "majorModelVersion");
Expand All @@ -220,6 +229,7 @@ public List<SurveyUnitModel> convertRawResponse(List<RawResponse> rawResponses,
.mode(rawResponse.mode())
.interrogationId(rawResponse.interrogationId())
.usualSurveyUnitId(usualSurveyUnitId)
.questionnaireState(questionnaireStateEnum)
.validationDate(validationDate)
.isCapturedIndirectly(isCapturedIndirectly)
.state(dataState)
Expand Down Expand Up @@ -313,7 +323,7 @@ private static Boolean getIsCapturedIndirectly(RawResponse rawResponse) {
private static LocalDateTime getValidationDate(RawResponse rawResponse) {
try{
return rawResponse.payload().get("validationDate") == null ? null :
LocalDateTime.parse(rawResponse.payload().get("validationDate").toString());
LocalDateTime.parse(rawResponse.payload().get("validationDate").toString(), DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}catch(Exception e){
log.warn("Exception when parsing validation date : {}",e.toString());
return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,318 @@
package fr.insee.genesis.domain.service.rawdata;

import fr.insee.bpm.metadata.model.MetadataModel;
import fr.insee.bpm.metadata.model.VariablesMap;
import fr.insee.genesis.TestConstants;
import fr.insee.genesis.controller.utils.ControllerUtils;
import fr.insee.genesis.domain.model.surveyunit.Mode;
import fr.insee.genesis.domain.model.surveyunit.SurveyUnitModel;
import fr.insee.genesis.domain.model.surveyunit.rawdata.RawResponse;
import fr.insee.genesis.domain.ports.spi.RawResponsePersistencePort;
import fr.insee.genesis.domain.ports.spi.SurveyUnitQualityToolPort;
import fr.insee.genesis.domain.service.context.DataProcessingContextService;
import fr.insee.genesis.domain.service.metadata.QuestionnaireMetadataService;
import fr.insee.genesis.domain.service.surveyunit.SurveyUnitQualityService;
import fr.insee.genesis.domain.service.surveyunit.SurveyUnitService;
import fr.insee.genesis.exceptions.GenesisException;
import fr.insee.genesis.infrastructure.utils.FileUtils;
import fr.insee.genesis.stubs.ConfigStub;
import fr.insee.modelefiliere.RawResponseDto;
import lombok.SneakyThrows;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.mockito.ArgumentCaptor;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

class RawResponseServiceUnitTest {

static RawResponseService rawResponseService;
static RawResponsePersistencePort rawResponsePersistencePort;
static ControllerUtils controllerUtils;
static QuestionnaireMetadataService metadataService;
static SurveyUnitService surveyUnitService;

private ArgumentCaptor<List<SurveyUnitModel>> surveyUnitModelsCaptor;

private static final String TEST_VALIDATION_DATE = "2025-11-11T06:00:00Z";

@BeforeEach
@SuppressWarnings("unchecked")
void init() {
rawResponsePersistencePort = mock(RawResponsePersistencePort.class);
controllerUtils = mock(ControllerUtils.class);
metadataService = mock(QuestionnaireMetadataService.class);
surveyUnitService = mock(SurveyUnitService.class);

rawResponseService = new RawResponseService(
controllerUtils,
metadataService,
surveyUnitService,
mock(SurveyUnitQualityService.class),
mock(SurveyUnitQualityToolPort.class),
mock(DataProcessingContextService.class),
new FileUtils(new ConfigStub()),
new ConfigStub(),
rawResponsePersistencePort
);

surveyUnitModelsCaptor = ArgumentCaptor.forClass(List.class);
}

@Nested
@DisplayName("Non regression tests of #22875 : validation date and questionnaire state in processed responses")
class ValidationDateAndQuestionnaireStateTests{
//OK cases
@ParameterizedTest
@DisplayName("Process by collection instrument id OK test")
@EnumSource(RawResponseDto.QuestionnaireStateEnum.class)
@SneakyThrows
void processRawResponses_byCollectionInstrumentId_validation_date_questionnaire_state_test(
RawResponseDto.QuestionnaireStateEnum questionnaireState
) {
//GIVEN
givenOkCase(questionnaireState);

//WHEN
List<SurveyUnitModel> createdModels = whenProcessByCollectionInstrumentIdAndInterrogationIdList();

//THEN
processRawResponsesThen(questionnaireState, createdModels);
}

@ParameterizedTest
@EnumSource(RawResponseDto.QuestionnaireStateEnum.class)
@DisplayName("Process by collection instrument id and interrogation id OK test")
@SneakyThrows
void processRawResponses_byCollectionInstrumentIdAndInterrogationList_validation_date_questionnaire_state_test(
RawResponseDto.QuestionnaireStateEnum questionnaireState
) {
//GIVEN
givenOkCase(questionnaireState);

//WHEN
List<SurveyUnitModel> createdModels = whenProcessRawResponsesCollectionInstrumentId();

//THEN
processRawResponsesThen(questionnaireState, createdModels);
}

//Non-blocking exception tests
//Invalid questionnaire state
@Test
@DisplayName("Invalid questionnaireState test (process by collection id)")
@SneakyThrows
void processRawResponses_byCollectionInstrumentId_invalid_questionnaire_state_test() {
//GIVEN
givenInvalidQuestionnaireState();

//WHEN
List<SurveyUnitModel> createdModels = whenProcessRawResponsesCollectionInstrumentId();

//THEN
processRawResponsesThenQuestionnaireStateNull(createdModels);
}
@Test
@DisplayName("Invalid questionnaireState test (process by collection id and interrogation id list)")
@SneakyThrows
void processRawResponses_byCollectionInstrumentIdAndInterrogationList_invalid_questionnaire_state_test() {
//GIVEN
givenInvalidQuestionnaireState();

//WHEN
List<SurveyUnitModel> createdModels = whenProcessByCollectionInstrumentIdAndInterrogationIdList();

//THEN
processRawResponsesThenQuestionnaireStateNull(createdModels);
}

//Invalid validationDate
@Test
@DisplayName("Invalid validationDate test (process by collection id)")
@SneakyThrows
void processRawResponses_byCollectionId_invalid_validation_date_test(){
//GIVEN
givenInvalidValidationDate();

//WHEN
List<SurveyUnitModel> createdModels = whenProcessByCollectionInstrumentIdAndInterrogationIdList();

//THEN
processRawResponsesThenValidationDateNull(createdModels);
}
@Test
@DisplayName("Invalid validationDate test (process by collection id and interrogation id list)")
@SneakyThrows
void processRawResponses_byCollectionIdAndInterrogationIds_invalid_validation_date_test(){
//GIVEN
givenInvalidValidationDate();

//WHEN
List<SurveyUnitModel> createdModels = whenProcessRawResponsesCollectionInstrumentId();

//THEN
processRawResponsesThenValidationDateNull(createdModels);
}

//GIVENS
@SneakyThrows
private void givenOkCase(RawResponseDto.QuestionnaireStateEnum questionnaireState){
VariablesMap variablesMap = new VariablesMap();
MetadataModel metadataModel = new MetadataModel();
metadataModel.setVariables(variablesMap);
String validationDate = questionnaireState.equals(RawResponseDto.QuestionnaireStateEnum.FINISHED) ?
TEST_VALIDATION_DATE : null;

List<RawResponse> rawResponses = new ArrayList<>();
RawResponse rawResponse = new RawResponse(
null,
TestConstants.DEFAULT_INTERROGATION_ID,
TestConstants.DEFAULT_COLLECTION_INSTRUMENT_ID,
Mode.WEB,
new HashMap<>(),
LocalDateTime.now(),
null
);
rawResponse.payload().put("validationDate", validationDate);
rawResponse.payload().put("questionnaireState", questionnaireState);
rawResponse.payload().put("usualSurveyUnitId", TestConstants.DEFAULT_SURVEY_UNIT_ID);
rawResponse.payload().put("majorModelVersion", 2);
Map<String, Map<String, Map<String, String>>> dataMap = new HashMap<>();
dataMap.put("COLLECTED", new HashMap<>());
dataMap.get("COLLECTED").put("VAR1", new HashMap<>());
dataMap.get("COLLECTED").get("VAR1").put("COLLECTED", "value");
rawResponse.payload().put("data", dataMap);
rawResponses.add(rawResponse);

//Mocks behaviour
doReturn(Collections.singletonList(Mode.WEB)).when(controllerUtils).getModesList(any(),any());
doReturn(Set.of(TestConstants.DEFAULT_INTERROGATION_ID))
.when(rawResponsePersistencePort).findUnprocessedInterrogationIdsByCollectionInstrumentId(any());
doReturn(metadataModel).when(metadataService).loadAndSaveIfNotExists(any(), any(), any(), any(), any());
doReturn(rawResponses).when(rawResponsePersistencePort).findRawResponses(any(), any(), any());
}
@SneakyThrows
private void givenInvalidQuestionnaireState(){
VariablesMap variablesMap = new VariablesMap();
MetadataModel metadataModel = new MetadataModel();
metadataModel.setVariables(variablesMap);

List<RawResponse> rawResponses = new ArrayList<>();
RawResponse rawResponse = new RawResponse(
null,
TestConstants.DEFAULT_INTERROGATION_ID,
TestConstants.DEFAULT_COLLECTION_INSTRUMENT_ID,
Mode.WEB,
new HashMap<>(),
LocalDateTime.now(),
null
);
rawResponse.payload().put("validationDate", TEST_VALIDATION_DATE);
rawResponse.payload().put("questionnaireState", "not a questionnaire state");
rawResponse.payload().put("usualSurveyUnitId", TestConstants.DEFAULT_SURVEY_UNIT_ID);
rawResponse.payload().put("majorModelVersion", 2);
Map<String, Map<String, Map<String, String>>> dataMap = new HashMap<>();
dataMap.put("COLLECTED", new HashMap<>());
dataMap.get("COLLECTED").put("VAR1", new HashMap<>());
dataMap.get("COLLECTED").get("VAR1").put("COLLECTED", "value");
rawResponse.payload().put("data", dataMap);
rawResponses.add(rawResponse);

//Mocks behaviour
doReturn(Collections.singletonList(Mode.WEB)).when(controllerUtils).getModesList(any(),any());
doReturn(Set.of(TestConstants.DEFAULT_INTERROGATION_ID))
.when(rawResponsePersistencePort).findUnprocessedInterrogationIdsByCollectionInstrumentId(any());
doReturn(metadataModel).when(metadataService).loadAndSaveIfNotExists(any(), any(), any(), any(), any());
doReturn(rawResponses).when(rawResponsePersistencePort).findRawResponses(any(), any(), any());
}
@SneakyThrows
private void givenInvalidValidationDate(){
VariablesMap variablesMap = new VariablesMap();
MetadataModel metadataModel = new MetadataModel();
metadataModel.setVariables(variablesMap);

List<RawResponse> rawResponses = new ArrayList<>();
RawResponse rawResponse = new RawResponse(
null,
TestConstants.DEFAULT_INTERROGATION_ID,
TestConstants.DEFAULT_COLLECTION_INSTRUMENT_ID,
Mode.WEB,
new HashMap<>(),
LocalDateTime.now(),
null
);
rawResponse.payload().put("validationDate", "not a validation date");
rawResponse.payload().put("questionnaireState", RawResponseDto.QuestionnaireStateEnum.FINISHED);
rawResponse.payload().put("usualSurveyUnitId", TestConstants.DEFAULT_SURVEY_UNIT_ID);
rawResponse.payload().put("majorModelVersion", 2);
Map<String, Map<String, Map<String, String>>> dataMap = new HashMap<>();
dataMap.put("COLLECTED", new HashMap<>());
dataMap.get("COLLECTED").put("VAR1", new HashMap<>());
dataMap.get("COLLECTED").get("VAR1").put("COLLECTED", "value");
rawResponse.payload().put("data", dataMap);
rawResponses.add(rawResponse);

//Mocks behaviour
doReturn(Collections.singletonList(Mode.WEB)).when(controllerUtils).getModesList(any(),any());
doReturn(Set.of(TestConstants.DEFAULT_INTERROGATION_ID))
.when(rawResponsePersistencePort).findUnprocessedInterrogationIdsByCollectionInstrumentId(any());
doReturn(metadataModel).when(metadataService).loadAndSaveIfNotExists(any(), any(), any(), any(), any());
doReturn(rawResponses).when(rawResponsePersistencePort).findRawResponses(any(), any(), any());
Comment on lines +247 to +277
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor suggestion: the RawResponse setup and mocks are repeated in several given methods.
Extracting a small helper could simplify the tests and make the variations (questionnaireState, validationDate) clearer.
@alexisszmundy

}

//WHENS
private List<SurveyUnitModel> whenProcessByCollectionInstrumentIdAndInterrogationIdList() throws GenesisException {
rawResponseService.processRawResponses(TestConstants.DEFAULT_COLLECTION_INSTRUMENT_ID);
verify(surveyUnitService).saveSurveyUnits(surveyUnitModelsCaptor.capture());
return surveyUnitModelsCaptor.getValue();
}
private List<SurveyUnitModel> whenProcessRawResponsesCollectionInstrumentId() throws GenesisException {
rawResponseService.processRawResponses(
TestConstants.DEFAULT_COLLECTION_INSTRUMENT_ID,
Collections.singletonList(TestConstants.DEFAULT_INTERROGATION_ID),
new ArrayList<>()
);
verify(surveyUnitService).saveSurveyUnits(surveyUnitModelsCaptor.capture());
return surveyUnitModelsCaptor.getValue();
}

//THENS
private void processRawResponsesThen(RawResponseDto.QuestionnaireStateEnum questionnaireState,
List<SurveyUnitModel> createdModels) {
Assertions.assertThat(createdModels).hasSize(1);
if(questionnaireState.equals(RawResponseDto.QuestionnaireStateEnum.FINISHED)){
Assertions.assertThat(createdModels.getFirst().getValidationDate()).isEqualTo(
LocalDateTime.parse(TEST_VALIDATION_DATE, DateTimeFormatter.ISO_OFFSET_DATE_TIME)
);
}
Assertions.assertThat(createdModels.getFirst().getQuestionnaireState()).isEqualTo(questionnaireState);
}
private void processRawResponsesThenValidationDateNull(
List<SurveyUnitModel> createdModels
){
Assertions.assertThat(createdModels).hasSize(1);
Assertions.assertThat(createdModels.getFirst().getValidationDate()).isNull();
}
private void processRawResponsesThenQuestionnaireStateNull(List<SurveyUnitModel> createdModels){
Assertions.assertThat(createdModels).hasSize(1);
Assertions.assertThat(createdModels.getFirst().getQuestionnaireState()).isNull();
}
}
}
Loading