Skip to content

Commit 35e4986

Browse files
committed
enhancements
1 parent 18d564d commit 35e4986

14 files changed

Lines changed: 136 additions & 101 deletions

File tree

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package de.symeda.sormas.backend.patch;
2+
3+
import java.util.Collections;
4+
import java.util.HashSet;
5+
import java.util.Set;
6+
7+
/**
8+
* List it too big to keep directly within {@link PatchFieldHelper}.
9+
*/
10+
public class DefaultForbiddenFields {
11+
12+
private DefaultForbiddenFields() {
13+
}
14+
15+
private static final Set<String> DEFAULT_FORBIDDEN_FIELDS = buildDefaultForbiddenFieldsList();
16+
17+
private static Set<String> buildDefaultForbiddenFieldsList() {
18+
Set<String> forbiddenFields = new HashSet<>();
19+
20+
// TECHNICAL
21+
forbiddenFields.add(".uuid");
22+
forbiddenFields.add(".creationDate");
23+
forbiddenFields.add(".changeDate");
24+
forbiddenFields.add(".pseudonymized");
25+
forbiddenFields.add(".inJurisdiction");
26+
27+
// lifecycle
28+
forbiddenFields.add(".deleted");
29+
forbiddenFields.add(".archived");
30+
forbiddenFields.add(".deletionReason");
31+
forbiddenFields.add(".otherDeletionReason");
32+
33+
// users
34+
forbiddenFields.add(".reportingUser");
35+
forbiddenFields.add(".surveillanceOfficer");
36+
forbiddenFields.add(".classificationUser");
37+
forbiddenFields.add(".classifiedBy");
38+
forbiddenFields.add(".classificationDate");
39+
40+
// references
41+
forbiddenFields.add("Immunization.relatedCase");
42+
forbiddenFields.add("Immunization.person");
43+
forbiddenFields.add("Vaccination.immunization");
44+
45+
// PERSON
46+
forbiddenFields.add("Person.birthdate");
47+
forbiddenFields.add("Person.birthdateDD");
48+
forbiddenFields.add("Person.birthdateMM");
49+
forbiddenFields.add("Person.birthdateYYYY");
50+
51+
return Collections.unmodifiableSet(forbiddenFields);
52+
}
53+
54+
public static Set<String> getDefaultForbiddenFields() {
55+
return DEFAULT_FORBIDDEN_FIELDS;
56+
}
57+
}

sormas-backend/src/main/java/de/symeda/sormas/backend/patch/PatchFieldHelper.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package de.symeda.sormas.backend.patch;
22

