New functions for Consent evaluation (#6250)

Little tools for consent evaluation.
This commit is contained in:
Michael Buckley 2024-08-28 15:02:26 -04:00 committed by GitHub
parent 18293eba89
commit 36cbca65a8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 204 additions and 0 deletions

View File

@ -19,6 +19,8 @@
*/
package ca.uhn.fhir.rest.server.interceptor.consent;
import java.util.stream.Stream;
public enum ConsentOperationStatusEnum {
/**
@ -39,4 +41,71 @@ public enum ConsentOperationStatusEnum {
* counting/caching methods)
*/
AUTHORIZED,
;
/**
* Assigns ordinals to the verdicts by strength:
* REJECT > AUTHORIZED > PROCEED.
* @return 2/1/0 for REJECT/AUTHORIZED/PROCEED
*/
int getPrecedence() {
switch (this) {
case REJECT:
return 2;
case AUTHORIZED:
return 1;
case PROCEED:
default:
return 0;
}
}
/**
* Evaluate verdicts in order, taking the first "decision" (i.e. first non-PROCEED) verdict.
*
* @return the first decisive verdict, or PROCEED when empty or all PROCEED.
*/
public static ConsentOperationStatusEnum serialEvaluate(Stream<ConsentOperationStatusEnum> theVoteStream) {
return theVoteStream.filter(verdict -> PROCEED != verdict).findFirst().orElse(PROCEED);
}
/**
* Evaluate verdicts in order, taking the first "decision" (i.e. first non-PROCEED) verdict.
*
* @param theNextVerdict the next verdict to consider
* @return the combined verdict
*/
public ConsentOperationStatusEnum serialReduce(ConsentOperationStatusEnum theNextVerdict) {
if (this != PROCEED) {
return this;
} else {
return theNextVerdict;
}
}
/**
* Evaluate all verdicts together, allowing any to veto (i.e. REJECT) the operation.
* <ul>
* <li>If any vote is REJECT, then the result is REJECT.
* <li>If no vote is REJECT, and any vote is AUTHORIZED, then the result is AUTHORIZED.
* <li>If no vote is REJECT or AUTHORIZED, the result is PROCEED.
* </ul>
*
* @return REJECT if any reject, AUTHORIZED if no REJECT and some AUTHORIZED, PROCEED if empty or all PROCEED
*/
public static ConsentOperationStatusEnum parallelEvaluate(Stream<ConsentOperationStatusEnum> theVoteStream) {
return theVoteStream.reduce(PROCEED, ConsentOperationStatusEnum::parallelReduce);
}
/**
* Evaluate two verdicts together, allowing either to veto (i.e. REJECT) the operation.
*
* @return REJECT if either reject, AUTHORIZED if no REJECT and some AUTHORIZED, PROCEED otherwise
*/
public ConsentOperationStatusEnum parallelReduce(ConsentOperationStatusEnum theNextVerdict) {
if (theNextVerdict.getPrecedence() > this.getPrecedence()) {
return theNextVerdict;
} else {
return this;
}
}
}

View File

@ -0,0 +1,135 @@
package ca.uhn.fhir.rest.server.interceptor.consent;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import java.util.Arrays;
import java.util.stream.Stream;
import static ca.uhn.fhir.rest.server.interceptor.consent.ConsentOperationStatusEnum.AUTHORIZED;
import static ca.uhn.fhir.rest.server.interceptor.consent.ConsentOperationStatusEnum.PROCEED;
import static ca.uhn.fhir.rest.server.interceptor.consent.ConsentOperationStatusEnum.REJECT;
import static org.junit.jupiter.api.Assertions.*;
class ConsentOperationStatusEnumTest {
/**
* With "serial" evaluation, the first non-PROCEED verdict wins.
*/
@ParameterizedTest
@CsvSource(textBlock = """
REJECT REJECT REJECT , REJECT
REJECT REJECT PROCEED , REJECT
REJECT REJECT AUTHORIZED, REJECT
REJECT PROCEED REJECT , REJECT
REJECT PROCEED PROCEED , REJECT
REJECT PROCEED AUTHORIZED, REJECT
REJECT AUTHORIZED REJECT , REJECT
REJECT AUTHORIZED PROCEED , REJECT
REJECT AUTHORIZED AUTHORIZED, REJECT
PROCEED REJECT REJECT , REJECT
PROCEED REJECT PROCEED , REJECT
PROCEED REJECT AUTHORIZED, REJECT
PROCEED PROCEED REJECT , REJECT
PROCEED PROCEED PROCEED , PROCEED
PROCEED PROCEED AUTHORIZED, AUTHORIZED
PROCEED AUTHORIZED REJECT , AUTHORIZED
PROCEED AUTHORIZED PROCEED , AUTHORIZED
PROCEED AUTHORIZED AUTHORIZED, AUTHORIZED
AUTHORIZED REJECT REJECT , AUTHORIZED
AUTHORIZED REJECT PROCEED , AUTHORIZED
AUTHORIZED REJECT AUTHORIZED, AUTHORIZED
AUTHORIZED PROCEED REJECT , AUTHORIZED
AUTHORIZED PROCEED PROCEED , AUTHORIZED
AUTHORIZED PROCEED AUTHORIZED, AUTHORIZED
AUTHORIZED AUTHORIZED REJECT , AUTHORIZED
AUTHORIZED AUTHORIZED PROCEED , AUTHORIZED
AUTHORIZED AUTHORIZED AUTHORIZED, AUTHORIZED
""")
void testSerialEvaluation_choosesFirstVerdict(String theInput, ConsentOperationStatusEnum theExpectedResult) {
// given
Stream<ConsentOperationStatusEnum> consentOperationStatusEnumStream = Arrays.stream(theInput.split(" +"))
.map(String::trim)
.map(ConsentOperationStatusEnum::valueOf);
// when
ConsentOperationStatusEnum result = ConsentOperationStatusEnum.serialEvaluate(consentOperationStatusEnumStream);
assertEquals(theExpectedResult, result);
}
@ParameterizedTest
@CsvSource(textBlock = """
REJECT , REJECT , REJECT
REJECT , PROCEED , REJECT
REJECT , AUTHORIZED, REJECT
AUTHORIZED, REJECT , AUTHORIZED
AUTHORIZED, PROCEED , AUTHORIZED
AUTHORIZED, AUTHORIZED, AUTHORIZED
PROCEED , REJECT , REJECT
PROCEED , PROCEED , PROCEED
PROCEED , AUTHORIZED, AUTHORIZED
""")
void testSerialReduction_choosesFirstVerdict(ConsentOperationStatusEnum theFirst, ConsentOperationStatusEnum theSecond, ConsentOperationStatusEnum theExpectedResult) {
// when
ConsentOperationStatusEnum result = theFirst.serialReduce(theSecond);
assertEquals(theExpectedResult, result);
}
/**
* With "parallel" evaluation, the "strongest" verdict wins.
* REJECT > AUTHORIZED > PROCEED.
*/
@ParameterizedTest
@CsvSource(textBlock = """
REJECT REJECT REJECT , REJECT
REJECT REJECT PROCEED , REJECT
REJECT REJECT AUTHORIZED, REJECT
REJECT PROCEED REJECT , REJECT
REJECT PROCEED PROCEED , REJECT
REJECT PROCEED AUTHORIZED, REJECT
REJECT AUTHORIZED REJECT , REJECT
REJECT AUTHORIZED PROCEED , REJECT
REJECT AUTHORIZED AUTHORIZED, REJECT
PROCEED REJECT REJECT , REJECT
PROCEED REJECT PROCEED , REJECT
PROCEED REJECT AUTHORIZED, REJECT
PROCEED PROCEED REJECT , REJECT
PROCEED PROCEED PROCEED , PROCEED
PROCEED PROCEED AUTHORIZED, AUTHORIZED
PROCEED AUTHORIZED REJECT , REJECT
PROCEED AUTHORIZED PROCEED , AUTHORIZED
PROCEED AUTHORIZED AUTHORIZED, AUTHORIZED
AUTHORIZED REJECT REJECT , REJECT
AUTHORIZED REJECT PROCEED , REJECT
AUTHORIZED REJECT AUTHORIZED, REJECT
AUTHORIZED PROCEED REJECT , REJECT
AUTHORIZED PROCEED PROCEED , AUTHORIZED
AUTHORIZED PROCEED AUTHORIZED, AUTHORIZED
AUTHORIZED AUTHORIZED REJECT , REJECT
AUTHORIZED AUTHORIZED PROCEED , AUTHORIZED
AUTHORIZED AUTHORIZED AUTHORIZED, AUTHORIZED
""")
void testParallelReduction_strongestVerdictWins(String theInput, ConsentOperationStatusEnum theExpectedResult) {
// given
Stream<ConsentOperationStatusEnum> consentOperationStatusEnumStream = Arrays.stream(theInput.split(" +"))
.map(String::trim)
.map(ConsentOperationStatusEnum::valueOf);
// when
ConsentOperationStatusEnum result = ConsentOperationStatusEnum.parallelEvaluate(consentOperationStatusEnumStream);
assertEquals(theExpectedResult, result);
}
@Test
void testStrengthOrder() {
assertTrue(REJECT.getPrecedence() > AUTHORIZED.getPrecedence());
assertTrue(AUTHORIZED.getPrecedence() > PROCEED.getPrecedence());
}
}