Skip to content

Commit a3526aa

Browse files
committed
🥅 Trying to insert group for singular fields
1 parent 61705b5 commit a3526aa

4 files changed

Lines changed: 82 additions & 5 deletions

File tree

sormas-api/src/main/java/de/symeda/sormas/api/patch/DataPatchFailureCause.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ public enum DataPatchFailureCause {
7575
*/
7676
DUPLICATE_FIELD,
7777

78+
/**
79+
* Means the field does not support being inserted multiple in groups. Most fields are "singulars".
80+
*/
81+
FORBIDDEN_MULTI_GROUP_FIELD,
82+
7883
/**
7984
* This means there is a hole in the implementation.
8085
*/

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

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,19 @@ public DataPatchResponse patch(CaseDataPatchRequest request) {
9999
List<SinglePatchResult> results = patchingTuples.stream().map(singleFieldPatchResult -> {
100100
PatchField patchField = singleFieldPatchResult.field;
101101
SinglePatchResult singlePatchResult = new SinglePatchResult().setField(patchField);
102-
103-
Supplier<AttachedEntityWrapper> target = () -> findAppropriateTarget(patchField, caseData, entityCache);
104-
105102
try {
103+
Supplier<AttachedEntityWrapper> target = () -> findAppropriateTarget(patchField, caseData, entityCache);
104+
106105
return produceSinglePatchResult(request, singleFieldPatchResult, disease, target);
106+
} catch (DataPatchingException e) {
107+
DataPatchFailureCause failureCause = e.getFailureCause();
108+
logger.error(
109+
"DataPatching-specific failure during patch operation for request: [{}], [{}], of type [{}]",
110+
request,
111+
singleFieldPatchResult,
112+
failureCause,
113+
e);
114+
return singlePatchResult.setFailure(new DataPatchFailure().setDataPatchFailureCause(failureCause));
107115
} catch (RuntimeException e) {
108116
logger.error("Failure during patch operation for request: [{}], [{}]", request, singleFieldPatchResult, e);
109117
return singlePatchResult.setFailure(new DataPatchFailure().setDataPatchFailureCause(DataPatchFailureCause.TECHNICAL));
@@ -373,8 +381,18 @@ private AttachedEntityWrapper findAppropriateTarget(
373381

374382
Optional<AttachedEntityWrapper> fetched = businessDtoFacade.tryFetchByI18nNameForCreateUpdate(prefix, caseData);
375383
if (fetched.isPresent()) {
376-
entityCache.put(key, fetched.get());
377-
return fetched.get();
384+
AttachedEntityWrapper appropriateEntityWrapper = fetched.get();
385+
386+
if (patchField.getGroupIndex() != null && appropriateEntityWrapper.isAttached()) {
387+
throw new DataPatchingException(
388+
String.format(
389+
"The field [%s] is already attached, this means no new entities can be added as group: only a single instance is valid for the Case-'data-Tree'",
390+
prefix),
391+
DataPatchFailureCause.FORBIDDEN_MULTI_GROUP_FIELD);
392+
}
393+
394+
entityCache.put(key, appropriateEntityWrapper);
395+
return appropriateEntityWrapper;
378396
}
379397

380398
logger.error("Fallbacked to entity for resolved path: [{}]. This should not occur as CaseData is already in entityCache", resolvedPath);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package de.symeda.sormas.backend.patch;
2+
3+
import de.symeda.sormas.api.patch.DataPatchFailureCause;
4+
5+
/**
6+
* In some scenarios, it's simpler to throw a specific exception for exceptional exceptions that should not happen too often.
7+
*/
8+
public class DataPatchingException extends RuntimeException {
9+
10+
private final DataPatchFailureCause failureCause;
11+
12+
public DataPatchingException(String message, DataPatchFailureCause failureCause) {
13+
super(message);
14+
this.failureCause = failureCause;
15+
}
16+
17+
public DataPatchFailureCause getFailureCause() {
18+
return failureCause;
19+
}
20+
}

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1027,6 +1027,40 @@ void patch_activityAsCase() {
10271027
() -> Assertions.assertEquals("office work", activities.get(0).getDescription()));
10281028
}
10291029

1030+
@Test
1031+
void patch_forbiddenMultiGroupField() {
1032+
// PREPARE
1033+
CaseDataDto originalCase = creator.createUnclassifiedCase(Disease.PERTUSSIS);
1034+
PersonDto originalPerson = getPersonFacade().getByUuid(originalCase.getPerson().getUuid());
1035+
String originalLastName = originalPerson.getLastName();
1036+
1037+
String secondPersonLastName = "secondPersonLastName";
1038+
1039+
// Use PatchDictionary with groupIndex=1 to simulate inserting a "second person".
1040+
// Person is a singular entity (always attached), so grouping is forbidden.
1041+
PatchDictionary patchDictionary = new PatchDictionary();
1042+
patchDictionary.put(PatchField.of("Person.lastName", 1), secondPersonLastName);
1043+
1044+
CaseDataPatchRequest request = new CaseDataPatchRequest().setCaseUuid(originalCase.getUuid())
1045+
.setReplacementStrategy(DataReplacementStrategy.ALWAYS)
1046+
.setPatchDictionary(patchDictionary);
1047+
1048+
// EXECUTE
1049+
DataPatchResponse response = victim().patch(request);
1050+
1051+
// CHECK
1052+
PersonDto actualPerson = getPersonFacade().getByUuid(originalCase.getPerson().getUuid());
1053+
1054+
PatchField expectedField = PatchField.of("Person.lastName", 1);
1055+
DataPatchFailure expectedFailure = new DataPatchFailure().setDataPatchFailureCause(DataPatchFailureCause.FORBIDDEN_MULTI_GROUP_FIELD);
1056+
1057+
Assertions.assertAll(
1058+
() -> Assertions.assertTrue(response.getValidPatchDictionary().isEmpty(), "Nothing should have been patched"),
1059+
() -> Assertions.assertFalse(response.isApplied()),
1060+
() -> Assertions.assertEquals(Map.of(expectedField, expectedFailure), response.getFailures()),
1061+
() -> Assertions.assertEquals(originalLastName, actualPerson.getLastName(), "Person lastName must not be altered"));
1062+
}
1063+
10301064
private static String toFieldName(String prefix, String field) {
10311065
return prefix + '.' + field;
10321066
}

0 commit comments

Comments
 (0)