33
import java.util.Arrays;
4+
import java.util.HashSet;
45
import java.util.Optional;
56
import java.util.Set;
67
import java.util.stream.Collectors;
@@ -33,9 +34,6 @@ public class PatchFieldHelper {
3334

3435
public static final String PATCH_FORBIDDEN_FIELDS_CONFIG_KEY = "PATCH_FORBIDDEN_FIELDS";
3536

36-
private static final Set<String> DEFAULT_FORBIDDEN_FIELDS =
37-
Set.of("Person.birthdate", "Person.birthdateDD", "Person.birthdateMM", "Person.birthdateYYYY");
38-
3937
@Inject
4038
private PathAliasHelper pathAliasHelper;
4139

@@ -106,8 +104,11 @@ public Tuple<String, PathFailureCause> resolveAlias(String pathWithPotentialAlia
106104
}
107105

108106
private boolean fieldIsForbidden(String path) {
109-
Set<String> configured = resolveConfiguredForbiddenFields();
110-
return configured.contains(path);
107+
Set<String> configuredForbiddenFields = resolveConfiguredForbiddenFields();
108+
return configuredForbiddenFields.contains(path)
109+
|| configuredForbiddenFields.stream()
110+
.anyMatch(
111+
forbiddenField -> forbiddenField.startsWith(".") ? path.endsWith(forbiddenField) : configuredForbiddenFields.contains(path));
111112
}
112113

113114
private Set<String> resolveConfiguredForbiddenFields() {
@@ -116,7 +117,7 @@ private Set<String> resolveConfiguredForbiddenFields() {
116117
return Optional.ofNullable(configValue)
117118
.filter(v -> !v.isBlank())
118119
.map(v -> Arrays.stream(v.split(",")).map(String::trim).filter(s -> !s.isEmpty()).collect(Collectors.toSet()))
119-
.orElse(DEFAULT_FORBIDDEN_FIELDS);
120+
.orElseGet(DefaultForbiddenFields::getDefaultForbiddenFields);
120121
}
121122

122123
private boolean startsWithAllowedPrefix(String path) {

sormas-backend/src/main/java/de/symeda/sormas/backend/patch/partial_retrieval/PartialRetrieverImpl.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,10 @@ private Tuple<String, Tuple<FieldInfo, PartialRetrievalFailureCause>> buildTuple
137137

138138
// Some fields are translated only by there "physical-path" from root level
139139
// example: Person.firstName has translation key "firstName", CaseData.disease has translation key "Disease"
140-
// best effort: UI falls-back to
140+
// best effort: UI falls-back to humanized last segment of aliasPath
141141
String translatedFieldName = Optional.ofNullable(I18nProperties.getCaption(aliasPath, null))
142142
.or(() -> Optional.ofNullable(I18nProperties.getCaption(physicalPathName, null)))
143-
.orElseGet(() -> I18nProperties.getDescription(aliasPath, aliasPath));
143+
.orElseGet(() -> humanizeCamelCase(physicalPathName));
144144

145145
return Tuple.of(
146146
originalFieldName,
@@ -196,6 +196,11 @@ private Optional<EntityDto> getAdequateBean(
196196
}
197197
}
198198

199+
static String humanizeCamelCase(String camelCase) {
200+
String spaced = camelCase.replaceAll("([A-Z])", " $1").toLowerCase();
201+
return Character.toUpperCase(spaced.charAt(0)) + spaced.substring(1);
202+
}
203+
199204
private FieldVisibilityCheckers getFieldVisibilityCheckers(Disease disease) {
200205
return FieldVisibilityCheckers.withCountry(configFacade.getCountryLocale())
201206
.andWithDisease(disease)

sormas-backend/src/main/java/de/symeda/sormas/backend/util/InstanceProvider.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
import javax.naming.InitialContext;
44
import javax.naming.NamingException;
55

6+
/**
7+
* Equivalent of {@link de.symeda.sormas.api.FacadeProvider} for backend code.
8+
*/
69
public class InstanceProvider {
710

811
private final InitialContext ic;

sormas-backend/src/main/resources/sql/sormas_schema_next.sql

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,12 @@ CREATE INDEX idx_externalmessage_report_id
9292

9393
UPDATE featureconfiguration
9494
SET properties = '{"FETCH_MODE":false,"FORCE_AUTOMATIC_PROCESSING":true,"SURVEY_FETCH_ENABLED":true}'
95-
where featuretype = 'EXTERNAL_MESSAGES'
95+
where featuretype = 'EXTERNAL_MESSAGES';
96+
97+
98+
INSERT INTO userroles_userrights (userrole_id, userright) SELECT id, 'EXTERNAL_MESSAGE_SURVEY_RESPONSE_VIEW' FROM public.userroles WHERE userroles.linkeddefaultuserrole in ('ADMIN');
99+
INSERT INTO userroles_userrights (userrole_id, userright) SELECT id, 'EXTERNAL_MESSAGE_SURVEY_RESPONSE_PROCESS' FROM public.userroles WHERE userroles.linkeddefaultuserrole in ('ADMIN');
100+
INSERT INTO userroles_userrights (userrole_id, userright) SELECT id, 'EXTERNAL_MESSAGE_SURVEY_RESPONSE_DELETE' FROM public.userroles WHERE userroles.linkeddefaultuserrole in ('ADMIN');
96101

97102

98103
INSERT INTO schema_version (version_number, comment)

sormas-backend/src/test/java/de/symeda/sormas/backend/externalmessage/survey/AutomaticSurveyResponseProcessorTest.java

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@ class AutomaticSurveyResponseProcessorTest extends AbstractUnitTest {
4545
@InjectMocks
4646
private AutomaticSurveyResponseProcessor victim;
4747

48-
// processSurveyResponses - happy path
49-
5048
@Test
5149
void processSurveyResponses_singleMessage_patchApplied_returnsDone() throws Exception {
5250
// PREPARE
@@ -109,8 +107,6 @@ void processSurveyResponses_multipleMessages_allSucceed_allReturnDone() throws E
109107
verify(dataPatcher, times(2)).patch(any());
110108
}
111109

112-
// processSurveyResponses - skipIfAlreadyProcessed
113-
114110
@Test
115111
void processSurveyResponses_skipIfAlreadyProcessed_resultAlreadySet_returnsCanceled() throws Exception {
116112
// PREPARE - message already has a result
@@ -173,8 +169,6 @@ void processSurveyResponses_skipIfAlreadyProcessed_resultNull_processes() throws
173169
assertEquals(ProcessingResultStatus.DONE, results.get(0).getResultStatus());
174170
}
175171

176-
// processSurveyResponses - token not found
177-
178172
@Test
179173
void processSurveyResponses_tokenNotInAvailableTokens_returnsCanceled() throws Exception {
180174
// PREPARE - token in message doesn't match any fetched survey token
@@ -208,8 +202,6 @@ void processSurveyResponses_surveyNotFound_tokenListEmpty_returnsCanceled() thro
208202
assertEquals(ProcessingResultStatus.CANCELED, results.get(0).getResultStatus());
209203
}
210204

211-
// processSurveyResponses - patch not applied
212-
213205
@Test
214206
void processSurveyResponses_patchNotApplied_resultSetOnWrapper_statusCanceled() throws Exception {
215207
// PREPARE
@@ -235,9 +227,6 @@ void processSurveyResponses_patchNotApplied_resultSetOnWrapper_statusCanceled()
235227
assertEquals(ExternalMessageStatus.UNPROCESSED, message.getStatus());
236228
}
237229

238-
// processSurveyResponses - runtime exception
239-
// BUG: resultStatus is left null when a RuntimeException is caught
240-
241230
@Test
242231
void processSurveyResponses_runtimeException_exceptionCaptured_resultStatusIsNull() throws Exception {
243232
// PREPARE
@@ -285,8 +274,6 @@ void processSurveyResponses_runtimeException_doesNotAbortOtherMessages() throws
285274
assertEquals(ProcessingResultStatus.DONE, results.get(1).getResultStatus());
286275
}
287276

288-
// processSurveyResponses - survey token is updated before patching
289-
290277
@Test
291278
void processSurveyResponses_surveyTokenUpdatedWithResponseData() throws Exception {
292279
// PREPARE
@@ -335,8 +322,6 @@ void processSurveyResponses_surveyTokenSavedEvenWhenPatchFails() throws Exceptio
335322
verify(surveyTokenFacade).save(any());
336323
}
337324

338-
// processSurveyResponses - CaseDataPatchRequest is built correctly from the survey request
339-
340325
@Test
341326
void processSurveyResponses_patchRequestBuiltCorrectlyFromSurveyRequest() throws Exception {
342327
// PREPARE
@@ -381,8 +366,6 @@ void processSurveyResponses_patchRequestBuiltCorrectlyFromSurveyRequest() throws
381366
assertEquals(List.of(Language.FR), builtRequest.getInputLanguages());
382367
}
383368

384-
// processSurveyResponses - result is stored on the response wrapper
385-
386369
@Test
387370
void processSurveyResponses_patchApplied_resultContainsCaseUuidAndResponse() throws Exception {
388371
// PREPARE
@@ -406,10 +389,6 @@ void processSurveyResponses_patchApplied_resultContainsCaseUuidAndResponse() thr
406389
assertEquals(patchResponse, result.getPatchResponse());
407390
}
408391

409-
// processSurveyResponses - duplicate externalSurveyId BUG
410-
// BUG: when two messages share the same externalSurveyId but have different tokens,
411-
// the first token is overwritten in the map, causing the first message to be canceled.
412-
413392
@Test
414393
void processSurveyResponses_duplicateExternalSurveyId_differentTokens_firstMessageCanceled() throws Exception {
415394
// PREPARE
@@ -435,8 +414,6 @@ void processSurveyResponses_duplicateExternalSurveyId_differentTokens_firstMessa
435414
assertEquals(ProcessingResultStatus.DONE, results.get(1).getResultStatus());
436415
}
437416

438-
// helpers
439-
440417
private static ExternalMessageDto buildMessage(String externalSurveyId, String token, ExternalMessageSurveyResponseResult result) {
441418
ExternalMessageSurveyResponseRequest request = new ExternalMessageSurveyResponseRequest().setExternalSurveyId(externalSurveyId)
442419
.setToken(token)

sormas-backend/src/test/java/de/symeda/sormas/backend/patch/PatchFieldHelperTest.java

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import de.symeda.sormas.api.systemconfiguration.SystemConfigurationValueFacade;
1717
import de.symeda.sormas.backend.AbstractUnitTest;
1818
import de.symeda.sormas.backend.patch.alias.PathAliasHelper;
19+
import org.mockito.Mockito;
1920

2021
class PatchFieldHelperTest extends AbstractUnitTest {
2122

@@ -33,12 +34,14 @@ class PatchFieldHelperTest extends AbstractUnitTest {
3334

3435
@BeforeEach
3536
void setUp() {
36-
when(pathAliasHelper.supportedPrefixes()).thenReturn(Set.of("CaseData", "Person", "Symptoms"));
37-
when(businessDtoFacade.fetchablePrefixes()).thenReturn(Set.of("CaseData", "Person", "Symptoms"));
37+
Mockito.lenient()
38+
.when(pathAliasHelper.supportedPrefixes())
39+
.thenReturn(Set.of("CaseData", "Person", "Symptoms", "Immunization", "Vaccination"));
40+
Mockito.lenient()
41+
.when(businessDtoFacade.fetchablePrefixes())
42+
.thenReturn(Set.of("CaseData", "Person", "Symptoms", "Immunization", "Vaccination"));
3843
}
3944

40-
// -- DUPLICATE_FIELD --
41-
4245
@ParameterizedTest
4346
@ValueSource(strings = {
4447
"Person.firstName_duplicate_",
@@ -73,8 +76,6 @@ void checkIfPathIsInvalid_duplicateMarkerRelatedRetrievalCause_mapsToPartialRetr
7376
cause.getRelatedRetrieveFailureCause());
7477
}
7578

76-
// -- valid path —- no failure expected --
77-
7879
@Test
7980
void checkIfPathIsInvalid_validPath_returnsNull() {
8081
// PREPARE
@@ -87,8 +88,6 @@ void checkIfPathIsInvalid_validPath_returnsNull() {
8788
assertNull(result);
8889
}
8990

90-
// -- INVALID_PATH_FORMAT takes precedence over DUPLICATE_FIELD --
91-
9291
@Test
9392
void checkIfPathIsInvalid_noDotAndDuplicateMarker_returnsInvalidPathFormat() {
9493
// PREPARE
@@ -100,4 +99,18 @@ void checkIfPathIsInvalid_noDotAndDuplicateMarker_returnsInvalidPathFormat() {
10099
// CHECK
101100
assertEquals(PathFailureCause.INVALID_PATH_FORMAT, result);
102101
}
102+
103+
@ParameterizedTest
104+
@ValueSource(strings = {
105+
"CaseData.uuid",
106+
"Person.deleted",
107+
"CaseData.reportingUser",
108+
"Immunization.relatedCase" })
109+
void checkIfPathIsInvalid_forbiddenField_returnsForbiddenField(String path) {
110+
// EXECUTE
111+
PathFailureCause result = victim.checkIfPathIsInvalid(path);
112+
113+
// CHECK
114+
assertEquals(PathFailureCause.FORBIDDEN_FIELD, result);
115+
}
103116
}

sormas-backend/src/test/java/de/symeda/sormas/backend/patch/mapping/impl/valuemapper/CustomizableEnumPatchMapperTest.java

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,11 @@ class CustomizableEnumPatchMapperTest extends AbstractUnitTest {
3434
@InjectMocks
3535
private CustomizableEnumPatchMapper victim;
3636

37-
// getSupportedTypes
38-
3937
@Test
4038
void getSupportedTypes_containsCustomizableEnumClass() {
4139
assertEquals(Set.of(CustomizableEnum.class), victim.getSupportedTypes());
4240
}
4341

44-
// map - illegal argument scenarios
45-
4642
@Test
4743
void map_nonCustomizableEnumTargetType_throwsIllegalArgumentException() {
4844
assertThrows(IllegalArgumentException.class, () -> victim.map("HEALTHCARE_WORKER", String.class));
@@ -55,7 +51,6 @@ void map_unregisteredCustomizableEnumType_throwsIllegalArgumentException() {
5551
() -> victim.map(new ValuePatchRequest<UnregisteredEnum>().setValue("VALUE").setTargetType(UnregisteredEnum.class)));
5652
}
5753

58-
5954
@Test
6055
void map_matchByValue_returnsEnumValue() {
6156
// PREPARE
@@ -108,7 +103,6 @@ void map_matchByCaptionWithAccents_returnsEnumValue() {
108103
assertEquals(expected, actual);
109104
}
110105

111-
112106
@Test
113107
void map_notFoundInDefaultLanguage_foundByInputLanguage_returnsEnumValue() {
114108
// PREPARE
@@ -155,7 +149,6 @@ void map_notFoundInDefaultLanguage_noInputLanguages_usesUserLanguage() {
155149
}
156150
}
157151

158-
159152
@Test
160153
void map_noMatch_fallbackAllowed_otherPresent_returnsOtherEnumValue() {
161154
// PREPARE
@@ -215,7 +208,6 @@ void map_noMatch_fallbackDisabled_returnsFailureCause() {
215208
}
216209
}
217210

218-
219211
private static OccupationType occupationType(String value, String caption) {
220212
OccupationType occupationType = new OccupationType();
221213
occupationType.setValue(value);

sormas-backend/src/test/java/de/symeda/sormas/backend/patch/mapping/impl/valuemapper/EnumMapperTest.java

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ class EnumMapperTest extends AbstractUnitTest {
1818
@InjectMocks
1919
private EnumPatchMapper victim;
2020

21-
// getSupportedTypes
22-
2321
@Test
2422
void getSupportedTypes_containsEnumClass() {
2523
// PREPARE
@@ -32,8 +30,6 @@ void getSupportedTypes_containsEnumClass() {
3230
assertEquals(expected, actual);
3331
}
3432

35-
// map - exact match (Sex enum, no OTHER/fallback ambiguity)
36-
3733
@Test
3834
void map_sex_exactMatch_male() {
3935
// EXECUTE & CHECK
@@ -70,8 +66,6 @@ void map_sex_trimsWhitespace() {
7066
assertEquals(Sex.MALE, victim.map(" MALE ", Sex.class).getData());
7167
}
7268

73-
// map - OTHER fallback (Sex has OTHER constant)
74-
7569
@Test
7670
void map_sex_unknownValue_fallsBackToOther() {
7771
// EXECUTE & CHECK
@@ -96,8 +90,6 @@ void map_infectionSetting_unknownValue_fallsBackToAnnotatedDefault() {
9690
assertEquals(InfectionSetting.UNKNOWN, victim.map("SOMETHING_UNKNOWN", InfectionSetting.class).getData());
9791
}
9892

99-
// map - @ValueMapperDefault fallback (Trimester, no OTHER constant)
100-
10193
@Test
10294
void map_trimester_exactMatch_first() {
10395
// EXECUTE & CHECK

0 commit comments

Comments
 (0)