From 533a6a67dda9bf8d2097fde1daba9fb5d5f628b3 Mon Sep 17 00:00:00 2001 From: JackBraceyCGI Date: Wed, 29 Mar 2023 12:28:12 +0100 Subject: [PATCH 01/67] Upgrade gradle wrapper to 7.4.2 Preparation work for Spring 3 Replacing deprecated main class in build.gradle --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 58910 -> 59536 bytes gradle/wrapper/gradle-wrapper.properties | 5 +- gradlew | 257 ++++++++++++++--------- gradlew.bat | 21 +- 5 files changed, 159 insertions(+), 126 deletions(-) diff --git a/build.gradle b/build.gradle index c00d1611a..2485a2d1b 100644 --- a/build.gradle +++ b/build.gradle @@ -216,7 +216,7 @@ task runAndPublishConsumerPactTests(type: Test, description: 'Publish pact test } task fortifyScan(type: JavaExec) { - main = "uk.gov.hmcts.fortifyclient.FortifyClientMainApp" + mainClass.set("uk.gov.hmcts.fortifyclient.FortifyClientMainApp") classpath += sourceSets.test.runtimeClasspath jvmArgs = ['--add-opens=java.base/java.lang.reflect=ALL-UNNAMED'] // This is a temporary fix to prevent the nightly build from failing if the Fortify scan detects issues diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 62d4c053550b91381bbd28b1afc82d634bf73a8a..7454180f2ae8848c63b8b4dea2cb829da983f2fa 100644 GIT binary patch delta 18328 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 Y(TzS~Xdo8@nTUZrK}a{fL*HNQe=4o%4FCWD delta 17568 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 \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index a9f778a7a..ac1b06f93 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,21 +64,6 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line @@ -86,7 +71,7 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell From 8b75ea883405c9fad10d16c15ccac2f4e8ae8211 Mon Sep 17 00:00:00 2001 From: lukasz-wolski <1005015+lukasz-wolski@users.noreply.github.com> Date: Mon, 24 Apr 2023 10:00:48 +0100 Subject: [PATCH 02/67] Addresses CVE-2023-20863 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 60d45e1e4..068cd8f2d 100644 --- a/build.gradle +++ b/build.gradle @@ -43,7 +43,7 @@ def versions = [ restAssured : '4.3.3', jackson : '2.14.2', log4j : '2.17.1', - springVersion : '5.3.26', + springVersion : '5.3.27', poi : '4.1.2', logback : '1.2.11', testContainer_postgresql: '1.17.6' From bf8a99f584d376a6332c722e283453587b6b2746 Mon Sep 17 00:00:00 2001 From: lukasz-wolski <1005015+lukasz-wolski@users.noreply.github.com> Date: Tue, 2 May 2023 15:01:25 +0100 Subject: [PATCH 03/67] addresses CVE-2023-20873, CVE-2023-20862 --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index dd3a62a12..7dfed25b8 100644 --- a/build.gradle +++ b/build.gradle @@ -18,7 +18,7 @@ plugins { id "info.solidsoft.pitest" version '1.7.0' id 'io.spring.dependency-management' version '1.1.0' id 'org.sonarqube' version '3.3' - id 'org.springframework.boot' version '2.7.7' + id 'org.springframework.boot' version '2.7.11' id "org.flywaydb.flyway" version '8.5.4' id 'au.com.dius.pact' version '4.1.7' // do not change, otherwise serenity report fails id 'org.owasp.dependencycheck' version '8.0.1' @@ -37,7 +37,7 @@ def versions = [ reformLogging : '6.0.1', serenity : '2.0.76', sonarPitest : '0.5', - springBoot : '2.7.7', + springBoot : '2.7.11', pact_version : '4.1.7', launchDarklySdk : '5.10.7', restAssured : '4.3.3', @@ -342,7 +342,7 @@ dependencies { implementation group: 'org.springframework.boot', name: 'spring-boot-starter-jdbc' implementation group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: versions.springBoot implementation group: 'org.springframework.boot', name: 'spring-boot-starter-cache', version: versions.springBoot - implementation group: 'org.springframework.security', name: 'spring-security-core', version: '5.7.5' + implementation group: 'org.springframework.security', name: 'spring-security-core', version: '5.7.8' implementation group: 'org.springframework', name: 'spring-aop', version: versions.springVersion implementation group: 'org.springframework', name: 'spring-aspects', version: versions.springVersion From 44107c7e420da1a87effbbdc693360723dcedd69 Mon Sep 17 00:00:00 2001 From: manukundloo-hmcts Date: Tue, 9 May 2023 12:01:57 +0100 Subject: [PATCH 04/67] [RDCC-6200]-Delete functional Test Data - Staff Reference Data --- Jenkinsfile_nightly | 33 +++++++++++++++++++ deletescript/delete-functional-data.sh | 20 +++++++++++ .../sql/delete-functional-test-data.sql | 6 ++++ 3 files changed, 59 insertions(+) create mode 100644 deletescript/delete-functional-data.sh create mode 100644 deletescript/sql/delete-functional-test-data.sql diff --git a/Jenkinsfile_nightly b/Jenkinsfile_nightly index 159beeb54..b9d30e91c 100644 --- a/Jenkinsfile_nightly +++ b/Jenkinsfile_nightly @@ -56,6 +56,28 @@ withNightlyPipeline(type, product, component) { enableSecurityScan() enableFortifyScan() + afterSuccess('fullFunctionalTest') { + steps.archiveArtifacts allowEmptyArchive: true, artifacts: '**/site/serenity/**/*' + steps.archiveArtifacts allowEmptyArchive: true, artifacts: 'build/reports/**/*' + + publishHTML target: [ + allowMissing : true, + alwaysLinkToLastBuild: true, + keepAll : true, + reportDir : "target/site/serenity/", + reportFiles : "index.html", + reportName : "Functional Tests Report" + ] + + print "calling delete script for nightly" + withSubscription(subscription) { + withTeamSecrets(config, params.ENVIRONMENT) { + deleteFunctionalDbData() + } + } + print "completed delete script for nightly" + } + afterSuccess('mutationTest') { publishHTML target: [ allowMissing : true, @@ -80,3 +102,14 @@ withNightlyPipeline(type, product, component) { ] } } + +def deleteFunctionalDbData() { + withDocker('jbergknoff/postgresql-client', "--entrypoint='' -e PGPASSWORD=${FUNC_DATABASE_PASS} -v ${WORKSPACE}/deletescript/:/deletescript") { + sh "chmod +x /deletescript/delete-functional-data.sh" + sh "/deletescript/delete-functional-data.sh \ + ${FUNC_DATABASE_USER} \ + ${FUNC_DATABASE_NAME} \ + ${FUNC_DATABASE_HOST} \ + ${FUNC_DATABASE_PORT}" + } +} \ No newline at end of file diff --git a/deletescript/delete-functional-data.sh b/deletescript/delete-functional-data.sh new file mode 100644 index 000000000..18f2b1d64 --- /dev/null +++ b/deletescript/delete-functional-data.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +DATABASE_USER=$1; +DATABASE_NAME=$2; +DATABASE_HOST=$3; +DATABASE_PORT=$4; +FILE_NAME="delete-functional-test-data.sql" + +echo "executing nightly build delete script" + +psql "dbname=$DATABASE_NAME sslmode=require" -h $DATABASE_HOST -U $DATABASE_USER -p $DATABASE_PORT -f /deletescript/sql/$FILE_NAME --set AUTOCOMMIT=off + +psql_exit_status=$? + +if [ $psql_exit_status != 0 ]; then + echo "psql failed while trying to run sql script" 1>&2 + exit $psql_exit_status +fi + +echo "completed nightly build delete script" \ No newline at end of file diff --git a/deletescript/sql/delete-functional-test-data.sql b/deletescript/sql/delete-functional-test-data.sql new file mode 100644 index 000000000..f075cc5c0 --- /dev/null +++ b/deletescript/sql/delete-functional-test-data.sql @@ -0,0 +1,6 @@ +delete from case_worker_profile cwp where + email_id like any (values('cwr-rd-func-test-user-only%')) + or email_id like any (values('cwr-func-test-user-%')) + or email_id like any(values('staff-rd-profile-func-test-user-only-%')) ; + +commit; \ No newline at end of file From 4ac203864bfc97fe0cc64dc6d4399015e0876ed9 Mon Sep 17 00:00:00 2001 From: manukundloo-hmcts Date: Wed, 10 May 2023 10:14:26 +0100 Subject: [PATCH 05/67] [RDCC-6200]-removed one extara email format --- deletescript/sql/delete-functional-test-data.sql | 1 - 1 file changed, 1 deletion(-) diff --git a/deletescript/sql/delete-functional-test-data.sql b/deletescript/sql/delete-functional-test-data.sql index f075cc5c0..278d3a750 100644 --- a/deletescript/sql/delete-functional-test-data.sql +++ b/deletescript/sql/delete-functional-test-data.sql @@ -1,6 +1,5 @@ delete from case_worker_profile cwp where email_id like any (values('cwr-rd-func-test-user-only%')) - or email_id like any (values('cwr-func-test-user-%')) or email_id like any(values('staff-rd-profile-func-test-user-only-%')) ; commit; \ No newline at end of file From de8c97bd15bb5b23845fb11c2e9af50b9a69c88e Mon Sep 17 00:00:00 2001 From: sahitya-desireddy <93707379+sahitya-desireddy@users.noreply.github.com> Date: Wed, 10 May 2023 15:16:13 +0100 Subject: [PATCH 06/67] To Delete test skills in skill table (#720) * To Delete test skills in skill table * To Delete test skills in skill table * Functional Tests --- .../reform/cwrdapi/FetchStaffProfileByIdFunctionalTest.java | 6 +++--- .../hmcts/reform/cwrdapi/StaffRefCreateFunctionalTest.java | 6 +++--- .../reform/cwrdapi/StaffRefDataSkillsFunctionalTest.java | 4 ++-- .../db/migration/V1_21__Delete_skill_TestSkill.sql | 1 + 4 files changed, 9 insertions(+), 8 deletions(-) create mode 100644 src/main/resources/db/migration/V1_21__Delete_skill_TestSkill.sql diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/FetchStaffProfileByIdFunctionalTest.java b/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/FetchStaffProfileByIdFunctionalTest.java index ef71d15c7..c3221535f 100644 --- a/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/FetchStaffProfileByIdFunctionalTest.java +++ b/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/FetchStaffProfileByIdFunctionalTest.java @@ -44,9 +44,9 @@ public class FetchStaffProfileByIdFunctionalTest extends AuthorizationFunctional void should_fetchStaffProfile_By_ID_200() { SkillsRequest skillsRequest = SkillsRequest .skillsRequest() - .skillId(9) - .description("testskill1") - .skillCode("SKILL:AAA7:TEST1") + .skillId(26) + .description("Check application C100") + .skillCode("SKILL:ABA5:CHECKAPPLICATIONC100") .build(); StaffProfileCreationRequest staffProfileCreationRequest = caseWorkerApiClient diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/StaffRefCreateFunctionalTest.java b/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/StaffRefCreateFunctionalTest.java index 2a6ee5371..11511a082 100644 --- a/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/StaffRefCreateFunctionalTest.java +++ b/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/StaffRefCreateFunctionalTest.java @@ -161,9 +161,9 @@ void createStaffProfile_LD_disabled() { void fetchCaseWorkerDetails() { SkillsRequest skillsRequest = SkillsRequest .skillsRequest() - .skillId(9) - .description("testskill1") - .skillCode("SKILL:AAA7:TEST1") + .skillId(26) + .description("Check application C100") + .skillCode("SKILL:ABA5:CHECKAPPLICATIONC100") .build(); StaffProfileCreationRequest staffProfileCreationRequest = caseWorkerApiClient diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/StaffRefDataSkillsFunctionalTest.java b/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/StaffRefDataSkillsFunctionalTest.java index b3b9aa2e9..50eef083d 100644 --- a/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/StaffRefDataSkillsFunctionalTest.java +++ b/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/StaffRefDataSkillsFunctionalTest.java @@ -51,10 +51,10 @@ void should_return_service_skills_with_status_code_200_when_flag_true() { fetchResponse.getBody().as(StaffWorkerSkillResponse.class); assertThat(staffWorkerSkillResponse).isNotNull(); assertThat(staffWorkerSkillResponse.getServiceSkills()).isNotNull(); - assertThat(staffWorkerSkillResponse.getServiceSkills().size()).isGreaterThan(1); + assertThat(staffWorkerSkillResponse.getServiceSkills().size()).isGreaterThan(0); ServiceSkill serviceSkill = staffWorkerSkillResponse.getServiceSkills().get(0); - assertThat(serviceSkill.getId()).isEqualTo("AAA7"); + assertThat(serviceSkill.getId()).isEqualTo("ABA5"); assertThat(serviceSkill.getSkills().size()).isGreaterThan(1); } diff --git a/src/main/resources/db/migration/V1_21__Delete_skill_TestSkill.sql b/src/main/resources/db/migration/V1_21__Delete_skill_TestSkill.sql new file mode 100644 index 000000000..941ef88e7 --- /dev/null +++ b/src/main/resources/db/migration/V1_21__Delete_skill_TestSkill.sql @@ -0,0 +1 @@ +DELETE FROM skill WHERE skill_id IN (9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25); \ No newline at end of file From baf124f0630d5bfae11529f2dbb23af380ab0a05 Mon Sep 17 00:00:00 2001 From: manukundloo-hmcts Date: Tue, 16 May 2023 14:48:33 +0100 Subject: [PATCH 07/67] [RDCC-6200] Fix for delete functional test user --- Jenkinsfile_nightly | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile_nightly b/Jenkinsfile_nightly index b9d30e91c..77aeecd76 100644 --- a/Jenkinsfile_nightly +++ b/Jenkinsfile_nightly @@ -2,6 +2,7 @@ properties([ pipelineTriggers([cron('21 01 * * *')]), // scheduling to trigger jenkins job parameters([ + string(name: 'ENVIRONMENT', defaultValue: 'aat', description: 'Environment to test'), string(name: 'URL_TO_TEST', defaultValue: 'http://rd-caseworker-ref-api-aat.service.core-compute-aat.internal', description: 'The URL you want to run these tests against'), string(name: 'SecurityRules', defaultValue: 'http://raw.githubusercontent.com/hmcts/security-test-rules/master/conf/security-rules.conf', @@ -22,7 +23,12 @@ def secrets = [ 'rd-${env}': [ secret('CA-REF-OAUTH2-CLIENT-SECRET', 'CA_REF_OAUTH2_CLIENT_SECRET'), secret('CA-REF-OAUTH2-CLIENT-ID', 'CA_REF_OAUTH2_CLIENT_ID'), - secret('LD-SDK-KEY', 'LD_SDK_KEY') + secret('LD-SDK-KEY', 'LD_SDK_KEY'), + secret('caseworker-ref-api-POSTGRES-HOST', 'FUNC_DATABASE_HOST'), + secret('caseworker-ref-api-POSTGRES-PASS', 'FUNC_DATABASE_PASS'), + secret('caseworker-ref-api-POSTGRES-USER', 'FUNC_DATABASE_USER'), + secret('caseworker-ref-api-POSTGRES-DATABASE', 'FUNC_DATABASE_NAME'), + secret('caseworker-ref-api-POSTGRES-PORT', 'FUNC_DATABASE_PORT'), ] ] @@ -40,6 +46,13 @@ def vaultOverrides = [ 'spreview': 'saat' ] + +import uk.gov.hmcts.contino.AppPipelineConfig + +def subscription = "nonprod" +def config = new AppPipelineConfig() +config.vaultSecrets = secrets + withNightlyPipeline(type, product, component) { env.S2S_URL_FOR_TESTS = "http://rpe-service-auth-provider-aat.service.core-compute-aat.internal" From c4fa8e730ec3c0ce1d22c0932f6949e89bf35dad Mon Sep 17 00:00:00 2001 From: manukundloo-hmcts Date: Tue, 16 May 2023 15:10:32 +0100 Subject: [PATCH 08/67] [RDCC-6200] Fix for delete functional test user --- Jenkinsfile_nightly | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile_nightly b/Jenkinsfile_nightly index 77aeecd76..1706756ab 100644 --- a/Jenkinsfile_nightly +++ b/Jenkinsfile_nightly @@ -28,7 +28,7 @@ def secrets = [ secret('caseworker-ref-api-POSTGRES-PASS', 'FUNC_DATABASE_PASS'), secret('caseworker-ref-api-POSTGRES-USER', 'FUNC_DATABASE_USER'), secret('caseworker-ref-api-POSTGRES-DATABASE', 'FUNC_DATABASE_NAME'), - secret('caseworker-ref-api-POSTGRES-PORT', 'FUNC_DATABASE_PORT'), + secret('caseworker-ref-api-POSTGRES-PORT', 'FUNC_DATABASE_PORT') ] ] From 78602a90b8d4e0d5ba5b9fe5fdbaca18ada95c24 Mon Sep 17 00:00:00 2001 From: sahitya-desireddy <93707379+sahitya-desireddy@users.noreply.github.com> Date: Wed, 17 May 2023 11:56:08 +0100 Subject: [PATCH 09/67] Remove StaffAdminrole from Idam (#716) * Remove StaffAdminrole from Idam * Remove StaffAdminrole from Idam * Remove StaffAdminrole from Idam * Integration Test Case * checkstyle fix * Functional Test * Add more assertions * New Functional Test case to delete role in idam directly * New Functional Test case to delete role in idam directly * New Functional Test case to delete role in idam directly * New Functional Test case to delete role in idam directly * New Functional Test case to delete role in idam directly * New Functional Test case to delete role in idam directly --- .../StaffRefUpdateProfileFunctionalTest.java | 146 ++++++++++++++++++ .../reform/cwrdapi/idam/IdamOpenIdClient.java | 31 ++++ .../UpdateStaffReferenceProfileTest.java | 30 ++++ .../client/domain/RoleDeletionResponse.java | 16 ++ .../domain/UserProfileRolesResponse.java | 3 + .../domain/UserProfileUpdatedData.java | 2 +- .../service/impl/StaffRefDataServiceImpl.java | 9 +- ...taffRefDataUpdateStaffServiceImplTest.java | 52 +++++++ 8 files changed, 286 insertions(+), 3 deletions(-) create mode 100644 src/main/java/uk/gov/hmcts/reform/cwrdapi/client/domain/RoleDeletionResponse.java diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/StaffRefUpdateProfileFunctionalTest.java b/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/StaffRefUpdateProfileFunctionalTest.java index e2ce0012d..ca935f7e1 100644 --- a/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/StaffRefUpdateProfileFunctionalTest.java +++ b/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/StaffRefUpdateProfileFunctionalTest.java @@ -29,8 +29,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static uk.gov.hmcts.reform.cwrdapi.util.FeatureToggleConditionExtension.getToggledOffMessage; @ComponentScan("uk.gov.hmcts.reform.cwrdapi") @@ -311,6 +313,150 @@ void updateStaffProfileDifferentThanUserPresentInUserProfileAndIdamAndFlags() th } + @Test + @ToggleEnable(mapKey = UPDATE_STAFF_PROFILE, withFeature = true) + @ExtendWith(FeatureToggleConditionExtension.class) + void updateStaffProfileDelStaffAdminRoleWhenStaffAdminIsFalse() throws JsonProcessingException { + + StaffProfileCreationRequest staffRequest = caseWorkerApiClient + .createStaffProfileCreationRequest(); + staffRequest.setStaffAdmin(true); + //Step 1: create user in IDM for active status + List userRoles = List.of(ROLE_CWD_ADMIN,ROLE_STAFF_ADMIN); + Map users = idamOpenIdClient.createUser(userRoles,staffRequest.getEmailId(), + staffRequest.getFirstName(),staffRequest.getFirstName()); + //Step 2: create user in UP + UserProfileCreationRequest userProfileRequest = caseWorkerApiClient.createUserProfileRequest(staffRequest); + createUserProfileFromUp(userProfileRequest); + + Response response = caseWorkerApiClient.createStaffUserProfileWithOutIdm(staffRequest); + + //Verify idam profile roles has staff admin + StaffProfileCreationResponse staffProfileCreationResponse = + response.getBody().as(StaffProfileCreationResponse.class); + String cwId = staffProfileCreationResponse.getCaseWorkerId(); + var idamResponse = idamOpenIdClient.getUserByUserID(cwId); + assertEquals(staffRequest.getEmailId(), idamResponse.get("email")); + assertTrue(((List)idamResponse.get("roles")).contains(ROLE_STAFF_ADMIN)); + assertTrue(((List)idamResponse.get("roles")).contains(CWD_USER)); + + //Step 3: create user in SRD with staff admin false + staffRequest.setStaffAdmin(false); + response = caseWorkerApiClient.updateStaffUserProfile(staffRequest); + assertThat(response).isNotNull(); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK.value()); + + //Step 4: Retrieve the user in SRD + Response fetchResponse = caseWorkerApiClient.getMultipleAuthHeadersInternal(ROLE_CWD_SYSTEM_USER) + .body(UserRequest.builder().userIds(List.of(cwId)).build()) + .post("/refdata/case-worker/users/fetchUsersById/") + .andReturn(); + fetchResponse.then() + .assertThat() + .statusCode(200); + + List fetchedList = + Arrays.asList(fetchResponse.getBody().as( + uk.gov.hmcts.reform.cwrdapi.client.domain.CaseWorkerProfile[].class)); + assertEquals(1, fetchedList.size()); + uk.gov.hmcts.reform.cwrdapi.client.domain.CaseWorkerProfile caseWorkerProfile = fetchedList.get(0); + + // validate Idam user doesn't have staff admin role + idamResponse = idamOpenIdClient.getUserByUserID(cwId); + assertEquals(caseWorkerProfile.getId(), idamResponse.get("id")); + assertEquals(caseWorkerProfile.getFirstName(), idamResponse.get("forename")); + assertEquals(caseWorkerProfile.getLastName(), idamResponse.get("surname")); + assertEquals(caseWorkerProfile.getOfficialEmail(), idamResponse.get("email")); + assertFalse(((List)idamResponse.get("roles")).contains(ROLE_STAFF_ADMIN)); + assertFalse(((List)idamResponse.get("roles")).isEmpty()); + assertTrue(((List)idamResponse.get("roles")).contains(CWD_USER)); + + idamOpenIdClient.getcwdAdminOpenIdToken("cwd-admin"); + UserProfileResponse upResponse = getUserProfileFromUp(caseWorkerProfile.getOfficialEmail()); + assertEquals(caseWorkerProfile.getId(), upResponse.getIdamId()); + assertEquals(caseWorkerProfile.getFirstName(), upResponse.getFirstName()); + assertEquals(caseWorkerProfile.getLastName(), upResponse.getLastName()); + assertEquals(caseWorkerProfile.getOfficialEmail(), upResponse.getEmail()); + assertFalse(upResponse.getRoles().contains(ROLE_STAFF_ADMIN)); + assertFalse((upResponse.getRoles()).isEmpty()); + assertTrue(upResponse.getRoles().contains(CWD_USER)); + + } + + @Test + @ToggleEnable(mapKey = UPDATE_STAFF_PROFILE, withFeature = true) + @ExtendWith(FeatureToggleConditionExtension.class) + void updateStaffProfileDelStaffAdminRoleDirectlyFromIdamAndStaffAdminIsTrue() throws JsonProcessingException { + + StaffProfileCreationRequest staffRequest = caseWorkerApiClient + .createStaffProfileCreationRequest(); + staffRequest.setStaffAdmin(true); + //Step 1: create user in IDM for active status + List userRoles = List.of(ROLE_CWD_ADMIN,ROLE_STAFF_ADMIN); + Map users = idamOpenIdClient.createUser(userRoles,staffRequest.getEmailId(), + staffRequest.getFirstName(),staffRequest.getFirstName()); + //Step 2: create user in UP + UserProfileCreationRequest userProfileRequest = caseWorkerApiClient.createUserProfileRequest(staffRequest); + createUserProfileFromUp(userProfileRequest); + + Response response = caseWorkerApiClient.createStaffUserProfileWithOutIdm(staffRequest); + + //Verify idam profile roles has staff admin + StaffProfileCreationResponse staffProfileCreationResponse = + response.getBody().as(StaffProfileCreationResponse.class); + String cwId = staffProfileCreationResponse.getCaseWorkerId(); + var idamResponse = idamOpenIdClient.getUserByUserID(cwId); + assertEquals(staffRequest.getEmailId(), idamResponse.get("email")); + assertTrue(((List)idamResponse.get("roles")).contains(ROLE_STAFF_ADMIN)); + assertTrue(((List)idamResponse.get("roles")).contains(CWD_USER)); + + idamOpenIdClient.deleteRoleByUserIdNRoleName(cwId,ROLE_STAFF_ADMIN); + idamResponse = idamOpenIdClient.getUserByUserID(cwId); + assertEquals(staffRequest.getEmailId(), idamResponse.get("email")); + assertFalse(((List)idamResponse.get("roles")).contains(ROLE_STAFF_ADMIN)); + assertTrue(((List)idamResponse.get("roles")).contains(CWD_USER)); + + //Step 3: create user in SRD with staff admin false + response = caseWorkerApiClient.updateStaffUserProfile(staffRequest); + assertThat(response).isNotNull(); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK.value()); + + //Step 4: Retrieve the user in SRD + Response fetchResponse = caseWorkerApiClient.getMultipleAuthHeadersInternal(ROLE_CWD_SYSTEM_USER) + .body(UserRequest.builder().userIds(List.of(cwId)).build()) + .post("/refdata/case-worker/users/fetchUsersById/") + .andReturn(); + fetchResponse.then() + .assertThat() + .statusCode(200); + + List fetchedList = + Arrays.asList(fetchResponse.getBody().as( + uk.gov.hmcts.reform.cwrdapi.client.domain.CaseWorkerProfile[].class)); + assertEquals(1, fetchedList.size()); + uk.gov.hmcts.reform.cwrdapi.client.domain.CaseWorkerProfile caseWorkerProfile = fetchedList.get(0); + + // validate Idam user doesn't have staff admin role + idamResponse = idamOpenIdClient.getUserByUserID(cwId); + assertEquals(caseWorkerProfile.getId(), idamResponse.get("id")); + assertEquals(caseWorkerProfile.getFirstName(), idamResponse.get("forename")); + assertEquals(caseWorkerProfile.getLastName(), idamResponse.get("surname")); + assertEquals(caseWorkerProfile.getOfficialEmail(), idamResponse.get("email")); + assertTrue(((List)idamResponse.get("roles")).contains(ROLE_STAFF_ADMIN)); + assertFalse(((List)idamResponse.get("roles")).isEmpty()); + assertTrue(((List)idamResponse.get("roles")).contains(CWD_USER)); + + idamOpenIdClient.getcwdAdminOpenIdToken("cwd-admin"); + UserProfileResponse upResponse = getUserProfileFromUp(caseWorkerProfile.getOfficialEmail()); + assertEquals(caseWorkerProfile.getId(), upResponse.getIdamId()); + assertEquals(caseWorkerProfile.getFirstName(), upResponse.getFirstName()); + assertEquals(caseWorkerProfile.getLastName(), upResponse.getLastName()); + assertEquals(caseWorkerProfile.getOfficialEmail(), upResponse.getEmail()); + assertTrue(upResponse.getRoles().contains(ROLE_STAFF_ADMIN)); + assertFalse((upResponse.getRoles()).isEmpty()); + assertTrue(upResponse.getRoles().contains(CWD_USER)); + } + @Test @ToggleEnable(mapKey = UPDATE_STAFF_PROFILE, withFeature = true) @ExtendWith(FeatureToggleConditionExtension.class) diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/idam/IdamOpenIdClient.java b/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/idam/IdamOpenIdClient.java index 170f85280..7be6a7f56 100644 --- a/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/idam/IdamOpenIdClient.java +++ b/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/idam/IdamOpenIdClient.java @@ -21,6 +21,8 @@ @Slf4j public class IdamOpenIdClient extends IdamOpenId { + private static final String AUTHORIZATION_HEADER = "Authorization"; + public static String cwdStaffAdminUserToken; public IdamOpenIdClient(TestConfigProperties testConfig) { @@ -41,6 +43,35 @@ public Map getUser(String idamId) { return generatedUserResponse.getBody().as(Map.class); } + public Map getUserByUserID(String idamId) { + log.info(":::: Get an User"); + + Response generatedUserResponse = RestAssured.given().relaxedHTTPSValidation() + .baseUri(testConfig.getIdamApiUrl()) + .header(AUTHORIZATION_HEADER, "Bearer " + getOpenIdTokenByRoles(List.of(ROLE_STAFF_ADMIN))) + .get("/api/v1/users/" + idamId) + .andReturn(); + if (generatedUserResponse.getStatusCode() == 404) { + log.info("SIDAM getUser response 404"); + } + return generatedUserResponse.getBody().as(Map.class); + } + + public void deleteRoleByUserIdNRoleName(String idamId, String roleName) { + log.info(":::: Delete a role By UserId and RoleName"); + + Response generatedUserResponse = RestAssured.given().relaxedHTTPSValidation() + .baseUri(testConfig.getIdamApiUrl()) + .header(AUTHORIZATION_HEADER, "Bearer " + getOpenIdTokenByRoles(List.of(ROLE_STAFF_ADMIN))) + .delete("/api/v1/users/" + idamId + "/roles/" + roleName) + .andReturn(); + if (generatedUserResponse.getStatusCode() == 404) { + log.info("SIDAM getUser response 404"); + } + + } + + public String getOpenIdTokenByRole(String role) { if (StringUtils.isNotEmpty(role)) { if (ROLE_CWD_ADMIN.equals(role)) { diff --git a/src/integrationTest/java/uk/gov/hmcts/reform/cwrdapi/UpdateStaffReferenceProfileTest.java b/src/integrationTest/java/uk/gov/hmcts/reform/cwrdapi/UpdateStaffReferenceProfileTest.java index dbf9a6a87..60726f33f 100644 --- a/src/integrationTest/java/uk/gov/hmcts/reform/cwrdapi/UpdateStaffReferenceProfileTest.java +++ b/src/integrationTest/java/uk/gov/hmcts/reform/cwrdapi/UpdateStaffReferenceProfileTest.java @@ -18,6 +18,7 @@ import uk.gov.hmcts.reform.cwrdapi.controllers.request.StaffProfileCreationRequest; import uk.gov.hmcts.reform.cwrdapi.controllers.request.StaffProfileRoleRequest; import uk.gov.hmcts.reform.cwrdapi.controllers.response.SearchStaffUserResponse; +import uk.gov.hmcts.reform.cwrdapi.domain.CaseWorkerProfile; import uk.gov.hmcts.reform.cwrdapi.domain.StaffAudit; import uk.gov.hmcts.reform.cwrdapi.repository.CaseWorkerLocationRepository; import uk.gov.hmcts.reform.cwrdapi.repository.CaseWorkerProfileRepository; @@ -360,6 +361,35 @@ void should_return_update_staff_user_with_status_code_400_duplicate_roles() thro } + @Test + void should_return_update_staff_user_with_status_code_200_del_roles() throws Exception { + StaffProfileCreationRequest request = caseWorkerReferenceDataClient.createStaffProfileCreationRequest(); + request.setFirstName("prashanth"); + request.setLastName("rao"); + userProfilePostUserWireMockForStaffProfile(HttpStatus.CREATED); + userProfileGetUserWireMock("ACTIVE", "[\"Senior Legal Caseworker\"]"); + + Map createResponse = caseworkerReferenceDataClient.createStaffProfile(request,ROLE_STAFF_ADMIN); + + request.setStaffAdmin(false); + + modifyUserRoles(); + Map resendResponse = caseworkerReferenceDataClient.updateStaffProfile(request,ROLE_STAFF_ADMIN); + Map createBody = (Map)createResponse.get("body"); + + assertThat(resendResponse).isNotNull(); + assertThat(resendResponse.get("http_status")).isEqualTo("200 OK"); + Map resendResponseBody = (Map) resendResponse.get("body"); + assertEquals(createBody.get("case_worker_id"), resendResponseBody.get("case_worker_id")); + + List caseWorkerProfiles = caseWorkerProfileRepository.findAll(); + assertThat(caseWorkerProfiles.size()).isEqualTo(1); + assertThat(caseWorkerProfiles.get(0).getUserAdmin()).isFalse(); + + } + + + @Test void should_return_update_staff_user_with_status_code_400_invalid_roles() throws Exception { diff --git a/src/main/java/uk/gov/hmcts/reform/cwrdapi/client/domain/RoleDeletionResponse.java b/src/main/java/uk/gov/hmcts/reform/cwrdapi/client/domain/RoleDeletionResponse.java new file mode 100644 index 000000000..c1f5f70a0 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/cwrdapi/client/domain/RoleDeletionResponse.java @@ -0,0 +1,16 @@ +package uk.gov.hmcts.reform.cwrdapi.client.domain; + + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +public class RoleDeletionResponse { + private String roleName; + private String idamStatusCode; + private String idamMessage; + +} diff --git a/src/main/java/uk/gov/hmcts/reform/cwrdapi/client/domain/UserProfileRolesResponse.java b/src/main/java/uk/gov/hmcts/reform/cwrdapi/client/domain/UserProfileRolesResponse.java index 5a58f1a89..87a94f337 100644 --- a/src/main/java/uk/gov/hmcts/reform/cwrdapi/client/domain/UserProfileRolesResponse.java +++ b/src/main/java/uk/gov/hmcts/reform/cwrdapi/client/domain/UserProfileRolesResponse.java @@ -6,6 +6,8 @@ import lombok.NoArgsConstructor; import lombok.Setter; +import java.util.List; + @Getter @Setter @NoArgsConstructor @@ -13,6 +15,7 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class UserProfileRolesResponse { private RoleAdditionResponse roleAdditionResponse; + private List roleDeletionResponse; @JsonProperty("statusUpdateResponse") private AttributeResponse attributeResponse; } \ No newline at end of file diff --git a/src/main/java/uk/gov/hmcts/reform/cwrdapi/domain/UserProfileUpdatedData.java b/src/main/java/uk/gov/hmcts/reform/cwrdapi/domain/UserProfileUpdatedData.java index 1c15f1eb7..fd6e885b0 100644 --- a/src/main/java/uk/gov/hmcts/reform/cwrdapi/domain/UserProfileUpdatedData.java +++ b/src/main/java/uk/gov/hmcts/reform/cwrdapi/domain/UserProfileUpdatedData.java @@ -10,7 +10,7 @@ public class UserProfileUpdatedData { private String idamStatus; - + private Set rolesDelete; private Set rolesAdd; private String firstName; private String lastName; diff --git a/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImpl.java b/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImpl.java index a7d42407b..08bf093ef 100644 --- a/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImpl.java +++ b/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImpl.java @@ -959,7 +959,7 @@ && nonNull(profileResponse.getIdamStatus())) { } var hasNameChanged = !cwrProfileRequest.getFirstName().equals(userProfileResponse.getFirstName()) || !cwrProfileRequest.getLastName().equals(userProfileResponse.getLastName()); - if (isNotEmpty(mergedRoles) || hasNameChanged) { + if (isNotEmpty(mergedRoles) || hasNameChanged || !cwrProfileRequest.isStaffAdmin()) { return updateMismatchedDatatoUP(cwrProfileRequest, idamId, mergedRoles, hasNameChanged); } @@ -976,6 +976,10 @@ private boolean updateMismatchedDatatoUP(StaffProfileCreationRequest cwrProfileR .rolesAdd(mergedRoles); } + if (!cwrProfileRequest.isStaffAdmin()) { + builder.rolesDelete(Set.of(new RoleName(ROLE_STAFF_ADMIN))); + } + if (hasNameChanged) { builder @@ -996,7 +1000,8 @@ public boolean isEachRoleUpdated(UserProfileUpdatedData userProfileUpdatedData, if (resultResponse.isPresent() && resultResponse.get() instanceof UserProfileRolesResponse userProfileRolesResponse) { if (nonNull(userProfileRolesResponse.getRoleAdditionResponse()) - || nonNull(userProfileRolesResponse.getAttributeResponse())) { + || nonNull(userProfileRolesResponse.getAttributeResponse()) + || nonNull(userProfileRolesResponse.getRoleDeletionResponse())) { isEachRoleUpdated = isRecordupdatedinUP(userProfileRolesResponse); } else { diff --git a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataUpdateStaffServiceImplTest.java b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataUpdateStaffServiceImplTest.java index 7a161af81..a2ff86b07 100644 --- a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataUpdateStaffServiceImplTest.java +++ b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataUpdateStaffServiceImplTest.java @@ -17,6 +17,7 @@ import uk.gov.hmcts.reform.cwrdapi.client.domain.AttributeResponse; import uk.gov.hmcts.reform.cwrdapi.client.domain.Role; import uk.gov.hmcts.reform.cwrdapi.client.domain.RoleAdditionResponse; +import uk.gov.hmcts.reform.cwrdapi.client.domain.RoleDeletionResponse; import uk.gov.hmcts.reform.cwrdapi.client.domain.UserProfileResponse; import uk.gov.hmcts.reform.cwrdapi.client.domain.UserProfileRolesResponse; import uk.gov.hmcts.reform.cwrdapi.controllers.advice.InvalidRequestException; @@ -996,6 +997,57 @@ void test_updateUserRolesInIdamDataMistmatch() throws JsonProcessingException { } + @Test + void test_updateUserRolesInIdam_with_StaffAdminRoleDelete_Idam_Status_Active() throws JsonProcessingException { + + UserProfileResponse userProfileResponse = new UserProfileResponse(); + userProfileResponse.setIdamId("12345678"); + List roles = Arrays.asList("IdamRole1", "IdamRole4"); + userProfileResponse.setIdamStatus(STATUS_ACTIVE); + + userProfileResponse.setRoles(roles); + userProfileResponse.setFirstName("testFN"); + userProfileResponse.setLastName("testLN"); + + when(userProfileFeignClient.getUserProfileWithRolesById(any())) + .thenReturn(Response.builder() + .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), + null)).body(mapper.writeValueAsString(userProfileResponse), + defaultCharset()) + .status(200).build()); + + UserProfileCreationResponse userProfileCreationResponse = new UserProfileCreationResponse(); + userProfileCreationResponse.setIdamId("12345678"); + userProfileCreationResponse.setIdamRegistrationResponse(1); + + UserProfileRolesResponse userProfileRolesResponse = new UserProfileRolesResponse(); + userProfileCreationResponse.setIdamId("12345678"); + RoleDeletionResponse roleDeletionResponse = new RoleDeletionResponse(); + roleDeletionResponse.setIdamStatusCode("201"); + userProfileRolesResponse.setRoleDeletionResponse(List.of(roleDeletionResponse)); + roleDeletionResponse.setIdamMessage("success"); + + when(userProfileFeignClient.modifyUserRoles(any(), any(), any())) + .thenReturn(Response.builder() + .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), + null)).body(mapper.writeValueAsString(userProfileRolesResponse), + defaultCharset()) + .status(200).build()); + + StaffProfileCreationRequest cwUiRequest = getStaffProfileUpdateRequest(); + cwUiRequest.setStaffAdmin(false); + + staffProfileAuditService.saveStaffAudit(AuditStatus.FAILURE,IDAM_STATUS, + StringUtils.EMPTY,cwUiRequest,STAFF_PROFILE_UPDATE); + + + boolean updateUserRolesInIdam = staffRefDataServiceImpl + .updateUserRolesInIdam(cwUiRequest,caseWorkerProfile.getCaseWorkerId(),STAFF_PROFILE_UPDATE); + assertThat(updateUserRolesInIdam).isTrue(); + } + + + @Test void test_check_staff_profile_for_update() throws JsonProcessingException { From 56957c3036d21bfb684cdc60e5baa99ecd940db2 Mon Sep 17 00:00:00 2001 From: manukundloo-hmcts Date: Wed, 17 May 2023 13:41:56 +0100 Subject: [PATCH 10/67] [RDCC-6750]Staff UI : Add new Skills for Family Private Law --- .../resources/db/migration/V1_22__update_skill_table.sql | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/main/resources/db/migration/V1_22__update_skill_table.sql diff --git a/src/main/resources/db/migration/V1_22__update_skill_table.sql b/src/main/resources/db/migration/V1_22__update_skill_table.sql new file mode 100644 index 000000000..95460472c --- /dev/null +++ b/src/main/resources/db/migration/V1_22__update_skill_table.sql @@ -0,0 +1,5 @@ +INSERT INTO SKILL(skill_id,skill_code,description,service_id,user_type,created_date,last_update) VALUES + (39,'SKILL:ABA5:GATEKEEPINGC100','Gatekeeping C100','ABA5','Legal Office',timezone('utc', now()),timezone('utc', now())); + +INSERT INTO SKILL(skill_id,skill_code,description,service_id,user_type,created_date,last_update) VALUES + (40,'SKILL:ABA5:GATEKEEPINGFL401','Gatekeeping FL401','ABA5','Legal Office',timezone('utc', now()),timezone('utc', now())); \ No newline at end of file From b8e429df46552638bd66b4582f1f91c6357177a6 Mon Sep 17 00:00:00 2001 From: manukundloo-hmcts Date: Wed, 17 May 2023 15:35:51 +0100 Subject: [PATCH 11/67] [RDCC-6200] Fix for delete functional test user --- Jenkinsfile_nightly | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile_nightly b/Jenkinsfile_nightly index 1706756ab..95ad452e9 100644 --- a/Jenkinsfile_nightly +++ b/Jenkinsfile_nightly @@ -14,7 +14,7 @@ properties([ def type = "java" def product = "rd" -def component = "caseworker-ref-api.jar" +def component = "caseworker-ref-api" def secrets = [ 's2s-${env}': [ @@ -24,6 +24,9 @@ def secrets = [ secret('CA-REF-OAUTH2-CLIENT-SECRET', 'CA_REF_OAUTH2_CLIENT_SECRET'), secret('CA-REF-OAUTH2-CLIENT-ID', 'CA_REF_OAUTH2_CLIENT_ID'), secret('LD-SDK-KEY', 'LD_SDK_KEY'), + secret('OAUTH2-CLIENT-SECRET', 'OAUTH2_CLIENT_SECRET'), + secret('OAUTH2-CLIENT-AUTH', 'OAUTH2_CLIENT_AUTH'), + secret('OAUTH2-CLIENT-ID', 'OAUTH2_CLIENT_ID'), secret('caseworker-ref-api-POSTGRES-HOST', 'FUNC_DATABASE_HOST'), secret('caseworker-ref-api-POSTGRES-PASS', 'FUNC_DATABASE_PASS'), secret('caseworker-ref-api-POSTGRES-USER', 'FUNC_DATABASE_USER'), @@ -55,6 +58,7 @@ config.vaultSecrets = secrets withNightlyPipeline(type, product, component) { + env.TEST_URL = params.URL_TO_TEST env.S2S_URL_FOR_TESTS = "http://rpe-service-auth-provider-aat.service.core-compute-aat.internal" env.IDAM_URL = "https://idam-api.aat.platform.hmcts.net" env.TEST_URL = "http://rd-caseworker-ref-api-aat.service.core-compute-aat.internal" From 99c51d6dfabf9348692d175ce7bf06079df91dbd Mon Sep 17 00:00:00 2001 From: manukundloo-hmcts Date: Thu, 18 May 2023 11:55:35 +0100 Subject: [PATCH 12/67] [RDCC-6200] Fix for delete functional test user --- Jenkinsfile_nightly | 13 ------------- .../resources/application-functional.yaml | 9 +++++++++ 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/Jenkinsfile_nightly b/Jenkinsfile_nightly index 95ad452e9..a065b29fe 100644 --- a/Jenkinsfile_nightly +++ b/Jenkinsfile_nightly @@ -105,19 +105,6 @@ withNightlyPipeline(type, product, component) { reportName : "Mutation Test Report" ] } - - afterSuccess('fullFunctionalTest') { - steps.archiveArtifacts allowEmptyArchive: true, artifacts: '**/site/serenity/**/*' - steps.archiveArtifacts allowEmptyArchive: true, artifacts: 'build/reports/**/*' - publishHTML target: [ - allowMissing : true, - alwaysLinkToLastBuild: true, - keepAll : true, - reportDir : "target/site/serenity/", - reportFiles : "index.html", - reportName : "Functional Tests Report" - ] - } } def deleteFunctionalDbData() { diff --git a/src/functionalTest/resources/application-functional.yaml b/src/functionalTest/resources/application-functional.yaml index d819469e1..22f64b45f 100644 --- a/src/functionalTest/resources/application-functional.yaml +++ b/src/functionalTest/resources/application-functional.yaml @@ -12,3 +12,12 @@ idam.auth.redirectUrl: ${OAUTH2_REDIRECT_URI:https://rd-caseworker-ref-api-aat.s s2s-secret: ${CASEWORKER_REF_API_S2S_SECRET:} userProfUrl: http://rd-user-profile-api-aat.service.core-compute-aat.internal locationRefDataUrl: http://rd-location-ref-api-aat.service.core-compute-aat.internal + + +####### +postgres.host_name: ${FUNC_DATABASE_HOST} +postgres.name: ${FUNC_DATABASE_NAME} +postgres.user: ${FUNC_DATABASE_USER} +postgres.password: ${FUNC_DATABASE_PASS} +postgres.port: ${FUNC_DATABASE_PORT} +####### \ No newline at end of file From 4f54c901f16c6a9d3e3ef2e4e50634c921e36d5b Mon Sep 17 00:00:00 2001 From: manukundloo-hmcts Date: Thu, 18 May 2023 14:49:35 +0100 Subject: [PATCH 13/67] [RDCC-6200] Sql query to delete functional test users from diff tables --- .../sql/delete-functional-test-data.sql | 44 +++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/deletescript/sql/delete-functional-test-data.sql b/deletescript/sql/delete-functional-test-data.sql index 278d3a750..df2830e37 100644 --- a/deletescript/sql/delete-functional-test-data.sql +++ b/deletescript/sql/delete-functional-test-data.sql @@ -1,5 +1,43 @@ + +delete from case_worker_location +where case_worker_id in ( + select cwp.case_worker_id from case_worker_profile cwp + where cwp.email_id like (values('cwr-rd-func-test-user-only-%')) + ); + +delete from case_worker_work_area +where case_worker_id in ( + select cwp.case_worker_id from case_worker_profile cwp + where cwp.email_id like (values('cwr-rd-func-test-user-only-%')) + ); + +delete from case_worker_role +where case_worker_id in ( + select cwp.case_worker_id from case_worker_profile cwp + where cwp.email_id like (values('cwr-rd-func-test-user-only-%')) +); + + +delete from case_worker_location +where case_worker_id in ( + select cwp.case_worker_id from case_worker_profile cwp + where cwp.email_id like (values('staff-rd-profile-func-test-user-only-%')) + ); + +delete from case_worker_work_area +where case_worker_id in ( + select cwp.case_worker_id from case_worker_profile cwp + where cwp.email_id like (values('staff-rd-profile-func-test-user-only-%')) + ); + +delete from case_worker_role +where case_worker_id in ( + select cwp.case_worker_id from case_worker_profile cwp + where cwp.email_id like (values('staff-rd-profile-func-test-user-only-%')) +); + delete from case_worker_profile cwp where - email_id like any (values('cwr-rd-func-test-user-only%')) - or email_id like any(values('staff-rd-profile-func-test-user-only-%')) ; + email_id like (values('cwr-rd-func-test-user-only-%')) + or email_id like (values('staff-rd-profile-func-test-user-only-%')) ; -commit; \ No newline at end of file +commit; From 5334471fcbb3ea9401595c9421ece6370a0f347c Mon Sep 17 00:00:00 2001 From: manukundloo-hmcts Date: Thu, 18 May 2023 18:23:17 +0100 Subject: [PATCH 14/67] [RDCC-6200] Sql query to delete functional test users from skill table --- deletescript/sql/delete-functional-test-data.sql | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/deletescript/sql/delete-functional-test-data.sql b/deletescript/sql/delete-functional-test-data.sql index df2830e37..8cc992afe 100644 --- a/deletescript/sql/delete-functional-test-data.sql +++ b/deletescript/sql/delete-functional-test-data.sql @@ -18,6 +18,12 @@ where case_worker_id in ( ); +delete from case_worker_skill +where case_worker_id in ( + select cwp.case_worker_id from case_worker_profile cwp + where cwp.email_id like (values('cwr-rd-func-test-user-only-%')) +); + delete from case_worker_location where case_worker_id in ( select cwp.case_worker_id from case_worker_profile cwp @@ -36,6 +42,13 @@ where case_worker_id in ( where cwp.email_id like (values('staff-rd-profile-func-test-user-only-%')) ); + +delete from case_worker_skill +where case_worker_id in ( + select cwp.case_worker_id from case_worker_profile cwp + where cwp.email_id like (values('staff-rd-profile-func-test-user-only-%')) +); + delete from case_worker_profile cwp where email_id like (values('cwr-rd-func-test-user-only-%')) or email_id like (values('staff-rd-profile-func-test-user-only-%')) ; From 991ba160cb432529b31f9d55be97c631fa267a91 Mon Sep 17 00:00:00 2001 From: arshinsalim <97666178+arshinsalim@users.noreply.github.com> Date: Thu, 25 May 2023 09:57:11 +0100 Subject: [PATCH 15/67] paramterized tests (#725) --- .../CaseWorkerProfileConverterTest.java | 30 ++++++---------- .../impl/CaseWorkerDeleteServiceImplTest.java | 34 ++++++------------ .../impl/StaffRefDataServiceImplTest.java | 35 ++++++++----------- 3 files changed, 37 insertions(+), 62 deletions(-) diff --git a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/CaseWorkerProfileConverterTest.java b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/CaseWorkerProfileConverterTest.java index 94616b293..e8af42616 100644 --- a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/CaseWorkerProfileConverterTest.java +++ b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/CaseWorkerProfileConverterTest.java @@ -1,6 +1,8 @@ package uk.gov.hmcts.reform.cwrdapi.service; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import uk.gov.hmcts.reform.cwrdapi.TestSupport; import uk.gov.hmcts.reform.cwrdapi.client.domain.CaseWorkerDomain; import uk.gov.hmcts.reform.cwrdapi.client.domain.CaseWorkerProfile; @@ -104,23 +106,9 @@ void testGetSuspendedRowIds() { assertThat(suspendedRowIds.get(0)).isEqualTo(1L); } - @Test - void testIsNotSuspended() { - CaseWorkerProfile caseWorkerProfile = CaseWorkerProfile.builder() - .firstName("test").lastName("test") - .officialEmail("email@gov.justice.uk") - .regionId(1) - .regionName("test") - .userType("testUser") - .idamRoles("role1, role2") - .suspended("N") - .build(); - - assertFalse(caseWorkerProfileConverter.isSuspended(caseWorkerProfile)); - } - - @Test - void testIsSuspended() { + @ParameterizedTest + @ValueSource(strings = {"N", "Y"}) + void testSuspended(String suspendFlag) { CaseWorkerProfile caseWorkerProfile = CaseWorkerProfile.builder() .firstName("test").lastName("test") .officialEmail("email@gov.justice.uk") @@ -128,10 +116,14 @@ void testIsSuspended() { .regionName("test") .userType("testUser") .idamRoles("role1, role2") - .suspended("Y") + .suspended(suspendFlag) .build(); + if ("N".equals(suspendFlag)) { + assertFalse(caseWorkerProfileConverter.isSuspended(caseWorkerProfile)); + } else { + assertTrue(caseWorkerProfileConverter.isSuspended(caseWorkerProfile)); + } - assertTrue(caseWorkerProfileConverter.isSuspended(caseWorkerProfile)); } } \ No newline at end of file diff --git a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerDeleteServiceImplTest.java b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerDeleteServiceImplTest.java index 385739189..aa76def6e 100644 --- a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerDeleteServiceImplTest.java +++ b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerDeleteServiceImplTest.java @@ -4,6 +4,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -192,38 +194,24 @@ void testDeleteUserProfileByEmailPattern_WhenUpReturns404() { verify(caseWorkerProfileRepository, times(1)).deleteAll(any()); } - @Test - @SuppressWarnings("unchecked") - void testValidateUserAfterUpDeleteWhenStatusIs204() { - Optional userProfile = mock(Optional.class); - String userId = UUID.randomUUID().toString(); - - when(userProfile.isPresent()).thenReturn(false); - - CaseWorkerProfilesDeletionResponse deletionResponse = - caseWorkerDeleteServiceImpl.validateUserAfterUpDelete(userProfile, userId, 204); - - assertThat(deletionResponse.getStatusCode()).isEqualTo(204); - assertThat(deletionResponse.getMessage()) - .isEqualTo("User deleted in UP but was not present in CRD with userId: " + userId); - - verify(userProfile, times(1)).isPresent(); - } - - @Test + @ParameterizedTest + @CsvSource({ + "204,User deleted in UP but was not present in CRD with userId", + "404,User was not present in UP or CRD with userId" + }) @SuppressWarnings("unchecked") - void testValidateUserAfterUpDeleteWhenStatusIsNot204() { + void testValidateUserAfterUpDeleteWhenStatusIs204(int status,String description) { Optional userProfile = mock(Optional.class); String userId = UUID.randomUUID().toString(); when(userProfile.isPresent()).thenReturn(false); CaseWorkerProfilesDeletionResponse deletionResponse = - caseWorkerDeleteServiceImpl.validateUserAfterUpDelete(userProfile, userId, 404); + caseWorkerDeleteServiceImpl.validateUserAfterUpDelete(userProfile, userId, status); - assertThat(deletionResponse.getStatusCode()).isEqualTo(404); + assertThat(deletionResponse.getStatusCode()).isEqualTo(status); assertThat(deletionResponse.getMessage()) - .isEqualTo("User was not present in UP or CRD with userId: " + userId); + .isEqualTo(description + ": " + userId); verify(userProfile, times(1)).isPresent(); } diff --git a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImplTest.java b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImplTest.java index 60c98a5a4..225db40aa 100644 --- a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImplTest.java +++ b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImplTest.java @@ -10,6 +10,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.EmptySource; import org.junit.jupiter.params.provider.NullSource; import org.mockito.InjectMocks; @@ -993,34 +994,28 @@ void test_createUserProfileInIdamUP() throws JsonProcessingException { assertNotNull(response); } - @Test - void test_createUserProfileInIdamUP_error() throws JsonProcessingException { - ErrorResponse errorResponse = new ErrorResponse(500, "Failure", "Method Not Allowed ", - "Internal Server Error", "2022-01-10"); + @ParameterizedTest + @CsvSource({ + "500,Internal Server Error", + "405,Method Not Allowed" + }) + void test_createUserProfileInIdamUP_error(int errorCode,String errorDescription) throws JsonProcessingException { + ErrorResponse errorResponse = new ErrorResponse(errorCode, "Failure", "Method Not Allowed ", + errorDescription, "2022-01-10"); String body = mapper.writeValueAsString(errorResponse); doReturn(Response.builder() - .request(mock(Request.class)).body(body, defaultCharset()).status(500).build()) + .request(mock(Request.class)).body(body, defaultCharset()).status(errorCode).build()) .when(userProfileFeignClient).createUserProfile(any(UserProfileCreationRequest.class), anyString()); StaffReferenceException thrown = Assertions.assertThrows(StaffReferenceException.class, () -> { staffRefDataServiceImpl.createUserProfileInIdamUP(staffProfileCreationRequest); }); - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, thrown.getStatus()); - } - - @Test - void test_createUserProfileInIdamUP_forbiddenError() throws JsonProcessingException { - ErrorResponse errorResponse = new ErrorResponse(405, "Failure", "Method Not Allowed ", - "Method Not Allowed", "2022-01-10"); - String body = mapper.writeValueAsString(errorResponse); + if (errorCode == 500) { + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, thrown.getStatus()); + } else if (errorCode == 405) { + assertEquals(HttpStatus.METHOD_NOT_ALLOWED, thrown.getStatus()); + } - doReturn(Response.builder() - .request(mock(Request.class)).body(body, defaultCharset()).status(405).build()) - .when(userProfileFeignClient).createUserProfile(any(UserProfileCreationRequest.class), anyString()); - StaffReferenceException thrown = Assertions.assertThrows(StaffReferenceException.class, () -> { - staffRefDataServiceImpl.createUserProfileInIdamUP(staffProfileCreationRequest); - }); - assertEquals(HttpStatus.METHOD_NOT_ALLOWED, thrown.getStatus()); } From 222ece0c58d4fa81698acbf3d63968264b09d21b Mon Sep 17 00:00:00 2001 From: mokainos Date: Thu, 25 May 2023 23:47:12 +0100 Subject: [PATCH 16/67] DTSPO-13810 reschedule nightlybuilds to start at 8am --- Jenkinsfile_nightly | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile_nightly b/Jenkinsfile_nightly index a065b29fe..55fde956d 100644 --- a/Jenkinsfile_nightly +++ b/Jenkinsfile_nightly @@ -1,6 +1,6 @@ #!groovy properties([ - pipelineTriggers([cron('21 01 * * *')]), // scheduling to trigger jenkins job + pipelineTriggers([cron('21 01 * * 1-5')]), // scheduling to trigger jenkins job parameters([ string(name: 'ENVIRONMENT', defaultValue: 'aat', description: 'Environment to test'), string(name: 'URL_TO_TEST', defaultValue: 'http://rd-caseworker-ref-api-aat.service.core-compute-aat.internal', description: 'The URL you want to run these tests against'), From 9ef40e6eb59b745a8166c006639236f71dcca91c Mon Sep 17 00:00:00 2001 From: sahitya-desireddy <93707379+sahitya-desireddy@users.noreply.github.com> Date: Wed, 31 May 2023 15:57:06 +0100 Subject: [PATCH 17/67] Update users regardless of active status (#721) * Update users regardless of active status * Update users regardless of active status * Unit Test cases and sonar fixes * Implementation changes and fix unit test cases * Implementation changes for caseworker template * functional test for srd * functional test for srd * checkstyle fix * revert template changes * FUNCTIONAL test cases * FUNCTIONAL test cases * Pointing PR to User Profile PR * changing to https * pointing to up PR * throwing error message when user is in pending state * throwing error message when user is in suspend state and tried to unsuspend * Pointing Back from UP PR to UP AAT ... * upgrade version to 1.0.0 chart * Support Version 1.0.0 chart * Support Version 1.0.0 chart * Support Version 1.0.0 chart * Support Version 1.0.0 chart * Support Version 1.0.0 chart * Support Version 1.0.0 chart * Support Version 1.0.0 chart * Update values.preview.template.yaml * Update charts/rd-caseworker-ref-api/values.preview.template.yaml --------- Co-authored-by: Praveen Adusumilli <47391951+adusumillipraveen@users.noreply.github.com> --- charts/rd-caseworker-ref-api/Chart.yaml | 6 +- .../values.preview.template.yaml | 15 +- config/owasp/suppressions.xml | 4 + .../StaffReferenceDataProviderTest.java | 2 +- .../StaffRefUpdateProfileFunctionalTest.java | 66 ++++-- .../feign/UserProfileFeignClient.java | 2 +- .../service/impl/CaseWorkerServiceImpl.java | 4 +- .../service/impl/StaffRefDataServiceImpl.java | 84 +++----- .../impl/CaseWorkerServiceImplTest.java | 48 ++--- .../impl/StaffRefDataServiceImplTest.java | 36 +++- ...taffRefDataUpdateStaffServiceImplTest.java | 189 ++++++++++++------ 11 files changed, 276 insertions(+), 180 deletions(-) diff --git a/charts/rd-caseworker-ref-api/Chart.yaml b/charts/rd-caseworker-ref-api/Chart.yaml index 63dec8733..0001d4ea8 100644 --- a/charts/rd-caseworker-ref-api/Chart.yaml +++ b/charts/rd-caseworker-ref-api/Chart.yaml @@ -3,7 +3,7 @@ appVersion: "1.0" description: A Helm chart for rd-caseworker-ref-api name: rd-caseworker-ref-api home: https://github.com/hmcts/rd-caseworker-ref-api -version: 0.0.28 +version: 1.0.0 maintainers: - name: Reference Data Team dependencies: @@ -11,6 +11,6 @@ dependencies: version: 4.0.13 repository: 'https://hmctspublic.azurecr.io/helm/v1/repo/' - name: servicebus - version: v0.4.0 - repository: https://hmctspublic.azurecr.io/helm/v1/repo/ + version: 1.0.2 + repository: 'https://hmctspublic.azurecr.io/helm/v1/repo/' condition: servicebus.enabled diff --git a/charts/rd-caseworker-ref-api/values.preview.template.yaml b/charts/rd-caseworker-ref-api/values.preview.template.yaml index 5b4bf5ec2..39b5bb3cc 100644 --- a/charts/rd-caseworker-ref-api/values.preview.template.yaml +++ b/charts/rd-caseworker-ref-api/values.preview.template.yaml @@ -4,11 +4,8 @@ java: secrets: CWRD_MQ_PASSWORD: - secretRef: servicebus-secret-namespace-{{ .Release.Name }}-servicebus + secretRef: rd-sb-preview key: primaryKey - CWRD_MQ_HOST: - secretRef: servicebus-secret-namespace-{{ .Release.Name }}-servicebus - key: namespaceName environment: POSTGRES_HOST: "{{ .Release.Name }}-postgresql" @@ -16,10 +13,10 @@ java: POSTGRES_USERNAME: "{{ .Values.postgresql.auth.username}}" POSTGRES_PASSWORD: "{{ .Values.postgresql.auth.password}}" POSTGRES_PORT: "{{ .Values.postgresql.auth.port}}" - CWRD_MQ_HOST: "$(CWRD_MQ_HOST).servicebus.windows.net" + CWRD_MQ_HOST: "rd-sb-preview.servicebus.windows.net" CASEWORKER_TOPIC_PRIMARY_SEND_LISTEN_SHARED_ACCESS_KEY: "$(CWRD_MQ_PASSWORD)" CWRD_MQ_USERNAME: RootManageSharedAccessKey - CWRD_MQ_TOPIC_NAME: crd-aks-topic + CWRD_MQ_TOPIC_NAME: "{{ .Release.Name }}-servicebus-crd-topic" CWRD_MQ_TRUST_ALL_CERTS: false CWRD_DATA_PER_MESSAGE: 50 LAUNCH_DARKLY_ENV: "preview" @@ -45,9 +42,9 @@ java: servicebus: enabled: true teamName: RD - resourceGroup: rd-aks - serviceplan: standard + resourceGroup: rd-aso-preview-rg + sbNamespace: rd setup: topics: - - name: crd-aks-topic + - name: crd-topic subscriptionNeeded: yes diff --git a/config/owasp/suppressions.xml b/config/owasp/suppressions.xml index bed068402..053e5e55a 100644 --- a/config/owasp/suppressions.xml +++ b/config/owasp/suppressions.xml @@ -15,4 +15,8 @@ file name: launchdarkly-java-server-sdk-5.10.2.jar (shaded: org.yaml:snakeyaml:1 ^pkg:maven/org\.yaml/snakeyaml@.*$ CVE-2022-1471 + + Temporary suppression for org.apache.tomcat.embed please remove it once cve is fixed + CVE-2023-28709 + diff --git a/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/StaffReferenceDataProviderTest.java b/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/StaffReferenceDataProviderTest.java index c10678b00..90d2067df 100644 --- a/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/StaffReferenceDataProviderTest.java +++ b/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/StaffReferenceDataProviderTest.java @@ -508,7 +508,7 @@ public void updateStaffUserProfile() throws JsonProcessingException { userProfileResponse.setLastName("testLNChanged"); ObjectMapper mapper = new ObjectMapper(); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(mapper.writeValueAsString(userProfileResponse), diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/StaffRefUpdateProfileFunctionalTest.java b/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/StaffRefUpdateProfileFunctionalTest.java index ca935f7e1..ff46fd1cc 100644 --- a/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/StaffRefUpdateProfileFunctionalTest.java +++ b/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/StaffRefUpdateProfileFunctionalTest.java @@ -30,7 +30,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static uk.gov.hmcts.reform.cwrdapi.util.FeatureToggleConditionExtension.getToggledOffMessage; @@ -75,9 +74,6 @@ void should_update_staff_profile_and_returns_status_200() { } - - - @Test @ExtendWith(FeatureToggleConditionExtension.class) @ToggleEnable(mapKey = UPDATE_STAFF_PROFILE, withFeature = false) @@ -297,18 +293,18 @@ void updateStaffProfileDifferentThanUserPresentInUserProfileAndIdamAndFlags() th // validate SRD user and IDM user are same var idamResponse = getIdamResponse(cwId); assertEquals(caseWorkerProfile.getId(), idamResponse.get("id")); - assertNotEquals(caseWorkerProfile.getFirstName(), idamResponse.get("forename")); - assertNotEquals(caseWorkerProfile.getLastName(), idamResponse.get("surname")); + assertEquals(caseWorkerProfile.getFirstName(), idamResponse.get("forename")); + assertEquals(caseWorkerProfile.getLastName(), idamResponse.get("surname")); assertEquals(caseWorkerProfile.getOfficialEmail(), idamResponse.get("email")); // validate SRD user and UserProfile are not same UserProfileResponse upResponse = getUserProfileFromUp(caseWorkerProfile.getOfficialEmail()); assertEquals(caseWorkerProfile.getId(), upResponse.getIdamId()); - assertNotEquals(caseWorkerProfile.getFirstName(), upResponse.getFirstName()); - assertNotEquals(caseWorkerProfile.getLastName(), upResponse.getLastName()); + assertEquals(caseWorkerProfile.getFirstName(), upResponse.getFirstName()); + assertEquals(caseWorkerProfile.getLastName(), upResponse.getLastName()); assertEquals(caseWorkerProfile.getOfficialEmail(), upResponse.getEmail()); assertEquals("SUSPENDED",upResponse.getIdamStatus()); - assertEquals(caseWorkerProfile.getSuspended(),"true"); + assertEquals("true", caseWorkerProfile.getSuspended()); } @@ -522,6 +518,55 @@ void createStaffProfileDifferentThanUserPresentInIdam() throws JsonProcessingExc } + + @Test + @ToggleEnable(mapKey = UPDATE_STAFF_PROFILE, withFeature = true) + @ExtendWith(FeatureToggleConditionExtension.class) + void should_update_staff_profile_for_suspended_user_and_returns_status_200() { + + StaffProfileCreationRequest staffProfileCreationRequest = caseWorkerApiClient + .createStaffProfileCreationRequest(); + Response response = caseWorkerApiClient.createStaffUserProfile(staffProfileCreationRequest); + staffProfileCreationRequest.setSuspended(true); + + StaffProfileCreationResponse staffProfileResponse1 = response.getBody().as(StaffProfileCreationResponse.class); + assertThat(staffProfileResponse1).isNotNull(); + + assertThat(staffProfileCreationRequest.isSuspended()).isTrue(); + assertThat(staffProfileCreationRequest.getFirstName()).isEqualTo("StaffProfilefirstName"); + assertThat(staffProfileCreationRequest.getLastName()).isEqualTo("StaffProfilelastName"); + response = caseWorkerApiClient.updateStaffUserProfile(staffProfileCreationRequest); + StaffProfileCreationResponse staffProfileResponse = response.getBody().as(StaffProfileCreationResponse.class); + + // validate SRD user and IDM user are same + var idamResponse = getIdamResponse(staffProfileResponse.getCaseWorkerId()); + assertFalse((Boolean) idamResponse.get("active")); + assertEquals(staffProfileResponse.getCaseWorkerId(), idamResponse.get("id")); + assertEquals(staffProfileCreationRequest.getFirstName(), idamResponse.get("forename")); + assertEquals(staffProfileCreationRequest.getLastName(), idamResponse.get("surname")); + assertEquals(staffProfileCreationRequest.getEmailId(), idamResponse.get("email")); + + // validate SRD user and UserProfile are same + //idamOpenIdClient.getUserByUserID("cwd-admin"); + UserProfileResponse upResponse = getUserProfileFromUp(staffProfileCreationRequest.getEmailId()); + assertEquals("SUSPENDED",upResponse.getIdamStatus()); + + + String firstNameUpdated = "StaffProfilefirstNameChanged"; + String lastNameUpdated = "StaffProfilelastNameChanged"; + staffProfileCreationRequest.setFirstName(firstNameUpdated); + staffProfileCreationRequest.setLastName(lastNameUpdated); + + response = caseWorkerApiClient.updateStaffUserProfile(staffProfileCreationRequest); + staffProfileResponse = response.getBody().as(StaffProfileCreationResponse.class); + + assertThat(response).isNotNull(); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK.value()); + assertThat(staffProfileResponse).isNotNull(); + assertThat(staffProfileResponse.getCaseWorkerId()).isNotBlank(); + + } + @AfterAll public static void cleanUpTestData() { try { @@ -530,7 +575,4 @@ public static void cleanUpTestData() { log.error("cleanUpTestData :: threw the following exception: " + e); } } - - - } \ No newline at end of file diff --git a/src/main/java/uk/gov/hmcts/reform/cwrdapi/controllers/feign/UserProfileFeignClient.java b/src/main/java/uk/gov/hmcts/reform/cwrdapi/controllers/feign/UserProfileFeignClient.java index d6c570673..b81de46b6 100644 --- a/src/main/java/uk/gov/hmcts/reform/cwrdapi/controllers/feign/UserProfileFeignClient.java +++ b/src/main/java/uk/gov/hmcts/reform/cwrdapi/controllers/feign/UserProfileFeignClient.java @@ -36,7 +36,7 @@ Response modifyUserRoles(@RequestBody UserProfileUpdatedData modifyRoles, @PathV @RequestLine("GET /v1/userprofile/{id}/roles") @Headers({"Authorization: {authorization}", "ServiceAuthorization: {serviceAuthorization}", "Content-Type: application/json"}) - Response getUserProfileWithRolesById(@PathVariable String id); + Response getUserProfileWithRolesById(@PathVariable String id,@RequestParam(value = "origin") String origin); @DeleteMapping(value = "/v1/userprofile/users") @RequestLine("DELETE /v1/userprofile/users") diff --git a/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerServiceImpl.java b/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerServiceImpl.java index 8ed9bf721..b27c2948d 100644 --- a/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerServiceImpl.java +++ b/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerServiceImpl.java @@ -591,7 +591,7 @@ public CaseWorkerProfile updateUserProfile(CaseWorkersProfileCreationRequest cwr public boolean updateUserRolesInIdam(CaseWorkersProfileCreationRequest cwrProfileRequest, String idamId) { try { - Response response = userProfileFeignClient.getUserProfileWithRolesById(idamId); + Response response = userProfileFeignClient.getUserProfileWithRolesById(idamId,null); ResponseEntity responseEntity = toResponseEntity(response, UserProfileResponse.class); Optional resultResponse = validateAndGetResponseEntity(responseEntity); @@ -869,8 +869,8 @@ private boolean updateMismatchedDatatoUP(CaseWorkersProfileCreationRequest cwrPr .firstName(cwrProfileRequest.getFirstName()) .lastName(cwrProfileRequest.getLastName()) .idamStatus(STATUS_ACTIVE); - } + return isEachRoleUpdated(builder.build(), idamId, "EXUI", cwrProfileRequest.getRowId()); } diff --git a/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImpl.java b/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImpl.java index 08bf093ef..6bac7ade6 100644 --- a/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImpl.java +++ b/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImpl.java @@ -90,11 +90,8 @@ import static java.util.stream.Collectors.toSet; import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; import static org.apache.commons.lang3.BooleanUtils.isNotTrue; -import static org.apache.commons.lang3.BooleanUtils.isTrue; import static org.apache.commons.lang3.ObjectUtils.isNotEmpty; -import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.ALREADY_SUSPENDED_ERROR_MESSAGE; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.CASE_ALLOCATOR; -import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.IDAM_STATUS_NOT_ACTIVE; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.IDAM_STATUS_SUSPENDED; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.IDAM_STATUS_USER_PROFILE; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.NO_USER_TO_SUSPEND_PROFILE; @@ -108,13 +105,12 @@ import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.STAFF_ADMIN; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.STAFF_PROFILE_CREATE; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.STAFF_PROFILE_UPDATE; -import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.STATUS_ACTIVE; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.TASK_SUPERVISOR; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.UP_FAILURE_ROLES; +import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.UP_STATUS_PENDING; import static uk.gov.hmcts.reform.cwrdapi.util.JsonFeignResponseUtil.toResponseEntity; import static uk.gov.hmcts.reform.cwrdapi.util.RequestUtils.convertToList; import static uk.gov.hmcts.reform.cwrdapi.util.RequestUtils.getAsIntegerList; -import static uk.gov.hmcts.reform.cwrdapi.util.RequestUtils.validateServiceCode; /** * The type Staff ref data service. @@ -716,33 +712,24 @@ private CaseWorkerProfile validateStaffProfileForUpdate(StaffProfileCreationRequ throw new StaffReferenceException(HttpStatus.NOT_FOUND,PROFILE_NOT_PRESENT_IN_SRD, PROFILE_NOT_PRESENT_IN_SRD); } - UserProfileResponse userProfileResponse = getUserProfileFromUP(caseWorkerProfile.getCaseWorkerId()); - - if (nonNull(userProfileResponse) && isNotTrue(STATUS_ACTIVE.equals(userProfileResponse.getIdamStatus()))) { - - throw new StaffReferenceException(HttpStatus.BAD_REQUEST, IDAM_STATUS_NOT_ACTIVE, - IDAM_STATUS_NOT_ACTIVE); - } else if (userProfileResponse == null) { + if (userProfileResponse == null) { staffProfileAuditService.saveStaffAudit(AuditStatus.FAILURE, PROFILE_NOT_PRESENT_IN_UP_OR_IDAM, StringUtils.EMPTY, profileRequest, STAFF_PROFILE_UPDATE); throw new StaffReferenceException(HttpStatus.NOT_FOUND, PROFILE_NOT_PRESENT_IN_UP_OR_IDAM, PROFILE_NOT_PRESENT_IN_UP_OR_IDAM); - } - - if (isTrue(caseWorkerProfile.getSuspended())) { - //when existing profile with delete flag is true then log exception add entry in exception table - staffProfileAuditService.saveStaffAudit(AuditStatus.FAILURE, ALREADY_SUSPENDED_ERROR_MESSAGE, - caseWorkerProfile.getCaseWorkerId(), profileRequest, STAFF_PROFILE_UPDATE); - throw new StaffReferenceException(HttpStatus.BAD_REQUEST, ALREADY_SUSPENDED_ERROR_MESSAGE, - ALREADY_SUSPENDED_ERROR_MESSAGE); + } else if (UP_STATUS_PENDING.equals(userProfileResponse.getIdamStatus())) { + throw new StaffReferenceException(HttpStatus.BAD_REQUEST, UP_FAILURE_ROLES, + UP_FAILURE_ROLES); + } else if (IDAM_STATUS_SUSPENDED.equals(userProfileResponse.getIdamStatus()) && !profileRequest.isSuspended()) { + throw new StaffReferenceException(HttpStatus.BAD_REQUEST, UP_FAILURE_ROLES, + UP_FAILURE_ROLES); } return caseWorkerProfile; - } public UserProfileResponse getUserProfileFromUP(String idamId) { - Response response = userProfileFeignClient.getUserProfileWithRolesById(idamId); + Response response = userProfileFeignClient.getUserProfileWithRolesById(idamId, "SRD"); ResponseEntity responseEntity = toResponseEntity(response, UserProfileResponse.class); @@ -796,17 +783,13 @@ public CaseWorkerProfile processExistingCaseWorkers( if (isUserSuspended(UserProfileUpdatedData.builder().idamStatus(IDAM_STATUS_SUSPENDED).build(), caseWorkerProfiles.getCaseWorkerId(), ORIGIN_EXUI)) { caseWorkerProfiles.setSuspended(true); - filteredProfile = caseWorkerProfiles; - return filteredProfile; } - } else { - //when existing profile with delete flag is false then update user in CRD db and roles in SIDAM - filteredProfile = updateSidamRoles(caseWorkerProfiles, cwUiRequest); - return filteredProfile; } - //add user roles in user profile and filter out UP failed records + filteredProfile = updateSidamRoles(caseWorkerProfiles,cwUiRequest); + return filteredProfile; - return null; + //when existing profile with delete flag is false then update user in CRD db and roles in SIDAM + //add user roles in user profile and filter out UP failed records } @@ -905,29 +888,17 @@ public CaseWorkerProfile updateSidamRoles(CaseWorkerProfile updateCaseWorkerProf return filteredUpdateCwProfile; } - public boolean updateUserRolesInIdam(StaffProfileCreationRequest cwrProfileRequest, String idamId, + public boolean updateUserRolesInIdam(StaffProfileCreationRequest cwrProfileRequest, String idamId, String operationType) { - Response response = userProfileFeignClient.getUserProfileWithRolesById(idamId); + Response response = userProfileFeignClient.getUserProfileWithRolesById(idamId, "SRD"); ResponseEntity responseEntity = toResponseEntity(response, UserProfileResponse.class); Optional resultResponse = validateAndGetResponseEntity(responseEntity); - if (resultResponse.isPresent() && resultResponse.get() instanceof UserProfileResponse profileResponse - && nonNull(profileResponse.getIdamStatus())) { - if (isNotTrue(profileResponse.getIdamStatus().equals(STATUS_ACTIVE))) { - - staffProfileAuditService.saveStaffAudit(AuditStatus.FAILURE, IDAM_STATUS_NOT_ACTIVE, - profileResponse.getIdamId(), cwrProfileRequest, operationType); - - log.error("{}:: updateUserRolesInIdam :: status code {}", loggingComponentName, - profileResponse.getIdamStatus()); - - throw new StaffReferenceException(HttpStatus.BAD_REQUEST, IDAM_STATUS_NOT_ACTIVE, - IDAM_STATUS_NOT_ACTIVE); - } + if (!resultResponse.isPresent() || !(resultResponse.get() instanceof UserProfileResponse profileResponse) + || !nonNull(profileResponse.getIdamStatus())) { - } else { log.error("{}:: updateUserRolesInIdam :: status code {}", loggingComponentName, UP_FAILURE_ROLES); throw new StaffReferenceException(HttpStatus.BAD_REQUEST, IDAM_STATUS_USER_PROFILE, @@ -960,7 +931,8 @@ && nonNull(profileResponse.getIdamStatus())) { var hasNameChanged = !cwrProfileRequest.getFirstName().equals(userProfileResponse.getFirstName()) || !cwrProfileRequest.getLastName().equals(userProfileResponse.getLastName()); if (isNotEmpty(mergedRoles) || hasNameChanged || !cwrProfileRequest.isStaffAdmin()) { - return updateMismatchedDatatoUP(cwrProfileRequest, idamId, mergedRoles, hasNameChanged); + return updateMismatchedDatatoUP(cwrProfileRequest, idamId, mergedRoles, hasNameChanged, + userProfileResponse.getIdamStatus()); } return true; @@ -968,12 +940,11 @@ && nonNull(profileResponse.getIdamStatus())) { private boolean updateMismatchedDatatoUP(StaffProfileCreationRequest cwrProfileRequest, String idamId, Set mergedRoles, - boolean hasNameChanged) { + boolean hasNameChanged, String idamStatus) { UserProfileUpdatedData.UserProfileUpdatedDataBuilder builder = UserProfileUpdatedData.builder(); if (isNotEmpty(mergedRoles)) { - builder - .rolesAdd(mergedRoles); + builder.rolesAdd(mergedRoles); } if (!cwrProfileRequest.isStaffAdmin()) { @@ -981,12 +952,13 @@ private boolean updateMismatchedDatatoUP(StaffProfileCreationRequest cwrProfileR } if (hasNameChanged) { - - builder - .firstName(cwrProfileRequest.getFirstName()) - .lastName(cwrProfileRequest.getLastName()) - .idamStatus(STATUS_ACTIVE); - + builder.firstName(cwrProfileRequest.getFirstName()) + .lastName(cwrProfileRequest.getLastName()); + } + if (!cwrProfileRequest.isSuspended()) { + builder.idamStatus(idamStatus); + } else { + builder.idamStatus(IDAM_STATUS_SUSPENDED); } return isEachRoleUpdated(builder.build(), idamId, "EXUI"); } diff --git a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerServiceImplTest.java b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerServiceImplTest.java index 5a6003534..8b730d807 100644 --- a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerServiceImplTest.java +++ b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerServiceImplTest.java @@ -245,7 +245,7 @@ void test_409WhileCwUserProfileCreation() throws JsonProcessingException { .request(mock(Request.class)).body(mapper.writeValueAsString(userProfileCreationResponse), defaultCharset()) .status(409).build()); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(mapper.writeValueAsString(userProfileResponse), @@ -594,7 +594,7 @@ void testUpdateRole() throws JsonProcessingException { when(caseWorkerStaticValueRepositoryAccessorImpl.getRoleTypes()).thenReturn(singletonList(roleType)); when(caseWorkerStaticValueRepositoryAccessorImpl.getUserTypes()).thenReturn(singletonList(userType)); when(caseWorkerProfileRepository.findByEmailIdIn(any())).thenReturn(profiles); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(userProfileResponseBody, defaultCharset()).status(200).build()); @@ -610,7 +610,7 @@ void testUpdateRole() throws JsonProcessingException { requests.add(cwProfileCreationRequest); caseWorkerServiceImpl.processCaseWorkerProfiles(requests); - verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any()); + verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any(),any()); verify(userProfileFeignClient, times(1)).modifyUserRoles(any(), any(), any()); verify(caseWorkerProfileRepository, times(1)).saveAll(any()); verify(caseWorkerProfileRepository, times(1)).findByEmailIdIn(any()); @@ -645,7 +645,7 @@ void updateRoleFailsForInvalidResponseFromIdam_Scenario1() throws JsonProcessing emails.add(cwProfileCreationRequest.getEmailId()); String userProfileResponseBody = mapper.writeValueAsString(userProfileResponse); when(caseWorkerProfileRepository.findByEmailIdIn(emails)).thenReturn(Arrays.asList(profile)); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(userProfileResponseBody, defaultCharset()) @@ -655,7 +655,7 @@ void updateRoleFailsForInvalidResponseFromIdam_Scenario1() throws JsonProcessing requests.add(cwProfileCreationRequest); caseWorkerServiceImpl.processCaseWorkerProfiles(requests); - verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any()); + verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any(),any()); verify(userProfileFeignClient, times(0)).modifyUserRoles(any(), any(), any()); verify(caseWorkerProfileRepository, times(0)).saveAll(any()); verify(caseWorkerProfileRepository, times(1)).findByEmailIdIn(any()); @@ -694,7 +694,7 @@ void updateRoleFailsForInvalidResponseFromIdam_Scenario2() throws JsonProcessing emails.add(cwProfileCreationRequest.getEmailId()); when(caseWorkerProfileRepository.findByEmailIdIn(emails)).thenReturn(Arrays.asList(profile)); String userProfileResponseBody = mapper.writeValueAsString(userProfileResponse); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(userProfileResponseBody, defaultCharset()) @@ -712,7 +712,7 @@ void updateRoleFailsForInvalidResponseFromIdam_Scenario2() throws JsonProcessing caseWorkerServiceImpl.processCaseWorkerProfiles(requests); verify(caseWorkerProfileRepository, times(1)).findByEmailIdIn(any()); - verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any()); + verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any(),any()); verify(userProfileFeignClient, times(1)).modifyUserRoles(any(), any(), any()); verify(caseWorkerProfileRepository, times(0)).saveAll(any()); @@ -746,7 +746,7 @@ void updateRoleFailsForInvalidResponseFromIdam_Scenario3() throws JsonProcessing UserProfileRolesResponse userProfileRolesResponse = new UserProfileRolesResponse(); userProfileCreationResponse.setIdamId("1"); String userProfileResponseBody = mapper.writeValueAsString(userProfileResponse); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(userProfileResponseBody, defaultCharset()) @@ -763,7 +763,7 @@ void updateRoleFailsForInvalidResponseFromIdam_Scenario3() throws JsonProcessing caseWorkerServiceImpl.processCaseWorkerProfiles(requests); verify(caseWorkerProfileRepository, times(1)).findByEmailIdIn(any()); - verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any()); + verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any(),any()); verify(userProfileFeignClient, times(1)).modifyUserRoles(any(), any(), any()); verify(caseWorkerProfileRepository, times(0)).saveAll(any()); @@ -801,7 +801,7 @@ void updateRoleFailsForInvalidResponseFromIdam_Scenario4() throws JsonProcessing emails.add(cwProfileCreationRequest.getEmailId()); when(caseWorkerProfileRepository.findByEmailIdIn(emails)).thenReturn(Arrays.asList(profile)); String userProfileResponseBody = mapper.writeValueAsString(userProfileResponse); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(userProfileResponseBody, defaultCharset()) @@ -816,7 +816,7 @@ void updateRoleFailsForInvalidResponseFromIdam_Scenario4() throws JsonProcessing requests.add(cwProfileCreationRequest); caseWorkerServiceImpl.processCaseWorkerProfiles(requests); - verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any()); + verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any(),any()); verify(userProfileFeignClient, times(1)).modifyUserRoles(any(), any(), any()); verify(caseWorkerProfileRepository, times(0)).saveAll(any()); verify(caseWorkerProfileRepository, times(1)).findByEmailIdIn(any()); @@ -855,7 +855,7 @@ void updateRoleFailsForInvalidResponseFromIdam_Scenario5() throws JsonProcessing emails.add(cwProfileCreationRequest.getEmailId()); when(caseWorkerProfileRepository.findByEmailIdIn(emails)).thenReturn(Arrays.asList(profile)); String userProfileResponseBody = mapper.writeValueAsString(userProfileResponse); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(userProfileResponseBody, defaultCharset()) @@ -867,7 +867,7 @@ void updateRoleFailsForInvalidResponseFromIdam_Scenario5() throws JsonProcessing requests.add(cwProfileCreationRequest); caseWorkerServiceImpl.processCaseWorkerProfiles(requests); - verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any()); + verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any(),any()); verify(userProfileFeignClient, times(1)).modifyUserRoles(any(), any(), any()); verify(caseWorkerProfileRepository, times(0)).saveAll(any()); verify(caseWorkerProfileRepository, times(1)).findByEmailIdIn(any()); @@ -1055,7 +1055,7 @@ void testNamesMismatch_Sc1() throws JsonProcessingException { cwProfileCreationRequest.setIdamRoles(idamroles); String userProfileResponseBody = mapper.writeValueAsString(userProfileResponse); String userProfileRolesResponseBody = mapper.writeValueAsString(userProfileRolesResponse); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(userProfileResponseBody, defaultCharset()) @@ -1066,7 +1066,7 @@ void testNamesMismatch_Sc1() throws JsonProcessingException { null)).body(userProfileRolesResponseBody, defaultCharset()) .status(200).build()); caseWorkerServiceImpl.updateUserRolesInIdam(cwProfileCreationRequest, "1"); - verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any()); + verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any(),any()); verify(userProfileFeignClient, times(1)).modifyUserRoles(any(), any(), any()); } @@ -1091,13 +1091,13 @@ void testNamesMismatch_Sc2() throws JsonProcessingException { cwProfileCreationRequest.setIdamRoles(idamroles); String userProfileResponseBody = mapper.writeValueAsString(userProfileResponse); String userProfileRolesResponseBody = mapper.writeValueAsString(userProfileRolesResponse); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(userProfileResponseBody, defaultCharset()) .status(200).build()); caseWorkerServiceImpl.updateUserRolesInIdam(cwProfileCreationRequest, "1"); - verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any()); + verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any(),any()); verify(userProfileFeignClient, times(0)).modifyUserRoles(any(), any(), any()); } @@ -1123,7 +1123,7 @@ void testNamesMismatch_Sc3() throws JsonProcessingException { cwProfileCreationRequest.setIdamRoles(idamroles); String userProfileResponseBody = mapper.writeValueAsString(userProfileResponse); String userProfileRolesResponseBody = mapper.writeValueAsString(userProfileRolesResponse); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(userProfileResponseBody, defaultCharset()) @@ -1134,7 +1134,7 @@ void testNamesMismatch_Sc3() throws JsonProcessingException { null)).body(userProfileRolesResponseBody, defaultCharset()) .status(200).build()); caseWorkerServiceImpl.updateUserRolesInIdam(cwProfileCreationRequest, "1"); - verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any()); + verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any(),any()); verify(userProfileFeignClient, times(1)).modifyUserRoles(any(), any(), any()); } @@ -1159,7 +1159,7 @@ void testNamesMismatch_Sc4() throws JsonProcessingException { cwProfileCreationRequest.setIdamRoles(idamroles); String userProfileResponseBody = mapper.writeValueAsString(userProfileResponse); String userProfileRolesResponseBody = mapper.writeValueAsString(userProfileRolesResponse); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(userProfileResponseBody, defaultCharset()) @@ -1170,7 +1170,7 @@ void testNamesMismatch_Sc4() throws JsonProcessingException { null)).body(userProfileRolesResponseBody, defaultCharset()) .status(200).build()); caseWorkerServiceImpl.updateUserRolesInIdam(cwProfileCreationRequest, "1"); - verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any()); + verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any(),any()); verify(userProfileFeignClient, times(1)).modifyUserRoles(any(), any(), any()); } @@ -1195,7 +1195,7 @@ void testNamesMismatch_Sc5() throws JsonProcessingException { cwProfileCreationRequest.setIdamRoles(idamroles); String userProfileResponseBody = mapper.writeValueAsString(userProfileResponse); String userProfileRolesResponseBody = mapper.writeValueAsString(userProfileRolesResponse); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(userProfileResponseBody, defaultCharset()) @@ -1206,7 +1206,7 @@ void testNamesMismatch_Sc5() throws JsonProcessingException { null)).body(userProfileRolesResponseBody, defaultCharset()) .status(200).build()); caseWorkerServiceImpl.updateUserRolesInIdam(cwProfileCreationRequest, "1"); - verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any()); + verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any(),any()); verify(userProfileFeignClient, times(1)).modifyUserRoles(any(), any(), any()); } @@ -1231,7 +1231,7 @@ void testNamesMismatch_Sc6() throws JsonProcessingException { cwProfileCreationRequest.setIdamRoles(idamroles); String userProfileResponseBody = mapper.writeValueAsString(userProfileResponse); String userProfileRolesResponseBody = mapper.writeValueAsString(userProfileRolesResponse); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(userProfileResponseBody, defaultCharset()) diff --git a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImplTest.java b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImplTest.java index 225db40aa..b05369fb8 100644 --- a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImplTest.java +++ b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImplTest.java @@ -94,13 +94,14 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static uk.gov.hmcts.reform.cwrdapi.TestSupport.validateSearchUserProfileResponse; -import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.IDAM_STATUS_NOT_ACTIVE; +import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.IDAM_STATUS_PENDING; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.IDAM_STATUS_USER_PROFILE; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.STAFF_PROFILE_CREATE; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.STAFF_PROFILE_UPDATE; @@ -1046,7 +1047,7 @@ void test_update_staff_profile_with_changed_values() throws JsonProcessingExcept userProfileResponse.setFirstName("testFNChanged"); userProfileResponse.setLastName("testLNChanged"); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(mapper.writeValueAsString(userProfileResponse), @@ -1248,7 +1249,7 @@ void test_create_staff_profile_with_changed_values_PresentInUP() throws JsonProc userProfileRolesResponse.setRoleAdditionResponse(roleAdditionResponse); roleAdditionResponse.setIdamMessage("success"); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(mapper.writeValueAsString(userProfileResponse), @@ -1279,23 +1280,36 @@ void staffProfilePendingStatusInUP() throws JsonProcessingException { UserProfileResponse userProfileResponse = new UserProfileResponse(); userProfileResponse.setIdamId("12345678"); List roles = Arrays.asList("IdamRole1", "IdamRole4"); - userProfileResponse.setIdamStatus(IDAM_STATUS_NOT_ACTIVE); + userProfileResponse.setIdamStatus(IDAM_STATUS_PENDING); + userProfileResponse.setRoles(roles); + + UserProfileRolesResponse userProfileRolesResponse = new UserProfileRolesResponse(); + RoleAdditionResponse roleAdditionResponse = new RoleAdditionResponse(); + roleAdditionResponse.setIdamStatusCode("201"); + userProfileRolesResponse.setRoleAdditionResponse(roleAdditionResponse); + roleAdditionResponse.setIdamMessage("success"); + + when(userProfileFeignClient.modifyUserRoles(any(), any(), any())) + .thenReturn(Response.builder() + .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), + null)).body(mapper.writeValueAsString(userProfileRolesResponse), + defaultCharset()) + .status(200).build()); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + when(userProfileFeignClient.getUserProfileWithRolesById(any(),eq("SRD"))) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(mapper.writeValueAsString(userProfileResponse), defaultCharset()) .status(200).build()); - StaffReferenceException thrown = Assertions.assertThrows(StaffReferenceException.class, () -> { - staffRefDataServiceImpl + Boolean response = staffRefDataServiceImpl .updateUserRolesInIdam(staffProfileCreationRequest, "1234", STAFF_PROFILE_CREATE); - }); + assertTrue(response); + verify(userProfileFeignClient, times(1)).modifyUserRoles(any(), any(), any()); + verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any(), any()); - assertThat(thrown.getStatus().value()).isEqualTo(HttpStatus.BAD_REQUEST.value()); - assertThat(thrown.getErrorDescription()).isEqualTo(IDAM_STATUS_NOT_ACTIVE); } @Test @@ -1303,7 +1317,7 @@ void staffProfileDoesNotExitInIdam() throws JsonProcessingException { UserProfileResponse userProfileResponse = new UserProfileResponse(); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(mapper.writeValueAsString(userProfileResponse), diff --git a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataUpdateStaffServiceImplTest.java b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataUpdateStaffServiceImplTest.java index a2ff86b07..e8b66af91 100644 --- a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataUpdateStaffServiceImplTest.java +++ b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataUpdateStaffServiceImplTest.java @@ -63,15 +63,17 @@ import java.util.Set; import static java.nio.charset.Charset.defaultCharset; +import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.IDAM_STATUS; -import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.IDAM_STATUS_NOT_ACTIVE; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.IDAM_STATUS_SUSPENDED; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.IDAM_STATUS_USER_PROFILE; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.ORIGIN_EXUI; @@ -80,6 +82,7 @@ import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.STAFF_PROFILE_CREATE; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.STAFF_PROFILE_UPDATE; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.STATUS_ACTIVE; +import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.UP_FAILURE_ROLES; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.UP_STATUS_PENDING; @ExtendWith(MockitoExtension.class) @@ -323,7 +326,7 @@ void test_updateStaffProfile_when_user_exists_in_idam_only() throws JsonProcessi UserProfileResponse userProfileResponseEmpty = new UserProfileResponse(); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(mapper.writeValueAsString(userProfileResponseEmpty), @@ -433,7 +436,7 @@ void test_updateStaffProfile_with_changed_values_UpAndIdam_Status_Pending() thro userProfileResponse.setFirstName("testFNChanged"); userProfileResponse.setLastName("testLNChanged"); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(mapper.writeValueAsString(userProfileResponse), @@ -451,7 +454,6 @@ void test_updateStaffProfile_with_changed_values_UpAndIdam_Status_Pending() thro userProfileRolesResponse.setRoleAdditionResponse(roleAdditionResponse); roleAdditionResponse.setIdamMessage("success"); - StaffProfileCreationRequest staffProfileCreationRequest = getStaffProfileUpdateRequest(); StaffReferenceException thrown = Assertions.assertThrows(StaffReferenceException.class, () -> { @@ -461,7 +463,7 @@ void test_updateStaffProfile_with_changed_values_UpAndIdam_Status_Pending() thro }); assertThat(thrown.getStatus().value()).isEqualTo(HttpStatus.BAD_REQUEST.value()); - assertThat(thrown.getErrorDescription()).isEqualTo(IDAM_STATUS_NOT_ACTIVE); + assertThat(thrown.getErrorDescription()).isEqualTo(UP_FAILURE_ROLES); } @Test @@ -487,7 +489,7 @@ void test_updateStaffProfile_with_changed_values_UpAndIdam_Status_Suspended() th userProfileResponse.setFirstName("testFNChanged"); userProfileResponse.setLastName("testLNChanged"); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(mapper.writeValueAsString(userProfileResponse), @@ -504,18 +506,13 @@ void test_updateStaffProfile_with_changed_values_UpAndIdam_Status_Suspended() th roleAdditionResponse.setIdamStatusCode("201"); userProfileRolesResponse.setRoleAdditionResponse(roleAdditionResponse); roleAdditionResponse.setIdamMessage("success"); - - StaffProfileCreationRequest staffProfileCreationRequest = getStaffProfileUpdateRequest(); StaffReferenceException thrown = Assertions.assertThrows(StaffReferenceException.class, () -> { - StaffProfileCreationResponse staffProfileCreationResponse = staffRefDataServiceImpl - .updateStaffProfile(staffProfileCreationRequest); - + staffRefDataServiceImpl.updateStaffProfile(staffProfileCreationRequest); }); - - assertThat(thrown.getStatus().value()).isEqualTo(HttpStatus.BAD_REQUEST.value()); - assertThat(thrown.getErrorDescription()).isEqualTo(IDAM_STATUS_NOT_ACTIVE); + assertThat(thrown.getMessage()).contains("An update to the user is not possible at this moment. Please " + + "try again later."); } @Test @@ -543,7 +540,7 @@ void test_updateStaffProfile_with_changed_values() throws JsonProcessingExceptio userProfileResponse.setFirstName("testFNChanged"); userProfileResponse.setLastName("testLNChanged"); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(mapper.writeValueAsString(userProfileResponse), @@ -602,7 +599,7 @@ void test_updateStaffProfile_with_changed_values_with_exception() throws JsonPro userProfileResponse.setFirstName("testFNChanged"); userProfileResponse.setLastName("testLNChanged"); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(mapper.writeValueAsString(userProfileResponse), @@ -620,6 +617,13 @@ void test_updateStaffProfile_with_changed_values_with_exception() throws JsonPro userProfileRolesResponse.setRoleAdditionResponse(roleAdditionResponse); roleAdditionResponse.setIdamMessage("success"); + when(userProfileFeignClient.modifyUserRoles(any(), any(), any())) + .thenReturn(Response.builder() + .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), + null)).body(mapper.writeValueAsString(userProfileRolesResponse), + defaultCharset()) + .status(200).build()); + StaffProfileCreationRequest staffProfileCreationRequest = getStaffProfileUpdateRequest(); StaffProfileCreationRequest staffProfileCreationRequestEmpty = StaffProfileCreationRequest @@ -633,18 +637,22 @@ void test_updateStaffProfile_with_changed_values_with_exception() throws JsonPro .lastName("testLN") .regionId(1) .region("testRegion") + .roles(emptyList()) + .services(emptyList()) .userType("testUser1").build(); List stafProfiles = List.of(staffProfileCreationRequest, staffProfileCreationRequestEmpty); + when(caseWorkerProfileRepository.save(any())).thenReturn(caseWorkerProfile); - StaffReferenceException thrown = Assertions.assertThrows(StaffReferenceException.class, () -> { - staffRefDataServiceImpl.updateStaffProfiles(staffProfileCreationRequestEmpty,caseWorkerProfile); - }); + CaseWorkerProfile caseWorkerProfile1 = staffRefDataServiceImpl + .updateStaffProfiles(staffProfileCreationRequestEmpty,caseWorkerProfile); - assertThat(thrown.getStatus().value()).isEqualTo(HttpStatus.BAD_REQUEST.value()); - assertThat(thrown.getErrorDescription()).isEqualTo(IDAM_STATUS_NOT_ACTIVE); + assertThat(caseWorkerProfile1).isNotNull(); + + verify(userProfileFeignClient, times(1)).modifyUserRoles(any(), any(), any()); + verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any(), any()); } @@ -706,12 +714,28 @@ void testSuspendStaffUserProfile() throws JsonProcessingException { profile.setSuspended(false); profile.setEmailId(staffProfileCreationRequest.getEmailId()); + UserProfileResponse userProfileResponse = new UserProfileResponse(); + userProfileResponse.setIdamId("12345678"); + List roles = Arrays.asList("IdamRole1", "IdamRole4"); + userProfileResponse.setIdamStatus(STATUS_ACTIVE); + + userProfileResponse.setRoles(roles); + userProfileResponse.setFirstName("testFNChanged"); + userProfileResponse.setLastName("testLNChanged"); UserProfileRolesResponse userProfileRolesResponse = new UserProfileRolesResponse(); AttributeResponse attributeResponse = new AttributeResponse(); attributeResponse.setIdamStatusCode(HttpStatus.OK.value()); userProfileRolesResponse.setAttributeResponse(attributeResponse); + when(caseWorkerProfileRepository.save(any())).thenReturn(caseWorkerProfile); + + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) + .thenReturn(Response.builder() + .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), + null)).body(mapper.writeValueAsString(userProfileResponse), + defaultCharset()) + .status(200).build()); when(userProfileFeignClient.modifyUserRoles(any(), any(), any())) .thenReturn(Response.builder() @@ -725,7 +749,7 @@ void testSuspendStaffUserProfile() throws JsonProcessingException { staffRefDataServiceImpl.updateStaffProfiles(staffProfileCreationRequest,profile); verify(caseWorkerProfileRepository, times(1)).save(any()); - verify(userProfileFeignClient, times(1)).modifyUserRoles(any(), any(), any()); + verify(userProfileFeignClient, times(2)).modifyUserRoles(any(), any(), any()); } @@ -750,7 +774,7 @@ void test_updateUserRolesInIdam_with_IdamRoles_Idam_Status_Active() throws JsonP userProfileResponse.setFirstName("testFNChanged"); userProfileResponse.setLastName("testLNChanged"); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(mapper.writeValueAsString(userProfileResponse), @@ -808,7 +832,7 @@ void test_updateUserRolesInIdam_with_IdamRoles_Idam_Status_InActive() throws Jso userProfileResponse.setFirstName("testFNChanged"); userProfileResponse.setLastName("testLNChanged"); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(mapper.writeValueAsString(userProfileResponse), @@ -826,6 +850,13 @@ void test_updateUserRolesInIdam_with_IdamRoles_Idam_Status_InActive() throws Jso userProfileRolesResponse.setRoleAdditionResponse(roleAdditionResponse); roleAdditionResponse.setIdamMessage("success"); + when(userProfileFeignClient.modifyUserRoles(any(), any(), any())) + .thenReturn(Response.builder() + .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), + null)).body(mapper.writeValueAsString(userProfileRolesResponse), + defaultCharset()) + .status(200).build()); + StaffProfileCreationRequest cwUiRequest = getStaffProfileUpdateRequest(); staffProfileAuditService.saveStaffAudit(AuditStatus.FAILURE,IDAM_STATUS, @@ -833,14 +864,13 @@ void test_updateUserRolesInIdam_with_IdamRoles_Idam_Status_InActive() throws Jso String caseWorkerId = caseWorkerProfile.getCaseWorkerId(); - StaffReferenceException thrown = Assertions.assertThrows(StaffReferenceException.class, () -> { - boolean updateUserRolesInIdam = staffRefDataServiceImpl - .updateUserRolesInIdam(cwUiRequest,caseWorkerId,STAFF_PROFILE_UPDATE); - }); + boolean updateUserRolesInIdam = staffRefDataServiceImpl + .updateUserRolesInIdam(cwUiRequest,caseWorkerId,STAFF_PROFILE_UPDATE); - assertThat(thrown.getStatus().value()).isEqualTo(HttpStatus.BAD_REQUEST.value()); - assertThat(thrown.getErrorDescription()).isEqualTo(IDAM_STATUS_NOT_ACTIVE); + assertTrue(updateUserRolesInIdam); + verify(userProfileFeignClient, times(1)).modifyUserRoles(any(), any(), any()); + verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any(), any()); } @@ -865,7 +895,7 @@ void test_updateUserRolesInIdam_with_IdamRoles_Empty() throws JsonProcessingExce userProfileResponse.setFirstName("testFNChanged"); userProfileResponse.setLastName("testLNChanged"); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(mapper.writeValueAsString(null), @@ -918,7 +948,7 @@ void test_updateUserRolesInIdam() throws JsonProcessingException { userProfileResponse.setFirstName("testFNChanged"); userProfileResponse.setLastName("testLNChanged"); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(mapper.writeValueAsString(userProfileResponse), @@ -932,20 +962,25 @@ void test_updateUserRolesInIdam() throws JsonProcessingException { userProfileRolesResponse.setRoleAdditionResponse(roleAdditionResponse); roleAdditionResponse.setIdamMessage("success"); + when(userProfileFeignClient.modifyUserRoles(any(), any(), any())) + .thenReturn(Response.builder() + .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), + null)).body(mapper.writeValueAsString(userProfileRolesResponse), + defaultCharset()) + .status(200).build()); + StaffProfileCreationRequest cwUiRequest = getStaffProfileUpdateRequest(); staffProfileAuditService.saveStaffAudit(AuditStatus.FAILURE,IDAM_STATUS, StringUtils.EMPTY,cwUiRequest,STAFF_PROFILE_UPDATE); String caserWorkerId = dbProfile.getCaseWorkerId(); - StaffReferenceException thrown = Assertions.assertThrows(StaffReferenceException.class, () -> { - boolean updateUserRolesInIdam = staffRefDataServiceImpl - .updateUserRolesInIdam(cwUiRequest,caserWorkerId,STAFF_PROFILE_UPDATE); - - }); - assertThat(thrown.getStatus().value()).isEqualTo(HttpStatus.BAD_REQUEST.value()); - assertThat(thrown.getErrorDescription()).isEqualTo(IDAM_STATUS_NOT_ACTIVE); + boolean updateUserRolesInIdam = staffRefDataServiceImpl + .updateUserRolesInIdam(cwUiRequest,caserWorkerId,STAFF_PROFILE_UPDATE); + assertTrue(updateUserRolesInIdam); + verify(userProfileFeignClient, times(1)).modifyUserRoles(any(), any(), any()); + verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any(), any()); } @Test @@ -965,7 +1000,7 @@ void test_updateUserRolesInIdamDataMistmatch() throws JsonProcessingException { userProfileResponse.setFirstName("testFNChanged"); userProfileResponse.setLastName("testLNChanged"); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(mapper.writeValueAsString(userProfileResponse), @@ -979,20 +1014,25 @@ void test_updateUserRolesInIdamDataMistmatch() throws JsonProcessingException { userProfileRolesResponse.setRoleAdditionResponse(roleAdditionResponse); roleAdditionResponse.setIdamMessage("success"); + when(userProfileFeignClient.modifyUserRoles(any(), any(), any())) + .thenReturn(Response.builder() + .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), + null)).body(mapper.writeValueAsString(userProfileRolesResponse), + defaultCharset()) + .status(200).build()); + StaffProfileCreationRequest cwUiRequest = getStaffProfileUpdateRequest(); staffProfileAuditService.saveStaffAudit(AuditStatus.FAILURE,IDAM_STATUS, StringUtils.EMPTY,cwUiRequest,STAFF_PROFILE_UPDATE); String caserWorkerId = dbProfile.getCaseWorkerId(); - StaffReferenceException thrown = Assertions.assertThrows(StaffReferenceException.class, () -> { - boolean updateUserRolesInIdam = staffRefDataServiceImpl - .updateUserRolesInIdam(cwUiRequest,caserWorkerId,STAFF_PROFILE_UPDATE); - - }); - assertThat(thrown.getStatus().value()).isEqualTo(HttpStatus.BAD_REQUEST.value()); - assertThat(thrown.getErrorDescription()).isEqualTo(IDAM_STATUS_NOT_ACTIVE); + boolean updateUserRolesInIdam = staffRefDataServiceImpl + .updateUserRolesInIdam(cwUiRequest,caserWorkerId,STAFF_PROFILE_UPDATE); + assertTrue(updateUserRolesInIdam); + verify(userProfileFeignClient, times(1)).modifyUserRoles(any(), any(), any()); + verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any(), any()); } @@ -1009,7 +1049,7 @@ void test_updateUserRolesInIdam_with_StaffAdminRoleDelete_Idam_Status_Active() t userProfileResponse.setFirstName("testFN"); userProfileResponse.setLastName("testLN"); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(mapper.writeValueAsString(userProfileResponse), @@ -1091,27 +1131,41 @@ void test_processExistingCaseWorkers() throws JsonProcessingException { userProfileResponse.setFirstName("testFNChanged"); userProfileResponse.setLastName("testLNChanged"); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + UserProfileRolesResponse userProfileRolesResponse = new UserProfileRolesResponse(); + RoleAdditionResponse roleAdditionResponse = new RoleAdditionResponse(); + roleAdditionResponse.setIdamStatusCode("201"); + userProfileRolesResponse.setRoleAdditionResponse(roleAdditionResponse); + roleAdditionResponse.setIdamMessage("success"); + + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(mapper.writeValueAsString(userProfileResponse), defaultCharset()) .status(200).build()); + when(userProfileFeignClient.modifyUserRoles(any(), any(), any())) + .thenReturn(Response.builder() + .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), + null)).body(mapper.writeValueAsString(userProfileRolesResponse), + defaultCharset()) + .status(200).build()); + StaffProfileCreationRequest staffProfileCreationRequest = getStaffProfileUpdateRequest(); Map emailToRequestMap = new HashMap<>(); emailToRequestMap.put("cwr-func-test-user@test.com",staffProfileCreationRequest); - StaffReferenceException thrown = Assertions.assertThrows(StaffReferenceException.class, () -> { - staffRefDataServiceImpl - .processExistingCaseWorkers(staffProfileCreationRequest, caseWorkerProfile); - }); + CaseWorkerProfile caseWorkerProfile1 = staffRefDataServiceImpl + .processExistingCaseWorkers(staffProfileCreationRequest, caseWorkerProfile); + + assertNotNull(caseWorkerProfile1); + + verify(userProfileFeignClient, times(1)).modifyUserRoles(any(), any(), any()); + verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any(), any()); - assertThat(thrown.getStatus().value()).isEqualTo(HttpStatus.BAD_REQUEST.value()); - assertThat(thrown.getErrorDescription()).isEqualTo(IDAM_STATUS_NOT_ACTIVE); } @@ -1276,27 +1330,40 @@ void test_processExistingCaseWorkers_suspendedUsers() throws JsonProcessingExcep userProfileResponse.setFirstName("testFNChanged"); userProfileResponse.setLastName("testLNChanged"); - when(userProfileFeignClient.getUserProfileWithRolesById(any())) + UserProfileRolesResponse userProfileRolesResponse = new UserProfileRolesResponse(); + RoleAdditionResponse roleAdditionResponse = new RoleAdditionResponse(); + roleAdditionResponse.setIdamStatusCode("201"); + userProfileRolesResponse.setRoleAdditionResponse(roleAdditionResponse); + roleAdditionResponse.setIdamMessage("success"); + + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) .thenReturn(Response.builder() .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), null)).body(mapper.writeValueAsString(userProfileResponse), defaultCharset()) .status(200).build()); + when(userProfileFeignClient.modifyUserRoles(any(), any(), any())) + .thenReturn(Response.builder() + .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), + null)).body(mapper.writeValueAsString(userProfileRolesResponse), + defaultCharset()) + .status(200).build()); + StaffProfileCreationRequest staffProfileCreationRequest = getStaffProfileUpdateRequest(); Map emailToRequestMap = new HashMap<>(); emailToRequestMap.put("cwr-func-test-user@test.com",staffProfileCreationRequest); - StaffReferenceException thrown = Assertions.assertThrows(StaffReferenceException.class, () -> { - staffRefDataServiceImpl + + CaseWorkerProfile caseWorkerProfile1 = staffRefDataServiceImpl .processExistingCaseWorkers(staffProfileCreationRequest, caseWorkerProfile); - }); + assertNotNull(caseWorkerProfile1); - assertThat(thrown.getStatus().value()).isEqualTo(HttpStatus.BAD_REQUEST.value()); - assertThat(thrown.getErrorDescription()).isEqualTo(IDAM_STATUS_NOT_ACTIVE); + verify(userProfileFeignClient, times(1)).modifyUserRoles(any(), any(), any()); + verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any(), any()); From fdabbd8fc196eaaa679afec11b09ee3e5f79518f Mon Sep 17 00:00:00 2001 From: fiyin-cgi <126156952+fiyin-cgi@users.noreply.github.com> Date: Wed, 31 May 2023 17:12:37 +0100 Subject: [PATCH 18/67] DTSRD-456: fix CVE-2023-28709 (#732) * DTSRD-456: fix CVE-2023-28709 * DTSRD-456: bump version * DTSRD-456: bump servicebus version * DTSRD-456: revert --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 7dfed25b8..9b09d9a88 100644 --- a/build.gradle +++ b/build.gradle @@ -520,8 +520,8 @@ dependencies { dependencyManagement { dependencies { - // CVE-2021-42340 - dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.72') { + // CVE-2023-28709 + dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.75') { entry 'tomcat-embed-core' entry 'tomcat-embed-el' entry 'tomcat-embed-websocket' From ca856e83090b93fe73ef91067843753c289a9676 Mon Sep 17 00:00:00 2001 From: fiyin-cgi <126156952+fiyin-cgi@users.noreply.github.com> Date: Tue, 13 Jun 2023 12:05:29 +0100 Subject: [PATCH 19/67] DTSRD-551: fix CVE-2023-20883 (#739) --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 9b09d9a88..6b0f42fec 100644 --- a/build.gradle +++ b/build.gradle @@ -18,7 +18,7 @@ plugins { id "info.solidsoft.pitest" version '1.7.0' id 'io.spring.dependency-management' version '1.1.0' id 'org.sonarqube' version '3.3' - id 'org.springframework.boot' version '2.7.11' + id 'org.springframework.boot' version '2.7.12' id "org.flywaydb.flyway" version '8.5.4' id 'au.com.dius.pact' version '4.1.7' // do not change, otherwise serenity report fails id 'org.owasp.dependencycheck' version '8.0.1' @@ -37,7 +37,7 @@ def versions = [ reformLogging : '6.0.1', serenity : '2.0.76', sonarPitest : '0.5', - springBoot : '2.7.11', + springBoot : '2.7.12', pact_version : '4.1.7', launchDarklySdk : '5.10.7', restAssured : '4.3.3', From 29059ca7805f63774080c1827e5132df3d8c90a8 Mon Sep 17 00:00:00 2001 From: lukasz-wolski <1005015+lukasz-wolski@users.noreply.github.com> Date: Thu, 15 Jun 2023 08:38:17 +0100 Subject: [PATCH 20/67] Update renovate --- renovate.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/renovate.json b/renovate.json index c5820bb43..c92183618 100644 --- a/renovate.json +++ b/renovate.json @@ -1,5 +1,10 @@ { "extends": [ "config:base" + ], + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "local>hmcts/.github:renovate-config", + "local>hmcts/.github//renovate/automerge-minor" ] -} \ No newline at end of file +} From b98bc5594c761a93cf123c4d5761b6c9b071ba4a Mon Sep 17 00:00:00 2001 From: arshinsalim <97666178+arshinsalim@users.noreply.github.com> Date: Thu, 15 Jun 2023 09:48:21 +0100 Subject: [PATCH 21/67] Rdcc 6758 (#737) Profile sync Put API with correct error message --- build.gradle | 2 + .../StaffReferenceDataProviderTest.java | 8 +- .../cwrdapi/AuthorizationFunctionalTest.java | 1 + .../cwrdapi/StaffRefCreateFunctionalTest.java | 46 ++++ .../cwrdapi/client/CaseWorkerApiClient.java | 17 ++ .../UpdateCaseWorkerIntegrationTest.java | 227 ++++++++++++++++++ .../util/CaseWorkerReferenceDataClient.java | 5 + .../reform/cwrdapi/config/WebMvcConfig.java | 1 + .../CaseWorkerRefUsersController.java | 72 ++++++ .../CaseWorkersProfileUpdationRequest.java | 61 +++++ .../CaseWorkersProfileUpdationResponse.java | 19 ++ .../CaseWorkerProfileUpdateservice.java | 12 + .../service/IJsrValidatorStaffProfile.java | 4 + .../CaseWorkerProfileUpdateserviceImpl.java | 53 ++++ .../impl/FeatureToggleServiceImpl.java | 3 + .../impl/JsrValidatorStaffProfile.java | 16 ++ .../cwrdapi/util/CaseWorkerConstants.java | 5 + .../util/CaseWorkerProfileValidator.java | 18 ++ .../util/ValidateCaseWorkerProfile.java | 23 ++ src/main/resources/application.yaml | 2 +- .../CaseWorkerRefUsersControllerTest.java | 33 ++- ...aseWorkerProfileUpdateserviceImplTest.java | 89 +++++++ .../JsrValidateCaseWorkerUpdateRequest.java | 110 +++++++++ 23 files changed, 824 insertions(+), 3 deletions(-) create mode 100644 src/integrationTest/java/uk/gov/hmcts/reform/cwrdapi/UpdateCaseWorkerIntegrationTest.java create mode 100644 src/main/java/uk/gov/hmcts/reform/cwrdapi/controllers/request/CaseWorkersProfileUpdationRequest.java create mode 100644 src/main/java/uk/gov/hmcts/reform/cwrdapi/controllers/response/CaseWorkersProfileUpdationResponse.java create mode 100644 src/main/java/uk/gov/hmcts/reform/cwrdapi/service/CaseWorkerProfileUpdateservice.java create mode 100644 src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerProfileUpdateserviceImpl.java create mode 100644 src/main/java/uk/gov/hmcts/reform/cwrdapi/util/CaseWorkerProfileValidator.java create mode 100644 src/main/java/uk/gov/hmcts/reform/cwrdapi/util/ValidateCaseWorkerProfile.java create mode 100644 src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerProfileUpdateserviceImplTest.java create mode 100644 src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/JsrValidateCaseWorkerUpdateRequest.java diff --git a/build.gradle b/build.gradle index 6b0f42fec..b470b8191 100644 --- a/build.gradle +++ b/build.gradle @@ -464,6 +464,8 @@ dependencies { testImplementation group: 'org.powermock', name: 'powermock-api-mockito2', version: '2.0.9' testImplementation group: 'org.mockito', name: 'mockito-junit-jupiter', version: '4.3.1' + + testImplementation group: 'org.skyscreamer', name: 'jsonassert', version: '1.5.1' diff --git a/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/StaffReferenceDataProviderTest.java b/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/StaffReferenceDataProviderTest.java index 90d2067df..aeb0db941 100644 --- a/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/StaffReferenceDataProviderTest.java +++ b/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/StaffReferenceDataProviderTest.java @@ -62,6 +62,7 @@ import uk.gov.hmcts.reform.cwrdapi.service.IJsrValidatorStaffProfile; import uk.gov.hmcts.reform.cwrdapi.service.IStaffProfileAuditService; import uk.gov.hmcts.reform.cwrdapi.service.impl.CaseWorkerDeleteServiceImpl; +import uk.gov.hmcts.reform.cwrdapi.service.impl.CaseWorkerProfileUpdateserviceImpl; import uk.gov.hmcts.reform.cwrdapi.service.impl.CaseWorkerServiceImpl; import uk.gov.hmcts.reform.cwrdapi.service.impl.StaffRefDataServiceImpl; import uk.gov.hmcts.reform.cwrdapi.util.StaffProfileCreateUpdateUtil; @@ -105,6 +106,10 @@ public class StaffReferenceDataProviderTest { @InjectMocks private CaseWorkerDeleteServiceImpl caseWorkerDeleteServiceImpl; + @InjectMocks + private CaseWorkerProfileUpdateserviceImpl caseWorkerProfileUpdateservice; + + @Mock private CaseWorkerProfileRepository caseWorkerProfileRepo; @@ -172,7 +177,8 @@ void beforeCreate(PactVerificationContext context) { testTarget.setControllers( new CaseWorkerRefUsersController( "RD-Caseworker-Ref-Api", 20, "caseWorkerId", - "preview", caseWorkerServiceImpl, caseWorkerDeleteServiceImpl), + "preview", caseWorkerServiceImpl, + caseWorkerDeleteServiceImpl,caseWorkerProfileUpdateservice,staffRefDataServiceImpl), new StaffReferenceInternalController( "RD-Caseworker-Ref-Api", 20, "caseWorkerId", caseWorkerServiceImpl), diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/AuthorizationFunctionalTest.java b/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/AuthorizationFunctionalTest.java index 257058b77..7d4a7e4b8 100644 --- a/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/AuthorizationFunctionalTest.java +++ b/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/AuthorizationFunctionalTest.java @@ -68,6 +68,7 @@ public class AuthorizationFunctionalTest { public static final String CASEWORKER_SENIOR_IAC = "caseworker-senior-iac"; public static final String USER_STATUS_SUSPENDED = "SUSPENDED"; public static final String ROLE_CWD_ADMIN = "cwd-admin"; + public static final String PRD_ADMIN = "prd-admin"; public static final String ROLE_STAFF_ADMIN = "staff-admin"; public static final String ROLE_CWD_SYSTEM_USER = "cwd-system-user"; diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/StaffRefCreateFunctionalTest.java b/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/StaffRefCreateFunctionalTest.java index 11511a082..2deab1f71 100644 --- a/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/StaffRefCreateFunctionalTest.java +++ b/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/StaffRefCreateFunctionalTest.java @@ -17,10 +17,12 @@ import org.springframework.http.HttpStatus; import org.springframework.test.context.ActiveProfiles; import uk.gov.hmcts.reform.cwrdapi.client.response.UserProfileResponse; +import uk.gov.hmcts.reform.cwrdapi.controllers.request.CaseWorkersProfileUpdationRequest; import uk.gov.hmcts.reform.cwrdapi.controllers.request.SkillsRequest; import uk.gov.hmcts.reform.cwrdapi.controllers.request.StaffProfileCreationRequest; import uk.gov.hmcts.reform.cwrdapi.controllers.request.UserProfileCreationRequest; import uk.gov.hmcts.reform.cwrdapi.controllers.request.UserRequest; +import uk.gov.hmcts.reform.cwrdapi.controllers.response.SearchStaffUserByIdResponse; import uk.gov.hmcts.reform.cwrdapi.controllers.response.StaffProfileCreationResponse; import uk.gov.hmcts.reform.cwrdapi.util.FeatureToggleConditionExtension; import uk.gov.hmcts.reform.cwrdapi.util.ToggleEnable; @@ -50,9 +52,11 @@ class StaffRefCreateFunctionalTest extends AuthorizationFunctionalTest { public static final String CREATE_STAFF_PROFILE = "StaffRefDataController.createStaffUserProfile"; + public static final String RD_CASEWORKER_SYNC = "CaseWorkerRefUsersController.updateCaseWorkerDetails"; public static List caseWorkerIds = new ArrayList<>(); public static final String FETCH_BY_CASEWORKER_ID = "CaseWorkerRefUsersController.fetchCaseworkersById"; + public static final String STAFF_PROFILE_URL = "/refdata/case-worker"; @Test @ToggleEnable(mapKey = CREATE_STAFF_PROFILE, withFeature = true) @@ -539,6 +543,48 @@ void createStaffProfileDifferentThanUserPresentInIdam() throws JsonProcessingExc assertEquals("200",upResponse.getIdamStatusCode()); } + @Test + @ToggleEnable(mapKey = RD_CASEWORKER_SYNC, withFeature = false) + @ExtendWith(FeatureToggleConditionExtension.class) + void updateCaseWorkerProfile() { + + StaffProfileCreationRequest staffProfileCreationRequest = caseWorkerApiClient + .createStaffProfileCreationRequest(); + Response response = caseWorkerApiClient.createStaffUserProfile(staffProfileCreationRequest); + assertEquals(201,response.statusCode()); + + StaffProfileCreationResponse staffProfileCreationResponse = + response.getBody().as(StaffProfileCreationResponse.class); + + CaseWorkersProfileUpdationRequest caseWorkersProfileUpdationRequest = CaseWorkersProfileUpdationRequest + .caseWorkersProfileUpdationRequest().firstName("StaffProfilefirstNamesnew") + .lastName("StaffProfilelastNamenew") + .userId(staffProfileCreationResponse.getCaseWorkerId()) + .emailId(staffProfileCreationRequest.getEmailId()) + .suspended(false) + .build(); + + Response updateResponse = caseWorkerApiClient.updateCaseWorkerProfile(caseWorkersProfileUpdationRequest); + assertEquals(200,updateResponse.statusCode()); + + assertNotNull(staffProfileCreationResponse); + assertNotNull(staffProfileCreationResponse.getCaseWorkerId()); + Response fetchResponse = caseWorkerApiClient.getMultipleAuthHeadersInternal(ROLE_STAFF_ADMIN) + .get(STAFF_PROFILE_URL + "/profile/search-by-id?id=" + staffProfileCreationResponse + .getCaseWorkerId()) + .andReturn(); + fetchResponse.then() + .assertThat() + .statusCode(200); + SearchStaffUserByIdResponse caseWorkerProfile = + fetchResponse.getBody().as(SearchStaffUserByIdResponse.class); + assertEquals(caseWorkersProfileUpdationRequest.getFirstName(), caseWorkerProfile.getFirstName()); + assertEquals(caseWorkersProfileUpdationRequest.getLastName(), caseWorkerProfile.getLastName()); + assertEquals(caseWorkersProfileUpdationRequest.getEmailId(), caseWorkerProfile.getEmailId()); + assertEquals(staffProfileCreationRequest.getRoles().get(0).getRole(), caseWorkerProfile + .getRoles().get(0).getRoleName()); + } + @AfterAll public static void cleanUpTestData() { try { diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/client/CaseWorkerApiClient.java b/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/client/CaseWorkerApiClient.java index 0fc0f3708..ee1b95e4a 100644 --- a/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/client/CaseWorkerApiClient.java +++ b/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/client/CaseWorkerApiClient.java @@ -14,6 +14,7 @@ import uk.gov.hmcts.reform.cwrdapi.controllers.request.CaseWorkerServicesRequest; import uk.gov.hmcts.reform.cwrdapi.controllers.request.CaseWorkerWorkAreaRequest; import uk.gov.hmcts.reform.cwrdapi.controllers.request.CaseWorkersProfileCreationRequest; +import uk.gov.hmcts.reform.cwrdapi.controllers.request.CaseWorkersProfileUpdationRequest; import uk.gov.hmcts.reform.cwrdapi.controllers.request.LanguagePreference; import uk.gov.hmcts.reform.cwrdapi.controllers.request.StaffProfileCreationRequest; import uk.gov.hmcts.reform.cwrdapi.controllers.request.StaffProfileRoleRequest; @@ -34,6 +35,7 @@ import static net.logstash.logback.encoder.org.apache.commons.lang3.ArrayUtils.isNotEmpty; import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static uk.gov.hmcts.reform.cwrdapi.AuthorizationFunctionalTest.PRD_ADMIN; import static uk.gov.hmcts.reform.cwrdapi.AuthorizationFunctionalTest.ROLE_CWD_ADMIN; import static uk.gov.hmcts.reform.cwrdapi.AuthorizationFunctionalTest.ROLE_STAFF_ADMIN; import static uk.gov.hmcts.reform.cwrdapi.AuthorizationFunctionalTest.STAFF_EMAIL_TEMPLATE; @@ -412,6 +414,21 @@ public Response createStaffUserProfile(StaffProfileCreationRequest request) { return response; } + public Response updateCaseWorkerProfile(CaseWorkersProfileUpdationRequest request) { + + Response response = getMultipleAuthHeadersInternal(PRD_ADMIN) + .body(request) + .put("/refdata/case-worker/users/sync") + .andReturn(); + log.info(":: Update staff profile response status code :: " + response.statusCode()); + + response.then() + .assertThat() + .statusCode(200); + + return response; + } + public Response updateStaffUserProfile(StaffProfileCreationRequest staffProfileCreationRequest) { Response response = getMultipleAuthHeadersInternal(List.of(ROLE_CWD_ADMIN,ROLE_STAFF_ADMIN)) diff --git a/src/integrationTest/java/uk/gov/hmcts/reform/cwrdapi/UpdateCaseWorkerIntegrationTest.java b/src/integrationTest/java/uk/gov/hmcts/reform/cwrdapi/UpdateCaseWorkerIntegrationTest.java new file mode 100644 index 000000000..b4bd7e84f --- /dev/null +++ b/src/integrationTest/java/uk/gov/hmcts/reform/cwrdapi/UpdateCaseWorkerIntegrationTest.java @@ -0,0 +1,227 @@ +package uk.gov.hmcts.reform.cwrdapi; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import uk.gov.hmcts.reform.cwrdapi.controllers.request.CaseWorkersProfileUpdationRequest; +import uk.gov.hmcts.reform.cwrdapi.controllers.request.StaffProfileCreationRequest; +import uk.gov.hmcts.reform.cwrdapi.controllers.response.SearchStaffUserByIdResponse; +import uk.gov.hmcts.reform.cwrdapi.repository.CaseWorkerLocationRepository; +import uk.gov.hmcts.reform.cwrdapi.repository.CaseWorkerProfileRepository; +import uk.gov.hmcts.reform.cwrdapi.repository.CaseWorkerRoleRepository; +import uk.gov.hmcts.reform.cwrdapi.repository.CaseWorkerSkillRepository; +import uk.gov.hmcts.reform.cwrdapi.repository.CaseWorkerWorkAreaRepository; +import uk.gov.hmcts.reform.cwrdapi.repository.StaffAuditRepository; +import uk.gov.hmcts.reform.cwrdapi.util.AuthorizationEnabledIntegrationTest; +import uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerReferenceDataClient; + +import java.util.Map; + +import static org.apache.logging.log4j.util.Strings.EMPTY; +import static org.assertj.core.api.Assertions.assertThat; +import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.INVALID_EMAIL; +import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.NO_DATA_FOUND; +import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.ROLE_PRD_ADMIN; +import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.ROLE_STAFF_ADMIN; + +public class UpdateCaseWorkerIntegrationTest extends AuthorizationEnabledIntegrationTest { + + + @Autowired + CaseWorkerProfileRepository caseWorkerProfileRepository; + + @Autowired + CaseWorkerLocationRepository caseWorkerLocationRepository; + + @Autowired + CaseWorkerRoleRepository caseWorkerRoleRepository; + + @Autowired + CaseWorkerWorkAreaRepository caseWorkerWorkAreaRepository; + + @Autowired + CaseWorkerReferenceDataClient caseWorkerReferenceDataClient; + @Autowired + CaseWorkerSkillRepository caseWorkerSkillRepository; + @Autowired + StaffAuditRepository staffAuditRepository; + + @BeforeEach + public void setUpClient() { + CaseWorkerReferenceDataClient.setBearerToken(EMPTY); + super.setUpClient(); + caseWorkerProfileRepository.deleteAll(); + caseWorkerLocationRepository.deleteAll(); + caseWorkerRoleRepository.deleteAll(); + caseWorkerWorkAreaRepository.deleteAll(); + caseWorkerSkillRepository.deleteAll(); + staffAuditRepository.deleteAll(); + } + + @AfterEach + public void cleanUpEach() { + caseWorkerProfileRepository.deleteAll(); + caseWorkerLocationRepository.deleteAll(); + caseWorkerRoleRepository.deleteAll(); + caseWorkerWorkAreaRepository.deleteAll(); + caseWorkerSkillRepository.deleteAll(); + staffAuditRepository.deleteAll(); + } + + @BeforeAll + public static void setup() { + CaseWorkerReferenceDataClient.setBearerToken(EMPTY); + } + + @AfterAll + public static void tearDown() { + CaseWorkerReferenceDataClient.setBearerToken(EMPTY); + } + + + @Test + void should_return_update_staff_user_with_status_code_200_from_profile_sync() throws Exception { + + CaseWorkerReferenceDataClient.setBearerToken(EMPTY); + userProfilePostUserWireMockForStaffProfile(HttpStatus.CREATED); + StaffProfileCreationRequest request = caseWorkerReferenceDataClient.createStaffProfileCreationRequest(); + Map createResponse = caseworkerReferenceDataClient + .createStaffProfile(request,ROLE_STAFF_ADMIN); + request.setFirstName("StaffProfilefirstNameCN"); + request.setLastName("StaffProfilelastNameCN"); + + CaseWorkersProfileUpdationRequest caseWorkersProfileUpdationRequest = CaseWorkersProfileUpdationRequest + .caseWorkersProfileUpdationRequest().firstName(request.getFirstName()) + .lastName(request.getLastName()) + .userId(String.valueOf(createResponse.get("case_worker_id"))) + .emailId(request.getEmailId()) + .suspended(false) + .build(); + + CaseWorkerReferenceDataClient.setBearerToken(EMPTY); + userProfilePostUserWireMockForStaffProfile(HttpStatus.CREATED); + + Map response = caseworkerReferenceDataClient + .updateCwProfile(caseWorkersProfileUpdationRequest,ROLE_PRD_ADMIN); + + assertThat(response).isNotNull(); + assertThat(response.get("case_worker_id")).isNotNull(); + } + + @Test + void should_return_resource_notfound_when_passing_an_invalid_caseworkerID() throws Exception { + + + + CaseWorkerReferenceDataClient.setBearerToken(EMPTY); + userProfilePostUserWireMockForStaffProfile(HttpStatus.CREATED); + StaffProfileCreationRequest request = caseWorkerReferenceDataClient.createStaffProfileCreationRequest(); + Map createResponse = caseworkerReferenceDataClient + .createStaffProfile(request,ROLE_STAFF_ADMIN); + + CaseWorkerReferenceDataClient.setBearerToken(EMPTY); + userProfilePostUserWireMockForStaffProfile(HttpStatus.CREATED); + + request.setFirstName("StaffProfilefirstNameCN"); + request.setLastName("StaffProfilelastNameCN"); + CaseWorkersProfileUpdationRequest caseWorkersProfileUpdationRequest = CaseWorkersProfileUpdationRequest + .caseWorkersProfileUpdationRequest().firstName(request.getFirstName()) + .lastName(request.getLastName()) + .userId("1234") + .emailId(request.getEmailId()) + .suspended(false) + .build(); + + Map response = caseworkerReferenceDataClient + .updateCwProfile(caseWorkersProfileUpdationRequest,ROLE_PRD_ADMIN); + + assertThat(response.get("http_status")).isEqualTo("404"); + String responseBody = (String) response.get("response_body"); + assertThat(responseBody.contains(NO_DATA_FOUND)).isTrue(); + + } + + @Test + void should_return_update_staff_user_with_status_code_400_invalid_email_id() throws Exception { + + + + CaseWorkerReferenceDataClient.setBearerToken(EMPTY); + userProfilePostUserWireMockForStaffProfile(HttpStatus.CREATED); + StaffProfileCreationRequest request = caseWorkerReferenceDataClient.createStaffProfileCreationRequest(); + Map createResponse = caseworkerReferenceDataClient + .createStaffProfile(request,ROLE_STAFF_ADMIN); + + request.setFirstName("StaffProfilefirstNameCN"); + request.setLastName("StaffProfilelastNameCN"); + CaseWorkersProfileUpdationRequest caseWorkersProfileUpdationRequest = CaseWorkersProfileUpdationRequest + .caseWorkersProfileUpdationRequest().firstName(request.getFirstName()) + .lastName(request.getLastName()) + .userId(String.valueOf(createResponse.get("case_worker_id"))) + .emailId("aaaaa") + .suspended(false) + .build(); + + CaseWorkerReferenceDataClient.setBearerToken(EMPTY); + userProfilePostUserWireMockForStaffProfile(HttpStatus.CREATED); + + Map response = caseworkerReferenceDataClient + .updateCwProfile(caseWorkersProfileUpdationRequest,ROLE_PRD_ADMIN); + + assertThat(response.get("http_status")).isEqualTo("400"); + String responseBody = (String) response.get("response_body"); + assertThat(responseBody.contains(INVALID_EMAIL)).isTrue(); + + } + + + @Test + void should_return_updated_staff_records_and_verify_the_fields() throws Exception { + + + + CaseWorkerReferenceDataClient.setBearerToken(EMPTY); + userProfilePostUserWireMockForStaffProfile(HttpStatus.CREATED); + StaffProfileCreationRequest request = caseWorkerReferenceDataClient.createStaffProfileCreationRequest(); + Map createResponse = caseworkerReferenceDataClient + .createStaffProfile(request,ROLE_STAFF_ADMIN); + + request.setFirstName("StaffProfilefirstNameCM"); + request.setLastName("StaffProfilelastNameCM"); + CaseWorkersProfileUpdationRequest caseWorkersProfileUpdationRequest = CaseWorkersProfileUpdationRequest + .caseWorkersProfileUpdationRequest().firstName(request.getFirstName()) + .lastName(request.getLastName()) + .userId(String.valueOf(createResponse.get("case_worker_id"))) + .emailId(request.getEmailId()) + .suspended(false) + .build(); + + CaseWorkerReferenceDataClient.setBearerToken(EMPTY); + userProfilePostUserWireMockForStaffProfile(HttpStatus.CREATED); + + Map response = caseworkerReferenceDataClient + .updateCwProfile(caseWorkersProfileUpdationRequest,ROLE_PRD_ADMIN); + + assertThat(response).isNotNull(); + assertThat(response.get("case_worker_id")).isNotNull(); + + String searchString = "?id=" + createResponse.get("case_worker_id"); + String path = "/profile/search-by-id"; + caseworkerReferenceDataClient.setBearerToken(null); + userProfileGetUserByIdWireMock(String.valueOf(createResponse.get("case_worker_id")), 200); + SearchStaffUserByIdResponse getResponse = (SearchStaffUserByIdResponse)caseworkerReferenceDataClient + .fetchStaffUserById(SearchStaffUserByIdResponse.class, path + searchString,ROLE_STAFF_ADMIN); + Assertions.assertEquals(caseWorkersProfileUpdationRequest.getUserId(), getResponse.getCaseWorkerId()); + Assertions.assertEquals(caseWorkersProfileUpdationRequest.getFirstName(), getResponse.getFirstName()); + Assertions.assertEquals(request.getRoles().get(0).getRole(), getResponse.getRoles().get(0).getRoleName()); + + } + + + +} \ No newline at end of file diff --git a/src/integrationTest/java/uk/gov/hmcts/reform/cwrdapi/util/CaseWorkerReferenceDataClient.java b/src/integrationTest/java/uk/gov/hmcts/reform/cwrdapi/util/CaseWorkerReferenceDataClient.java index 7ab24d85f..ce8dcb55f 100644 --- a/src/integrationTest/java/uk/gov/hmcts/reform/cwrdapi/util/CaseWorkerReferenceDataClient.java +++ b/src/integrationTest/java/uk/gov/hmcts/reform/cwrdapi/util/CaseWorkerReferenceDataClient.java @@ -33,6 +33,7 @@ import uk.gov.hmcts.reform.cwrdapi.controllers.request.CaseWorkerLocationRequest; import uk.gov.hmcts.reform.cwrdapi.controllers.request.CaseWorkerServicesRequest; import uk.gov.hmcts.reform.cwrdapi.controllers.request.CaseWorkersProfileCreationRequest; +import uk.gov.hmcts.reform.cwrdapi.controllers.request.CaseWorkersProfileUpdationRequest; import uk.gov.hmcts.reform.cwrdapi.controllers.request.SkillsRequest; import uk.gov.hmcts.reform.cwrdapi.controllers.request.StaffProfileCreationRequest; import uk.gov.hmcts.reform.cwrdapi.controllers.request.StaffProfileRoleRequest; @@ -658,6 +659,10 @@ public Map updateStaffProfile(StaffProfileCreationRequest reques return putRequest(baseUrl + "/profile", request, role, null); } + public Map updateCwProfile(CaseWorkersProfileUpdationRequest request, String role) { + return putRequest(baseUrl + "/users/sync", request, role, null); + } + public Object fetchStaffUserById(Class clazz, String userId, String role) throws JsonProcessingException { ResponseEntity responseEntity = getRequest(userId, clazz, role); diff --git a/src/main/java/uk/gov/hmcts/reform/cwrdapi/config/WebMvcConfig.java b/src/main/java/uk/gov/hmcts/reform/cwrdapi/config/WebMvcConfig.java index 64ebe4e5c..acaf13e0e 100644 --- a/src/main/java/uk/gov/hmcts/reform/cwrdapi/config/WebMvcConfig.java +++ b/src/main/java/uk/gov/hmcts/reform/cwrdapi/config/WebMvcConfig.java @@ -33,6 +33,7 @@ public void addInterceptors(InterceptorRegistry registry) { .addPathPatterns("/refdata/case-worker/users/fetchUsersById", "/refdata/case-worker/idam-roles-mapping", "/refdata/case-worker/users", + "/refdata/case-worker/users/sync", "/refdata/case-worker/upload-file", "/refdata/internal/staff/usersByServiceName", "/refdata/case-worker/skill", diff --git a/src/main/java/uk/gov/hmcts/reform/cwrdapi/controllers/CaseWorkerRefUsersController.java b/src/main/java/uk/gov/hmcts/reform/cwrdapi/controllers/CaseWorkerRefUsersController.java index fdb3d9277..3af3f5865 100644 --- a/src/main/java/uk/gov/hmcts/reform/cwrdapi/controllers/CaseWorkerRefUsersController.java +++ b/src/main/java/uk/gov/hmcts/reform/cwrdapi/controllers/CaseWorkerRefUsersController.java @@ -19,6 +19,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -27,16 +28,22 @@ import org.springframework.web.bind.annotation.RestController; import uk.gov.hmcts.reform.cwrdapi.controllers.advice.InvalidRequestException; import uk.gov.hmcts.reform.cwrdapi.controllers.request.CaseWorkersProfileCreationRequest; +import uk.gov.hmcts.reform.cwrdapi.controllers.request.CaseWorkersProfileUpdationRequest; import uk.gov.hmcts.reform.cwrdapi.controllers.request.UserRequest; import uk.gov.hmcts.reform.cwrdapi.controllers.response.CaseWorkerProfileCreationResponse; import uk.gov.hmcts.reform.cwrdapi.controllers.response.CaseWorkerProfilesDeletionResponse; +import uk.gov.hmcts.reform.cwrdapi.controllers.response.CaseWorkersProfileUpdationResponse; +import uk.gov.hmcts.reform.cwrdapi.controllers.response.StaffProfileCreationResponse; import uk.gov.hmcts.reform.cwrdapi.domain.CaseWorkerProfile; import uk.gov.hmcts.reform.cwrdapi.service.CaseWorkerDeleteService; +import uk.gov.hmcts.reform.cwrdapi.service.CaseWorkerProfileUpdateservice; import uk.gov.hmcts.reform.cwrdapi.service.CaseWorkerService; +import uk.gov.hmcts.reform.cwrdapi.service.StaffRefDataService; import java.util.List; import static java.lang.String.format; +import static org.apache.commons.lang3.ObjectUtils.isEmpty; import static org.apache.commons.lang3.ObjectUtils.isNotEmpty; import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; @@ -78,6 +85,12 @@ public class CaseWorkerRefUsersController { @Autowired CaseWorkerDeleteService caseWorkerDeleteService; + @Autowired + CaseWorkerProfileUpdateservice caseWorkerProfileUpdateservice; + + @Autowired + StaffRefDataService staffRefDataService; + @Operation( hidden = true, summary = "This API creates caseworker profiles", @@ -272,4 +285,63 @@ public ResponseEntity deleteCaseWorkerProfil return ResponseEntity.status(resource.getStatusCode()).body(resource); } + + @Operation( + summary = "This API update the Caseworker Profile from profile sync", + description = "This API will be invoked by user having idam role of prd-admin", + security = { + @SecurityRequirement(name = "ServiceAuthorization"), + @SecurityRequirement(name = "Authorization") + } + ) + @ApiResponse( + responseCode = "200", + description = "Successfully updated the caseworkerProfile", + content = @Content(schema = @Schema(implementation = CaseWorkersProfileUpdationResponse.class)) + ) + @ApiResponse( + responseCode = "400", + description = BAD_REQUEST, + content = @Content + ) + @ApiResponse( + responseCode = "401", + description = UNAUTHORIZED_ERROR, + content = @Content + ) + @ApiResponse( + responseCode = "403", + description = FORBIDDEN_ERROR, + content = @Content + ) + @ApiResponse( + responseCode = "500", + description = INTERNAL_SERVER_ERROR, + content = @Content + ) + @PutMapping( + path = "/sync", + produces = APPLICATION_JSON_VALUE + ) + //Sonar reported the below code as Security Hotspot with LOW priority. + //Currently it has been made false positive and reviewed as safe. + @Secured("prd-admin") + @Transactional + public ResponseEntity updateCaseWorkerDetails(@RequestBody CaseWorkersProfileUpdationRequest + caseWorkersProfileUpdationRequest) { + log.info("Inside update createStaffUserProfile Controller"); + if (isEmpty(caseWorkersProfileUpdationRequest)) { + throw new InvalidRequestException(BAD_REQUEST); + } + CaseWorkersProfileUpdationResponse caseWorkersProfileUpdationResponse = null; + caseWorkersProfileUpdationResponse = caseWorkerProfileUpdateservice + .updateCaseWorkerProfile(caseWorkersProfileUpdationRequest); + if (isNotEmpty(caseWorkersProfileUpdationResponse)) { + StaffProfileCreationResponse staffProfileCreationResponse = StaffProfileCreationResponse + .builder().caseWorkerId(caseWorkersProfileUpdationResponse.getUserId()).build(); + staffRefDataService.publishStaffProfileToTopic(staffProfileCreationResponse); + } + return ResponseEntity + .status(HttpStatus.OK).body(caseWorkersProfileUpdationResponse); + } } diff --git a/src/main/java/uk/gov/hmcts/reform/cwrdapi/controllers/request/CaseWorkersProfileUpdationRequest.java b/src/main/java/uk/gov/hmcts/reform/cwrdapi/controllers/request/CaseWorkersProfileUpdationRequest.java new file mode 100644 index 000000000..e3bee84ba --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/cwrdapi/controllers/request/CaseWorkersProfileUpdationRequest.java @@ -0,0 +1,61 @@ +package uk.gov.hmcts.reform.cwrdapi.controllers.request; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import uk.gov.hmcts.reform.cwrdapi.config.TrimStringFields; +import uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants; +import uk.gov.hmcts.reform.cwrdapi.util.ValidateCaseWorkerProfile; +import uk.gov.hmcts.reform.cwrdapi.util.ValidateEmail; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; + +import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.CASEWORKER_ID_MISSING; +import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.FIRST_NAME_INVALID; +import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.FIRST_NAME_NOT_PRESENT; +import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.LAST_NAME_INVALID; +import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.LAST_NAME_NOT_PRESENT; +import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.NAME_REGEX; +import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.SUSPENDED_FLAG_MANDATORY; + +@Getter +@Setter +@Builder(builderMethodName = "caseWorkersProfileUpdationRequest") +@ValidateCaseWorkerProfile +public class CaseWorkersProfileUpdationRequest { + + + @JsonProperty("case_worker_id") + @JsonDeserialize(using = TrimStringFields.class) + @NotEmpty(message = CASEWORKER_ID_MISSING) + private String userId; + + @JsonProperty("first_name") + @JsonDeserialize(using = TrimStringFields.class) + @Pattern(regexp = NAME_REGEX, message = FIRST_NAME_INVALID) + @NotEmpty(message = FIRST_NAME_NOT_PRESENT) + private String firstName; + + @JsonProperty("last_name") + @Pattern(regexp = NAME_REGEX, message = LAST_NAME_INVALID) + @NotEmpty(message = LAST_NAME_NOT_PRESENT) + @JsonDeserialize(using = TrimStringFields.class) + private String lastName; + + @JsonProperty("email_id") + @JsonDeserialize(using = TrimStringFields.class) + @ValidateEmail(message = CaseWorkerConstants.INVALID_EMAIL) + @NotEmpty(message = CaseWorkerConstants.INVALID_EMAIL) + private String emailId; + + @JsonProperty("suspended") + @JsonInclude(JsonInclude.Include.NON_NULL) + @NotNull(message = SUSPENDED_FLAG_MANDATORY) + private Boolean suspended; + +} diff --git a/src/main/java/uk/gov/hmcts/reform/cwrdapi/controllers/response/CaseWorkersProfileUpdationResponse.java b/src/main/java/uk/gov/hmcts/reform/cwrdapi/controllers/response/CaseWorkersProfileUpdationResponse.java new file mode 100644 index 000000000..dbc20f6eb --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/cwrdapi/controllers/response/CaseWorkersProfileUpdationResponse.java @@ -0,0 +1,19 @@ +package uk.gov.hmcts.reform.cwrdapi.controllers.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@Builder(builderMethodName = "caseWorkersProfileUpdationResponse") +@AllArgsConstructor +@NoArgsConstructor +public class CaseWorkersProfileUpdationResponse { + + @JsonProperty("case_worker_id") + private String userId; +} diff --git a/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/CaseWorkerProfileUpdateservice.java b/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/CaseWorkerProfileUpdateservice.java new file mode 100644 index 000000000..5d76c2756 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/CaseWorkerProfileUpdateservice.java @@ -0,0 +1,12 @@ +package uk.gov.hmcts.reform.cwrdapi.service; + +import org.springframework.stereotype.Service; +import uk.gov.hmcts.reform.cwrdapi.controllers.request.CaseWorkersProfileUpdationRequest; +import uk.gov.hmcts.reform.cwrdapi.controllers.response.CaseWorkersProfileUpdationResponse; + +@Service +public interface CaseWorkerProfileUpdateservice { + + CaseWorkersProfileUpdationResponse + updateCaseWorkerProfile(CaseWorkersProfileUpdationRequest caseWorkersProfileUpdationRequest); +} diff --git a/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/IJsrValidatorStaffProfile.java b/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/IJsrValidatorStaffProfile.java index cd6a84d0f..0f8c482eb 100644 --- a/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/IJsrValidatorStaffProfile.java +++ b/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/IJsrValidatorStaffProfile.java @@ -1,6 +1,7 @@ package uk.gov.hmcts.reform.cwrdapi.service; +import uk.gov.hmcts.reform.cwrdapi.controllers.request.CaseWorkersProfileUpdationRequest; import uk.gov.hmcts.reform.cwrdapi.controllers.request.StaffProfileCreationRequest; public interface IJsrValidatorStaffProfile { @@ -13,4 +14,7 @@ public interface IJsrValidatorStaffProfile { */ void validateStaffProfile(StaffProfileCreationRequest staffProfile,String operationType); + void validateCaseWorkerUpdateRequest(CaseWorkersProfileUpdationRequest cwUpdateProfileRequest, + String operationType); + } diff --git a/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerProfileUpdateserviceImpl.java b/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerProfileUpdateserviceImpl.java new file mode 100644 index 000000000..3982797f4 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerProfileUpdateserviceImpl.java @@ -0,0 +1,53 @@ +package uk.gov.hmcts.reform.cwrdapi.service.impl; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import uk.gov.hmcts.reform.cwrdapi.controllers.advice.ResourceNotFoundException; +import uk.gov.hmcts.reform.cwrdapi.controllers.request.CaseWorkersProfileUpdationRequest; +import uk.gov.hmcts.reform.cwrdapi.controllers.response.CaseWorkersProfileUpdationResponse; +import uk.gov.hmcts.reform.cwrdapi.domain.CaseWorkerProfile; +import uk.gov.hmcts.reform.cwrdapi.repository.CaseWorkerProfileRepository; +import uk.gov.hmcts.reform.cwrdapi.service.CaseWorkerProfileUpdateservice; +import uk.gov.hmcts.reform.cwrdapi.service.IJsrValidatorStaffProfile; +import uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants; + +import java.util.Optional; + +import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.STAFF_PROFILE_CREATE; + + +@Service +public class CaseWorkerProfileUpdateserviceImpl implements CaseWorkerProfileUpdateservice { + + @Autowired + CaseWorkerProfileRepository caseWorkerProfileRepo; + + @Autowired + IJsrValidatorStaffProfile jsrValidatorStaffProfile; + + @Override + @Transactional + public CaseWorkersProfileUpdationResponse updateCaseWorkerProfile( + CaseWorkersProfileUpdationRequest caseWorkersProfileUpdationRequest) { + + jsrValidatorStaffProfile + .validateCaseWorkerUpdateRequest(caseWorkersProfileUpdationRequest,STAFF_PROFILE_CREATE); + + Optional caseWorkerProfile = caseWorkerProfileRepo + .findByCaseWorkerId(caseWorkersProfileUpdationRequest.getUserId()); + if (!caseWorkerProfile.isPresent()) { + throw new ResourceNotFoundException(CaseWorkerConstants.NO_DATA_FOUND); + } + + CaseWorkerProfile cwProfile = caseWorkerProfile.get(); + cwProfile.setCaseWorkerId(caseWorkersProfileUpdationRequest.getUserId()); + cwProfile.setFirstName(caseWorkersProfileUpdationRequest.getFirstName()); + cwProfile.setLastName(caseWorkersProfileUpdationRequest.getLastName()); + cwProfile.setEmailId(caseWorkersProfileUpdationRequest.getEmailId()); + cwProfile.setSuspended(caseWorkersProfileUpdationRequest.getSuspended()); + caseWorkerProfileRepo.save(cwProfile); + return CaseWorkersProfileUpdationResponse + .caseWorkersProfileUpdationResponse().userId(cwProfile.getCaseWorkerId()).build(); + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/FeatureToggleServiceImpl.java b/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/FeatureToggleServiceImpl.java index 1a964b61b..ed9ebca84 100644 --- a/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/FeatureToggleServiceImpl.java +++ b/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/FeatureToggleServiceImpl.java @@ -18,6 +18,7 @@ public class FeatureToggleServiceImpl implements FeatureToggleService { public static final String CWD_DELETE_BY_ID_OR_EMAILPATTERN_FLAG = "delete-caseworker-by-id-or-emailpattern"; public static final String CWD_FETCH_STAFF_BY_CCD_SERVICE_NAMES = "fetch-staff-by-ccd-service-names"; public static final String STAFF_REF_DATA_RD_STAFF_UI = "rd-staff-ui"; + public static final String RD_CASEWORKER_SYNC = "crd-update-caseworker-profile-by-id"; public static final String SRD_RD_STAFF_UI = "rd-staff-ui"; @@ -75,6 +76,8 @@ public void mapServiceToFlag() { RD_STAFF_UI); launchDarklyMap.put("StaffRefDataController.fetchStaffProfileById", RD_STAFF_UI); + launchDarklyMap.put("CaseWorkerRefUsersController.updateCaseWorkerDetails", + RD_CASEWORKER_SYNC); } @Override diff --git a/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/JsrValidatorStaffProfile.java b/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/JsrValidatorStaffProfile.java index 3dbc78f6d..bce36955e 100644 --- a/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/JsrValidatorStaffProfile.java +++ b/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/JsrValidatorStaffProfile.java @@ -6,6 +6,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import uk.gov.hmcts.reform.cwrdapi.controllers.advice.InvalidRequestException; +import uk.gov.hmcts.reform.cwrdapi.controllers.request.CaseWorkersProfileUpdationRequest; import uk.gov.hmcts.reform.cwrdapi.controllers.request.StaffProfileCreationRequest; import uk.gov.hmcts.reform.cwrdapi.service.IJsrValidatorStaffProfile; import uk.gov.hmcts.reform.cwrdapi.service.IStaffProfileAuditService; @@ -62,6 +63,21 @@ public void validateStaffProfile(StaffProfileCreationRequest profileRequest, Str } log.info("{}:: JsrValidatorStaffProfile data processing validate complete::", logComponentName); } + + @Override + public void validateCaseWorkerUpdateRequest(CaseWorkersProfileUpdationRequest cwUpdateProfileRequest, + String operationType) { + log.info("{}:: JsrValidatorStaffProfile data processing validate starts::", logComponentName); + Set> constraintErrors = + validator.validate(cwUpdateProfileRequest); + if (isNotEmpty(constraintErrors)) { + constraintErrors.forEach(constraintError -> { + String errorMsg = constraintError.getMessage(); + throw new InvalidRequestException(errorMsg); + }); + } + log.info("{}:: JsrValidatorStaffProfile data processing validate complete::", logComponentName); + } } diff --git a/src/main/java/uk/gov/hmcts/reform/cwrdapi/util/CaseWorkerConstants.java b/src/main/java/uk/gov/hmcts/reform/cwrdapi/util/CaseWorkerConstants.java index 5a1d7879d..d23d7581e 100644 --- a/src/main/java/uk/gov/hmcts/reform/cwrdapi/util/CaseWorkerConstants.java +++ b/src/main/java/uk/gov/hmcts/reform/cwrdapi/util/CaseWorkerConstants.java @@ -127,6 +127,7 @@ private CaseWorkerConstants() { + "Please try again or check with HMCTS Support Team"; public static final String ROLE_CWD_USER = "cwd-user"; public static final String ROLE_STAFF_ADMIN = "staff-admin"; + public static final String ROLE_PRD_ADMIN = "prd-admin"; public static final String ROLE_CWD_SYSTEM_USER = "cwd-system-user"; public static final String ROLE_CWD_ADMIN = "cwd-admin"; @@ -228,6 +229,10 @@ private CaseWorkerConstants() { public static final String IDAM_STATUS_USER_PROFILE = "User does not exists in IDAM"; public static final String NO_PRIMARY_ROLE_PRESENT_PROFILE = "You must add Primary Role" + TRY_AGAIN; + public static final String CASEWORKER_ID_MISSING = "CaseWorker id missing"; + public static final String FIRST_NAME_NOT_PRESENT = "You must add the first name"; + public static final String LAST_NAME_NOT_PRESENT = "You must add the last name"; + public static final String SUSPENDED_FLAG_MANDATORY = "You must add the suspended flag"; } diff --git a/src/main/java/uk/gov/hmcts/reform/cwrdapi/util/CaseWorkerProfileValidator.java b/src/main/java/uk/gov/hmcts/reform/cwrdapi/util/CaseWorkerProfileValidator.java new file mode 100644 index 000000000..b39e75927 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/cwrdapi/util/CaseWorkerProfileValidator.java @@ -0,0 +1,18 @@ +package uk.gov.hmcts.reform.cwrdapi.util; + +import uk.gov.hmcts.reform.cwrdapi.controllers.request.CaseWorkersProfileUpdationRequest; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +public class CaseWorkerProfileValidator implements ConstraintValidator { + + @Override + public boolean isValid(CaseWorkersProfileUpdationRequest caseWorkersProfileUpdationRequest, + ConstraintValidatorContext constraintValidatorContext) { + constraintValidatorContext.disableDefaultConstraintViolation(); + + return true; + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/cwrdapi/util/ValidateCaseWorkerProfile.java b/src/main/java/uk/gov/hmcts/reform/cwrdapi/util/ValidateCaseWorkerProfile.java new file mode 100644 index 000000000..fe851d686 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/cwrdapi/util/ValidateCaseWorkerProfile.java @@ -0,0 +1,23 @@ +package uk.gov.hmcts.reform.cwrdapi.util; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import javax.validation.Constraint; +import javax.validation.Payload; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Target({TYPE, ANNOTATION_TYPE}) +@Retention(RUNTIME) +@Constraint(validatedBy = {CaseWorkerProfileValidator.class}) +@Documented +public @interface ValidateCaseWorkerProfile { + String message() default ""; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index ebcf1f446..af3580b52 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -107,7 +107,7 @@ idam: microservice: rd_caseworker_ref_api url: ${S2S_URL:http://rpe-service-auth-provider-aat.service.core-compute-aat.internal} s2s-authorised: - services: ${CRD_S2S_AUTHORISED_SERVICES:rd_caseworker_ref_api,am_org_role_mapping_service,iac,xui_webapp} + services: ${CRD_S2S_AUTHORISED_SERVICES:rd_caseworker_ref_api,am_org_role_mapping_service,iac,xui_webapp,rd_profile_sync} api.url: ${idam_url:https://idam-api.aat.platform.hmcts.net} oidc.issuer: ${OIDC_ISSUER_URL:https://forgerock-am.service.core-compute-idam-aat.internal:8443/openam/oauth2/hmcts} diff --git a/src/test/java/uk/gov/hmcts/reform/cwrdapi/controllers/CaseWorkerRefUsersControllerTest.java b/src/test/java/uk/gov/hmcts/reform/cwrdapi/controllers/CaseWorkerRefUsersControllerTest.java index c3bc8935b..6f0c687bf 100644 --- a/src/test/java/uk/gov/hmcts/reform/cwrdapi/controllers/CaseWorkerRefUsersControllerTest.java +++ b/src/test/java/uk/gov/hmcts/reform/cwrdapi/controllers/CaseWorkerRefUsersControllerTest.java @@ -14,9 +14,11 @@ import uk.gov.hmcts.reform.cwrdapi.controllers.request.CaseWorkerRoleRequest; import uk.gov.hmcts.reform.cwrdapi.controllers.request.CaseWorkerWorkAreaRequest; import uk.gov.hmcts.reform.cwrdapi.controllers.request.CaseWorkersProfileCreationRequest; +import uk.gov.hmcts.reform.cwrdapi.controllers.request.CaseWorkersProfileUpdationRequest; import uk.gov.hmcts.reform.cwrdapi.controllers.request.UserRequest; import uk.gov.hmcts.reform.cwrdapi.controllers.response.CaseWorkerProfileCreationResponse; import uk.gov.hmcts.reform.cwrdapi.domain.CaseWorkerProfile; +import uk.gov.hmcts.reform.cwrdapi.service.CaseWorkerProfileUpdateservice; import uk.gov.hmcts.reform.cwrdapi.service.CaseWorkerService; import java.util.ArrayList; @@ -37,6 +39,7 @@ class CaseWorkerRefUsersControllerTest { CaseWorkerService caseWorkerServiceMock; + CaseWorkerProfileUpdateservice caseWorkerProfileUpdateserviceMock; List caseWorkersProfileCreationRequest = new ArrayList<>(); CaseWorkersProfileCreationRequest cwRequest; CaseWorkerProfileCreationResponse cwProfileCreationResponse; @@ -48,7 +51,7 @@ class CaseWorkerRefUsersControllerTest { @BeforeEach void setUp() { caseWorkerServiceMock = mock(CaseWorkerService.class); - + caseWorkerProfileUpdateserviceMock = mock(CaseWorkerProfileUpdateservice.class); cwResponse = CaseWorkerProfileCreationResponse .builder() .caseWorkerRegistrationResponse("Case Worker Profiles Created.") @@ -201,4 +204,32 @@ void createCaseWorkerProfileWithNewRole() { verify(caseWorkerServiceMock, times(1)) .processCaseWorkerProfiles(caseWorkersProfileCreationRequest); } + + @Test + void updateCaseWorkerProfileFromProfileSync() { + + CaseWorkersProfileUpdationRequest caseWorkersProfileUpdationRequest = CaseWorkersProfileUpdationRequest + .caseWorkersProfileUpdationRequest().firstName("first_name") + .lastName("last_name") + .userId("185a0254-ff80-458b-8f62-2a759788afd2") + .emailId("firstname@justice.gov.uk") + .build(); + when(caseWorkerProfileUpdateserviceMock.updateCaseWorkerProfile(caseWorkersProfileUpdationRequest)) + .thenReturn(any()); + ResponseEntity actual = + caseWorkerRefUsersController.updateCaseWorkerDetails(caseWorkersProfileUpdationRequest); + + assertNotNull(actual); + verify(caseWorkerProfileUpdateserviceMock, times(1)) + .updateCaseWorkerProfile(caseWorkersProfileUpdationRequest); + + } + + @Test + void updateCaseWorkerProfileFromProfileSyncShouldThrow400() { + CaseWorkersProfileUpdationRequest caseWorkersProfileUpdationRequest = null; + Assertions.assertThrows(InvalidRequestException.class, () -> + caseWorkerRefUsersController.updateCaseWorkerDetails(caseWorkersProfileUpdationRequest)); + + } } diff --git a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerProfileUpdateserviceImplTest.java b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerProfileUpdateserviceImplTest.java new file mode 100644 index 000000000..7194d6c0b --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerProfileUpdateserviceImplTest.java @@ -0,0 +1,89 @@ +package uk.gov.hmcts.reform.cwrdapi.service.impl; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import uk.gov.hmcts.reform.cwrdapi.controllers.advice.ResourceNotFoundException; +import uk.gov.hmcts.reform.cwrdapi.controllers.request.CaseWorkersProfileUpdationRequest; +import uk.gov.hmcts.reform.cwrdapi.controllers.response.CaseWorkersProfileUpdationResponse; +import uk.gov.hmcts.reform.cwrdapi.domain.CaseWorkerProfile; +import uk.gov.hmcts.reform.cwrdapi.repository.CaseWorkerProfileRepository; +import uk.gov.hmcts.reform.cwrdapi.service.IJsrValidatorStaffProfile; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class CaseWorkerProfileUpdateserviceImplTest { + + @InjectMocks + private CaseWorkerProfileUpdateserviceImpl caseWorkerProfileUpdateservice; + @Mock + private CaseWorkerProfileRepository caseWorkerProfileRepository; + private CaseWorkerProfile caseWorkerProfile; + + @Mock + IJsrValidatorStaffProfile jsrValidatorStaffProfile; + + @Test + void testCaseWorkerProfileUpdateService() { + + + + doReturn(Optional.of(buildCaseWorkerProfile())) + .when(caseWorkerProfileRepository).findByCaseWorkerId("185a0254-ff80-458b-8f62-2a759788afd2"); + CaseWorkerProfile cwProfile = buildCaseWorkerProfile(); + cwProfile.setSuspended(false); + CaseWorkersProfileUpdationRequest caseWorkersProfileUpdationRequest = CaseWorkersProfileUpdationRequest + .caseWorkersProfileUpdationRequest().firstName("first_name") + .lastName("last_name") + .userId("185a0254-ff80-458b-8f62-2a759788afd2") + .emailId("CWR-func-test-user@test.com") + .suspended(false) + .build(); + when(caseWorkerProfileRepository.save(any())).thenReturn(cwProfile); + CaseWorkersProfileUpdationResponse caseWorkersProfileUpdationResponse = + new CaseWorkersProfileUpdationResponse(); + caseWorkersProfileUpdationResponse = caseWorkerProfileUpdateservice + .updateCaseWorkerProfile(caseWorkersProfileUpdationRequest); + assertEquals(caseWorkersProfileUpdationResponse.getUserId(),caseWorkersProfileUpdationRequest.getUserId()); + verify(jsrValidatorStaffProfile, times(1)).validateCaseWorkerUpdateRequest(any(), any()); + } + + @Test + void testCaseWorkerProfileUpdateServiceWhenCwIdNotPresnt() { + CaseWorkersProfileUpdationResponse caseWorkersProfileUpdationResponse = + new CaseWorkersProfileUpdationResponse(); + CaseWorkersProfileUpdationRequest caseWorkersProfileUpdationRequest = CaseWorkersProfileUpdationRequest + .caseWorkersProfileUpdationRequest().firstName("first_name") + .lastName("last_name") + .userId("185a0254-ff80-458b-8f62-2a759788afd2") + .emailId("CWR-func-test-user@test.com") + .build(); + doReturn(Optional.empty()) + .when(caseWorkerProfileRepository).findByCaseWorkerId(anyString()); + Assertions.assertThrows(ResourceNotFoundException.class, () -> + caseWorkerProfileUpdateservice.updateCaseWorkerProfile(caseWorkersProfileUpdationRequest)); + } + + CaseWorkerProfile buildCaseWorkerProfile() { + + caseWorkerProfile = new CaseWorkerProfile(); + caseWorkerProfile.setCaseWorkerId("185a0254-ff80-458b-8f62-2a759788afd2"); + caseWorkerProfile.setFirstName("CWFirstName"); + caseWorkerProfile.setLastName("CWLastName"); + caseWorkerProfile.setEmailId("CWR-func-test-user@test.com"); + caseWorkerProfile.setSuspended(true); + return caseWorkerProfile; + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/JsrValidateCaseWorkerUpdateRequest.java b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/JsrValidateCaseWorkerUpdateRequest.java new file mode 100644 index 000000000..9a0608353 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/JsrValidateCaseWorkerUpdateRequest.java @@ -0,0 +1,110 @@ +package uk.gov.hmcts.reform.cwrdapi.service.impl; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.InjectMocks; +import org.mockito.Spy; +import uk.gov.hmcts.reform.cwrdapi.config.EmailDomainPropertyInitiator; +import uk.gov.hmcts.reform.cwrdapi.controllers.advice.InvalidRequestException; +import uk.gov.hmcts.reform.cwrdapi.controllers.request.CaseWorkersProfileUpdationRequest; +import uk.gov.hmcts.reform.cwrdapi.oidc.JwtGrantedAuthoritiesConverter; +import uk.gov.hmcts.reform.cwrdapi.repository.StaffAuditRepository; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.MockitoAnnotations.openMocks; +import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.FIRST_NAME_INVALID; +import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.INVALID_EMAIL; +import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.LAST_NAME_INVALID; +import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.STAFF_PROFILE_CREATE; + +class JsrValidateCaseWorkerUpdateRequest { + + + @Spy + @InjectMocks + JsrValidatorStaffProfile jsrValidatorStaffProfile; + + StaffAuditRepository staffAuditRepository = mock(StaffAuditRepository.class); + + private JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = mock(JwtGrantedAuthoritiesConverter.class); + + @BeforeEach + void init() { + EmailDomainPropertyInitiator.emailDomains = "justice.gov.uk,DWP.GOV.UK,hmrc.gov.uk"; + openMocks(this); + jsrValidatorStaffProfile.initializeFactory(); + } + + @ParameterizedTest + @ValueSource(strings = {"", "abc.com", "test.gov", "user@gmail.com"}) + void testValidateStaffProfileEmail(String email) { + CaseWorkersProfileUpdationRequest profile = buildCaseWorkersProfileUpdationRequest(); + profile.setEmailId(email); + InvalidRequestException exception = Assertions.assertThrows(InvalidRequestException.class, () -> + jsrValidatorStaffProfile.validateCaseWorkerUpdateRequest(profile, STAFF_PROFILE_CREATE)); + assertThat(exception.getMessage()).contains(INVALID_EMAIL); + } + + @ParameterizedTest + @ValueSource(strings = {"123.name", "name&", "vilas_shelke", "vilas.:\"_shelke", "*()"}) + @DisplayName("staff profile invalid first name") + void testValidateStaffProfileInvalidFirstName(String name) { + CaseWorkersProfileUpdationRequest profile = buildCaseWorkersProfileUpdationRequest(); + profile.setFirstName(name); + InvalidRequestException exception = Assertions.assertThrows(InvalidRequestException.class, () -> + jsrValidatorStaffProfile.validateCaseWorkerUpdateRequest(profile, STAFF_PROFILE_CREATE)); + assertThat(exception.getMessage()).contains(FIRST_NAME_INVALID); + } + + @ParameterizedTest + @ValueSource(strings = {"123.name", "name&", "vilas_shelke", "vilas.:\"_shelke", "*()"}) + @DisplayName("staff profile invalid last name") + void testValidateStaffProfileInvalidLastName(String name) { + CaseWorkersProfileUpdationRequest profile = buildCaseWorkersProfileUpdationRequest(); + profile.setLastName(name); + + InvalidRequestException lastNameException = Assertions.assertThrows(InvalidRequestException.class, () -> + jsrValidatorStaffProfile.validateCaseWorkerUpdateRequest(profile, STAFF_PROFILE_CREATE)); + Assertions.assertNotNull(lastNameException.getLocalizedMessage()); + assertThat(lastNameException.getMessage()).contains(LAST_NAME_INVALID); + } + + @ParameterizedTest + @ValueSource(strings = {"123name", "IIIIVVI", "vilas-shelke", "Nando's", "Æâçdëøœoo", "Æmaze"}) + @DisplayName("staff profile valid first name") + void testValidateStaffProfileValidFirstName(String name) { + CaseWorkersProfileUpdationRequest profile = buildCaseWorkersProfileUpdationRequest(); + profile.setFirstName(name); + jsrValidatorStaffProfile.validateCaseWorkerUpdateRequest(profile, STAFF_PROFILE_CREATE); + verify(staffAuditRepository, times(0)).save(any()); + } + + @ParameterizedTest + @ValueSource(strings = {"123name", "IIIIVVI", "vilas-shelke", "Nando's", "Æâçdëøœoo", "Æmaze"}) + @DisplayName("staff profile valid last name") + void testValidateStaffProfileValidLastName(String name) { + CaseWorkersProfileUpdationRequest profile = buildCaseWorkersProfileUpdationRequest(); + profile.setLastName(name); + jsrValidatorStaffProfile.validateCaseWorkerUpdateRequest(profile, STAFF_PROFILE_CREATE); + verify(staffAuditRepository, times(0)).save(any()); + } + + CaseWorkersProfileUpdationRequest buildCaseWorkersProfileUpdationRequest() { + + CaseWorkersProfileUpdationRequest caseWorkersProfileUpdationRequest = CaseWorkersProfileUpdationRequest + .caseWorkersProfileUpdationRequest().firstName("firstname") + .lastName("lastname") + .userId("d1f888d5-8819-41d7-922e-2699444e1a47") + .emailId("cwr-func-test-user-7u7ph1em1k@justice.gov.uk") + .suspended(false) + .build(); + return caseWorkersProfileUpdationRequest; + } +} From b07cd9441558f4e3fb38037c745f8cc44a996615 Mon Sep 17 00:00:00 2001 From: sahitya-desireddy <93707379+sahitya-desireddy@users.noreply.github.com> Date: Thu, 15 Jun 2023 12:08:45 +0100 Subject: [PATCH 22/67] Support Unsuspend User from suspended state (#734) * Support Unsuspend User from suspended state * Cover sonar issues * Cover sonar issues * Cover sonar issues * Unit Test Coverage * Unit Test Coverage * Unit Test Coverage * Functional Test Coverage * Test Coverage * Test Coverage * Test Coverage * when in request suspend flag is not set * Adding suppression to remove cve issue * Removing unused status * functional test * checkstyle fix * checkstyle fix * sonar issues * Removing suppressions --- config/owasp/suppressions.xml | 4 - .../StaffRefUpdateProfileFunctionalTest.java | 136 +++++++++ .../request/StaffProfileCreationRequest.java | 6 +- .../service/impl/StaffRefDataServiceImpl.java | 12 +- .../impl/StaffRefDataServiceImplTest.java | 8 + ...taffRefDataUpdateStaffServiceImplTest.java | 269 +++++++++++++++++- 6 files changed, 413 insertions(+), 22 deletions(-) diff --git a/config/owasp/suppressions.xml b/config/owasp/suppressions.xml index 053e5e55a..bed068402 100644 --- a/config/owasp/suppressions.xml +++ b/config/owasp/suppressions.xml @@ -15,8 +15,4 @@ file name: launchdarkly-java-server-sdk-5.10.2.jar (shaded: org.yaml:snakeyaml:1 ^pkg:maven/org\.yaml/snakeyaml@.*$ CVE-2022-1471 - - Temporary suppression for org.apache.tomcat.embed please remove it once cve is fixed - CVE-2023-28709 - diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/StaffRefUpdateProfileFunctionalTest.java b/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/StaffRefUpdateProfileFunctionalTest.java index ff46fd1cc..09acfb995 100644 --- a/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/StaffRefUpdateProfileFunctionalTest.java +++ b/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/StaffRefUpdateProfileFunctionalTest.java @@ -18,7 +18,9 @@ import uk.gov.hmcts.reform.cwrdapi.controllers.request.StaffProfileCreationRequest; import uk.gov.hmcts.reform.cwrdapi.controllers.request.UserProfileCreationRequest; import uk.gov.hmcts.reform.cwrdapi.controllers.request.UserRequest; +import uk.gov.hmcts.reform.cwrdapi.controllers.response.SearchStaffUserByIdResponse; import uk.gov.hmcts.reform.cwrdapi.controllers.response.StaffProfileCreationResponse; +import uk.gov.hmcts.reform.cwrdapi.idam.IdamOpenIdClient; import uk.gov.hmcts.reform.cwrdapi.util.FeatureToggleConditionExtension; import uk.gov.hmcts.reform.cwrdapi.util.ToggleEnable; import uk.gov.hmcts.reform.lib.util.serenity5.SerenityTest; @@ -45,6 +47,7 @@ class StaffRefUpdateProfileFunctionalTest extends AuthorizationFunctionalTest { public static final String UPDATE_STAFF_PROFILE = "StaffRefDataController.updateStaffUserProfile"; + public static final String STAFF_PROFILE_URL = "/refdata/case-worker"; @Test @ToggleEnable(mapKey = UPDATE_STAFF_PROFILE, withFeature = true) @@ -567,6 +570,139 @@ void should_update_staff_profile_for_suspended_user_and_returns_status_200() { } + @Test + @ToggleEnable(mapKey = UPDATE_STAFF_PROFILE, withFeature = true) + @ExtendWith(FeatureToggleConditionExtension.class) + void should_update_staff_profile_for_Unsuspend_a_suspend_user_and_returns_status_200() { + + StaffProfileCreationRequest staffProfileCreationRequest = caseWorkerApiClient + .createStaffProfileCreationRequest(); + + Response response = caseWorkerApiClient.createStaffUserProfile(staffProfileCreationRequest); + + StaffProfileCreationResponse staffProfileResponse1 = response.getBody().as(StaffProfileCreationResponse.class); + assertThat(staffProfileResponse1).isNotNull(); + + assertThat(staffProfileCreationRequest.isSuspended()).isFalse(); + assertThat(staffProfileCreationRequest.getFirstName()).isEqualTo("StaffProfilefirstName"); + assertThat(staffProfileCreationRequest.getLastName()).isEqualTo("StaffProfilelastName"); + staffProfileCreationRequest.setSuspended(true); + response = caseWorkerApiClient.updateStaffUserProfile(staffProfileCreationRequest); + StaffProfileCreationResponse staffProfileResponse = response.getBody().as(StaffProfileCreationResponse.class); + + // validate SRD user and IDM user are same + var idamResponse = getIdamResponse(staffProfileResponse.getCaseWorkerId()); + assertFalse((Boolean) idamResponse.get("active")); + assertEquals(staffProfileResponse.getCaseWorkerId(), idamResponse.get("id")); + assertEquals(staffProfileCreationRequest.getFirstName(), idamResponse.get("forename")); + assertEquals(staffProfileCreationRequest.getLastName(), idamResponse.get("surname")); + assertEquals(staffProfileCreationRequest.getEmailId(), idamResponse.get("email")); + + // validate SRD user and UserProfile are same + //idamOpenIdClient.getUserByUserID("cwd-admin"); + UserProfileResponse upResponse = getUserProfileFromUp(staffProfileCreationRequest.getEmailId()); + assertEquals("SUSPENDED",upResponse.getIdamStatus()); + + // UnSuspend an User + staffProfileCreationRequest.setSuspended(false); + + response = caseWorkerApiClient.updateStaffUserProfile(staffProfileCreationRequest); + staffProfileResponse = response.getBody().as(StaffProfileCreationResponse.class); + + assertThat(response).isNotNull(); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK.value()); + assertThat(staffProfileResponse).isNotNull(); + assertThat(staffProfileResponse.getCaseWorkerId()).isNotBlank(); + //Verify Unsuspend User in UserProfile + UserProfileResponse upResponse1 = getUserProfileFromUp(staffProfileCreationRequest.getEmailId()); + assertEquals("ACTIVE",upResponse1.getIdamStatus()); + + //Verify Unsuspend User in Idam + var idamResponse1 = getIdamResponse(staffProfileResponse.getCaseWorkerId()); + assertTrue((Boolean) idamResponse1.get("active")); + assertEquals(staffProfileResponse.getCaseWorkerId(), idamResponse1.get("id")); + assertEquals(staffProfileCreationRequest.getFirstName(), idamResponse1.get("forename")); + assertEquals(staffProfileCreationRequest.getLastName(), idamResponse1.get("surname")); + assertEquals(staffProfileCreationRequest.getEmailId(), idamResponse1.get("email")); + + } + + @Test + @ToggleEnable(mapKey = UPDATE_STAFF_PROFILE, withFeature = true) + @ExtendWith(FeatureToggleConditionExtension.class) + void should_update_staff_profile_for_Suspend_Not_SetIn_Request_Profile_status_Suspend() { + + StaffProfileCreationRequest staffProfileCreationRequest = caseWorkerApiClient + .createStaffProfileCreationRequest(); + + Response response = caseWorkerApiClient.createStaffUserProfile(staffProfileCreationRequest); + + StaffProfileCreationResponse staffProfileResponse1 = response.getBody().as(StaffProfileCreationResponse.class); + assertThat(staffProfileResponse1).isNotNull(); + + assertThat(staffProfileCreationRequest.isSuspended()).isFalse(); + assertThat(staffProfileCreationRequest.getFirstName()).isEqualTo("StaffProfilefirstName"); + assertThat(staffProfileCreationRequest.getLastName()).isEqualTo("StaffProfilelastName"); + staffProfileCreationRequest.setSuspended(true); + response = caseWorkerApiClient.updateStaffUserProfile(staffProfileCreationRequest); + StaffProfileCreationResponse staffProfileResponse = response.getBody().as(StaffProfileCreationResponse.class); + + // validate SRD user and IDM user are same + var idamResponse = getIdamResponse(staffProfileResponse.getCaseWorkerId()); + assertFalse((Boolean) idamResponse.get("active")); + assertEquals(staffProfileResponse.getCaseWorkerId(), idamResponse.get("id")); + assertEquals(staffProfileCreationRequest.getFirstName(), idamResponse.get("forename")); + assertEquals(staffProfileCreationRequest.getLastName(), idamResponse.get("surname")); + assertEquals(staffProfileCreationRequest.getEmailId(), idamResponse.get("email")); + + // validate SRD user and UserProfile are same + //idamOpenIdClient.getUserByUserID("cwd-admin"); + UserProfileResponse upResponse = getUserProfileFromUp(staffProfileCreationRequest.getEmailId()); + assertEquals("SUSPENDED",upResponse.getIdamStatus()); + + // UnSuspend an User + staffProfileCreationRequest.setSuspended(null); + + response = caseWorkerApiClient.updateStaffUserProfile(staffProfileCreationRequest); + staffProfileResponse = response.getBody().as(StaffProfileCreationResponse.class); + + assertThat(response).isNotNull(); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK.value()); + assertThat(staffProfileResponse).isNotNull(); + assertThat(staffProfileResponse.getCaseWorkerId()).isNotBlank(); + //Verify Unsuspend User in UserProfile + UserProfileResponse upResponse1 = getUserProfileFromUp(staffProfileCreationRequest.getEmailId()); + assertEquals("SUSPENDED",upResponse1.getIdamStatus()); + + //Verify Unsuspend User in Idam + + var idamResponse1 = getIdamResponse(staffProfileResponse.getCaseWorkerId()); + assertFalse((Boolean) idamResponse1.get("active")); + assertEquals(staffProfileResponse.getCaseWorkerId(), idamResponse1.get("id")); + assertEquals(staffProfileCreationRequest.getFirstName(), idamResponse1.get("forename")); + assertEquals(staffProfileCreationRequest.getLastName(), idamResponse1.get("surname")); + assertEquals(staffProfileCreationRequest.getEmailId(), idamResponse1.get("email")); + + String firstCaseworkerId = staffProfileResponse.getCaseWorkerId(); + + // Fetching cwp from db to verify the profile suspend status not changed when suspend flag is not set in Request + + IdamOpenIdClient.cwdStaffAdminUserToken = null; + Response fetchResponse = caseWorkerApiClient.getMultipleAuthHeadersInternal(ROLE_STAFF_ADMIN) + .get(STAFF_PROFILE_URL + "/profile/search-by-id?id=" + firstCaseworkerId) + .andReturn(); + fetchResponse.then() + .assertThat() + .statusCode(200); + + SearchStaffUserByIdResponse caseWorkerProfile = + fetchResponse.getBody().as(SearchStaffUserByIdResponse.class); + + assertThat(caseWorkerProfile.isSuspended()).isTrue(); + + } + + @AfterAll public static void cleanUpTestData() { try { diff --git a/src/main/java/uk/gov/hmcts/reform/cwrdapi/controllers/request/StaffProfileCreationRequest.java b/src/main/java/uk/gov/hmcts/reform/cwrdapi/controllers/request/StaffProfileCreationRequest.java index 3eed002b1..b49900ff6 100644 --- a/src/main/java/uk/gov/hmcts/reform/cwrdapi/controllers/request/StaffProfileCreationRequest.java +++ b/src/main/java/uk/gov/hmcts/reform/cwrdapi/controllers/request/StaffProfileCreationRequest.java @@ -71,7 +71,7 @@ public class StaffProfileCreationRequest { @JsonProperty("suspended") @JsonInclude(JsonInclude.Include.NON_NULL) - private boolean suspended; + private Boolean suspended; @JsonProperty("case_allocator") @JsonInclude(JsonInclude.Include.NON_NULL) @@ -94,4 +94,8 @@ public class StaffProfileCreationRequest { @JsonProperty("is_resend_invite") private boolean resendInvite; + public boolean isSuspended() { + return suspended != null ? suspended.booleanValue() : false; + } + } diff --git a/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImpl.java b/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImpl.java index 6bac7ade6..cfb947514 100644 --- a/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImpl.java +++ b/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImpl.java @@ -105,6 +105,7 @@ import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.STAFF_ADMIN; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.STAFF_PROFILE_CREATE; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.STAFF_PROFILE_UPDATE; +import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.STATUS_ACTIVE; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.TASK_SUPERVISOR; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.UP_FAILURE_ROLES; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.UP_STATUS_PENDING; @@ -721,9 +722,6 @@ private CaseWorkerProfile validateStaffProfileForUpdate(StaffProfileCreationRequ } else if (UP_STATUS_PENDING.equals(userProfileResponse.getIdamStatus())) { throw new StaffReferenceException(HttpStatus.BAD_REQUEST, UP_FAILURE_ROLES, UP_FAILURE_ROLES); - } else if (IDAM_STATUS_SUSPENDED.equals(userProfileResponse.getIdamStatus()) && !profileRequest.isSuspended()) { - throw new StaffReferenceException(HttpStatus.BAD_REQUEST, UP_FAILURE_ROLES, - UP_FAILURE_ROLES); } return caseWorkerProfile; } @@ -777,13 +775,18 @@ public CaseWorkerProfile processExistingCaseWorkers( StaffProfileCreationRequest cwUiRequest, CaseWorkerProfile caseWorkerProfiles) { CaseWorkerProfile filteredProfile = null; - + if (cwUiRequest.getSuspended() == null) { + cwUiRequest.setSuspended(caseWorkerProfiles.getSuspended()); + } if (cwUiRequest.isSuspended()) { //when existing profile with delete flag is true in request then suspend user if (isUserSuspended(UserProfileUpdatedData.builder().idamStatus(IDAM_STATUS_SUSPENDED).build(), caseWorkerProfiles.getCaseWorkerId(), ORIGIN_EXUI)) { caseWorkerProfiles.setSuspended(true); } + } else if (Boolean.TRUE.equals(caseWorkerProfiles.getSuspended()) && isUserSuspended(UserProfileUpdatedData + .builder().idamStatus(STATUS_ACTIVE).build(), caseWorkerProfiles.getCaseWorkerId(), ORIGIN_EXUI)) { + caseWorkerProfiles.setSuspended(cwUiRequest.isSuspended()); } filteredProfile = updateSidamRoles(caseWorkerProfiles,cwUiRequest); return filteredProfile; @@ -910,7 +913,6 @@ public boolean updateUserRolesInIdam(StaffProfileCreationRequest cwrProfileRequ Set idamRolesCwr = new HashSet<>(); - if (cwrProfileRequest.isStaffAdmin()) { idamRolesCwr.add(ROLE_CWD_USER); idamRolesCwr.add(ROLE_STAFF_ADMIN); diff --git a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImplTest.java b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImplTest.java index b05369fb8..415642929 100644 --- a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImplTest.java +++ b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImplTest.java @@ -1031,6 +1031,7 @@ void test_update_staff_profile_with_changed_values() throws JsonProcessingExcept caseWorkerProfile.setFirstName("CWFirstName"); caseWorkerProfile.setLastName("CWLastName"); caseWorkerProfile.setEmailId("cwr-func-test-user@test.com"); + caseWorkerProfile.setSuspended(true); when(caseWorkerProfileRepository.findByEmailId(any())).thenReturn(caseWorkerProfile); @@ -1072,6 +1073,13 @@ void test_update_staff_profile_with_changed_values() throws JsonProcessingExcept defaultCharset()) .status(200).build()); + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) + .thenReturn(Response.builder() + .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), + null)).body(mapper.writeValueAsString(userProfileResponse), + defaultCharset()) + .status(200).build()); + StaffProfileCreationRequest staffProfileCreationRequest = getStaffProfileUpdateRequest(); StaffProfileCreationResponse staffProfileCreationResponse = staffRefDataServiceImpl diff --git a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataUpdateStaffServiceImplTest.java b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataUpdateStaffServiceImplTest.java index e8b66af91..4f354a3bc 100644 --- a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataUpdateStaffServiceImplTest.java +++ b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataUpdateStaffServiceImplTest.java @@ -74,6 +74,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.IDAM_STATUS; +import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.IDAM_STATUS_ACTIVE; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.IDAM_STATUS_SUSPENDED; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.IDAM_STATUS_USER_PROFILE; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.ORIGIN_EXUI; @@ -90,8 +91,6 @@ class StaffRefDataUpdateStaffServiceImplTest { @Mock private SkillRepository skillRepository; - - @InjectMocks private StaffRefDataServiceImpl staffRefDataServiceImpl; @Mock @@ -475,6 +474,7 @@ void test_updateStaffProfile_with_changed_values_UpAndIdam_Status_Suspended() th caseWorkerProfile.setFirstName("CWFirstName"); caseWorkerProfile.setLastName("CWLastName"); caseWorkerProfile.setEmailId("cwr-func-test-user@test.com"); + caseWorkerProfile.setSuspended(true); when(caseWorkerProfileRepository.findByEmailId(any())).thenReturn(caseWorkerProfile); @@ -508,11 +508,22 @@ void test_updateStaffProfile_with_changed_values_UpAndIdam_Status_Suspended() th roleAdditionResponse.setIdamMessage("success"); StaffProfileCreationRequest staffProfileCreationRequest = getStaffProfileUpdateRequest(); - StaffReferenceException thrown = Assertions.assertThrows(StaffReferenceException.class, () -> { - staffRefDataServiceImpl.updateStaffProfile(staffProfileCreationRequest); - }); - assertThat(thrown.getMessage()).contains("An update to the user is not possible at this moment. Please " - + "try again later."); + when(caseWorkerProfileRepository.save(any())).thenReturn(caseWorkerProfile); + + when(userProfileFeignClient.modifyUserRoles(any(), any(), any())) + .thenReturn(Response.builder() + .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), + null)).body(mapper.writeValueAsString(userProfileRolesResponse), + defaultCharset()) + .status(200).build()); + + StaffProfileCreationResponse staffProfileCreationResponse = staffRefDataServiceImpl + .updateStaffProfile(staffProfileCreationRequest); + + + assertThat(staffProfileCreationResponse).isNotNull(); + assertThat(staffProfileCreationResponse.getCaseWorkerId()).isEqualTo("CWID1"); + } @Test @@ -524,6 +535,7 @@ void test_updateStaffProfile_with_changed_values() throws JsonProcessingExceptio caseWorkerProfile.setFirstName("CWFirstName"); caseWorkerProfile.setLastName("CWLastName"); caseWorkerProfile.setEmailId("cwr-func-test-user@test.com"); + caseWorkerProfile.setSuspended(true); when(caseWorkerProfileRepository.findByEmailId(any())).thenReturn(caseWorkerProfile); @@ -585,7 +597,7 @@ void test_updateStaffProfile_with_changed_values_with_exception() throws JsonPro caseWorkerProfile.setFirstName("CWFirstName"); caseWorkerProfile.setLastName("CWLastName"); caseWorkerProfile.setEmailId("cwr-func-test-user@test.com"); - + caseWorkerProfile.setSuspended(true); List caseWorkerProfiles = singletonList(caseWorkerProfile); @@ -651,7 +663,7 @@ void test_updateStaffProfile_with_changed_values_with_exception() throws JsonPro assertThat(caseWorkerProfile1).isNotNull(); - verify(userProfileFeignClient, times(1)).modifyUserRoles(any(), any(), any()); + verify(userProfileFeignClient, times(2)).modifyUserRoles(any(), any(), any()); verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any(), any()); @@ -1107,7 +1119,6 @@ void test_check_staff_profile_for_update() throws JsonProcessingException { assertThat(thrown.getStatus().value()).isEqualTo(HttpStatus.NOT_FOUND.value()); assertThat(thrown.getErrorDescription()).isEqualTo(PROFILE_NOT_PRESENT_IN_SRD); - } @Test @@ -1121,6 +1132,7 @@ void test_processExistingCaseWorkers() throws JsonProcessingException { caseWorkerProfile.setFirstName("CWFirstName"); caseWorkerProfile.setLastName("CWLastName"); caseWorkerProfile.setEmailId("cwr-func-test-user@test.com"); + caseWorkerProfile.setSuspended(true); UserProfileResponse userProfileResponse = new UserProfileResponse(); userProfileResponse.setIdamId("12345678"); @@ -1163,10 +1175,242 @@ void test_processExistingCaseWorkers() throws JsonProcessingException { assertNotNull(caseWorkerProfile1); - verify(userProfileFeignClient, times(1)).modifyUserRoles(any(), any(), any()); + verify(userProfileFeignClient, times(2)).modifyUserRoles(any(), any(), any()); + verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any(), any()); + } + + @Test + void test_processExistingCaseWorkerWhenUserSuspendScenarioSupportUnsuspendNegatve() throws JsonProcessingException { + + staffProfileAuditService.saveStaffAudit(AuditStatus.FAILURE, null, + "1234", staffProfileCreationRequest,STAFF_PROFILE_UPDATE); + + CaseWorkerProfile caseWorkerProfile = new CaseWorkerProfile(); + caseWorkerProfile.setCaseWorkerId("CWID1"); + caseWorkerProfile.setFirstName("CWFirstName"); + caseWorkerProfile.setLastName("CWLastName"); + caseWorkerProfile.setEmailId("cwr-func-test-user@test.com"); + caseWorkerProfile.setSuspended(true); + + staffProfileCreationRequest.setSuspended(false); + + UserProfileResponse userProfileResponse = new UserProfileResponse(); + userProfileResponse.setIdamId("12345678"); + List roles = Arrays.asList("IdamRole1", "IdamRole4"); + userProfileResponse.setIdamStatus(IDAM_STATUS_SUSPENDED); + + userProfileResponse.setRoles(roles); + userProfileResponse.setFirstName("testFNChanged"); + userProfileResponse.setLastName("testLNChanged"); + + UserProfileRolesResponse userProfileRolesResponse = new UserProfileRolesResponse(); + RoleAdditionResponse roleAdditionResponse = new RoleAdditionResponse(); + roleAdditionResponse.setIdamStatusCode("201"); + userProfileRolesResponse.setRoleAdditionResponse(roleAdditionResponse); + roleAdditionResponse.setIdamMessage("success"); + + when(userProfileFeignClient.modifyUserRoles(any(), any(), any())) + .thenReturn(Response.builder() + .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), + null)).body(mapper.writeValueAsString(userProfileRolesResponse), + defaultCharset()) + .status(404).build()); + + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) + .thenReturn(Response.builder() + .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), + null)).body(mapper.writeValueAsString(userProfileResponse), + defaultCharset()) + .status(200).build()); + + CaseWorkerProfile caseWorkerProfile1 = + staffRefDataServiceImpl.processExistingCaseWorkers(staffProfileCreationRequest, caseWorkerProfile); + + assertNotNull(caseWorkerProfile1); + + verify(userProfileFeignClient, times(2)).modifyUserRoles(any(), any(), any()); + verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any(), any()); + + + } + + + @Test + void test_processExistingCaseWorkersWhenUserSuspendToSupportUnsuspendPositive() throws JsonProcessingException { + + staffProfileAuditService.saveStaffAudit(AuditStatus.FAILURE, null, + "1234", staffProfileCreationRequest,STAFF_PROFILE_UPDATE); + + CaseWorkerProfile caseWorkerProfile = new CaseWorkerProfile(); + caseWorkerProfile.setCaseWorkerId("CWID1"); + caseWorkerProfile.setFirstName("CWFirstName"); + caseWorkerProfile.setLastName("CWLastName"); + caseWorkerProfile.setEmailId("cwr-func-test-user@test.com"); + caseWorkerProfile.setSuspended(true); + + staffProfileCreationRequest.setSuspended(false); + + UserProfileResponse userProfileResponse = new UserProfileResponse(); + userProfileResponse.setIdamId("12345678"); + List roles = Arrays.asList("IdamRole1", "IdamRole4"); + userProfileResponse.setIdamStatus(IDAM_STATUS_ACTIVE); + + userProfileResponse.setRoles(roles); + userProfileResponse.setFirstName("testFNChanged"); + userProfileResponse.setLastName("testLNChanged"); + + UserProfileRolesResponse userProfileRolesResponse = new UserProfileRolesResponse(); + RoleAdditionResponse roleAdditionResponse = new RoleAdditionResponse(); + roleAdditionResponse.setIdamStatusCode("201"); + userProfileRolesResponse.setRoleAdditionResponse(roleAdditionResponse); + roleAdditionResponse.setIdamMessage("success"); + + AttributeResponse attributeResponse = new AttributeResponse(); + attributeResponse.setIdamStatusCode(HttpStatus.OK.value()); + userProfileRolesResponse.setAttributeResponse(attributeResponse); + + when(userProfileFeignClient.modifyUserRoles(any(), any(), any())) + .thenReturn(Response.builder() + .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), + null)).body(mapper.writeValueAsString(userProfileRolesResponse), + defaultCharset()) + .status(200).build()); + + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) + .thenReturn(Response.builder() + .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), + null)).body(mapper.writeValueAsString(userProfileResponse), + defaultCharset()) + .status(200).build()); + + CaseWorkerProfile caseWorkerProfile1 = + staffRefDataServiceImpl.processExistingCaseWorkers(staffProfileCreationRequest, caseWorkerProfile); + + assertNotNull(caseWorkerProfile1); + + verify(userProfileFeignClient, times(2)).modifyUserRoles(any(), any(), any()); + verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any(), any()); + + + } + + @Test + void test_processExistingCaseWorkersWhenSuspendNotSetInRequestAndUserIsSuspended() throws JsonProcessingException { + + staffProfileAuditService.saveStaffAudit(AuditStatus.FAILURE, null, + "1234", staffProfileCreationRequest,STAFF_PROFILE_UPDATE); + + CaseWorkerProfile caseWorkerProfile = new CaseWorkerProfile(); + caseWorkerProfile.setCaseWorkerId("CWID1"); + caseWorkerProfile.setFirstName("CWFirstName"); + caseWorkerProfile.setLastName("CWLastName"); + caseWorkerProfile.setEmailId("cwr-func-test-user@test.com"); + caseWorkerProfile.setSuspended(true); + + staffProfileCreationRequest.setSuspended(null); + + UserProfileResponse userProfileResponse = new UserProfileResponse(); + userProfileResponse.setIdamId("12345678"); + List roles = Arrays.asList("IdamRole1", "IdamRole4"); + userProfileResponse.setIdamStatus(IDAM_STATUS_SUSPENDED); + + userProfileResponse.setRoles(roles); + userProfileResponse.setFirstName("testFNChanged"); + userProfileResponse.setLastName("testLNChanged"); + + UserProfileRolesResponse userProfileRolesResponse = new UserProfileRolesResponse(); + RoleAdditionResponse roleAdditionResponse = new RoleAdditionResponse(); + roleAdditionResponse.setIdamStatusCode("201"); + userProfileRolesResponse.setRoleAdditionResponse(roleAdditionResponse); + roleAdditionResponse.setIdamMessage("success"); + + AttributeResponse attributeResponse = new AttributeResponse(); + attributeResponse.setIdamStatusCode(HttpStatus.OK.value()); + userProfileRolesResponse.setAttributeResponse(attributeResponse); + + when(userProfileFeignClient.modifyUserRoles(any(), any(), any())) + .thenReturn(Response.builder() + .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), + null)).body(mapper.writeValueAsString(userProfileRolesResponse), + defaultCharset()) + .status(200).build()); + + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) + .thenReturn(Response.builder() + .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), + null)).body(mapper.writeValueAsString(userProfileResponse), + defaultCharset()) + .status(200).build()); + + CaseWorkerProfile caseWorkerProfile1 = + staffRefDataServiceImpl.processExistingCaseWorkers(staffProfileCreationRequest, caseWorkerProfile); + + assertNotNull(caseWorkerProfile1); + assertThat(caseWorkerProfile1.getSuspended()).isTrue(); + + verify(userProfileFeignClient, times(2)).modifyUserRoles(any(), any(), any()); verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any(), any()); + } + + @Test + void test_processExistingCaseWorkersWhenSuspendNotSetInRequestAndUserIsActive() throws JsonProcessingException { + + staffProfileAuditService.saveStaffAudit(AuditStatus.FAILURE, null, + "1234", staffProfileCreationRequest,STAFF_PROFILE_UPDATE); + + CaseWorkerProfile caseWorkerProfile = new CaseWorkerProfile(); + caseWorkerProfile.setCaseWorkerId("CWID1"); + caseWorkerProfile.setFirstName("CWFirstName"); + caseWorkerProfile.setLastName("CWLastName"); + caseWorkerProfile.setEmailId("cwr-func-test-user@test.com"); + caseWorkerProfile.setSuspended(false); + + staffProfileCreationRequest.setSuspended(null); + + UserProfileResponse userProfileResponse = new UserProfileResponse(); + userProfileResponse.setIdamId("12345678"); + List roles = Arrays.asList("IdamRole1", "IdamRole4"); + userProfileResponse.setIdamStatus(IDAM_STATUS_ACTIVE); + + userProfileResponse.setRoles(roles); + userProfileResponse.setFirstName("testFNChanged"); + userProfileResponse.setLastName("testLNChanged"); + + UserProfileRolesResponse userProfileRolesResponse = new UserProfileRolesResponse(); + RoleAdditionResponse roleAdditionResponse = new RoleAdditionResponse(); + roleAdditionResponse.setIdamStatusCode("201"); + userProfileRolesResponse.setRoleAdditionResponse(roleAdditionResponse); + roleAdditionResponse.setIdamMessage("success"); + + AttributeResponse attributeResponse = new AttributeResponse(); + attributeResponse.setIdamStatusCode(HttpStatus.OK.value()); + userProfileRolesResponse.setAttributeResponse(attributeResponse); + + when(userProfileFeignClient.modifyUserRoles(any(), any(), any())) + .thenReturn(Response.builder() + .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), + null)).body(mapper.writeValueAsString(userProfileRolesResponse), + defaultCharset()) + .status(200).build()); + + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) + .thenReturn(Response.builder() + .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), + null)).body(mapper.writeValueAsString(userProfileResponse), + defaultCharset()) + .status(200).build()); + + CaseWorkerProfile caseWorkerProfile1 = + staffRefDataServiceImpl.processExistingCaseWorkers(staffProfileCreationRequest, caseWorkerProfile); + + assertNotNull(caseWorkerProfile1); + assertThat(caseWorkerProfile1.getSuspended()).isFalse(); + + verify(userProfileFeignClient, times(1)).modifyUserRoles(any(), any(), any()); + verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any(), any()); + } @@ -1320,6 +1564,7 @@ void test_processExistingCaseWorkers_suspendedUsers() throws JsonProcessingExcep caseWorkerProfile.setFirstName("CWFirstName"); caseWorkerProfile.setLastName("CWLastName"); caseWorkerProfile.setEmailId("cwr-func-test-user@test.com"); + caseWorkerProfile.setSuspended(true); UserProfileResponse userProfileResponse = new UserProfileResponse(); userProfileResponse.setIdamId("12345678"); @@ -1362,7 +1607,7 @@ void test_processExistingCaseWorkers_suspendedUsers() throws JsonProcessingExcep assertNotNull(caseWorkerProfile1); - verify(userProfileFeignClient, times(1)).modifyUserRoles(any(), any(), any()); + verify(userProfileFeignClient, times(2)).modifyUserRoles(any(), any(), any()); verify(userProfileFeignClient, times(1)).getUserProfileWithRolesById(any(), any()); From aa4fe8dd3311148ead4cbf4063542c7a47db3f3b Mon Sep 17 00:00:00 2001 From: arshinsalim <97666178+arshinsalim@users.noreply.github.com> Date: Thu, 22 Jun 2023 09:53:35 +0100 Subject: [PATCH 23/67] Increase Mutation coverage for Staff Reference Data (#741) * rd caseworker mutation upgrade --- build.gradle | 1 - .../reform/cwrdapi/config/WebMvcConfig.java | 2 +- .../domain/UserProfileRolesResponseTest.java | 12 +- .../cwrdapi/domain/CaseWorkerSkillTest.java | 2 + .../domain/RoleDeletionResponseTest.java | 27 ++++ .../cwrdapi/domain/ServiceResponseTest.java | 33 +++++ .../cwrdapi/domain/SkillResponseTest.java | 61 +++++++++ .../domain/UserProfileUpdatedDataTest.java | 4 + .../impl/CaseWorkerServiceFacadeImplTest.java | 39 ++++++ .../impl/JsrValidatorStaffProfileTest.java | 1 + .../StaffProfileAuditServiceImplTest.java | 119 ++++++++++++++++++ .../impl/StaffRefDataServiceImplTest.java | 54 +++++++- ...taffRefDataUpdateStaffServiceImplTest.java | 45 +++++++ 13 files changed, 391 insertions(+), 9 deletions(-) create mode 100644 src/test/java/uk/gov/hmcts/reform/cwrdapi/domain/RoleDeletionResponseTest.java create mode 100644 src/test/java/uk/gov/hmcts/reform/cwrdapi/domain/ServiceResponseTest.java create mode 100644 src/test/java/uk/gov/hmcts/reform/cwrdapi/domain/SkillResponseTest.java diff --git a/build.gradle b/build.gradle index b470b8191..5b5335b16 100644 --- a/build.gradle +++ b/build.gradle @@ -476,7 +476,6 @@ dependencies { testImplementation 'info.solidsoft.gradle.pitest:gradle-pitest-plugin:1.7.0' testImplementation 'org.codehaus.sonar-plugins:sonar-pitest-plugin:0.5' - testImplementation 'io.github.openfeign:feign-jackson:12.1' testImplementation group: 'com.github.mifmif', name: 'generex', version: '1.0.2' diff --git a/src/main/java/uk/gov/hmcts/reform/cwrdapi/config/WebMvcConfig.java b/src/main/java/uk/gov/hmcts/reform/cwrdapi/config/WebMvcConfig.java index acaf13e0e..af67f1682 100644 --- a/src/main/java/uk/gov/hmcts/reform/cwrdapi/config/WebMvcConfig.java +++ b/src/main/java/uk/gov/hmcts/reform/cwrdapi/config/WebMvcConfig.java @@ -30,7 +30,7 @@ public void addInterceptors(InterceptorRegistry registry) { //Launch Darkly Feature Toggle registry.addInterceptor(featureConditionEvaluation) - .addPathPatterns("/refdata/case-worker/users/fetchUsersById", + .addPathPatterns("/refdata/case-worker", "/refdata/case-worker/idam-roles-mapping", "/refdata/case-worker/users", "/refdata/case-worker/users/sync", diff --git a/src/test/java/uk/gov/hmcts/reform/cwrdapi/client/domain/UserProfileRolesResponseTest.java b/src/test/java/uk/gov/hmcts/reform/cwrdapi/client/domain/UserProfileRolesResponseTest.java index 349a73e3a..02f54c88b 100644 --- a/src/test/java/uk/gov/hmcts/reform/cwrdapi/client/domain/UserProfileRolesResponseTest.java +++ b/src/test/java/uk/gov/hmcts/reform/cwrdapi/client/domain/UserProfileRolesResponseTest.java @@ -2,6 +2,9 @@ import org.junit.jupiter.api.Test; +import java.util.ArrayList; +import java.util.List; + import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -14,19 +17,26 @@ void testLocation() { AttributeResponse attributeResponse = new AttributeResponse(); attributeResponse.setIdamMessage("OK"); attributeResponse.setIdamStatusCode(200); - + RoleDeletionResponse roleDeletionResponse = new RoleDeletionResponse(); + roleDeletionResponse.setRoleName("role"); + roleDeletionResponse.setIdamMessage("message"); + roleDeletionResponse.setIdamStatusCode("OK"); RoleAdditionResponse roleAdditionResponse = new RoleAdditionResponse(); roleAdditionResponse.setIdamMessage("OK"); roleAdditionResponse.setIdamStatusCode("200"); + List roleDeletionResponses = new ArrayList<>(); + roleDeletionResponses.add(roleDeletionResponse); UserProfileRolesResponse userProfileRolesResponse = new UserProfileRolesResponse(); userProfileRolesResponse.setAttributeResponse(attributeResponse); userProfileRolesResponse.setRoleAdditionResponse(roleAdditionResponse); + userProfileRolesResponse.setRoleDeletionResponse(roleDeletionResponses); assertNotNull(userProfileRolesResponse); assertFalse(attributeResponse.getIdamMessage().isEmpty()); assertThat(userProfileRolesResponse.getAttributeResponse(), is(attributeResponse)); assertThat(userProfileRolesResponse.getRoleAdditionResponse(), is(roleAdditionResponse)); + assertThat(userProfileRolesResponse.getRoleDeletionResponse(), is(roleDeletionResponses)); } } diff --git a/src/test/java/uk/gov/hmcts/reform/cwrdapi/domain/CaseWorkerSkillTest.java b/src/test/java/uk/gov/hmcts/reform/cwrdapi/domain/CaseWorkerSkillTest.java index fe88d79e3..fcbad2b29 100644 --- a/src/test/java/uk/gov/hmcts/reform/cwrdapi/domain/CaseWorkerSkillTest.java +++ b/src/test/java/uk/gov/hmcts/reform/cwrdapi/domain/CaseWorkerSkillTest.java @@ -22,6 +22,7 @@ void testCaseWorkerSkill() { caseWorkerSkill.setCreatedDate(LocalDateTime.now()); caseWorkerSkill.setLastUpdate(LocalDateTime.now()); caseWorkerSkill.setCaseWorkerProfile(new CaseWorkerProfile()); + caseWorkerSkill.setCaseWorkerSkill(new CaseWorkerSkill()); assertNotNull(caseWorkerSkill); @@ -29,6 +30,7 @@ void testCaseWorkerSkill() { assertNotNull(caseWorkerSkill.getCaseWorkerProfile()); assertNotNull(caseWorkerSkill.getCreatedDate()); assertNotNull(caseWorkerSkill.getLastUpdate()); + assertNotNull(caseWorkerSkill.getCaseWorkerSkill()); assertThat(caseWorkerSkill.getCaseWorkerId(), is("case_worker_id")); assertThat(caseWorkerSkill.getSkillId(), is(1L)); diff --git a/src/test/java/uk/gov/hmcts/reform/cwrdapi/domain/RoleDeletionResponseTest.java b/src/test/java/uk/gov/hmcts/reform/cwrdapi/domain/RoleDeletionResponseTest.java new file mode 100644 index 000000000..519f9b1d3 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/cwrdapi/domain/RoleDeletionResponseTest.java @@ -0,0 +1,27 @@ +package uk.gov.hmcts.reform.cwrdapi.domain; + +import org.junit.jupiter.api.Test; +import uk.gov.hmcts.reform.cwrdapi.client.domain.RoleDeletionResponse; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.junit.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class RoleDeletionResponseTest { + + @Test + void testSkillResposne_for_valid_data() { + + RoleDeletionResponse roleDeletionResponse = new RoleDeletionResponse(); + + roleDeletionResponse.setRoleName("role"); + roleDeletionResponse.setIdamStatusCode("active"); + roleDeletionResponse.setIdamMessage("message"); + + assertNotNull(roleDeletionResponse); + assertThat(roleDeletionResponse.getRoleName(),is("role")); + assertThat(roleDeletionResponse.getIdamMessage(), is("message")); + assertThat(roleDeletionResponse.getIdamStatusCode(), is("active")); + + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/cwrdapi/domain/ServiceResponseTest.java b/src/test/java/uk/gov/hmcts/reform/cwrdapi/domain/ServiceResponseTest.java new file mode 100644 index 000000000..9a8de15d8 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/cwrdapi/domain/ServiceResponseTest.java @@ -0,0 +1,33 @@ +package uk.gov.hmcts.reform.cwrdapi.domain; + +import org.junit.jupiter.api.Test; +import uk.gov.hmcts.reform.cwrdapi.client.domain.ServiceResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ServiceResponseTest { + + @Test + void testSkillResposne_for_tostring_builder() { + + ServiceResponse serviceResponse = ServiceResponse.builder() + .service("testservice") + .serviceCode("testServiceCode") + .build(); + + assertNotNull(serviceResponse); + assertEquals("testservice",serviceResponse.getService()); + assertEquals("testServiceCode",serviceResponse.getServiceCode()); + + + String description = ServiceResponse.builder() + .serviceCode("testServiceCode").toString(); + + + assertTrue(description + .contains("ServiceResponse.ServiceResponseBuilder(service=null, serviceCode=testServiceCode)")); + + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/cwrdapi/domain/SkillResponseTest.java b/src/test/java/uk/gov/hmcts/reform/cwrdapi/domain/SkillResponseTest.java new file mode 100644 index 000000000..2082b9d46 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/cwrdapi/domain/SkillResponseTest.java @@ -0,0 +1,61 @@ +package uk.gov.hmcts.reform.cwrdapi.domain; + +import org.junit.jupiter.api.Test; +import uk.gov.hmcts.reform.cwrdapi.client.domain.SkillResponse; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.junit.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SkillResponseTest { + + + @Test + void testSkillResposne_for_valid_data() { + + SkillResponse skillResponse = new SkillResponse(); + skillResponse.setSkillId(123L); + skillResponse.setDescription("testresponsedescription"); + + assertNotNull(skillResponse); + assertThat(skillResponse.getSkillId(),is(123L)); + assertThat(skillResponse.getDescription(), is("testresponsedescription")); + + } + + @Test + void testSkillResposne_for_empty_data() { + + SkillResponse skillResponse = new SkillResponse(); + skillResponse.setSkillId(123L); + skillResponse.setDescription(" "); + + assertNotNull(skillResponse); + assertThat(skillResponse.getSkillId(),is(123L)); + assertThat(skillResponse.getDescription(), is(" ")); + + } + + + @Test + void testSkillResposne_for_tostring_builder() { + + SkillResponse skillResponse = SkillResponse.builder() + .skillId(123L) + .description("testresponsedescription") + .build(); + + assertNotNull(skillResponse); + assertEquals(123L, skillResponse.getSkillId()); + assertEquals("testresponsedescription", skillResponse.getDescription()); + + + String description = SkillResponse.builder().description("test").toString(); + + assertTrue(description.contains("SkillResponse.SkillResponseBuilder(skillId=null, description=test)")); + + } + +} diff --git a/src/test/java/uk/gov/hmcts/reform/cwrdapi/domain/UserProfileUpdatedDataTest.java b/src/test/java/uk/gov/hmcts/reform/cwrdapi/domain/UserProfileUpdatedDataTest.java index b047b5bb7..9bb548217 100644 --- a/src/test/java/uk/gov/hmcts/reform/cwrdapi/domain/UserProfileUpdatedDataTest.java +++ b/src/test/java/uk/gov/hmcts/reform/cwrdapi/domain/UserProfileUpdatedDataTest.java @@ -21,18 +21,22 @@ void test_should_add_roles_add_when_modified() { void testUserProfileUpdatedDataBuilder() { Set rolesAdd = new HashSet<>(); rolesAdd.add(new RoleName("Role Name")); + Set rolesDel = new HashSet<>(); + rolesDel.add(new RoleName("Role Name")); UserProfileUpdatedData userProfileUpdatedData = UserProfileUpdatedData.builder() .idamStatus("ACTIVE") .firstName("firstName") .lastName("lastName") .rolesAdd(rolesAdd) + .rolesDelete(rolesDel) .build(); assertThat(userProfileUpdatedData.getIdamStatus()).isEqualTo("ACTIVE"); assertThat(userProfileUpdatedData.getFirstName()).isEqualTo("firstName"); assertThat(userProfileUpdatedData.getLastName()).isEqualTo("lastName"); assertThat(userProfileUpdatedData.getRolesAdd()).isNotEmpty(); + assertThat(userProfileUpdatedData.getRolesDelete()).isNotEmpty(); String userProfileUpdatedDataString = UserProfileUpdatedData.builder().idamStatus("ACTIVE").toString(); diff --git a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerServiceFacadeImplTest.java b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerServiceFacadeImplTest.java index dbd500fb4..2bc790760 100644 --- a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerServiceFacadeImplTest.java +++ b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerServiceFacadeImplTest.java @@ -39,6 +39,7 @@ import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.springframework.util.ResourceUtils.getFile; @@ -98,6 +99,7 @@ void shouldProcessCaseWorkerFileWithPartialSuccess() throws IOException { ImmutableList.of(ExceptionCaseWorker.builder().errorDescription("Up Failed").excelRowId("1").build()); when(exceptionCaseWorkerRepository.findByJobId(anyLong())).thenReturn(exceptionCaseWorkers); ResponseEntity responseEntity = caseWorkerServiceFacade.processFile(multipartFile); + verify(validationServiceFacadeImpl,times(1)).saveJsrExceptionsForCaseworkerJob(anyLong()); assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); } @@ -233,12 +235,49 @@ void shouldProcessCaseWorkerFileWithSuspendedRowFailed() throws IOException { when(validationServiceFacadeImpl.getInvalidRecords(anyList())).thenReturn(Collections.emptyList()); when(caseWorkerProfileConverter.getSuspendedRowIds()).thenReturn(List.of(1L)); ResponseEntity responseEntity = caseWorkerServiceFacade.processFile(multipartFile); + verify(caseWorkerInternalApiClient,times(1)).postRequest(any(),anyString()); assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); ObjectMapper mapper = new ObjectMapper(); String responseString = mapper.writeValueAsString(responseEntity.getBody()); assertTrue(responseString.contains("1 record(s) failed validation and 1 record(s) uploaded")); } + @Test + void shouldProcessCaseWorkerFileWithDuplicateEmail() throws IOException { + CaseWorkerProfile caseWorkerProfile1 = CaseWorkerProfile.builder() + .firstName("test").lastName("test") + .officialEmail("test@justice.gov.uk") + .regionId(1) + .regionName("test") + .userType("testUser") + .build(); + + CaseWorkerProfile caseWorkerProfile2 = CaseWorkerProfile.builder() + .firstName("test1").lastName("test1") + .officialEmail("test@justice.gov.uk") + .regionId(1) + .regionName("test1") + .userType("testUser") + .build(); + List caseWorkerProfiles = new ArrayList<>(); + + caseWorkerProfiles.add(caseWorkerProfile1); + caseWorkerProfiles.add(caseWorkerProfile2); + MultipartFile multipartFile = createCaseWorkerMultiPartFile("Staff Data Upload.xls"); + + List exceptionCaseWorkers = + ImmutableList.of(ExceptionCaseWorker.builder().errorDescription("Up Failed").excelRowId("1").build()); + when(exceptionCaseWorkerRepository.findByJobId(anyLong())).thenReturn(exceptionCaseWorkers); + when(excelAdaptorService + .parseExcel(workbook, CaseWorkerProfile.class)) + .thenReturn(caseWorkerProfiles); + when(validationServiceFacadeImpl.getInvalidRecords(anyList())).thenReturn(Collections.emptyList()); + when(caseWorkerProfileConverter.getSuspendedRowIds()).thenReturn(List.of(1L)); + ResponseEntity responseEntity = caseWorkerServiceFacade.processFile(multipartFile); + verify(validationServiceFacadeImpl,times(2)).logFailures(anyString(),anyLong()); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); + } + @Test void shouldProcessCaseWorkerFileWithUploadedAndSuspendedMessage() throws IOException { CaseWorkerProfile caseWorkerProfile1 = CaseWorkerProfile.builder() diff --git a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/JsrValidatorStaffProfileTest.java b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/JsrValidatorStaffProfileTest.java index 2d5104212..788691628 100644 --- a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/JsrValidatorStaffProfileTest.java +++ b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/JsrValidatorStaffProfileTest.java @@ -134,6 +134,7 @@ void testValidateStaffProfileMissingServices() { InvalidRequestException lastNameException = Assertions.assertThrows(InvalidRequestException.class, () -> jsrValidatorStaffProfile.validateStaffProfile(profile, STAFF_PROFILE_CREATE)); Assertions.assertNotNull(lastNameException.getLocalizedMessage()); + verify(staffProfileAuditService,times(1)).saveStaffAudit(any(),any(),any(),any(),any()); assertThat(lastNameException.getMessage()).contains(MISSING_REGION_PROFILE); } diff --git a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffProfileAuditServiceImplTest.java b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffProfileAuditServiceImplTest.java index f309dbd2b..3c4505142 100644 --- a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffProfileAuditServiceImplTest.java +++ b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffProfileAuditServiceImplTest.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; @@ -25,6 +26,7 @@ import static java.util.Collections.singletonList; import static java.util.Objects.nonNull; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -77,6 +79,8 @@ void test_SaveStaffAudit() throws JsonProcessingException { + "have exceeded limit of index columns. Use settings.setMaxColumns(int) " + "to define the maximum number of columns your input can have"; + testErrorDescription = testErrorDescription.substring(0, 511); + StaffAudit staffAudit = StaffAudit.builder() .status(AuditStatus.FAILURE.getStatus().toUpperCase()) .requestTimeStamp(LocalDateTime.now()) @@ -96,6 +100,121 @@ void test_SaveStaffAudit() throws JsonProcessingException { verify(staffAuditRepository, times(1)) .save(any()); + assertThat(staffAudit.getErrorDescription().length()).isEqualTo(511); + + } + + @Test + void test_SaveStaffAudit_for_error_message_less_than_512() throws JsonProcessingException { + StaffProfileCreationRequest staffProfileCreationRequest = getStaffProfileUpdateRequest(); + UserInfo userInfo = UserInfo.builder() + .uid("12345") + .build(); + + when(jwtGrantedAuthoritiesConverter.getUserInfo()).thenReturn(userInfo); + + String userId = (nonNull(userInfo) && nonNull(userInfo.getUid())) ? userInfo.getUid() : null; + String request = objectMapper.writeValueAsString(staffProfileCreationRequest); + + String testErrorDescription = "Error message length is less than 512 "; + + StaffAudit staffAudit = StaffAudit.builder() + .status(AuditStatus.FAILURE.getStatus().toUpperCase()) + .requestTimeStamp(LocalDateTime.now()) + .errorDescription(testErrorDescription) + .authenticatedUserId(userId) + .caseWorkerId("1234") + .operationType(STAFF_PROFILE_UPDATE) + .requestLog(request) + .build(); + + when(staffAuditRepository.save(any())).thenReturn(staffAudit); + + + staffProfileAuditServiceImpl.saveStaffAudit(AuditStatus.FAILURE, testErrorDescription, + "1234", staffProfileCreationRequest, STAFF_PROFILE_UPDATE); + + verify(staffAuditRepository, times(1)) + .save(any()); + + } + + @Test + void test_SaveStaffAudit_for_error_message_is_null_2() throws JsonProcessingException { + + StaffProfileCreationRequest staffProfileCreationRequest = getStaffProfileUpdateRequest(); + UserInfo userInfo = UserInfo.builder() + .uid("12345") + .build(); + + when(jwtGrantedAuthoritiesConverter.getUserInfo()).thenReturn(userInfo); + + String userId = (nonNull(userInfo) && nonNull(userInfo.getUid())) ? userInfo.getUid() : null; + String request = objectMapper.writeValueAsString(staffProfileCreationRequest); + + String testErrorDescription = null; + + StaffAudit staffAudit = StaffAudit.builder() + .status(AuditStatus.FAILURE.getStatus().toUpperCase()) + .requestTimeStamp(LocalDateTime.now()) + .errorDescription(testErrorDescription) + .authenticatedUserId(userId) + .caseWorkerId("1234") + .operationType(STAFF_PROFILE_UPDATE) + .requestLog(request) + .build(); + + Assertions.assertThatCode(() -> + staffProfileAuditServiceImpl + .saveStaffAudit(AuditStatus.FAILURE, testErrorDescription, + "1234",staffProfileCreationRequest, STAFF_PROFILE_UPDATE) + ) + .doesNotThrowAnyException(); + + when(staffAuditRepository.save(any())).thenReturn(staffAudit); + + + staffProfileAuditServiceImpl.saveStaffAudit(AuditStatus.FAILURE, testErrorDescription, + "1234", staffProfileCreationRequest, STAFF_PROFILE_UPDATE); + + verify(staffAuditRepository, times(2)) + .save(any()); + } + + @Test + void test_SaveStaffAudit_for_error_message_is_null() throws JsonProcessingException { + + StaffProfileCreationRequest staffProfileCreationRequest = getStaffProfileUpdateRequest(); + String testErrorDescription = null; + + Assertions.assertThatCode(() -> + staffProfileAuditServiceImpl + .saveStaffAudit(AuditStatus.FAILURE, testErrorDescription, + "1234",staffProfileCreationRequest, STAFF_PROFILE_UPDATE) + ) + .doesNotThrowAnyException(); + } + + @Test + void test_SaveStaffAudit_for_error_message_is_null2() throws JsonProcessingException { + + StaffProfileCreationRequest staffProfileCreationRequest = getStaffProfileUpdateRequest(); + UserInfo userInfo = null; + + Assertions.assertThatCode(() -> userInfo.getUid() + + ) + .isInstanceOf(NullPointerException.class) + .hasMessageContaining("\"userInfo\" is null"); + } + + @Test + @SuppressWarnings("unchecked") + void test_SaveStaffAudit_with_empty_userInfo_userid_null() throws JsonProcessingException { + + UserInfo userInfo = UserInfo.builder().uid(null).build(); + + Assertions.assertThatCode(() -> userInfo.getUid()).doesNotThrowAnyException(); } @Test diff --git a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImplTest.java b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImplTest.java index 415642929..70ac61212 100644 --- a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImplTest.java +++ b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImplTest.java @@ -565,7 +565,8 @@ void should_return_case_worker_profileSearch_with_status_code_200() { && !responseEntity.getHeaders().get("total-records").isEmpty()) { assertThat(responseEntity.getHeaders().get("total-records").get(0)).isEqualTo("1"); } - + List searchStaffUserResponses = responseEntity.getBody(); + assertThat(searchStaffUserResponses.get(0).isCaseAllocator()).isFalse(); assertThat(responseEntity.getBody()).isNotNull(); assertTrue(validateSearchUserProfileResponse(responseEntity, searchReq)); } @@ -592,7 +593,7 @@ void should_return_empty_list_case_worker_profileSearch_with_status_code_200() { && !responseEntity.getHeaders().get("total-records").isEmpty()) { assertThat(responseEntity.getHeaders().get("total-records").get(0)).isEqualTo("0"); } - + responseEntity.getBody(); assertThat(responseEntity.getBody()).isNotNull(); assertThat(responseEntity.getBody()).isEmpty(); } @@ -912,6 +913,7 @@ void test_createUserProfileRequest() { assertThat(response.getRoles()).hasSizeGreaterThanOrEqualTo(1); } + @Test void test_createUserProfileRequest_StaffAdmin() { staffProfileCreationRequest.setStaffAdmin(true); @@ -924,6 +926,8 @@ void test_createUserProfileRequest_StaffAdmin() { assertThat(response.getUserCategory().toString()).hasToString("CASEWORKER"); assertThat(response.getUserType().toString()).hasToString("INTERNAL"); assertThat(response.getRoles()).hasSizeGreaterThanOrEqualTo(1); + assertThat(response.getRoles()).contains("staff-admin"); + assertTrue(staffProfileCreationRequest.isStaffAdmin()); } @Test @@ -969,6 +973,7 @@ void test_publishCaseWorkerDataToTopic() { void test_persistStaffProfileNull() { when(caseWorkerProfileRepository.save(any())).thenReturn(null); caseWorkerProfile = staffRefDataServiceImpl.persistStaffProfile(caseWorkerProfile, staffProfileCreationRequest); + verify(staffProfileAuditService,times(1)).saveStaffAudit(any(),any(),any(),any(),any()); assertNull(caseWorkerProfile); } @@ -976,9 +981,11 @@ void test_persistStaffProfileNull() { void test_persistStaffProfile() { when(caseWorkerProfileRepository.save(any())).thenReturn(caseWorkerProfile); caseWorkerProfile = staffRefDataServiceImpl.persistStaffProfile(caseWorkerProfile, staffProfileCreationRequest); + verify(staffProfileAuditService,times(1)).saveStaffAudit(any(),any(),any(),any(),any()); assertThat(caseWorkerProfile.getCaseWorkerId()).isEqualTo("CWID1"); assertThat(caseWorkerProfile.getFirstName()).isEqualTo("CWFirstName"); assertThat(caseWorkerProfile.getLastName()).isEqualTo("CWLastName"); + assertThat(caseWorkerProfile.isNew()).isEqualTo(true); } @Test @@ -1013,10 +1020,17 @@ void test_createUserProfileInIdamUP_error(int errorCode,String errorDescription) }); if (errorCode == 500) { assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, thrown.getStatus()); + assertNotNull(thrown.getErrorMessage()); + assertNotNull(thrown.getErrorDescription()); + verify(staffProfileAuditService,times(1)) + .saveStaffAudit(any(),any(),any(),any(),any()); } else if (errorCode == 405) { assertEquals(HttpStatus.METHOD_NOT_ALLOWED, thrown.getStatus()); + assertNotNull(thrown.getErrorMessage()); + assertNotNull(thrown.getErrorDescription()); + verify(staffProfileAuditService,times(1)) + .saveStaffAudit(any(),any(),any(),any(),any()); } - } @@ -1085,7 +1099,8 @@ void test_update_staff_profile_with_changed_values() throws JsonProcessingExcept StaffProfileCreationResponse staffProfileCreationResponse = staffRefDataServiceImpl .updateStaffProfile(staffProfileCreationRequest); - + verify(jsrValidatorStaffProfile,times(1)).validateStaffProfile(any(),any()); + verify(staffProfileAuditService,times(1)).saveStaffAudit(any(),any(),any(),any(),any()); assertThat(staffProfileCreationResponse).isNotNull(); assertThat(staffProfileCreationResponse.getCaseWorkerId()).isEqualTo("CWID1"); } @@ -1103,6 +1118,8 @@ void test_populateStaffProfile() throws JsonProcessingException { caseWorkerProfile.setFirstName("CWFirstName"); caseWorkerProfile.setLastName("CWLastName"); caseWorkerProfile.setEmailId("cwr-func-test-user@test.com"); + caseWorkerProfile.setUserTypeId(staffProfileCreateUpdateUtil + .getUserTypeIdByDesc(staffProfileCreationRequest.getUserType())); StaffProfileCreationRequest staffProfileCreationRequest = getStaffProfileUpdateRequest(); @@ -1118,11 +1135,30 @@ void test_populateStaffProfile() throws JsonProcessingException { assertThat(caseWorkerProfile.getEmailId()).isEqualTo("cwr-func-test-user@test.com"); assertThat(caseWorkerProfile.getFirstName()).isEqualTo("testFN"); assertThat(caseWorkerProfile.getLastName()).isEqualTo("testLN"); + assertThat(caseWorkerProfile.getCaseWorkerId()).isEqualTo("CWID1"); + assertThat(caseWorkerProfile.getUserTypeId()).isNotNull(); assertThat(caseWorkerProfile.getRegionId()).isEqualTo(1); assertThat(caseWorkerProfile.getRegion()).isEqualTo("testRegion"); } + @Test + void test_populateStaffProfileWithCaseWorkerProfile() throws JsonProcessingException { + + //ValidateStaffProfile + staffProfileAuditService.saveStaffAudit(AuditStatus.FAILURE, null, + "1234", staffProfileCreationRequest, STAFF_PROFILE_UPDATE); + + CaseWorkerProfile caseWorkerProfiles = mock(CaseWorkerProfile.class); + + + StaffProfileCreationRequest staffProfileCreationRequest = getStaffProfileUpdateRequest(); + + staffRefDataServiceImpl.populateStaffProfile(staffProfileCreationRequest, caseWorkerProfiles, "CWID1"); + verify(caseWorkerProfiles,times(1)).setCaseWorkerId(any()); + verify(caseWorkerProfiles,times(1)).setUserTypeId(any()); + } + @Test void test_updateUserProfile() throws JsonProcessingException { @@ -1474,6 +1510,7 @@ void test_reInviteStaffProfile_when_no_emailId_found() throws JsonProcessingExce when(caseWorkerProfileRepository.findByEmailId(any())).thenReturn(null); Exception ex = assertThrows(StaffReferenceException.class, () -> staffRefDataServiceImpl.reinviteStaffProfile( staffProfileCreationRequest)); + verify(staffProfileAuditService,times(1)).saveStaffAudit(any(),any(),any(),any(),any()); assertNotNull(ex); assertEquals("User does not exist in SRD", ex.getMessage()); //verify(staffRefDataServiceImpl, times(0)) @@ -1490,10 +1527,15 @@ void test_reInviteStaffProfile_success() throws JsonProcessingException { String body = mapper.writeValueAsString(userProfileCreationResponse); when(userProfileFeignClient.createUserProfile(any(), any())).thenReturn(Response.builder() .request(mock(Request.class)).body(body, defaultCharset()).status(201).build()); - staffRefDataServiceImpl.reinviteStaffProfile(staffProfileCreationRequest); + var staffProfileCreationResponse = staffRefDataServiceImpl + .reinviteStaffProfile(staffProfileCreationRequest); + assertNotNull(staffProfileCreationResponse.getCaseWorkerId()); verify(staffAuditRepository, times(0)).save(any()); verify(caseWorkerProfileRepository, times(1)) .findByEmailId(any()); + verify(caseWorkerProfileRepository,times(1)).delete(any()); + verify(cwrCommonRepository,times(1)).flush(); + } @Test @@ -1520,7 +1562,7 @@ void test_checkStaffProfileEmailAndSuspendFlag_ProfileAlreadyPresent() { InvalidRequestException thrown = Assertions.assertThrows(InvalidRequestException.class, () -> { staffRefDataServiceImpl.checkStaffProfileEmailAndSuspendFlag(staffProfileCreationRequest); }); - + verify(staffProfileAuditService,times(1)).saveStaffAudit(any(),any(),any(),any(),any()); assertThat(thrown.getMessage()).contains("The profile is already created for the given email Id"); } diff --git a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataUpdateStaffServiceImplTest.java b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataUpdateStaffServiceImplTest.java index 4f354a3bc..5ab6e791f 100644 --- a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataUpdateStaffServiceImplTest.java +++ b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataUpdateStaffServiceImplTest.java @@ -80,6 +80,8 @@ import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.ORIGIN_EXUI; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.PROFILE_NOT_PRESENT_IN_SRD; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.PROFILE_NOT_PRESENT_IN_UP_OR_IDAM; +import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.ROLE_CWD_USER; +import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.ROLE_STAFF_ADMIN; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.STAFF_PROFILE_CREATE; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.STAFF_PROFILE_UPDATE; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.STATUS_ACTIVE; @@ -1048,6 +1050,49 @@ void test_updateUserRolesInIdamDataMistmatch() throws JsonProcessingException { } + @Test + void test_updateUserRolesInIdamDataNoMismatch() throws JsonProcessingException { + CaseWorkerProfile dbProfile = new CaseWorkerProfile(); + dbProfile.setCaseWorkerId("CWID1"); + dbProfile.setFirstName("CWFirstNameU"); + dbProfile.setLastName("CWLastNameU"); + dbProfile.setEmailId("cwr-func-test-user@test.com"); + + UserProfileResponse userProfileResponse = new UserProfileResponse(); + userProfileResponse.setIdamId("12345678"); + List roles = Arrays.asList(ROLE_CWD_USER, ROLE_STAFF_ADMIN); + userProfileResponse.setIdamStatus(IDAM_STATUS_SUSPENDED); + + userProfileResponse.setRoles(roles); + userProfileResponse.setFirstName("testFN"); + userProfileResponse.setLastName("testLN"); + + when(userProfileFeignClient.getUserProfileWithRolesById(any(),any())) + .thenReturn(Response.builder() + .request(Request.create(Request.HttpMethod.POST, "", new HashMap<>(), Request.Body.empty(), + null)).body(mapper.writeValueAsString(userProfileResponse), + defaultCharset()) + .status(200).build()); + + UserProfileRolesResponse userProfileRolesResponse = new UserProfileRolesResponse(); + + RoleAdditionResponse roleAdditionResponse = new RoleAdditionResponse(); + roleAdditionResponse.setIdamStatusCode("201"); + userProfileRolesResponse.setRoleAdditionResponse(roleAdditionResponse); + roleAdditionResponse.setIdamMessage("success"); + + StaffProfileCreationRequest cwUiRequest = getStaffProfileUpdateRequest(); + staffProfileAuditService.saveStaffAudit(AuditStatus.FAILURE,IDAM_STATUS, + StringUtils.EMPTY,cwUiRequest,STAFF_PROFILE_UPDATE); + String caserWorkerId = dbProfile.getCaseWorkerId(); + boolean updateUserRolesInIdam = staffRefDataServiceImpl + .updateUserRolesInIdam(cwUiRequest,caserWorkerId,STAFF_PROFILE_UPDATE); + + assertTrue(updateUserRolesInIdam); + + + } + @Test void test_updateUserRolesInIdam_with_StaffAdminRoleDelete_Idam_Status_Active() throws JsonProcessingException { From bbf0cedf227c0248a7b4c0e56a5b83af4645575e Mon Sep 17 00:00:00 2001 From: manukundloo-hmcts Date: Fri, 23 Jun 2023 16:03:27 +0100 Subject: [PATCH 24/67] [RDCC-6819]SonarQube Issue fix --- .../service/impl/StaffRefDataServiceImpl.java | 7 +++---- .../impl/CaseWorkerProfileUpdateserviceImplTest.java | 2 +- ...a => JsrValidateCaseWorkerUpdateRequestTest.java} | 2 +- .../service/impl/StaffRefDataServiceImplTest.java | 4 ++-- .../impl/StaffRefDataUpdateStaffServiceImplTest.java | 12 ++++++------ 5 files changed, 13 insertions(+), 14 deletions(-) rename src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/{JsrValidateCaseWorkerUpdateRequest.java => JsrValidateCaseWorkerUpdateRequestTest.java} (99%) diff --git a/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImpl.java b/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImpl.java index cfb947514..53d5aafdd 100644 --- a/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImpl.java +++ b/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImpl.java @@ -273,7 +273,7 @@ public ResponseEntity createUserProfileInIdamUP(StaffProfileCreationRequ //validate the request info with UserProfile response. UserProfileCreationResponse upResponse = (UserProfileCreationResponse) (responseEntity.getBody()); if (nonNull(upResponse)) { - updateUserRolesInIdam(staffProfileRequest, upResponse.getIdamId(),STAFF_PROFILE_CREATE); + updateUserRolesInIdam(staffProfileRequest, upResponse.getIdamId()); } } @@ -884,15 +884,14 @@ public CaseWorkerProfile updateSidamRoles(CaseWorkerProfile updateCaseWorkerProf StaffProfileCreationRequest cwUiRequest) { CaseWorkerProfile filteredUpdateCwProfile = null; boolean isAddRoleSuccess = updateUserRolesInIdam(cwUiRequest, - updateCaseWorkerProfiles.getCaseWorkerId(),STAFF_PROFILE_UPDATE); + updateCaseWorkerProfiles.getCaseWorkerId()); if (isAddRoleSuccess) { filteredUpdateCwProfile = updateCaseWorkerProfiles; } return filteredUpdateCwProfile; } - public boolean updateUserRolesInIdam(StaffProfileCreationRequest cwrProfileRequest, String idamId, - String operationType) { + public boolean updateUserRolesInIdam(StaffProfileCreationRequest cwrProfileRequest, String idamId) { Response response = userProfileFeignClient.getUserProfileWithRolesById(idamId, "SRD"); diff --git a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerProfileUpdateserviceImplTest.java b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerProfileUpdateserviceImplTest.java index 7194d6c0b..9edbd520f 100644 --- a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerProfileUpdateserviceImplTest.java +++ b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerProfileUpdateserviceImplTest.java @@ -24,7 +24,7 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) -public class CaseWorkerProfileUpdateserviceImplTest { +class CaseWorkerProfileUpdateserviceImplTest { @InjectMocks private CaseWorkerProfileUpdateserviceImpl caseWorkerProfileUpdateservice; diff --git a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/JsrValidateCaseWorkerUpdateRequest.java b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/JsrValidateCaseWorkerUpdateRequestTest.java similarity index 99% rename from src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/JsrValidateCaseWorkerUpdateRequest.java rename to src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/JsrValidateCaseWorkerUpdateRequestTest.java index 9a0608353..fbb04acf7 100644 --- a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/JsrValidateCaseWorkerUpdateRequest.java +++ b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/JsrValidateCaseWorkerUpdateRequestTest.java @@ -24,7 +24,7 @@ import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.LAST_NAME_INVALID; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.STAFF_PROFILE_CREATE; -class JsrValidateCaseWorkerUpdateRequest { +class JsrValidateCaseWorkerUpdateRequestTest { @Spy diff --git a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImplTest.java b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImplTest.java index 415642929..67e9d6ef5 100644 --- a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImplTest.java +++ b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImplTest.java @@ -1312,7 +1312,7 @@ void staffProfilePendingStatusInUP() throws JsonProcessingException { .status(200).build()); Boolean response = staffRefDataServiceImpl - .updateUserRolesInIdam(staffProfileCreationRequest, "1234", STAFF_PROFILE_CREATE); + .updateUserRolesInIdam(staffProfileCreationRequest, "1234"); assertTrue(response); verify(userProfileFeignClient, times(1)).modifyUserRoles(any(), any(), any()); @@ -1334,7 +1334,7 @@ void staffProfileDoesNotExitInIdam() throws JsonProcessingException { StaffReferenceException thrown = Assertions.assertThrows(StaffReferenceException.class, () -> { staffRefDataServiceImpl - .updateUserRolesInIdam(staffProfileCreationRequest, "1234", STAFF_PROFILE_CREATE); + .updateUserRolesInIdam(staffProfileCreationRequest, "1234"); }); diff --git a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataUpdateStaffServiceImplTest.java b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataUpdateStaffServiceImplTest.java index 4f354a3bc..eeae9fb51 100644 --- a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataUpdateStaffServiceImplTest.java +++ b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataUpdateStaffServiceImplTest.java @@ -818,7 +818,7 @@ void test_updateUserRolesInIdam_with_IdamRoles_Idam_Status_Active() throws JsonP boolean updateUserRolesInIdam = staffRefDataServiceImpl - .updateUserRolesInIdam(cwUiRequest,caseWorkerProfile.getCaseWorkerId(),STAFF_PROFILE_UPDATE); + .updateUserRolesInIdam(cwUiRequest,caseWorkerProfile.getCaseWorkerId()); assertThat(updateUserRolesInIdam).isTrue(); } @@ -878,7 +878,7 @@ void test_updateUserRolesInIdam_with_IdamRoles_Idam_Status_InActive() throws Jso boolean updateUserRolesInIdam = staffRefDataServiceImpl - .updateUserRolesInIdam(cwUiRequest,caseWorkerId,STAFF_PROFILE_UPDATE); + .updateUserRolesInIdam(cwUiRequest,caseWorkerId); assertTrue(updateUserRolesInIdam); verify(userProfileFeignClient, times(1)).modifyUserRoles(any(), any(), any()); @@ -934,7 +934,7 @@ void test_updateUserRolesInIdam_with_IdamRoles_Empty() throws JsonProcessingExce String caseWorkerId = caseWorkerProfile.getCaseWorkerId(); StaffReferenceException thrown = Assertions.assertThrows(StaffReferenceException.class, () -> { boolean updateUserRolesInIdam = staffRefDataServiceImpl - .updateUserRolesInIdam(cwUiRequest,caseWorkerId,STAFF_PROFILE_UPDATE); + .updateUserRolesInIdam(cwUiRequest,caseWorkerId); }); @@ -988,7 +988,7 @@ void test_updateUserRolesInIdam() throws JsonProcessingException { String caserWorkerId = dbProfile.getCaseWorkerId(); boolean updateUserRolesInIdam = staffRefDataServiceImpl - .updateUserRolesInIdam(cwUiRequest,caserWorkerId,STAFF_PROFILE_UPDATE); + .updateUserRolesInIdam(cwUiRequest,caserWorkerId); assertTrue(updateUserRolesInIdam); verify(userProfileFeignClient, times(1)).modifyUserRoles(any(), any(), any()); @@ -1039,7 +1039,7 @@ void test_updateUserRolesInIdamDataMistmatch() throws JsonProcessingException { String caserWorkerId = dbProfile.getCaseWorkerId(); boolean updateUserRolesInIdam = staffRefDataServiceImpl - .updateUserRolesInIdam(cwUiRequest,caserWorkerId,STAFF_PROFILE_UPDATE); + .updateUserRolesInIdam(cwUiRequest,caserWorkerId); assertTrue(updateUserRolesInIdam); @@ -1094,7 +1094,7 @@ void test_updateUserRolesInIdam_with_StaffAdminRoleDelete_Idam_Status_Active() t boolean updateUserRolesInIdam = staffRefDataServiceImpl - .updateUserRolesInIdam(cwUiRequest,caseWorkerProfile.getCaseWorkerId(),STAFF_PROFILE_UPDATE); + .updateUserRolesInIdam(cwUiRequest,caseWorkerProfile.getCaseWorkerId()); assertThat(updateUserRolesInIdam).isTrue(); } From 21f9fcb1e257829e1601ebaac7964f3d87feb29c Mon Sep 17 00:00:00 2001 From: manukundloo-hmcts Date: Fri, 23 Jun 2023 16:32:36 +0100 Subject: [PATCH 25/67] [RDCC-6819]SonarQube Issue fix --- .../reform/cwrdapi/service/impl/StaffRefDataServiceImpl.java | 3 +-- .../service/impl/StaffRefDataUpdateStaffServiceImplTest.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImpl.java b/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImpl.java index 53d5aafdd..5cb97cc8a 100644 --- a/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImpl.java +++ b/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataServiceImpl.java @@ -901,8 +901,7 @@ public boolean updateUserRolesInIdam(StaffProfileCreationRequest cwrProfileReque if (!resultResponse.isPresent() || !(resultResponse.get() instanceof UserProfileResponse profileResponse) || !nonNull(profileResponse.getIdamStatus())) { - log.error("{}:: updateUserRolesInIdam :: status code {}", loggingComponentName, - UP_FAILURE_ROLES); + log.error("{}:: updateUserRolesInIdam :: status code {}", loggingComponentName); throw new StaffReferenceException(HttpStatus.BAD_REQUEST, IDAM_STATUS_USER_PROFILE, IDAM_STATUS_USER_PROFILE); diff --git a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataUpdateStaffServiceImplTest.java b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataUpdateStaffServiceImplTest.java index 197a094cd..9517436b5 100644 --- a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataUpdateStaffServiceImplTest.java +++ b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/StaffRefDataUpdateStaffServiceImplTest.java @@ -1086,7 +1086,7 @@ void test_updateUserRolesInIdamDataNoMismatch() throws JsonProcessingException { StringUtils.EMPTY,cwUiRequest,STAFF_PROFILE_UPDATE); String caserWorkerId = dbProfile.getCaseWorkerId(); boolean updateUserRolesInIdam = staffRefDataServiceImpl - .updateUserRolesInIdam(cwUiRequest,caserWorkerId,STAFF_PROFILE_UPDATE); + .updateUserRolesInIdam(cwUiRequest,caserWorkerId); assertTrue(updateUserRolesInIdam); From a5c6c9ce796be4ebcae84bc8410c77190a262b21 Mon Sep 17 00:00:00 2001 From: sahitya-desireddy <93707379+sahitya-desireddy@users.noreply.github.com> Date: Tue, 27 Jun 2023 10:39:31 +0100 Subject: [PATCH 26/67] Replace iac roles (#744) * Replace iac roles * Replace iac roles * Replace iac roles * Replace iac roles * Replace iac roles * Replace iac roles * SUPPRESS CVE --- config/owasp/suppressions.xml | 3 +++ .../cwrdapi/AuthorizationFunctionalTest.java | 4 ++-- .../cwrdapi/CaseWorkerRefFunctionalTest.java | 2 +- .../resources/ServiceRoleMapping_ABA1.xls | Bin 9065 -> 9064 bytes .../resources/ServiceRoleMapping_BBA9.xls | Bin 8980 -> 9062 bytes .../resources/ServiceRoleMapping_BBA9.xlsx | Bin 8979 -> 9063 bytes ...Staff Data Upload with non idam roles.xlsx | Bin 200141 -> 200164 bytes .../resources/Staff Data Upload.xls | Bin 199940 -> 199962 bytes .../resources/Staff Data Upload.xlsx | Bin 199653 -> 199673 bytes .../Staff_Data_Upload_with_non_idam_roles.xls | Bin 199951 -> 199973 bytes 10 files changed, 6 insertions(+), 3 deletions(-) diff --git a/config/owasp/suppressions.xml b/config/owasp/suppressions.xml index bed068402..193475dde 100644 --- a/config/owasp/suppressions.xml +++ b/config/owasp/suppressions.xml @@ -15,4 +15,7 @@ file name: launchdarkly-java-server-sdk-5.10.2.jar (shaded: org.yaml:snakeyaml:1 ^pkg:maven/org\.yaml/snakeyaml@.*$ CVE-2022-1471 + + CVE-2023-35116 + diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/AuthorizationFunctionalTest.java b/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/AuthorizationFunctionalTest.java index 7d4a7e4b8..af81369a8 100644 --- a/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/AuthorizationFunctionalTest.java +++ b/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/AuthorizationFunctionalTest.java @@ -64,8 +64,8 @@ public class AuthorizationFunctionalTest { public static final String EMAIL_TEMPLATE = "CWR-rd-func-test-user-only-%s@justice.gov.uk"; public static final String CWD_USER = "cwd-user"; public static final String CASEWORKER_IAC_BULKSCAN = "caseworker-iac-bulkscan"; - public static final String CASEWORKER_IAC = "caseworker-iac"; - public static final String CASEWORKER_SENIOR_IAC = "caseworker-senior-iac"; + public static final String CASEWORKER_CIVIL = "caseworker-civil"; + public static final String CASEWORKER_CIVIL_STAFF = "caseworker-civil-staff"; public static final String USER_STATUS_SUSPENDED = "SUSPENDED"; public static final String ROLE_CWD_ADMIN = "cwd-admin"; public static final String PRD_ADMIN = "prd-admin"; diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/CaseWorkerRefFunctionalTest.java b/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/CaseWorkerRefFunctionalTest.java index af97a905d..c27b5d607 100644 --- a/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/CaseWorkerRefFunctionalTest.java +++ b/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/CaseWorkerRefFunctionalTest.java @@ -149,7 +149,7 @@ public void createCwWhenUserNotExistsInCwrAndUpAndExistsInSidam_Ac2() { UserProfileResponse upResponse = getUserProfileFromUp(profileCreateRequests.get(0).getEmailId()); Assertions.assertThat(upResponse.getRoles()) .containsExactlyInAnyOrderElementsOf(ImmutableList - .of(CWD_USER, CASEWORKER_IAC_BULKSCAN, CASEWORKER_SENIOR_IAC)); + .of(CWD_USER, CASEWORKER_IAC_BULKSCAN, CASEWORKER_CIVIL, CASEWORKER_CIVIL_STAFF)); } @Test diff --git a/src/functionalTest/resources/ServiceRoleMapping_ABA1.xls b/src/functionalTest/resources/ServiceRoleMapping_ABA1.xls index c95c9b593b039b57e0e43620cadb29bd254708cd..9b00a9e557e82af21484702a1c55b64476f66008 100644 GIT binary patch delta 2387 zcmV-Z39R<%M(9Sc{Rn@3lxrTC0{{T92mk;N0001ZY%h0ja%*C5Z)+}iZEUPnT~FgU z6n$T5|3j2_kL`RnqKrtJ?jpemK?fd@NgQI4#97;cA~gSfuhXW$uv&GQMa4;M_vD;= z&vB2wZra9qS5?nA=@4x?nEQZ^9brXZIPbdcT%yt z24rt&=xh{xBKUu&1O{(<6D)mrRVxZ5wXDn+fW}+WAM{g#DScOh&Bq!Xh7@4MABFAV zo|TF|$tv1oMXz9#{4yE^YmHt>p?IG}3-{bS_xPw14Zq%6*I|ACjkUH@jq5Z_o4gc; zmvc9UNBP9xO{k&I2GPJOB@v}AKHl5;LOCU`*wC0Y7yy~q&;7~8 zeGP1BoY+YSj%77>U%Y&F;zjuk(PgwmWENy7i^Bz4XpL8= z8{=}t)uZ*BkP-(kN=a90~NEx3|I;U9c~&39+M##C4Wh; zn=llG_m%n|EboEf3@@ONr-%gg?W&GzV=lo8#?*EuRsHY1K!!?LGTLCzNgvKVc za;}Yoz={eKx4v$n!9S%oiaY!?+ke2e26)j=Qgy&aqgkN1Z0JCU%`*&LmQui4i@k!z z4G;r0cj(_%vb9&563^fiH@ki739VX0Yq@*y~>eBg04hGMU@uMILxVVuX^FM_NCxL;3>^AU@V(+sQPH!xBhkuRV37g$u z-Ex_DFN=i!eqoz5n!ZfD30p@VTTdb{$~H4EOBq|OW~(flZ~oBbqL8@5T`FRrOz0|% z(i@tt-NI2Sm+QjkvE7TwyphieZSDv}nyKdkcc8!o|`vD!13nvedLR}{`#iaIE zuaUBgMsO2ZF7EBCVAj|4XMaO-ZXwn6Pg%GMRq^Z;Q1X54zUbp8klV^(?+mkd#S<6j zHCTaFi*9_@MG?A2CO(Oh-RZ-kL zG4$fk$80nX*?%cPZ{;rl0096000030|CG}03WFdN2H;x>FJR_J8-IieTiu8_VZYi) zaogSR6vLb-%zxGU9(X(^?9CWADW{}Z?$2f!5J8Q7sM*3(&rtIsTKVH5#lpLnt-xLitiRbM&d6^z#u*J620>uZ3I=oogC&d=>tzavCwLwJXa9Q- z&@BR377$N#c>u2fKp(dV;2U6H^LBM(JaNnex(Wbvi-0Z*h$l`70Pnx$2b13!kPa~} z@&4cd004;r000yKlfWVte}s_BPQx$|MfXVjgEjlq2@6zJj;lb0)CC(TA7DA-#;PB| z;{^D79Cd+|MOn@{cQm6b_TS#I857wUT4**kFbq2OHk@1dIK5O`V3bwpm5Tve_(6ib zyuTAfnU&o@Eetu2JI+ZD=oM|^h@lu0lUKQn>C9TVTdp6uS2h6CaSFNI zZGcU|eopv&1zV7UtQ1+kW16i-c0354rQi#_nz!h1&5noel`VqSrhDp|HPedlE2ayk z%Bq{PTB6aI@=3Z8)ONP+KVOwTjrmLQQlXYw-Bh!P8zupNUu%Of6vf{M z`wo(Kjan-usD*9xX=Q_1Hue^hYa6DCB-w1g{i3$cu|JTP4sW=ch!5`Quapofa5SsaRZieYANo4emfD-8Ohm%Y~OV~064=? zLj%$p90d{n@>~Q|o3r$DOk|8^y_eG-5J#T^DqVA%@Ez;g{|G(=e(p zWWOs0sK_`YOarw_7hA!!GODuPLsba|-p+u|@XXO~v!=#w-Qp`w0sQ9lSJ6+S>og2D zwgc|DsAL@MqC78d@5&7-;xLZ<(4AEoEy*$=N%Gp~_*UOXA0L`?n*WIlKTQ2NEu)kq z5edT?@d79^{NuY6p8%7Q0~E6i9+?9PeUxh+m;(R+u#>JKP64!&{UJI5jgvScCL0}Y z8VDW(002M-000;O00000000000000043l{xD;qH`@&4cd004;r000yK0000000000 z00000V3WThCmY`a{oPan004Ue000pH000000000000000a+3igJ^}rcKqDgt(jWi; F005-tY~%m{ delta 2386 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 delta 2406 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 E29<<>YF-Tmiw*QwK0 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 diff --git a/src/functionalTest/resources/Staff Data Upload.xls b/src/functionalTest/resources/Staff Data Upload.xls index 34d0390480c361e92113d54b8d5d169dee64b8f9..d8bca22917540815c0cb8e057eb8f5fcd6f93008 100644 GIT binary patch delta 22747 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 jsJ>T7)2y9>;MW*Y&x-qV}z#_T|5!rzaFa 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 IlSc8s06bxd=Kufz delta 11066 zcmcI~2{@GR{yxT%C0n*c2%+q4w#JgIMYc8>p|bDBHjP)75Lp@;m96YcAtGY3Q}%ru zd&oA(GHNWpchGXa*ZH3FKi4^buFE{{jo zBv(;|)B*s_6nB>!D=*f_=9sp%> zPPNSRULLg4jmDpuyC)}?@AlX|fE8Z+d}ah)!ZdqR!s*)RL%3Vo$2QoG_xfZ+r)3=v z4+o;FLaDcPRVx^7_xyaORVd+5Q5yGrbXf8Ais)RR$){OF{Kq5x_OogBH!%INJ+VEH<9C@hsndX#6Zw zt(OxUdQp$|X45K3+Wa$&@o2T&;aN&|rnih8%>_{{*KVD(3JFpZeb9hd;r&X8TF}(w ztt(@S_PE{_4JY$_zcS|`n{JoR3VCSTySn&tiiHNC-Jrl!1#5=!{5UCk!hW>*(z%cL z(Xt!wR9C0Gl$Tj86*Jeu^)1l#0shzWlXs-nZWMjw6IGaSVNwd*+y0 z!p&JE4=~UHxO;U9&0_o!IbGrUR1{`QhvLS3E^!=HeaHXs^!ZMc+m)3)g2VG3k*o~x zWy*=}0VLh`P04w@k5BIi%NB(Ho>S0{4y0<7RR*50L|UPej8+V-XLJcw(6Pi!APhdOcOo zLnw=6FH5{afoXv!18E%s!-t)7_mDznhF{;t)DH<_09e5%vv+BvWBHZ=rs}cs> zO6O+#q=gOQk34A9Z_KMOhzWVPqIaR=rZFZb{r#&DKZFzcr0$hdZ3J~i-VuHQqjv@x z)LjqVfy##Uo>(j8>Lc$qBhqhm#?0nU9BxsSlZm|7EU$!9mDv=&A9|!AptL$onT$Tn z>6noTL6jNG_|@>+%eQQNjaQl7_{F`Q-Dz~Kn9t-ElA^EN8mLGbEbjSUs7{{k>=8{X>Ff5@ZphKnrDATNad_)`eu z5ZC0x&|{4n%p+&YL_Nw#?#@&-j#Oyj8+C^TUS<}Gut82==)c{q@YDih%+21oQ1!-c z_)P7F94dUiaOd(8WtrpGsH*P^QGPSUjgym6Z`ng9w@iF;mNun7P04$$@FZJg&41E! zZoy7NHs{$<%OyvM%orJsHSQ{PE-qtJ?5b?|%DY5D%g{%kM+n;rmQD}g#b#&!^Ktg)J~F_jNxe0P zqpNM!Ia|4N zabEYTnz)yv%q zT&K)dFZYT_A+2b+!ui!V9FM$)%=1qq2_wdHd{11?P?34{p6kUKK8pTmvIM?&T(vBA zUs$aCY#vOSr9aCzFYGvSgW`GK&D+*Gt`@ZoWl`6XsBsS@0{9WQlKy910=>b3u}BF$^r+Y0*WW}dGq0V$jooSLL+WRGgwq3W6s+QE zA~t$%gta2B-snTac%oA@M<@kfzT^Eg`lC2Zwlr-($V*l-S?DZmSlGv_My1y!zFY5! zlKR=aQzz5$Hy6C!Lf72_=E~te2Du+@#2GHMmhfFs%fJj~j!d`qUv&3aMt*p1s-)~7 zeqeFj;%y`Pe9Z9c-WL%&JR~GLs1*oP!A^234XxPMZDwZ?5)x5&QWO)61GW5y4G3T+ zH{?F>+OZp&c)`5FhWXSWOIzU%##Sht|6Nv`R_fUhB<>yZ%?+&S+-C&ett&mENETI$ z`g6aSLRrzRM8zW1vqkD>=kCJtABt`rVcOoBT}iJET()!;Q1w=_s_9VF@_HWhQaC)N zy2P_w{^8okp8=i<)#Xvey;Qw}a#E1%7g=}n0e3qYMf zM}Cy)KuH#sI#qVd0?@?c6xq7IeeI7vzj*iqX41sNaB$-L8$YVVQx0uFTTe* zkw9KBDlDydORQ=eu31U`&N~9N)d482P?2Oxc{fOCiJQ0KN$~N8DUsxFl`-k6!&2)x zl+dbVjoKav%P9!~B%iImkv8xeuo;U8CmVuNpJ^939ahvmtLW^ArX@SMaar3dAo7a2 zVE^>A)<96fZMF$vHJJrd9}9GDz|nB#v%3`iv{aA+!c8!XBlDC8D6sxnN?Ffb#i zWP49XJt*iN_y(-kRfm%9!+xyd(C)RcOZ7_aDW$P*UrB*&|d> zq>dlUKE_qukfo+}v0F;|ps>T4kjGE5Rw!$i(@vS2pPX{K7+$i&47T5PV zQ)f`BD2l~cQ*Ru`IjiO)`#CNE%N1=>AIz{d zkaKJIi#PJ~_2F2F(knZUcBnw;?2r)uG{xJxPSiaM-1(eiV`FjcvBd*+z31=J$zaN% z0@5ckVB54;BDl=4*q1vaL0Lf@v;c+ET*j$Hqc`lE?b^&>|0XdmTSNHqA2^ zqP?Xn?Mc9`;dh=x?|b5xvR}mSlz6O9z3^D`PgzU8kLck^D0kW!^*^$+d|b07XypD@ z{R>BoPj5<{0=!-4vZqa#&SlNnGVW*R)x5bg8r^Mlj#~ARnl1tAr8bKCbw@K@U9^8_ zpyN@5@beu>$U@sEhvPhS9J&L<6BH!;SLpFSq)t2StbO^UEqCpd<58O~Et9nY!xvMV z2}dP0-~3oGR8AbvZaM7mqQ2CQCw~PS-E0BZkDv3Y=E-&)I_VQ0AL`u*4vjqK^S!6z zv%Py2uFhqxx1fOawSkrT*;@p}_VsvMU^Jq;WZin@5e{A<$F6w3JVidgrCaw!uN%Hy z4h>#sBti$KQJVup`Ou*vEO!=sPmn@XYNkk(_X!oj`c76UN?;rWY^r zU=$I{+hakFt##$S6AwPfV~}O8+w%_omX617-1N}{(!RSXWaI7?Y=(x8WoNHQeB5j; z26pO!a$msvjCahcx2ezf@tuvmm}03 z9-sDL0D$e~V#V!Ef@v$p&M7RYjPurGaY}ao#%8hinQ;Fk+b4byy4o_|?JOon>icKVr*eQ(6;NGPgB5KW`-58K*jJX`vIW7LOjO zQhAPtZCP)wS67F2J1!3UxLNUR8&(VEnzjI@>r_fYHAmWpg%88r9j+WsUMQx7)rOG{ZS9t*^y6IT#0~M|y5%svq@`RkR-l zv`9+yl(eX85>+B;lpI(iwaAv!8ShZ>deAiJ><$@t@iYxE;_wRzuXR==mjE+BquJ4V zR&nT1M~L4z+lkye{A#z;R@Gd;e^=`q7+AF^It!O6duN^QWZOB=@}73R{>AKsqR*_| z2^9txEYIa1xk3(v^)ICH$vck$<8U*V$!%mm58b>NRY`dK<@tH3@qU-pK(Qd9PAoy* zFX7NDJv=fq@bqH6y=>KT=hKPJV~5>`>+d95=}Ht&hBSBFQ=@;rKKocLcdkW=#S}gg zMJqFplORFVnqG;A+di$@qEPsuRwC?WI-u=o;3-1$NPgb#NHNVVzRK?gK>FkG4-bkQ zB?NEf90^R1_S_VC`;jElm?Gtg(%w*>;*QXR4TC!X>CZqkY?CQ<81l(#2yC+qn(}^D zRo1L$4rQ_+gr-ZrE)O7NsO-e1&ctmP)#+OU>&U!v?(++(-q(^F=_fq`mBA`b#3#-&zWbQsFWHCSR)1OZ0t4l1=Zld`dQfXW1 zl41)VOuc3Ya;wc23JhJky^hFOr#Ce#_sv1@UMp5ITqbdYFd?h>bDcyV<}OsuB{XTO z#*c^`I_A0Pz}Fn)3}dL*O`0#42g_RC_-5f@+52)C75l1S-uTIB!Cnv=) zKUZA)RBk=}mGxE@Sk_O#I%-xQfcyD{R;OGx+^#R`j-Fe6kns*s65>UFCsBYKNH!FCr^tn4YRq>Sg;B#v?a=EAcn2xi@pJOr(%#Y;Q5?OS}44Csy zUW#UpPz>}~{{`R%7`YOTB{CjNcvSfTU+hU<1c>ls2qqFugTODmQ5yz!5`MymCwdrf@<--}^umL`g zlRwIzfUMW(^YD~TKvMB>!g1TQwARo^gB}4B2fyBRJU#D$=MF0(SVn_XMxO}lB81a! z+^Pvr5bjUZW7g{PSh{A2Zx!~+Dv-YcXdwpxk34w1q6IeY4XR|3Lz6)7INOP_z?zA3 zn00LR0ojsl%*l#t&+-X)1}76omIVFr<@5#&yp{kq5$SBU|03 z{D{q`f%sF5A%fgguu0vW)3Wil)d2F#C(R(OPUdN+I>e6w^XH|FBW%6^YSM?*a zh5U8vxp>)VYBr^H(#K?8-SsUpY?F4$aWnkjN{%Z`4qoNvHL1QsfjV=^CDtz5pix4w z*~`FFg~Hd4HBtqIFO~X*FcinD)8{rKYXqLUaV>M|7$39NoIjyv4u3Bu6?^Hnbl|6X zXXePh7+xvehZ=OdOPCK@KoM^(Cwq$qY3`WRuQ**Y z5AMK@USLLD?{KcMm9Y;=5zMba5@5hd_e;fR9wt+Vr^!4&lkEHXiEHIu3}e(`KT(0m z5_z?*Yqfz2Oq}Clf@NXc;^kTK?i4z-cS5=t=Q!0*a=mtr1}3cd<6jvImo)S6yIhEU zO>ST%sTOvC-poNDR^MOBT-D`(>&Z$(&M+qaR~KUCsqdv%a!4JM4=6$3UMEggUEt_t zG8pN+khsBpnv=gSMcqk9?tr!Kgmd@V;Xx8oo6vN7OsVsN~ibH^-aUTTZQz4(nGnawH(AW!4V1>LCLbtYb?U zD&8#1nv7XJPUccSG9xgg(u^GDaOH{XyXq%$nuEU%OC@=Z`BElm-xSohBanPJXqaAQ>UPKA9Hlj>f^ydie7cHXm{w zpzT9cTT&C`YKOQkJD;{ynS}_|VD}6SXPU-1C6q8MJ!OW~En&5j+Uv4pFS(iDSTz;{l_`G=_6(@ z#WLBhh*S|87OFuzxr=>?#$&%V!J84XsM1r8ExOr6%Tn?-jz zO-cFPESj}Ok{1BIyR$Q#Pps%UA2TK6_>mWw!(9{mWWKcS<`S5?agO9G&i|XAGucm^ zL=vLK<7-4atv@h0u`@Sdx%pd=Hi0^WNZ;bkx4XOXI7vP$P=8x4SCgWDzu>xzwZE$=g``7w{#`5v1@~B75<8}! z#0HAn<4Fhi(o~}R+8MNZuk1QfaQ47qb4P9ACM?zM-i^37PK9-P;mMEPo;bXm!US_Z zBm?slW}H#J80ss+1e)ng%erf)?spLB)*#YL7!XDRvGJfIv4lM1i+=sK#L0-&Y>m@Y zkL!Z<@KNEc)b%C0b~l!%GYDnpPik&m_8vkml@peiN#$_uvf z<8a0yu1?eJvd&nl=FkV}n&Fr$Ac7M*ZSVXef~5|M9w>;pO#>!0ACJ6VeQZDB)hF0~ zCcGzK<7dJJL&*#F@|}4%YA@daj=x)!nDFZ@S%q=Hd92-{oZ<*II_yAr>7vM$ zMr3)$RkTIv(5&Cjdh{iDYh{N@1fb2sd1_>^?oDOyo^!e60bQwqv0wVxPs+v5m-kYD zWWPs1t+R0DJ@zfUhA+K!JC_LSpmOoQ7Noby9|Z|RCD*V3x}C(Jf$`gT*3aKmz8_0j z-Y~=nexoaJ7_WI6M*_8)g?7vx83rU%RC>q>IqZ98F-s`d*{;b3z#@ULie2CUi-iaUiko~n8?cWaOED> z@E)1Z&V^Ar9mKSB&iP4#J)-S)7|NmX7;3Be0Ps+fYLnC*hajgb(e;)sfvAGKR;nS> zf;7+7X1QW~-u{2&{x9!|(g`F~lRfE#!gibhG(ZIbmPMR*=C^M}F60@n6N(70Ko9%o zet{osMlf{3@SGnA{X00Ku&`V!<=quPuz_rZ$v&Q{VeEh9&T{de+$n{xG{QvP*4pcf zxuHMF9hKKEU@Cig9=^-o<>KWTFpzmxip7`CZ&Ro3YR3P1dDP1;P5l|LlDIm#iGcba zJ{Fi!CqC95KlW2Ej$$w!bK|sQsj>VGDrC;I6Ih`yOcEDB+)T4QfuUhTb1n~vMe5As zXEI^+N8c92f4A^7r>L?MVgT+2bM6f+ik$v zl*~Occjc*+rRG~5a(^tuU0;UjEb_kjadEEKv>J^t37-ikj3PD<{ef@fZ+y=p6C*7_ zNmBv}40jnkvlH%!*)GDt#_cKck4orLE-h$htcpKCq&}w@$Gw6AS#V#A69v(}7AI=m zJuMDSa!h0DpCKL3Tb))5D`jxjQ8`em3+CSS&@ZT2`~_-1{w36o{%fqEPJf01f3h6U zF3S!7r3x^AQ`@-+6Se<{hU!F13#t2HV37#C zAAbejKQ|8Cd352bVQ8XgL6}6s|0tw0_}bM{-lm_M4wP!`QQ*(Lg;|u=iJu}5L_SgE z_11zS4?zQVRIo|${IyB-?lr0RI}%A6`19Gl6kyx`xu;k!4XA1YdzIf`<^JzZ3X&m8 z1>8t6gc|CHOeM8*Nc}Lxb1VBkcXiAF=ySo+_>d7FF!;=JPj*u>OdHAKpXk-}huxTnz`fAt1_zeL+ZU z2K$0A_m?32*#e1zuyt@wI(QZ?n>L86{wcC5kdV~gTJ|b!DRoe0jkDF2=r-{0Zv+L( z#kWd(?*{xW$pODuZWbQ$;J4~m!v;z2?@q>{0VR986C(HRV+WknG}tSkDX5<-v&z>G z3!J)g;a-}lpj7Y~IhXPAp#*-HmPRSQosAxn34G2_5>7Fx1Q0MIqb7^6LM+AL#T3oe}Dh)_228Q7?3djC{eoAUnMH@XN4+*ooYWaf0a|e^aQA;Gz}YhvwA0Eaqk&0^@^2U zzqGA%+dtK|+Enn|M!|na8z_ZlO@lpqC!iZgcgPKXiTl3^lLU8Hxj6dYu{DaSNdk-u zrvV*Ewg03aLVowd7UXZe>vjdyyPzohmwNZV!InJEe{os&zt0NAPm!ys|0^y3(`^5L zdMwZ=Nh~6V4-7w*J}LfS42<3MfJE*7Y11^6FT(RMxMf!;j$AFJn}wmZ=i$_WAq2+x z{iDLYvnJt^R-WoZC-;tN^mDc8E`@-{Zehm4$l~*qX)2(SsuPRG3?79P5RVJ%#09xtM<;`KwWj|xz_I4f)t)94#lW4V&Ci8;oQWaGjO$a!ej8`_EKkcb^Xj>gyYDj z1d0F4?x2_5q{T10$>ur9nR1e@7e_6T36lcVyexqump2&>E{YXqyYktiGx|}Xb@28L zwNZEeqBfG!g%Rx!uie`>8qNnrxH>dU5z^=}71uM3!pDP?NLYGRvMz`BOdgASSs|B3 zp8d{`i@V~LmoqYE`KgQjX#ZEXFw|{`uKv?+XVs0TZ$8SOG`5O;Tz0`Lza!18g{PSk zC=LVi4vq#yia1nARdQ8xOWIK#ljEb{iCjp}MEH(!*jOpahE53ADZ;4I@&VY_n5cp% zT-y(w^!FA;qw3C1i?6xDJ`9NuT>ilU1;R$2Mr31bgpG<%(q}Y~r(;Q62Ut$>$I!u~ z6`sQh+0WqGA7{1v-pjLSe|lV0>QZTN#YmIBa=`D?l4G(Z-YA^G_rQG_?;Mw_Fd;m{ zBpEBUriO+?wSd^Cur!hKXBZDzah!))g#T3Z6kyI==d95oecVktbtcx6k_CxtWYE74 zK`lX8in&(E3YmVtqKWP&hv<+E97HQqLRisI3Wx;R664-Q0P(_|ZTA9|$3}r}rGT6u zTQu4GFC%#Wk>9U#k&tkJZw&nY5kV_aLN1b#-`)GV(p?HPD;;ZVjnJ7i5P{udMYq#HSV?Ws(=-q$*^h5%AzFw64;aTuegFUf diff --git a/src/functionalTest/resources/Staff_Data_Upload_with_non_idam_roles.xls b/src/functionalTest/resources/Staff_Data_Upload_with_non_idam_roles.xls index 22fefc8c4089645aa0db6872980b2b887f146ff2..0b77c106270607bb8e7db2a2d5b6191aa5a40c3c 100644 GIT binary patch delta 22979 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 From ee8cacf25ab8fc6db2a021a1ad1ad02f72bca233 Mon Sep 17 00:00:00 2001 From: prudhvi-maddineni <111352154+prudhvi-maddineni@users.noreply.github.com> Date: Tue, 27 Jun 2023 15:12:49 +0100 Subject: [PATCH 27/67] Update README.md (#733) * Update README.md --- README.md | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a4ff4b0f4..bca1d5f1e 100644 --- a/README.md +++ b/README.md @@ -6,17 +6,43 @@ CaseWorker Ref API Provides Case worker user profile data to clients, implemented as a Java/SpringBoot application. +for more Info about the CaseWorker, Please refer to the confluence + +Architecture of the Caseworker (https://tools.hmcts.net/confluence/pages/viewpage.action?pageId=1444742328) and + +Business of the CaseWorker (https://tools.hmcts.net/confluence/display/RTRD/Caseworker+Reference+Data) + + + ### Prerequisites To run the project you will need to have the following installed: -* Java 11 +* Java 17 * Docker For information about the software versions used to build this API and a complete list of it's dependencies see build.gradle +While not essential, it is highly recommended to use the pre-push git hook included in this repository to ensure that all tests are passing. This can be done by running the following command: +`$ git config core.hooksPath .githooks` + +### Environment Vars + +If running locally for development or testing you will need to set the following environment variables + +* export POSTGRES_USERNAME=dbrefdata +* export POSTGRES_PASSWORD= +* export client-secret= +* export totp_secret= +* export password= +* export key= + ### Running the application +Please Make sure you are connected to the VPN before running the Application. +(https://portal.platform.hmcts.net/vdesk/webtop.eui?webtop=/Common/webtop_full&webtop_type=webtop_full) + + To run the API quickly use the docker helper script as follows: ``` @@ -29,7 +55,7 @@ docker-compose up ``` -Alternatively, you can start the application from the current source files using Gradle as follows: +After, you can start the application from the current source files using Gradle as follows: ``` ./gradlew clean bootRun @@ -46,7 +72,7 @@ If required, to run with a low memory consumption, the following can be used: To understand if the application is working, you can call it's health endpoint: ``` -curl http://localhost:8091/health +curl http://localhost:8095/health ``` If the API is running, you should see this response: @@ -55,6 +81,12 @@ If the API is running, you should see this response: {"status":"UP"} ``` +If the API is running, you can see API's in swagger : + +``` +http://localhost:8095/swagger-ui.html +``` + ### DB Initialisation˙ The application uses a Postgres database which can be run through a docker container on its own if required. @@ -112,7 +144,7 @@ http://pitest.org/ To test in Postman the easiest way is to start this service using the ./bin/run-in-docker.sh script. The in postman paste the following script: ``` -pm.sendRequest('http://127.0.0.1:8089/token', function (err, res) { +pm.sendRequest('http://127.0.0.1:8095/token', function (err, res) { if (err) { console.log(err); } else { @@ -129,8 +161,31 @@ ServiceAuthorization: Bearer {{token}} Authorization : Bearer copy IDAM access token ### Contract testing with pact + +To publish against remote broker: +`./gradlew pactPublish` + +Turn on VPN and verify on url `https://pact-broker.platform.hmcts.net/` +The pact contract(s) should be published + + +To publish against local broker: +Uncomment out the line found in the build.gradle: +`pactBrokerUrl = 'http://localhost:9292'` +comment out the real broker + +Start the docker container from the root dir run +`docker-compose -f broker-compose.yml up` + +Publish via the gradle command +`./gradlew pactPublish` + +Once Verify on url `http://localhost:9292/` +The pact contract(s) should be published + +Remember to return the localhost back to the remote broker -Please refer to the confluence on how to run and publish PACT tests. +for more information, Please refer to the confluence on how to run and publish PACT tests. https://tools.hmcts.net/confluence/display/RTRD/PACT+testing From 6cf9cfd7ff25d740fee3476a3ea1a0707a03e99d Mon Sep 17 00:00:00 2001 From: lukasz-wolski <1005015+lukasz-wolski@users.noreply.github.com> Date: Tue, 4 Jul 2023 10:55:12 +0100 Subject: [PATCH 28/67] Update build.gradle --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 5b5335b16..3d5feb88b 100644 --- a/build.gradle +++ b/build.gradle @@ -39,7 +39,7 @@ def versions = [ sonarPitest : '0.5', springBoot : '2.7.12', pact_version : '4.1.7', - launchDarklySdk : '5.10.7', + launchDarklySdk : '5.10.9', restAssured : '4.3.3', jackson : '2.14.2', log4j : '2.17.1', @@ -384,7 +384,7 @@ dependencies { implementation group: 'org.flywaydb', name: 'flyway-core', version: '6.5.5' implementation group: 'org.postgresql', name: 'postgresql', version: '42.5.1' - implementation group: 'com.google.guava', name: 'guava', version: '31.1-jre' + implementation group: 'com.google.guava', name: 'guava', version: '32.1.1-jre' //Added org.glassfish to support javax.el implementation group: 'org.glassfish', name: 'javax.el', version: '3.0.0' implementation group: 'javax.el', name: 'javax.el-api', version: '3.0.0' @@ -578,7 +578,7 @@ configurations.all { } if (details.requested.group == 'io.netty' && details.requested.name != 'netty-tcnative-boringssl-static' ) { - details.useVersion "4.1.86.Final" + details.useVersion "4.1.94.Final" } } } From ea1a32f3c665da45100815276e1db587d8f19824 Mon Sep 17 00:00:00 2001 From: lukasz-wolski <1005015+lukasz-wolski@users.noreply.github.com> Date: Tue, 4 Jul 2023 11:58:25 +0100 Subject: [PATCH 29/67] Update build.gradle --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 3d5feb88b..e744fe0b3 100644 --- a/build.gradle +++ b/build.gradle @@ -389,7 +389,7 @@ dependencies { implementation group: 'org.glassfish', name: 'javax.el', version: '3.0.0' implementation group: 'javax.el', name: 'javax.el-api', version: '3.0.0' implementation group: 'org.yaml', name: 'snakeyaml', version: '1.33' - implementation group: 'com.launchdarkly', name: 'launchdarkly-java-server-sdk', version: '5.10.2' + implementation group: 'com.launchdarkly', name: 'launchdarkly-java-server-sdk', version: '5.10.9' implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: versions.log4j implementation group: 'org.apache.logging.log4j', name: 'log4j', version: versions.log4j From 5bf1aa722629d2225c44b544ef5db8295fa9efb1 Mon Sep 17 00:00:00 2001 From: JackBraceyCGI <125360867+JackBraceyCGI@users.noreply.github.com> Date: Fri, 7 Jul 2023 15:07:55 +0100 Subject: [PATCH 30/67] Fixed 500 error when primary location is null (#747) * Fixed 500 error when primary location is null * Moved to use nullSafeEquals instead of prior null check * Reimplement ignore case portion of comparison * Change to use Apache StringUtils instead This should null check as well as compare, while ignoring case * Remove unused imports * Fixing checkstyle issues with imports --- .../cwrdapi/util/CaseWorkerChildListValidator.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/uk/gov/hmcts/reform/cwrdapi/util/CaseWorkerChildListValidator.java b/src/main/java/uk/gov/hmcts/reform/cwrdapi/util/CaseWorkerChildListValidator.java index 05ba17475..1af298a59 100644 --- a/src/main/java/uk/gov/hmcts/reform/cwrdapi/util/CaseWorkerChildListValidator.java +++ b/src/main/java/uk/gov/hmcts/reform/cwrdapi/util/CaseWorkerChildListValidator.java @@ -1,5 +1,6 @@ package uk.gov.hmcts.reform.cwrdapi.util; +import org.apache.commons.lang3.StringUtils; import uk.gov.hmcts.reform.cwrdapi.client.domain.CaseWorkerProfile; import uk.gov.hmcts.reform.cwrdapi.client.domain.Location; import uk.gov.hmcts.reform.cwrdapi.client.domain.WorkArea; @@ -54,12 +55,15 @@ public boolean isValidLocations(CaseWorkerProfile caseWorkerProfile, ConstraintV if (isNotEmpty(caseWorkerProfile.getLocations()) && caseWorkerProfile.getLocations().size() > 1) { //@TO do remove getLocationName with Id problem with excel sheet - isValidLocations = negate(caseWorkerProfile.getLocations().get(0).getLocationName() - .equalsIgnoreCase(caseWorkerProfile.getLocations().get(1).getLocationName())); + isValidLocations = negate( + StringUtils.equalsIgnoreCase( + caseWorkerProfile.getLocations().get(0).getLocationName(), + caseWorkerProfile.getLocations().get(1).getLocationName())); + if (FALSE.equals(isValidLocations)) { context.buildConstraintViolationWithTemplate(DUPLICATE_PRIMARY_AND_SECONDARY_LOCATIONS) - .addPropertyNode(LOCATION_FIELD) - .addConstraintViolation(); + .addPropertyNode(LOCATION_FIELD) + .addConstraintViolation(); } } else if (isEmpty(caseWorkerProfile.getLocations()) || caseWorkerProfile.getLocations() From e6c1dfb30fe2693d2ad20be34959e4f00f729522 Mon Sep 17 00:00:00 2001 From: Kiran Kumar Yenigala <51748133+kiran-yenigala-hmcts@users.noreply.github.com> Date: Fri, 7 Jul 2023 15:37:02 +0100 Subject: [PATCH 31/67] DTSRD-471: Update skill table with the new skills for ET (#745) * DTSRD-471: Update skill table with the new skills for ET * DTSRD-471: Update skill table with the new skills for ET * Fix CVE-2023-2976 issue * Fix CVE-2023-2976 issue (launchdarkly-java-server-sdk upgrade to 6.2.1) * Fix CVE-2023-2976 issue (launchdarkly-java-server-sdk upgrade to 6.2.1) --- build.gradle | 2 +- .../reform/cwrdapi/StaffRefDataSkillsFunctionalTest.java | 4 ++-- src/main/resources/db/migration/V1_23__update_skill_table.sql | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 src/main/resources/db/migration/V1_23__update_skill_table.sql diff --git a/build.gradle b/build.gradle index e744fe0b3..8d820d93d 100644 --- a/build.gradle +++ b/build.gradle @@ -389,7 +389,7 @@ dependencies { implementation group: 'org.glassfish', name: 'javax.el', version: '3.0.0' implementation group: 'javax.el', name: 'javax.el-api', version: '3.0.0' implementation group: 'org.yaml', name: 'snakeyaml', version: '1.33' - implementation group: 'com.launchdarkly', name: 'launchdarkly-java-server-sdk', version: '5.10.9' + implementation group: 'com.launchdarkly', name: 'launchdarkly-java-server-sdk', version: '6.2.1' implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: versions.log4j implementation group: 'org.apache.logging.log4j', name: 'log4j', version: versions.log4j diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/StaffRefDataSkillsFunctionalTest.java b/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/StaffRefDataSkillsFunctionalTest.java index 50eef083d..351cc83f7 100644 --- a/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/StaffRefDataSkillsFunctionalTest.java +++ b/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/StaffRefDataSkillsFunctionalTest.java @@ -54,8 +54,8 @@ void should_return_service_skills_with_status_code_200_when_flag_true() { assertThat(staffWorkerSkillResponse.getServiceSkills().size()).isGreaterThan(0); ServiceSkill serviceSkill = staffWorkerSkillResponse.getServiceSkills().get(0); - assertThat(serviceSkill.getId()).isEqualTo("ABA5"); - assertThat(serviceSkill.getSkills().size()).isGreaterThan(1); + assertThat(serviceSkill.getId()).isEqualTo("BHA1"); + assertThat(serviceSkill.getSkills().size()).isGreaterThanOrEqualTo(1); } diff --git a/src/main/resources/db/migration/V1_23__update_skill_table.sql b/src/main/resources/db/migration/V1_23__update_skill_table.sql new file mode 100644 index 000000000..f418830b1 --- /dev/null +++ b/src/main/resources/db/migration/V1_23__update_skill_table.sql @@ -0,0 +1,2 @@ +INSERT INTO SKILL(skill_id,skill_code,description,service_id,user_type,created_date,last_update) VALUES + (41,'SKILL:BHA1:ET1Vetting','ET1 Vetting','BHA1','CTSC',timezone('utc', now()),timezone('utc', now())); From e27436e805c3a8b0f3910d5a14731bd27f0b7ae6 Mon Sep 17 00:00:00 2001 From: Kiran Kumar Yenigala <51748133+kiran-yenigala-hmcts@users.noreply.github.com> Date: Fri, 14 Jul 2023 08:28:37 +0100 Subject: [PATCH 32/67] DTSRD-742, DTSRD-541: add new roles (#749) * DTSRD-742, DTSRD-541: add new roles * DTSRD-742, DTSRD-541: add new roles --- .../CreateCaseWorkerProfilesIntegrationTest.java | 6 +++++- ...reateUpdateStaffRefDataProfilesIntegrationTest.java | 6 +++++- .../db/testmigration/V1_20__insert_roletype.sql | 10 ++++++++++ .../resources/db/migration/V1_24__insert_roletype.sql | 10 ++++++++++ 4 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 src/integrationTest/resources/db/testmigration/V1_20__insert_roletype.sql create mode 100644 src/main/resources/db/migration/V1_24__insert_roletype.sql diff --git a/src/integrationTest/java/uk/gov/hmcts/reform/cwrdapi/CreateCaseWorkerProfilesIntegrationTest.java b/src/integrationTest/java/uk/gov/hmcts/reform/cwrdapi/CreateCaseWorkerProfilesIntegrationTest.java index a4b7118d2..6657114a1 100644 --- a/src/integrationTest/java/uk/gov/hmcts/reform/cwrdapi/CreateCaseWorkerProfilesIntegrationTest.java +++ b/src/integrationTest/java/uk/gov/hmcts/reform/cwrdapi/CreateCaseWorkerProfilesIntegrationTest.java @@ -149,10 +149,12 @@ void shouldCreateCaseworkerWithNewRoles() { CaseWorkerRoleRequest cwRoleRequest1 = new CaseWorkerRoleRequest("Regional Centre Team Leader", false); CaseWorkerRoleRequest cwRoleRequest2 = new CaseWorkerRoleRequest("DWP Caseworker", false); CaseWorkerRoleRequest cwRoleRequest3 = new CaseWorkerRoleRequest("Registrar", false); + CaseWorkerRoleRequest cwRoleRequest4 = new CaseWorkerRoleRequest("CICA Caseworker", false); + CaseWorkerRoleRequest cwRoleRequest5 = new CaseWorkerRoleRequest("Cafcass Cymru Caseworker", false); List caseWorkerRoleRequests = ImmutableList - .of(cwRoleRequest,cwRoleRequest1,cwRoleRequest2,cwRoleRequest3); + .of(cwRoleRequest,cwRoleRequest1,cwRoleRequest2,cwRoleRequest3, cwRoleRequest4, cwRoleRequest5); caseWorkersProfileCreationRequests.get(0).setRoles(caseWorkerRoleRequests); caseWorkersProfileCreationRequests.get(0).setUserType("Other Government Department"); @@ -163,6 +165,8 @@ void shouldCreateCaseworkerWithNewRoles() { assertEquals(13, (long) caseWorkerRoles.get(0).getRoleId()); assertEquals(14,(long)caseWorkerRoles.get(2).getRoleId()); assertEquals(16,(long)caseWorkerRoles.get(3).getRoleId()); + assertEquals(17,(long)caseWorkerRoles.get(4).getRoleId()); + assertEquals(18,(long)caseWorkerRoles.get(5).getRoleId()); var caseWorkerProfile = caseWorkerProfileRepository.findAll(); assertEquals(5,caseWorkerProfile.get(0).getUserTypeId()); } diff --git a/src/integrationTest/java/uk/gov/hmcts/reform/cwrdapi/CreateUpdateStaffRefDataProfilesIntegrationTest.java b/src/integrationTest/java/uk/gov/hmcts/reform/cwrdapi/CreateUpdateStaffRefDataProfilesIntegrationTest.java index 18b6fdcb5..b0cd42dc6 100644 --- a/src/integrationTest/java/uk/gov/hmcts/reform/cwrdapi/CreateUpdateStaffRefDataProfilesIntegrationTest.java +++ b/src/integrationTest/java/uk/gov/hmcts/reform/cwrdapi/CreateUpdateStaffRefDataProfilesIntegrationTest.java @@ -151,10 +151,12 @@ void shouldCreateCaseworkerWithNewRoles() { CaseWorkerRoleRequest cwRoleRequest1 = new CaseWorkerRoleRequest("Regional Centre Team Leader", false); CaseWorkerRoleRequest cwRoleRequest2 = new CaseWorkerRoleRequest("DWP Caseworker", false); CaseWorkerRoleRequest cwRoleRequest3 = new CaseWorkerRoleRequest("Registrar", false); + CaseWorkerRoleRequest cwRoleRequest4 = new CaseWorkerRoleRequest("CICA Caseworker", false); + CaseWorkerRoleRequest cwRoleRequest5 = new CaseWorkerRoleRequest("Cafcass Cymru Caseworker", false); List caseWorkerRoleRequests = ImmutableList - .of(cwRoleRequest, cwRoleRequest1, cwRoleRequest2, cwRoleRequest3); + .of(cwRoleRequest, cwRoleRequest1, cwRoleRequest2, cwRoleRequest3, cwRoleRequest4, cwRoleRequest5); caseWorkersProfileCreationRequests.get(0).setRoles(caseWorkerRoleRequests); caseWorkersProfileCreationRequests.get(0).setUserType("Other Government Department"); @@ -165,6 +167,8 @@ void shouldCreateCaseworkerWithNewRoles() { assertEquals(13, (long) caseWorkerRoles.get(0).getRoleId()); assertEquals(14, (long) caseWorkerRoles.get(2).getRoleId()); assertEquals(16, (long) caseWorkerRoles.get(3).getRoleId()); + assertEquals(17, (long) caseWorkerRoles.get(4).getRoleId()); + assertEquals(18, (long) caseWorkerRoles.get(5).getRoleId()); var caseWorkerProfile = caseWorkerProfileRepository.findAll(); assertEquals(5, caseWorkerProfile.get(0).getUserTypeId()); } diff --git a/src/integrationTest/resources/db/testmigration/V1_20__insert_roletype.sql b/src/integrationTest/resources/db/testmigration/V1_20__insert_roletype.sql new file mode 100644 index 000000000..e91644fb5 --- /dev/null +++ b/src/integrationTest/resources/db/testmigration/V1_20__insert_roletype.sql @@ -0,0 +1,10 @@ + +INSERT INTO +role_type (role_id, description, created_date) +VALUES +(17, 'CICA Caseworker', timezone('utc', now())); + +INSERT INTO +role_type (role_id, description, created_date) +VALUES +(18, 'Cafcass Cymru Caseworker', timezone('utc', now())); diff --git a/src/main/resources/db/migration/V1_24__insert_roletype.sql b/src/main/resources/db/migration/V1_24__insert_roletype.sql new file mode 100644 index 000000000..6b3914f50 --- /dev/null +++ b/src/main/resources/db/migration/V1_24__insert_roletype.sql @@ -0,0 +1,10 @@ + +INSERT INTO + role_type (role_id, description, created_date) +VALUES + (17, 'CICA Caseworker', timezone('utc', now())); + +INSERT INTO + role_type (role_id, description, created_date) +VALUES + (18, 'Cafcass Cymru Caseworker', timezone('utc', now())); \ No newline at end of file From 4b92c86ccd0c2f40dd418b32039c131b5b680392 Mon Sep 17 00:00:00 2001 From: Kiran Kumar Yenigala <51748133+kiran-yenigala-hmcts@users.noreply.github.com> Date: Thu, 3 Aug 2023 10:12:01 +0100 Subject: [PATCH 33/67] Address CVE-2023-34034 (#751) --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 8d820d93d..69eae2f15 100644 --- a/build.gradle +++ b/build.gradle @@ -18,7 +18,7 @@ plugins { id "info.solidsoft.pitest" version '1.7.0' id 'io.spring.dependency-management' version '1.1.0' id 'org.sonarqube' version '3.3' - id 'org.springframework.boot' version '2.7.12' + id 'org.springframework.boot' version '2.7.14' id "org.flywaydb.flyway" version '8.5.4' id 'au.com.dius.pact' version '4.1.7' // do not change, otherwise serenity report fails id 'org.owasp.dependencycheck' version '8.0.1' @@ -37,7 +37,7 @@ def versions = [ reformLogging : '6.0.1', serenity : '2.0.76', sonarPitest : '0.5', - springBoot : '2.7.12', + springBoot : '2.7.14', pact_version : '4.1.7', launchDarklySdk : '5.10.9', restAssured : '4.3.3', @@ -342,7 +342,7 @@ dependencies { implementation group: 'org.springframework.boot', name: 'spring-boot-starter-jdbc' implementation group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: versions.springBoot implementation group: 'org.springframework.boot', name: 'spring-boot-starter-cache', version: versions.springBoot - implementation group: 'org.springframework.security', name: 'spring-security-core', version: '5.7.8' + implementation group: 'org.springframework.security', name: 'spring-security-core', version: '5.7.10' implementation group: 'org.springframework', name: 'spring-aop', version: versions.springVersion implementation group: 'org.springframework', name: 'spring-aspects', version: versions.springVersion From 4ef642f63f8b74e62929427d2e10c3d9bbf04917 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 3 Aug 2023 09:12:26 +0000 Subject: [PATCH 34/67] Update Helm release servicebus to v1.0.4 --- charts/rd-caseworker-ref-api/Chart.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/rd-caseworker-ref-api/Chart.yaml b/charts/rd-caseworker-ref-api/Chart.yaml index 0001d4ea8..53b640192 100644 --- a/charts/rd-caseworker-ref-api/Chart.yaml +++ b/charts/rd-caseworker-ref-api/Chart.yaml @@ -3,7 +3,7 @@ appVersion: "1.0" description: A Helm chart for rd-caseworker-ref-api name: rd-caseworker-ref-api home: https://github.com/hmcts/rd-caseworker-ref-api -version: 1.0.0 +version: 1.0.1 maintainers: - name: Reference Data Team dependencies: @@ -11,6 +11,6 @@ dependencies: version: 4.0.13 repository: 'https://hmctspublic.azurecr.io/helm/v1/repo/' - name: servicebus - version: 1.0.2 + version: 1.0.4 repository: 'https://hmctspublic.azurecr.io/helm/v1/repo/' condition: servicebus.enabled From 397368dbf9a2afe46a3c0c13f8567496936ad9fc Mon Sep 17 00:00:00 2001 From: Kiran Kumar Yenigala <51748133+kiran-yenigala-hmcts@users.noreply.github.com> Date: Mon, 25 Sep 2023 09:38:31 +0100 Subject: [PATCH 35/67] Fix CVE-2023-39533 (#758) * Fix CVE-2023-39533 * Fix CVE-2023-41080 * Enable AppInsights * Fix build issues * revert the changes done * Change sbNamespace to rd-servicebus-preview * Fix TEST_URL issues * Fix TEST_URL issues * Update renovate config --- .github/renovate.json | 21 +++++-------------- Jenkinsfile_CNP | 4 ---- build.gradle | 6 +++--- .../values.preview.template.yaml | 2 +- 4 files changed, 9 insertions(+), 24 deletions(-) diff --git a/.github/renovate.json b/.github/renovate.json index 667f79aa5..690ad059b 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -1,18 +1,7 @@ { - "enabledManagers": ["helm-requirements","gradle-wrapper","regex"], - "helm-requirements": - { - "fileMatch": ["\\Chart.yaml$"], - "aliases": { - "hmctspublic": "https://hmctspublic.azurecr.io/helm/v1/repo/" - } - }, - "regexManagers": [ - { - "fileMatch": ["^Dockerfile$"], - "matchStrings": [ - "datasource=(?.*?) depName=(?.*?)( versioning=(?.*?))?\\sARG .*?_VERSION=(?.*)\\s" - ] - } + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "local>hmcts/.github:renovate-config", + "local>hmcts/.github//renovate/automerge-minor" ] -} +} \ No newline at end of file diff --git a/Jenkinsfile_CNP b/Jenkinsfile_CNP index b52aad38c..4d78ef2b3 100644 --- a/Jenkinsfile_CNP +++ b/Jenkinsfile_CNP @@ -58,22 +58,18 @@ withPipeline(type, product, component) { before('functionalTest:preview') { env.execution_environment = "preview" - env.TEST_URL = "http://rd-caseworker-ref-api-preview.preview.platform.hmcts.net" } before('functionalTest:aat') { env.execution_environment = "aat" - env.TEST_URL = "http://rd-caseworker-ref-api-aat.aat.platform.hmcts.net" } before('smoketest:preview') { env.execution_environment = "preview" - env.TEST_URL = "http://rd-caseworker-ref-api-preview.preview.platform.hmcts.net" } before('smoketest:aat') { env.execution_environment = "aat" - env.TEST_URL = "http://rd-caseworker-ref-api-aat.aat.platform.hmcts.net" } // Sync demo and perftest with master branch diff --git a/build.gradle b/build.gradle index 69eae2f15..bc8aa0a9b 100644 --- a/build.gradle +++ b/build.gradle @@ -395,8 +395,8 @@ dependencies { implementation group: 'org.apache.logging.log4j', name: 'log4j', version: versions.log4j implementation group: 'org.apache.logging.log4j', name: 'log4j-to-slf4j', version: versions.log4j - implementation group: 'com.azure', name: 'azure-core', version: '1.13.0' - implementation group: 'com.azure', name: 'azure-messaging-servicebus', version: '7.0.2' + implementation group: 'com.azure', name: 'azure-core', version: '1.43.0' + implementation group: 'com.azure', name: 'azure-messaging-servicebus', version: '7.14.3' implementation group: 'com.google.code.gson', name: 'gson', version: '2.9.0' implementation group: 'org.springframework', name: 'spring-core', version: versions.springVersion implementation group: 'org.springframework', name: 'spring-beans', version: versions.springVersion @@ -522,7 +522,7 @@ dependencyManagement { dependencies { // CVE-2023-28709 - dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.75') { + dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.80') { entry 'tomcat-embed-core' entry 'tomcat-embed-el' entry 'tomcat-embed-websocket' diff --git a/charts/rd-caseworker-ref-api/values.preview.template.yaml b/charts/rd-caseworker-ref-api/values.preview.template.yaml index 39b5bb3cc..318e6911a 100644 --- a/charts/rd-caseworker-ref-api/values.preview.template.yaml +++ b/charts/rd-caseworker-ref-api/values.preview.template.yaml @@ -43,7 +43,7 @@ servicebus: enabled: true teamName: RD resourceGroup: rd-aso-preview-rg - sbNamespace: rd + sbNamespace: rd-servicebus-preview setup: topics: - name: crd-topic From 20adafa798553e18c18ca0dfb396d306eeab944e Mon Sep 17 00:00:00 2001 From: Kiran Kumar Yenigala <51748133+kiran-yenigala-hmcts@users.noreply.github.com> Date: Mon, 25 Sep 2023 10:45:34 +0100 Subject: [PATCH 36/67] CVE CVE-2023-35116 suppression (#759) --- config/owasp/suppressions.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/owasp/suppressions.xml b/config/owasp/suppressions.xml index 193475dde..4eb69ecb5 100644 --- a/config/owasp/suppressions.xml +++ b/config/owasp/suppressions.xml @@ -17,5 +17,6 @@ file name: launchdarkly-java-server-sdk-5.10.2.jar (shaded: org.yaml:snakeyaml:1 CVE-2023-35116 + CVE-2022-26336 From fbbbffdd8aa6538906aa52e272b921867400b5f8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 25 Sep 2023 09:46:00 +0000 Subject: [PATCH 37/67] Update dependency com.fasterxml.woodstox:woodstox-core to v6.5.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index bc8aa0a9b..c2053397f 100644 --- a/build.gradle +++ b/build.gradle @@ -528,7 +528,7 @@ dependencyManagement { entry 'tomcat-embed-websocket' } //CVE-2022-40152 - dependencySet(group: 'com.fasterxml.woodstox', version: '6.5.0') { + dependencySet(group: 'com.fasterxml.woodstox', version: '6.5.1') { entry 'woodstox-core' } //CVE-2023-24998 From 359dd43176f60a78141e5152322e5d010e33073f Mon Sep 17 00:00:00 2001 From: Kiran Kumar Yenigala <51748133+kiran-yenigala-hmcts@users.noreply.github.com> Date: Mon, 25 Sep 2023 13:15:13 +0100 Subject: [PATCH 38/67] CVE fix (#760) * CVE CVE-2023-35116 suppression * CVE CVE-2022-26336 suppression --- config/owasp/suppressions.xml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/config/owasp/suppressions.xml b/config/owasp/suppressions.xml index 4eb69ecb5..bc29c4363 100644 --- a/config/owasp/suppressions.xml +++ b/config/owasp/suppressions.xml @@ -1,9 +1,5 @@ - - ^pkg:maven/org\.apache\.poi.*$ - CVE-2022-26336 - CVE-2022-45688 CVE-2022-1471 From 98ef91eecd58105c4776b86951e6bac9b3d8df65 Mon Sep 17 00:00:00 2001 From: SabinaHMCTS Date: Tue, 26 Sep 2023 10:13:09 +0100 Subject: [PATCH 39/67] DTSRD-1089.CVE-2023-39533 --- audit.json | 1 + 1 file changed, 1 insertion(+) diff --git a/audit.json b/audit.json index 846aed61b..129c707a3 100644 --- a/audit.json +++ b/audit.json @@ -1,4 +1,5 @@ { + "10049_Non-Storable Content_http://rd-professional-api-aat.service.core-compute-aat.internal/v2/api-docs_GET": "ignore", "10021_X-Content-Type-Options Header Missing_http://rd-caseworker-ref-api-aat.service.core-compute-aat.internal/_GET": "ignore", "10010_Cookie No HttpOnly Flag_http://rd-caseworker-ref-api-aat.service.core-compute-aat.internal/_GET": "ignore", "100001_Unexpected Content-Type was returned_http://rd-caseworker-ref-api-aat.service.core-compute-aat.internal/_GET": "ignore", From 4a077a9a5932532bacf24fdc322d94e1b5efa811 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 26 Sep 2023 06:10:32 +0000 Subject: [PATCH 40/67] Update dependency com.azure:azure-messaging-servicebus to v7.14.4 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c2053397f..c460d03b8 100644 --- a/build.gradle +++ b/build.gradle @@ -396,7 +396,7 @@ dependencies { implementation group: 'org.apache.logging.log4j', name: 'log4j-to-slf4j', version: versions.log4j implementation group: 'com.azure', name: 'azure-core', version: '1.43.0' - implementation group: 'com.azure', name: 'azure-messaging-servicebus', version: '7.14.3' + implementation group: 'com.azure', name: 'azure-messaging-servicebus', version: '7.14.4' implementation group: 'com.google.code.gson', name: 'gson', version: '2.9.0' implementation group: 'org.springframework', name: 'spring-core', version: versions.springVersion implementation group: 'org.springframework', name: 'spring-beans', version: versions.springVersion From ad89f71de859bc34116c742d4946977f21843ce4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 26 Sep 2023 09:49:39 +0000 Subject: [PATCH 41/67] Update dependency com.google.guava:guava to v32.1.2-jre --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c460d03b8..0e0533806 100644 --- a/build.gradle +++ b/build.gradle @@ -384,7 +384,7 @@ dependencies { implementation group: 'org.flywaydb', name: 'flyway-core', version: '6.5.5' implementation group: 'org.postgresql', name: 'postgresql', version: '42.5.1' - implementation group: 'com.google.guava', name: 'guava', version: '32.1.1-jre' + implementation group: 'com.google.guava', name: 'guava', version: '32.1.2-jre' //Added org.glassfish to support javax.el implementation group: 'org.glassfish', name: 'javax.el', version: '3.0.0' implementation group: 'javax.el', name: 'javax.el-api', version: '3.0.0' From 37037091dc30028568094db2fc0274658e276ccc Mon Sep 17 00:00:00 2001 From: SabinaHMCTS Date: Tue, 26 Sep 2023 11:28:14 +0100 Subject: [PATCH 42/67] DTSRD-1089.CVE-2023-39533 --- audit.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/audit.json b/audit.json index 129c707a3..736d403b3 100644 --- a/audit.json +++ b/audit.json @@ -1,5 +1,6 @@ { - "10049_Non-Storable Content_http://rd-professional-api-aat.service.core-compute-aat.internal/v2/api-docs_GET": "ignore", + "10112_Session Management Response Identified_http://rd-caseworker-ref-api-aat.service.core-compute-aat.internal/v2/api-docs_GET": "ignore", + "10049_Non-Storable Content_http://rd-caseworker-ref-api-aat.service.core-compute-aat.internal/v2/api-docs_GET": "ignore", "10021_X-Content-Type-Options Header Missing_http://rd-caseworker-ref-api-aat.service.core-compute-aat.internal/_GET": "ignore", "10010_Cookie No HttpOnly Flag_http://rd-caseworker-ref-api-aat.service.core-compute-aat.internal/_GET": "ignore", "100001_Unexpected Content-Type was returned_http://rd-caseworker-ref-api-aat.service.core-compute-aat.internal/_GET": "ignore", From 593e25f79b1baa6c2c23eaf937561e56944dda5a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 27 Sep 2023 08:13:17 +0000 Subject: [PATCH 43/67] Update dependency org.projectlombok:lombok to v1.18.30 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 0e0533806..abf4b5329 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ apply plugin: 'project-report' apply plugin: 'idea' def versions = [ - lombok : '1.18.22', + lombok : '1.18.30', gradlePitest : '1.5.1', pitest : '1.7.4', reformHealthStarter: '0.0.5', From 913ae0f9d8153344348125cdab4930e4e6391eb7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 27 Sep 2023 09:34:13 +0000 Subject: [PATCH 44/67] Update plugin io.spring.dependency-management to v1.1.3 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index abf4b5329..4881dcc63 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ plugins { id 'pmd' id 'com.github.ben-manes.versions' version '0.43.0' id "info.solidsoft.pitest" version '1.7.0' - id 'io.spring.dependency-management' version '1.1.0' + id 'io.spring.dependency-management' version '1.1.3' id 'org.sonarqube' version '3.3' id 'org.springframework.boot' version '2.7.14' id "org.flywaydb.flyway" version '8.5.4' From 0cfb899a0c5450ddd62f7ec39d617b1133e5f8c5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 27 Sep 2023 09:51:59 +0000 Subject: [PATCH 45/67] Update spring boot to v2.7.16 --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 4881dcc63..69ff092ac 100644 --- a/build.gradle +++ b/build.gradle @@ -18,7 +18,7 @@ plugins { id "info.solidsoft.pitest" version '1.7.0' id 'io.spring.dependency-management' version '1.1.3' id 'org.sonarqube' version '3.3' - id 'org.springframework.boot' version '2.7.14' + id 'org.springframework.boot' version '2.7.16' id "org.flywaydb.flyway" version '8.5.4' id 'au.com.dius.pact' version '4.1.7' // do not change, otherwise serenity report fails id 'org.owasp.dependencycheck' version '8.0.1' @@ -37,7 +37,7 @@ def versions = [ reformLogging : '6.0.1', serenity : '2.0.76', sonarPitest : '0.5', - springBoot : '2.7.14', + springBoot : '2.7.16', pact_version : '4.1.7', launchDarklySdk : '5.10.9', restAssured : '4.3.3', From 723d32af97f7c3d69e4f33d1555a27ed6da4a9fa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 28 Sep 2023 07:14:42 +0000 Subject: [PATCH 46/67] Update spring cloud --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 69ff092ac..cf17b67a2 100644 --- a/build.gradle +++ b/build.gradle @@ -414,7 +414,7 @@ dependencies { implementation group: 'org.yaml', name: 'snakeyaml', version: '1.33' implementation group: 'org.springdoc', name: 'springdoc-openapi-ui', version: '1.6.8' - implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-bootstrap', version: '3.1.5' + implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-bootstrap', version: '3.1.7' // https://mvnrepository.com/artifact/net.minidev/json-smart implementation group: 'net.minidev', name: 'json-smart', version: '2.4.8' @@ -479,7 +479,7 @@ dependencies { testImplementation 'io.github.openfeign:feign-jackson:12.1' testImplementation group: 'com.github.mifmif', name: 'generex', version: '1.0.2' - testImplementation 'org.springframework.cloud:spring-cloud-contract-wiremock:3.1.0' + testImplementation 'org.springframework.cloud:spring-cloud-contract-wiremock:3.1.8' testImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: versions.springBoot testImplementation('com.opentable.components:otj-pg-embedded:0.13.4') testImplementation group: 'org.springframework', name: 'spring-test', version: '5.3.17' @@ -536,7 +536,7 @@ dependencyManagement { entry 'commons-fileupload' } //CVE-2021-22044 - dependencySet(group: 'org.springframework.cloud', version: '3.1.5') { + dependencySet(group: 'org.springframework.cloud', version: '3.1.8') { entry 'spring-cloud-starter-openfeign' entry 'spring-cloud-openfeign-core' } From 74d1b1ffe68e1cefe25f0ce0377e066d6a5d9542 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 28 Sep 2023 09:37:48 +0000 Subject: [PATCH 47/67] Update spring core to v5.3.30 --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index cf17b67a2..6504f090d 100644 --- a/build.gradle +++ b/build.gradle @@ -43,7 +43,7 @@ def versions = [ restAssured : '4.3.3', jackson : '2.14.2', log4j : '2.17.1', - springVersion : '5.3.27', + springVersion : '5.3.30', poi : '4.1.2', logback : '1.2.11', testContainer_postgresql: '1.17.6' @@ -482,7 +482,7 @@ dependencies { testImplementation 'org.springframework.cloud:spring-cloud-contract-wiremock:3.1.8' testImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: versions.springBoot testImplementation('com.opentable.components:otj-pg-embedded:0.13.4') - testImplementation group: 'org.springframework', name: 'spring-test', version: '5.3.17' + testImplementation group: 'org.springframework', name: 'spring-test', version: '5.3.30' testImplementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.10' testImplementation group: 'ch.qos.logback', name: 'logback-core', version: '1.2.10' From ac11a00cbc035aac1807111e8d34d78423f495f5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 28 Sep 2023 09:56:51 +0000 Subject: [PATCH 48/67] Update Helm release java to v4.2.0 --- charts/rd-caseworker-ref-api/Chart.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/rd-caseworker-ref-api/Chart.yaml b/charts/rd-caseworker-ref-api/Chart.yaml index 53b640192..97be35d85 100644 --- a/charts/rd-caseworker-ref-api/Chart.yaml +++ b/charts/rd-caseworker-ref-api/Chart.yaml @@ -3,12 +3,12 @@ appVersion: "1.0" description: A Helm chart for rd-caseworker-ref-api name: rd-caseworker-ref-api home: https://github.com/hmcts/rd-caseworker-ref-api -version: 1.0.1 +version: 1.0.2 maintainers: - name: Reference Data Team dependencies: - name: java - version: 4.0.13 + version: 4.2.0 repository: 'https://hmctspublic.azurecr.io/helm/v1/repo/' - name: servicebus version: 1.0.4 From 7dd89526c7dd11977f00340309ce11169e79234e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Oct 2023 06:04:56 +0000 Subject: [PATCH 49/67] Update Terraform azuread to v2.43.0 --- infrastructure/terraform.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/terraform.tf b/infrastructure/terraform.tf index 50d9cc15a..409001775 100644 --- a/infrastructure/terraform.tf +++ b/infrastructure/terraform.tf @@ -13,7 +13,7 @@ terraform { azuread = { source = "hashicorp/azuread" - version = "2.33.0" + version = "2.43.0" } } } \ No newline at end of file From fd915c4ccf5c9db7314cb6c21dc5e12a1509a7b1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 3 Oct 2023 06:28:05 +0000 Subject: [PATCH 50/67] Update Terraform azurerm to ~> 3.75.0 --- infrastructure/terraform.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/terraform.tf b/infrastructure/terraform.tf index 409001775..aacf64fd5 100644 --- a/infrastructure/terraform.tf +++ b/infrastructure/terraform.tf @@ -8,7 +8,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "~> 3.40.0" + version = "~> 3.75.0" } azuread = { From 4342b70cf97510832d58a9c2c68f8c9ed7604d50 Mon Sep 17 00:00:00 2001 From: Andy Boyle Date: Thu, 12 Oct 2023 12:57:05 +0100 Subject: [PATCH 51/67] DTSRD-1456 - Updating Netty versions for CVE-2023-4586. - Updating helm version; Version of java helm chart below 5.0.0 is deprecated, please upgrade to latest release https://github.com/hmcts/chart-java/releases This configuration will stop working by 23/10/2023 --- build.gradle | 38 ++++++++++++++++--------- charts/rd-caseworker-ref-api/Chart.yaml | 4 +-- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/build.gradle b/build.gradle index 6504f090d..edb0f5192 100644 --- a/build.gradle +++ b/build.gradle @@ -540,6 +540,30 @@ dependencyManagement { entry 'spring-cloud-starter-openfeign' entry 'spring-cloud-openfeign-core' } + + + // Resolves CVE-2023-4586 + dependencySet(group: 'io.netty', version: '4.1.100.Final') { + entry 'netty-buffer' + entry 'netty-codec' + entry 'netty-codec-dns' + entry 'netty-codec-http' + entry 'netty-codec-http2' + entry 'netty-codec-socks' + entry 'netty-common' + entry 'netty-handler' + entry 'netty-handler-proxy' + entry 'netty-resolver' + entry 'netty-resolver-dns' + entry 'netty-resolver-dns-classes-macos' + entry 'netty-resolver-dns-native-macos' + entry 'netty-transport' + entry 'netty-transport-classes-epoll' + entry 'netty-transport-classes-kqueue' + entry 'netty-transport-native-epoll' + entry 'netty-transport-native-kqueue' + entry 'netty-transport-native-unix-common' + } } } @@ -568,20 +592,6 @@ bootJar { attributes('Implementation-Version': project.version.toString()) } } -configurations.all { - resolutionStrategy.eachDependency { details -> -// added netty-tcnative-boringssl-static because its required for -// group: 'com.azure', name: 'azure-core', version: '1.13.0' in spring boot upgrade - if (details.requested.group == 'io.netty' - && details.requested.name == 'netty-tcnative-boringssl-static' ){ - details.useVersion "2.0.35.Final" - } - if (details.requested.group == 'io.netty' - && details.requested.name != 'netty-tcnative-boringssl-static' ) { - details.useVersion "4.1.94.Final" - } - } -} tasks.withType(Test) { useJUnitPlatform() diff --git a/charts/rd-caseworker-ref-api/Chart.yaml b/charts/rd-caseworker-ref-api/Chart.yaml index 97be35d85..04057ff4d 100644 --- a/charts/rd-caseworker-ref-api/Chart.yaml +++ b/charts/rd-caseworker-ref-api/Chart.yaml @@ -3,12 +3,12 @@ appVersion: "1.0" description: A Helm chart for rd-caseworker-ref-api name: rd-caseworker-ref-api home: https://github.com/hmcts/rd-caseworker-ref-api -version: 1.0.2 +version: 1.0.3 maintainers: - name: Reference Data Team dependencies: - name: java - version: 4.2.0 + version: 5.0.0 repository: 'https://hmctspublic.azurecr.io/helm/v1/repo/' - name: servicebus version: 1.0.4 From c26dda86bdeacc3e78ec8ef31f7611669ab34d31 Mon Sep 17 00:00:00 2001 From: SabinaHMCTS Date: Fri, 13 Oct 2023 11:05:29 +0100 Subject: [PATCH 52/67] DTSRD-1446.Remove branches to sync -caseworker demo and master --- Jenkinsfile_CNP | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile_CNP b/Jenkinsfile_CNP index 4d78ef2b3..ba2b2cc38 100644 --- a/Jenkinsfile_CNP +++ b/Jenkinsfile_CNP @@ -35,7 +35,7 @@ def vaultOverrides = [ ] // Configure branches to sync with master branch -def branchesToSync = ['demo', 'ithc', 'perftest'] +def branchesToSync = [] // Vars for Kubernetes PACT env.PACT_BROKER_FULL_URL = 'https://pact-broker.platform.hmcts.net' From 70d0d0539c225763407fee3d88ea4a584d238b94 Mon Sep 17 00:00:00 2001 From: lukasz-wolski <1005015+lukasz-wolski@users.noreply.github.com> Date: Tue, 17 Oct 2023 14:21:16 +0100 Subject: [PATCH 53/67] Update build.gradle --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index edb0f5192..ad9fc76d4 100644 --- a/build.gradle +++ b/build.gradle @@ -522,7 +522,7 @@ dependencyManagement { dependencies { // CVE-2023-28709 - dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.80') { + dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.81') { entry 'tomcat-embed-core' entry 'tomcat-embed-el' entry 'tomcat-embed-websocket' From 1a346c3f9aae28718b2761a73015d5b0b6849150 Mon Sep 17 00:00:00 2001 From: manukundloo-hmcts Date: Mon, 23 Oct 2023 12:04:07 +0100 Subject: [PATCH 54/67] [RDCC-7094]-Judicial : Consumer Pact Test Cases needs to be added --- .../cwrdapi/JrdUserRequestV1ConsumerTest.java | 157 ++++++++++++++++++ .../cwrdapi/client/domain/JrdUserRequest.java | 25 +++ 2 files changed, 182 insertions(+) create mode 100644 src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/JrdUserRequestV1ConsumerTest.java create mode 100644 src/main/java/uk/gov/hmcts/reform/cwrdapi/client/domain/JrdUserRequest.java diff --git a/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/JrdUserRequestV1ConsumerTest.java b/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/JrdUserRequestV1ConsumerTest.java new file mode 100644 index 000000000..dd7d03481 --- /dev/null +++ b/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/JrdUserRequestV1ConsumerTest.java @@ -0,0 +1,157 @@ +package uk.gov.hmcts.reform.cwrdapi; + +import au.com.dius.pact.consumer.MockServer; +import au.com.dius.pact.consumer.dsl.DslPart; +import au.com.dius.pact.consumer.dsl.PactDslWithProvider; +import au.com.dius.pact.consumer.junit5.PactConsumerTestExt; +import au.com.dius.pact.core.model.RequestResponsePact; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import groovy.util.logging.Slf4j; +import io.restassured.http.ContentType; +import net.serenitybdd.rest.SerenityRest; +import org.apache.http.client.fluent.Executor; +import org.assertj.core.api.Assertions; +import org.jetbrains.annotations.NotNull; +import org.json.JSONArray; +import org.json.JSONException; +import org.junit.After; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import uk.gov.hmcts.reform.cwrdapi.client.domain.JrdUserRequest; + +import java.util.Map; +import java.util.Set; + +import static io.pactfoundation.consumer.dsl.LambdaDsl.newJsonArray; + +@Slf4j +@ExtendWith(PactConsumerTestExt.class) +@ExtendWith(SpringExtension.class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +//@PactTestFor(providerName = "referenceData_judicial") +//@PactFolder("pacts") +public class JrdUserRequestV1ConsumerTest { + + private static final String JRD_GET_PROFILES_URL = "/refdata/judicial/users"; + + private static final String SIDAM_ID = "44362987-4b00-f2e7-4ff8-761b87f16bf9"; + + + @BeforeEach + public void setUpEachTest() throws InterruptedException { + Thread.sleep(2000); + } + + @After + void teardown() { + Executor.closeIdleConnections(); + } + + //@Pact(provider = "referenceData_judicial", consumer = "JRD_API_ConsumerTest") + public RequestResponsePact getJrdProfilesListOfIds(PactDslWithProvider builder) throws JsonProcessingException { + + return builder + .given("return judicial user profiles along with their active appointments and authorisations") + .uponReceiving("the api returns judicial user profiles " + + "based on the provided list of user ids") + .path(JRD_GET_PROFILES_URL) + .body(new ObjectMapper().writeValueAsString( + JrdUserRequest.builder().sidamIds(Set.of(SIDAM_ID)).build())) + .method(HttpMethod.POST.toString()) + .willRespondWith() + .status(HttpStatus.OK.value()) + .headers(getResponseHeaders()) + .body(createJrdProfilesResponse()) + .toPact(); + } + + //@Test + //@PactTestFor(pactMethod = "getJrdProfilesListOfIds") + void executeGetJrdProfilesListOfIds(MockServer mockServer) + throws JSONException { + var actualResponseBody = + SerenityRest + .given() + .headers(getHttpHeaders()) + .body(JrdUserRequest.builder().sidamIds( + Set.of(SIDAM_ID)).build()) + .contentType(ContentType.JSON) + .post(mockServer.getUrl() + JRD_GET_PROFILES_URL) + .then() + .log().all().extract().asString(); + + JSONArray response = new JSONArray(actualResponseBody); + Assertions.assertThat(response).isNotNull(); + + } + + //@Pact(provider = "referenceData_judicial", consumer = "JRD_API_consumerTest") + public RequestResponsePact getJrdProfilesServiceName(PactDslWithProvider builder) throws JsonProcessingException { + + return builder + .given("return judicial user profiles along with their active appointments and authorisations") + .uponReceiving("the api returns judicial user profiles " + + "based on the provided service name") + .path(JRD_GET_PROFILES_URL) + .body(new ObjectMapper().writeValueAsString( + JrdUserRequest.builder().ccdServiceNames("CMC").build())) + .method(HttpMethod.POST.toString()) + .willRespondWith() + .status(HttpStatus.OK.value()) + .headers(getResponseHeaders()) + .body(createJrdProfilesResponse()) + .toPact(); + } + + //@Test + //@PactTestFor(pactMethod = "getJrdProfilesServiceName") + void executeGetJrdProfilesServiceName(MockServer mockServer) + throws JSONException { + var actualResponseBody = + SerenityRest + .given() + .headers(getHttpHeaders()) + .body(JrdUserRequest.builder().ccdServiceNames("CMC").build()) + .contentType(ContentType.JSON) + .post(mockServer.getUrl() + JRD_GET_PROFILES_URL) + .then() + .log().all().extract().asString(); + + JSONArray response = new JSONArray(actualResponseBody); + Assertions.assertThat(response).isNotNull(); + + } + + private DslPart createJrdProfilesResponse() { + return newJsonArray(o -> o.object(ob -> ob + .stringType("sidam_id", SIDAM_ID) + .stringType("object_id", "fcb4f03c-4b3f-4c3c-bf3a-662b4557b470") + .stringType("email_id", "e@mail.com") + .minArrayLike("appointments", 1, r -> r + .stringType("location_id", "1") + ) + .minArrayLike("authorisations", 1, r -> r + .stringType("jurisdiction", "IA") + ) + )).build(); + } + + @NotNull + private Map getResponseHeaders() { + Map responseHeaders = Map.of("Content-Type", "application/json"); + return responseHeaders; + } + + private HttpHeaders getHttpHeaders() { + HttpHeaders headers = new HttpHeaders(); + headers.add("ServiceAuthorization", "Bearer " + "1234"); + headers.add("Authorization", "Bearer " + "2345"); + return headers; + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/cwrdapi/client/domain/JrdUserRequest.java b/src/main/java/uk/gov/hmcts/reform/cwrdapi/client/domain/JrdUserRequest.java new file mode 100644 index 000000000..f63963b51 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/cwrdapi/client/domain/JrdUserRequest.java @@ -0,0 +1,25 @@ +package uk.gov.hmcts.reform.cwrdapi.client.domain; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Set; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class JrdUserRequest { + + @JsonProperty("ccdServiceName") + private String ccdServiceNames; + + @JsonProperty("object_ids") + private Set objectIds; + + @JsonProperty("sidam_ids") + private Set sidamIds; +} From 476a6a7653793f762e5e5556a1bf08f1e87e23f6 Mon Sep 17 00:00:00 2001 From: manukundloo-hmcts Date: Mon, 23 Oct 2023 13:15:53 +0100 Subject: [PATCH 55/67] [RDCC-7094]Fix cve issue --- config/owasp/suppressions.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/owasp/suppressions.xml b/config/owasp/suppressions.xml index bc29c4363..deaef5803 100644 --- a/config/owasp/suppressions.xml +++ b/config/owasp/suppressions.xml @@ -14,5 +14,6 @@ file name: launchdarkly-java-server-sdk-5.10.2.jar (shaded: org.yaml:snakeyaml:1 CVE-2023-35116 CVE-2022-26336 + CVE-2023-5072 From 2d44e507dc493036a17b47de75302ef2dffb3c1c Mon Sep 17 00:00:00 2001 From: manukundloo-hmcts Date: Mon, 23 Oct 2023 18:43:54 +0100 Subject: [PATCH 56/67] [RDCC-7094]Added more testcases for V1 and new test cases for V2 --- .../cwrdapi/JrdUserRequestV1ConsumerTest.java | 149 +++++++++- .../cwrdapi/JrdUserRequestV2ConsumerTest.java | 267 ++++++++++++++++++ .../cwrdapi/client/domain/JrdUserRequest.java | 25 -- 3 files changed, 406 insertions(+), 35 deletions(-) create mode 100644 src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/JrdUserRequestV2ConsumerTest.java delete mode 100644 src/main/java/uk/gov/hmcts/reform/cwrdapi/client/domain/JrdUserRequest.java diff --git a/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/JrdUserRequestV1ConsumerTest.java b/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/JrdUserRequestV1ConsumerTest.java index dd7d03481..d4e045a05 100644 --- a/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/JrdUserRequestV1ConsumerTest.java +++ b/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/JrdUserRequestV1ConsumerTest.java @@ -2,11 +2,11 @@ import au.com.dius.pact.consumer.MockServer; import au.com.dius.pact.consumer.dsl.DslPart; +import au.com.dius.pact.consumer.dsl.PactDslJsonRootValue; import au.com.dius.pact.consumer.dsl.PactDslWithProvider; import au.com.dius.pact.consumer.junit5.PactConsumerTestExt; import au.com.dius.pact.core.model.RequestResponsePact; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import groovy.util.logging.Slf4j; import io.restassured.http.ContentType; import net.serenitybdd.rest.SerenityRest; @@ -23,12 +23,11 @@ import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.test.context.junit.jupiter.SpringExtension; -import uk.gov.hmcts.reform.cwrdapi.client.domain.JrdUserRequest; import java.util.Map; -import java.util.Set; import static io.pactfoundation.consumer.dsl.LambdaDsl.newJsonArray; +import static io.pactfoundation.consumer.dsl.LambdaDsl.newJsonBody; @Slf4j @ExtendWith(PactConsumerTestExt.class) @@ -53,6 +52,79 @@ void teardown() { Executor.closeIdleConnections(); } + + //To run the Pact tests please uncommnet all Pact Test annotations + //@Pact(provider = "referenceData_judicial", consumer = "JRD_API_V1_Search_ConsumerTest") + public RequestResponsePact searchJrdProfiles(PactDslWithProvider builder) throws JsonProcessingException { + + return builder + .given("User profile details exist for the search request provided") + .uponReceiving("the api returns judicial user profiles " + + "based on the provided list of user ids") + .path(JRD_GET_PROFILES_URL + "/search") + .body(createJrdProfileSearchRequest()) + .method(HttpMethod.POST.toString()) + .willRespondWith() + .status(HttpStatus.OK.value()) + .headers(getResponseHeaders()) + .body(searchJrdProfilesResponse()) + .toPact(); + } + + //@Test + //@PactTestFor(pactMethod = "searchJrdProfiles") + void searchJrdProfiles(MockServer mockServer) + throws JSONException { + var actualResponseBody = + SerenityRest + .given() + .headers(getHttpHeaders()) + .body(createJrdProfileSearchRequest().toString()) + .contentType(ContentType.JSON) + .post(mockServer.getUrl() + JRD_GET_PROFILES_URL + "/search") + .then() + .log().all().extract().asString(); + + JSONArray response = new JSONArray(actualResponseBody); + Assertions.assertThat(response).isNotNull(); + + } + + //@Pact(provider = "referenceData_judicial", consumer = "JRD_Fetch_API_ConsumerTest") + public RequestResponsePact getJudicialUserProfiles(PactDslWithProvider builder) throws JsonProcessingException { + + return builder + .given("User profile details exist") + .uponReceiving("the api returns judicial user profiles ") + .path(JRD_GET_PROFILES_URL + "/fetch") + .body(createJrdProfileUserRequest()) + .method(HttpMethod.POST.toString()) + .willRespondWith() + .status(HttpStatus.OK.value()) + .headers(getResponseHeaders()) + .body(createJrdFetchProfilesResponse()) + .toPact(); + } + + //@Test + //@PactTestFor(pactMethod = "getJudicialUserProfiles") + void executeGetJudicialUserProfiles(MockServer mockServer) + throws JSONException { + var actualResponseBody = + SerenityRest + .given() + .headers(getHttpHeaders()) + .body(createJrdProfileUserRequest().toString()) + .contentType(ContentType.JSON) + .post(mockServer.getUrl() + JRD_GET_PROFILES_URL + "/fetch") + .then() + .log().all().extract().asString(); + + JSONArray response = new JSONArray(actualResponseBody); + Assertions.assertThat(response).isNotNull(); + + } + //@Pact(provider = "referenceData_judicial", consumer = "JRD_API_ConsumerTest") public RequestResponsePact getJrdProfilesListOfIds(PactDslWithProvider builder) throws JsonProcessingException { @@ -61,8 +133,7 @@ public RequestResponsePact getJrdProfilesListOfIds(PactDslWithProvider builder) .uponReceiving("the api returns judicial user profiles " + "based on the provided list of user ids") .path(JRD_GET_PROFILES_URL) - .body(new ObjectMapper().writeValueAsString( - JrdUserRequest.builder().sidamIds(Set.of(SIDAM_ID)).build())) + .body(createJrdProfileUpdateRequest()) .method(HttpMethod.POST.toString()) .willRespondWith() .status(HttpStatus.OK.value()) @@ -79,8 +150,7 @@ void executeGetJrdProfilesListOfIds(MockServer mockServer) SerenityRest .given() .headers(getHttpHeaders()) - .body(JrdUserRequest.builder().sidamIds( - Set.of(SIDAM_ID)).build()) + .body(createJrdProfileUpdateRequest().toString()) .contentType(ContentType.JSON) .post(mockServer.getUrl() + JRD_GET_PROFILES_URL) .then() @@ -99,8 +169,7 @@ public RequestResponsePact getJrdProfilesServiceName(PactDslWithProvider builder .uponReceiving("the api returns judicial user profiles " + "based on the provided service name") .path(JRD_GET_PROFILES_URL) - .body(new ObjectMapper().writeValueAsString( - JrdUserRequest.builder().ccdServiceNames("CMC").build())) + .body(createJrdProfileForServiceNameRequest()) .method(HttpMethod.POST.toString()) .willRespondWith() .status(HttpStatus.OK.value()) @@ -117,7 +186,7 @@ void executeGetJrdProfilesServiceName(MockServer mockServer) SerenityRest .given() .headers(getHttpHeaders()) - .body(JrdUserRequest.builder().ccdServiceNames("CMC").build()) + .body(createJrdProfileForServiceNameRequest().toString()) .contentType(ContentType.JSON) .post(mockServer.getUrl() + JRD_GET_PROFILES_URL) .then() @@ -142,6 +211,46 @@ private DslPart createJrdProfilesResponse() { )).build(); } + private DslPart createJrdFetchProfilesResponse() { + return newJsonArray(o -> o.object(ob -> ob + .stringType("idamId", SIDAM_ID) + .minArrayLike("appointments", 1, r -> r + .stringType("appointmentId", "1") + ) + .minArrayLike("authorisations", 1, r -> r + + .stringType("authorisationId", "1234") + ) + )).build(); + } + + + private DslPart createJrdProfileUpdateRequest() { + return newJsonBody(o -> o + .stringType("ccdServiceName", null) + .stringType("object_ids", null) + .minArrayLike("sidam_ids", 1, + PactDslJsonRootValue.stringType("44362987-4b00-f2e7-4ff8-761b87f16bf9"),1) + ).build(); + } + + + private DslPart createJrdProfileUserRequest() { + return newJsonBody(o -> o + .minArrayLike("userIds", 1, + PactDslJsonRootValue.stringType("44362987-4b00-f2e7-4ff8-761b87f16bf9"),1) + ).build(); + } + + private DslPart createJrdProfileForServiceNameRequest() { + return newJsonBody(o -> o + .stringType("ccdServiceName", "CMC") + .stringType("object_ids", null) + .minArrayLike("sidam_ids", 1, + PactDslJsonRootValue.stringType("44362987-4b00-f2e7-4ff8-761b87f16bf9"),1) + ).build(); + } + @NotNull private Map getResponseHeaders() { Map responseHeaders = Map.of("Content-Type", "application/json"); @@ -154,4 +263,24 @@ private HttpHeaders getHttpHeaders() { headers.add("Authorization", "Bearer " + "2345"); return headers; } + + private DslPart createJrdProfileSearchRequest() { + return newJsonBody(o -> o + .stringType("searchString", "testFullName") + .stringType("serviceCode", "CMC") + .stringType("location", "location") + ).build(); + } + + private DslPart searchJrdProfilesResponse() { + return newJsonArray(o -> o.object(ob -> ob + .stringType("idamId", SIDAM_ID) + .stringType("fullName", "testFullName") + .stringType("knownAs", "testKnownAs") + .stringType("surname", "surname") + .stringType("emailId", "test@test.com") + .stringType("title", "Family Judge") + .stringType("personalCode", "1234"))) + .build(); + } } diff --git a/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/JrdUserRequestV2ConsumerTest.java b/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/JrdUserRequestV2ConsumerTest.java new file mode 100644 index 000000000..7dd218423 --- /dev/null +++ b/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/JrdUserRequestV2ConsumerTest.java @@ -0,0 +1,267 @@ +package uk.gov.hmcts.reform.cwrdapi; + +import au.com.dius.pact.consumer.MockServer; +import au.com.dius.pact.consumer.dsl.DslPart; +import au.com.dius.pact.consumer.dsl.PactDslJsonRootValue; +import au.com.dius.pact.consumer.dsl.PactDslWithProvider; +import au.com.dius.pact.consumer.junit5.PactConsumerTestExt; +import au.com.dius.pact.core.model.RequestResponsePact; +import com.fasterxml.jackson.core.JsonProcessingException; +import groovy.util.logging.Slf4j; +import io.restassured.http.ContentType; +import net.serenitybdd.rest.SerenityRest; +import org.apache.http.client.fluent.Executor; +import org.assertj.core.api.Assertions; +import org.jetbrains.annotations.NotNull; +import org.json.JSONArray; +import org.json.JSONException; +import org.junit.After; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.Map; + +import static io.pactfoundation.consumer.dsl.LambdaDsl.newJsonArray; +import static io.pactfoundation.consumer.dsl.LambdaDsl.newJsonBody; + +@Slf4j +@ExtendWith(PactConsumerTestExt.class) +@ExtendWith(SpringExtension.class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +//@PactTestFor(providerName = "referenceData_judicialv2") +//@PactFolder("pacts") +public class JrdUserRequestV2ConsumerTest { + + private static final String JRD_GET_PROFILES_URL = "/refdata/judicial/users"; + + private static final String SIDAM_ID = "44362987-4b00-f2e7-4ff8-761b87f16bf9"; + + + @BeforeEach + public void setUpEachTest() throws InterruptedException { + Thread.sleep(2000); + } + + @After + void teardown() { + Executor.closeIdleConnections(); + } + + + //To run the Pact tests please uncommnet all Pact Test annotations + //@Pact(provider = "referenceData_judicialv2", consumer = "JRD_API_V2_Search_ConsumerTest") + public RequestResponsePact searchJrdProfiles(PactDslWithProvider builder) throws JsonProcessingException { + + return builder + .given("return judicial user profiles") + .uponReceiving("the api returns judicial user profiles " + + "based on the provided list of user ids") + .path(JRD_GET_PROFILES_URL + "/search") + .body(createJrdProfileSearchRequest()) + .method(HttpMethod.POST.toString()) + .willRespondWith() + .status(HttpStatus.OK.value()) + .headers(getResponseHeaders()) + .body(searchJrdProfilesResponse()) + .toPact(); + } + + //@Test + //@PactTestFor(pactMethod = "searchJrdProfiles") + void searchJrdProfiles(MockServer mockServer) + throws JSONException { + var actualResponseBody = + SerenityRest + .given() + .headers(getHttpHeaders()) + .body(createJrdProfileSearchRequest().toString()) + .contentType(ContentType.JSON) + .post(mockServer.getUrl() + JRD_GET_PROFILES_URL + "/search") + .then() + .log().all().extract().asString(); + + JSONArray response = new JSONArray(actualResponseBody); + Assertions.assertThat(response).isNotNull(); + + } + + //@Pact(provider = "referenceData_judicialv2", consumer = "JRD_API_V2_ConsumerTest") + public RequestResponsePact getJrdProfilesListOfIds(PactDslWithProvider builder) throws JsonProcessingException { + + return builder + .given("return judicial user profiles v2 along with their active appointments and authorisations") + .uponReceiving("the api returns judicial user profiles " + + "based on the provided list of user ids") + .path(JRD_GET_PROFILES_URL) + .body(createJrdProfileUpdateRequest()) + .method(HttpMethod.POST.toString()) + .willRespondWith() + .status(HttpStatus.OK.value()) + .headers(getResponseHeaders()) + .body(createJrdProfilesResponse()) + .toPact(); + } + + //@Test + //@PactTestFor(pactMethod = "getJrdProfilesListOfIds") + void executeGetJrdProfilesListOfIds(MockServer mockServer) + throws JSONException { + var actualResponseBody = + SerenityRest + .given() + .headers(getHttpHeaders()) + .body(createJrdProfileUpdateRequest().toString()) + .contentType(ContentType.JSON) + .post(mockServer.getUrl() + JRD_GET_PROFILES_URL) + .then() + .log().all().extract().asString(); + + JSONArray response = new JSONArray(actualResponseBody); + Assertions.assertThat(response).isNotNull(); + + } + + //@Pact(provider = "referenceData_judicialv2", consumer = "JRD_API_V2_consumerTest") + public RequestResponsePact getJrdProfilesServiceName(PactDslWithProvider builder) throws JsonProcessingException { + + return builder + .given("return judicial user profiles v2 along with their active appointments and authorisations") + .uponReceiving("the api returns judicial user profiles " + + "based on the provided service name") + .path(JRD_GET_PROFILES_URL) + .body(createJrdProfileForServiceNameRequest()) + .method(HttpMethod.POST.toString()) + .willRespondWith() + .status(HttpStatus.OK.value()) + .headers(getResponseHeaders()) + .body(createJrdProfilesResponse()) + .toPact(); + } + + //@Test + //@PactTestFor(pactMethod = "getJrdProfilesServiceName") + void executeGetJrdProfilesServiceName(MockServer mockServer) + throws JSONException { + var actualResponseBody = + SerenityRest + .given() + .headers(getHttpHeaders()) + .body(createJrdProfileForServiceNameRequest().toString()) + .contentType(ContentType.JSON) + .post(mockServer.getUrl() + JRD_GET_PROFILES_URL) + .then() + .log().all().extract().asString(); + + JSONArray response = new JSONArray(actualResponseBody); + Assertions.assertThat(response).isNotNull(); + + } + + private DslPart searchJrdProfilesResponse() { + return newJsonArray(o -> o.object(ob -> ob + .stringType("idamId", SIDAM_ID) + .stringType("fullName", "testFullName") + .stringType("knownAs", "testKnownAs") + .stringType("surname", "surname") + .stringType("emailId", "test@test.com") + .stringType("title", "Family Judge") + .stringType("personalCode", "1234") + .stringType("postNominals", "Mr") + .stringType("initials", "I N"))) + .build(); + } + + private DslPart createJrdProfilesResponse() { + return newJsonArray(o -> o.object(ob -> ob + .stringType("sidam_id", SIDAM_ID) + .stringType("full_name", "testFullName") + .stringType("known_as", "testKnownAs") + .stringType("surname", "surname") + .stringType("email_id", "test@test.com") + .stringType("title", "Family Judge") + .stringType("personal_code", "1234") + .stringType("post_nominals", "Mr") + .stringType("initials", "I N") + .minArrayLike("appointments", 1, r -> r + .stringType("base_location_id") + .stringType("epimms_id") + .stringType("cft_region_id") + .stringType("cft_region") + .stringType("is_principal_appointment") + .date("start_date", "yyyy-MM-dd") + .date("end_date", "yyyy-MM-dd") + .stringType("appointment") + .stringType("appointment_type") + .array("service_codes", (s) -> { + s.stringType("BFA1"); + }) + .stringType("appointment_id") + ) + .minArrayLike("authorisations", 1, r -> r + .stringType("jurisdiction") + .stringType("ticket_description") + .date("start_date", "yyyy-MM-dd") + .minArrayLike("service_codes", 0, (s) -> { + s.stringType("BFA1"); + }) + .stringType("ticket_code") + .date("end_date", "yyyy-MM-dd") + .stringType("appointment_id") + .stringType("authorisation_id") + .stringType("jurisdiction_id") + ) + .minArrayLike("roles", 1, r -> r + .stringType("jurisdiction_role_name") + .stringType("jurisdiction_role_id") + .stringType("start_date") + .stringType("end_date") + ) + )).build(); + } + + + private DslPart createJrdProfileSearchRequest() { + return newJsonBody(o -> o + .stringType("searchString", "testFullName") + .stringType("serviceCode", "CMC") + .stringType("location", "location") + ).build(); + } + + private DslPart createJrdProfileUpdateRequest() { + return newJsonBody(o -> o + .stringType("ccdServiceName", null) + .stringType("object_ids", null) + .minArrayLike("sidam_ids", 1, + PactDslJsonRootValue.stringType("44362987-4b00-f2e7-4ff8-761b87f16bf9"),1) + ).build(); + } + + private DslPart createJrdProfileForServiceNameRequest() { + return newJsonBody(o -> o + .stringType("ccdServiceName", "CMC") + .stringType("object_ids", null) + .minArrayLike("sidam_ids", 1, + PactDslJsonRootValue.stringType("44362987-4b00-f2e7-4ff8-761b87f16bf9"),1) + ).build(); + } + + @NotNull + private Map getResponseHeaders() { + Map responseHeaders = Map.of("Content-Type", "application/vnd.jrd.api+json;Version=2.0"); + return responseHeaders; + } + + private HttpHeaders getHttpHeaders() { + HttpHeaders headers = new HttpHeaders(); + headers.add("ServiceAuthorization", "Bearer " + "1234"); + headers.add("Authorization", "Bearer " + "2345"); + headers.add("Accept", "application/vnd.jrd.api+json;Version=2.0"); + return headers; + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/cwrdapi/client/domain/JrdUserRequest.java b/src/main/java/uk/gov/hmcts/reform/cwrdapi/client/domain/JrdUserRequest.java deleted file mode 100644 index f63963b51..000000000 --- a/src/main/java/uk/gov/hmcts/reform/cwrdapi/client/domain/JrdUserRequest.java +++ /dev/null @@ -1,25 +0,0 @@ -package uk.gov.hmcts.reform.cwrdapi.client.domain; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.util.Set; - -@Data -@Builder -@AllArgsConstructor -@NoArgsConstructor -public class JrdUserRequest { - - @JsonProperty("ccdServiceName") - private String ccdServiceNames; - - @JsonProperty("object_ids") - private Set objectIds; - - @JsonProperty("sidam_ids") - private Set sidamIds; -} From ad4059fd7d65e5f95061919158ec9e204700c407 Mon Sep 17 00:00:00 2001 From: Kiran Kumar Yenigala <51748133+kiran-yenigala-hmcts@users.noreply.github.com> Date: Tue, 24 Oct 2023 10:52:45 +0100 Subject: [PATCH 57/67] suppress CVE-2023-5072 (#795) --- config/owasp/suppressions.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/owasp/suppressions.xml b/config/owasp/suppressions.xml index bc29c4363..deaef5803 100644 --- a/config/owasp/suppressions.xml +++ b/config/owasp/suppressions.xml @@ -14,5 +14,6 @@ file name: launchdarkly-java-server-sdk-5.10.2.jar (shaded: org.yaml:snakeyaml:1 CVE-2023-35116 CVE-2022-26336 + CVE-2023-5072 From be0034749d54321ca762ae6e084152072f05fa9f Mon Sep 17 00:00:00 2001 From: SabinaHMCTS Date: Wed, 25 Oct 2023 11:32:21 +0100 Subject: [PATCH 58/67] DTSRD-1456.suppressing as no known netty handler version available without vulnerability --- config/owasp/suppressions.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/owasp/suppressions.xml b/config/owasp/suppressions.xml index deaef5803..1338781d5 100644 --- a/config/owasp/suppressions.xml +++ b/config/owasp/suppressions.xml @@ -15,5 +15,6 @@ file name: launchdarkly-java-server-sdk-5.10.2.jar (shaded: org.yaml:snakeyaml:1 CVE-2023-35116 CVE-2022-26336 CVE-2023-5072 + CVE-2023-4586 From 921c9b86c0c54e1c8211abff2637dd091b09061a Mon Sep 17 00:00:00 2001 From: manukundloo-hmcts Date: Fri, 27 Oct 2023 09:37:34 +0100 Subject: [PATCH 59/67] [RDCC-7096]Location : Consumer Pact Test Cases needs to be added --- .../LocationReferenceDataConsumerTest.java | 317 +++++++++++++++++- 1 file changed, 312 insertions(+), 5 deletions(-) diff --git a/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/LocationReferenceDataConsumerTest.java b/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/LocationReferenceDataConsumerTest.java index 523699ce1..ece39dabc 100644 --- a/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/LocationReferenceDataConsumerTest.java +++ b/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/LocationReferenceDataConsumerTest.java @@ -6,7 +6,6 @@ import au.com.dius.pact.consumer.junit5.PactConsumerTestExt; import au.com.dius.pact.consumer.junit5.PactTestFor; import au.com.dius.pact.core.model.RequestResponsePact; -import au.com.dius.pact.core.model.annotations.Pact; import au.com.dius.pact.core.model.annotations.PactFolder; import com.google.common.collect.Maps; import groovy.util.logging.Slf4j; @@ -16,9 +15,9 @@ import org.jetbrains.annotations.NotNull; import org.json.JSONArray; import org.json.JSONException; +import org.json.JSONObject; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.http.HttpHeaders; @@ -29,6 +28,7 @@ import java.util.Map; import static io.pactfoundation.consumer.dsl.LambdaDsl.newJsonArray; +import static io.pactfoundation.consumer.dsl.LambdaDsl.newJsonBody; import static org.junit.Assert.assertNotNull; @Slf4j @@ -40,6 +40,8 @@ public class LocationReferenceDataConsumerTest { private static final String LRD_URL = "/refdata/location/orgServices"; + private static final String GET_LRD_URL = "/refdata/location"; + private static final String GET_LRD_COURT_VENUE_URL = "/refdata/location/court-venues"; @BeforeEach public void setUpEachTest() throws InterruptedException { @@ -51,8 +53,199 @@ void teardown() { Executor.closeIdleConnections(); } + + //@Pact(provider = "referenceData_location", consumer = "lrd_ref_api_for_court_venue") + public RequestResponsePact executeReturnCourtVenues(PactDslWithProvider builder) { + + return builder + .given("Court Venues exist for the input request provided") + .uponReceiving("valid request to retrieve court venue") + .path(GET_LRD_COURT_VENUE_URL) + .method(HttpMethod.GET.toString()) + .willRespondWith() + .status(HttpStatus.OK.value()) + .headers(getResponseHeaders()) + .body(createLrdCourtVenueResponse()) + .toPact(); + } + + //@Test + //@PactTestFor(pactMethod = "executeReturnCourtVenues") + void getExecuteReturnCourtVenues(MockServer mockServer) throws JSONException { + String actualResponseBody = + SerenityRest + .given() + .headers(getHttpHeaders()) + .contentType(ContentType.JSON) + .get(mockServer.getUrl() + GET_LRD_COURT_VENUE_URL) + .then() + .log().all().extract().asString(); + JSONArray jsonArray = new JSONArray(actualResponseBody); + assertNotNull(jsonArray); + } + + //@Pact(provider = "referenceData_location", consumer = "lrd_ref_api_for_court_venue_by_service_code") + public RequestResponsePact executeReturnCourtVenuesByServiceCode(PactDslWithProvider builder) { + + return builder + .given("Court Venues exist for the service code provided") + .uponReceiving("valid request to retrieve court venue for given service code") + .path(GET_LRD_COURT_VENUE_URL + "/services") + .query("service_code=1") + .method(HttpMethod.GET.toString()) + .willRespondWith() + .status(HttpStatus.OK.value()) + .headers(getResponseHeaders()) + .body(createLrdCourtVenueForServiceCodeResponse()) + .toPact(); + } + + //@Test + //@PactTestFor(pactMethod = "executeReturnCourtVenuesByServiceCode") + void getExecuteReturnCourtVenuesByServiceCode(MockServer mockServer) throws JSONException { + String actualResponseBody = + SerenityRest + .given() + .headers(getHttpHeaders()) + .contentType(ContentType.JSON) + .get(mockServer.getUrl() + GET_LRD_COURT_VENUE_URL + "/services" + "?service_code=1") + .then() + .log().all().extract().asString(); + JSONObject jsonArray = new JSONObject(actualResponseBody); + assertNotNull(jsonArray); + } + + + //@Pact(provider = "referenceData_location", consumer = "lrd_ref_api_for_court_venue_by_search_string") + public RequestResponsePact executeReturnCourtVenuesBySearchString(PactDslWithProvider builder) { + + return builder + .given("Court Venues exist for the search string provided") + .uponReceiving("valid request to retrieve court venue by search string") + .path(GET_LRD_COURT_VENUE_URL + "/venue-search") + .query("search-string=456") + .method(HttpMethod.GET.toString()) + .willRespondWith() + .status(HttpStatus.OK.value()) + .headers(getResponseHeaders()) + .body(createLrdCourtVenueResponse()) + .toPact(); + } + + //@Test + //@PactTestFor(pactMethod = "executeReturnCourtVenuesBySearchString") + void getExecuteReturnCourtVenuesBySearchString(MockServer mockServer) throws JSONException { + String actualResponseBody = + SerenityRest + .given() + .headers(getHttpHeaders()) + .contentType(ContentType.JSON) + .get(mockServer.getUrl() + GET_LRD_COURT_VENUE_URL + "/venue-search" + "?search-string=456") + .then() + .log().all().extract().asString(); + JSONArray jsonArray = new JSONArray(actualResponseBody); + assertNotNull(jsonArray); + } + + //@Pact(provider = "referenceData_location", consumer = "lrd_ref_api_building_locations_service") + public RequestResponsePact executeRetrieveBuildingLocationsDetails(PactDslWithProvider builder) { + + return builder + .given("Building Location details exist for the request provided") + .uponReceiving("valid request for building location details") + .path(GET_LRD_URL + "/building-locations") + .query("building_location_name=Taylor House Tribunal Hearing Centre") + .method(HttpMethod.GET.toString()) + .willRespondWith() + .status(HttpStatus.OK.value()) + .headers(getResponseHeaders()) + .body(createLrdBuildingLocationsResponse()) + .toPact(); + } + + //@Test + //@PactTestFor(pactMethod = "executeRetrieveBuildingLocationsDetails") + void getExecuteRetrieveBuildingLocationsDetailsTest(MockServer mockServer) throws JSONException { + String actualResponseBody = + SerenityRest + .given() + .headers(getHttpHeaders()) + .contentType(ContentType.JSON) + .get(mockServer.getUrl() + GET_LRD_URL + "/building-locations" + + "?building_location_name=Taylor House Tribunal Hearing Centre") + .then() + .log().all().extract().asString(); + JSONObject jsonArray = new JSONObject(actualResponseBody); + assertNotNull(jsonArray); + } + + //@Pact(provider = "referenceData_location", consumer = "lrd_ref_api_building_locations_search_service") + public RequestResponsePact executeRetrieveBuildingLocationsSearchDetails(PactDslWithProvider builder) { + + return builder + .given("Building Location details exist for the searchString provided") + .uponReceiving("valid request for building location details") + .path(GET_LRD_URL + "/building-locations/search") + .query("search=Taylor") + .method(HttpMethod.GET.toString()) + .willRespondWith() + .status(HttpStatus.OK.value()) + .headers(getResponseHeaders()) + .body(createLrdBuildingSearchLocationsResponse()) + .toPact(); + } + + //@Test + //@PactTestFor(pactMethod = "executeRetrieveBuildingLocationsSearchDetails") + void getExecuteRetrieveBuildingLocationsSearchDetailsTest(MockServer mockServer) throws JSONException { + String actualResponseBody = + SerenityRest + .given() + .headers(getHttpHeaders()) + .contentType(ContentType.JSON) + .get(mockServer.getUrl() + GET_LRD_URL + "/building-locations/search" + "?search=Taylor") + .then() + .log().all().extract().asString(); + JSONArray jsonArray = new JSONArray(actualResponseBody); + assertNotNull(jsonArray); + } + + + + //@Pact(provider = "referenceData_location", consumer = "lrd_ref_api_service") + public RequestResponsePact executeRetrieveRegionDetails(PactDslWithProvider builder) { + + return builder + .given("Region Details exist") + .uponReceiving("valid request for retrieve region details") + .path(GET_LRD_URL + "/regions") + .query("region=National") + .method(HttpMethod.GET.toString()) + .willRespondWith() + .status(HttpStatus.OK.value()) + .headers(getResponseHeaders()) + .body(createLrdRegionResponse()) + .toPact(); + } + + //@Test + //@PactTestFor(pactMethod = "executeRetrieveRegionDetails") + void getExecuteRetrieveRegionDetailsTest(MockServer mockServer) throws JSONException { + String actualResponseBody = + SerenityRest + .given() + .headers(getHttpHeaders()) + .contentType(ContentType.JSON) + .get(mockServer.getUrl() + GET_LRD_URL + "/regions" + "?region=National") + .then() + .log().all().extract().asString(); + JSONArray jsonArray = new JSONArray(actualResponseBody); + assertNotNull(jsonArray); + } + + //GET - @Pact(provider = "referenceData_location", consumer = "crd_case_worker_ref_service") + //@Pact(provider = "referenceData_location", consumer = "crd_case_worker_ref_service") public RequestResponsePact executeGetOrgService(PactDslWithProvider builder) { return builder @@ -68,8 +261,8 @@ public RequestResponsePact executeGetOrgService(PactDslWithProvider builder) { .toPact(); } - @Test - @PactTestFor(pactMethod = "executeGetOrgService") + //@Test + //@PactTestFor(pactMethod = "executeGetOrgService") void getOrgServiceTest(MockServer mockServer) throws JSONException { String actualResponseBody = SerenityRest @@ -91,6 +284,120 @@ private DslPart createLrdOrgServiceResponse() { )).build(); } + private DslPart createLrdRegionResponse() { + + return newJsonArray(o -> o.object(ob -> ob + .stringType("region_id", "1") + .stringType("description", "National") + .stringType("welsh_description", "") + ) + .object(ob -> ob + .stringType("region_id", "2") + .stringType("description", "London") + .stringType("welsh_description", ""))).build(); + } + + + private DslPart createLrdBuildingLocationsResponse() { + + return newJsonBody(o -> o + .stringType("address", "Address 123") + .stringType("area", "Area 1") + .stringType("building_location_id", "123") + .stringType("cluster_id", "456") + .stringType("epimms_id", "4567") + .stringType("building_location_status", "OPEN") + .stringType("building_location_name", "Taylor House Tribunal Hearing Centre") + .minArrayLike("court_venues", 1, r -> r + .stringType("court_venue_id") + .stringType("epimms_id") + .stringType("site_name") + .stringType("region_id") + ) + + ).build(); + } + + + + private DslPart createLrdCourtVenueResponse() { + + return newJsonArray(o -> o.object(ob -> ob + .stringType("cluster_name", "ClusterXYZ") + .stringType("court_address", "courtAddress") + .stringType("court_location_code", "courtLocationCode") + .stringType("court_name", "courtName") + .stringType("court_status", "Closed") + .stringType("court_type", "Immigration and Asylum") + .stringType("court_type_id", "17") + .stringType("epimms_id", "12345") + ) + .object(ob -> ob + .stringType("cluster_name", "ClusterXYZ") + .stringType("court_address", "courtAddress") + .stringType("court_location_code", "courtLocationCode") + .stringType("court_name", "courtName") + .stringType("court_status", "Open") + .stringType("court_type", "Immigration and Asylum") + .stringType("court_type_id", "17") + .stringType("epimms_id", "123456")) + ).build(); + } + + + private DslPart createLrdCourtVenueForServiceCodeResponse() { + + return newJsonBody(o -> o + .stringType("court_type_id", "17") + .stringType("service_code", "1") + .stringType("court_type", "Immigration and Asylum") + + + ).build(); + } + + private DslPart createLrdCourtVenueForSearchString() { + + return newJsonBody(o -> o + .stringType("welsh_court_type") + .stringType("service_code", "1") + .stringType("court_type", "Immigration and Asylum") + .stringType("court_type_id", "17") + + + ).build(); + } + + private DslPart createLrdBuildingSearchLocationsResponse() { + + return newJsonArray(o -> + o.object(ob -> ob + .stringType("region", "Region XYZ") + .stringType("cluster_name", "ClusterXYZ") + .stringType("cluster_id", "456") + .stringType("epimms_id", "4567") + .stringType("building_location_status", "OPEN") + .stringType("building_location_name", "Taylor House Tribunal Hearing Centre") + .stringType("address", "Address 123") + .stringType("area", "Area 1") + .stringType("postcode", "XY2 YY3") + .stringType("region_id", "123") + ) + .object(obj -> obj + .stringType("region", "Region XYZ") + .stringType("cluster_name", "ClusterXYZ") + .stringType("cluster_id", "456") + .stringType("epimms_id", "45678") + .stringType("building_location_status", "CLOSED") + .stringType("building_location_name", "Taylor House Tribunal Hearing Centre 2") + .stringType("address", "Address 123456") + .stringType("area", "Area 2") + .stringType("postcode", "XY21 YY3") + .stringType("region_id", "123") + )).build(); + } + + @NotNull private Map getResponseHeaders() { Map responseHeaders = Maps.newHashMap(); From d75db508794eceb95c9359be2d7720b9258c883f Mon Sep 17 00:00:00 2001 From: manukundloo-hmcts Date: Fri, 27 Oct 2023 16:12:09 +0100 Subject: [PATCH 60/67] [RDCC-7093]Common : Consumer Pact Test Cases needs to be added --- .../cwrdapi/CommonDataApiConsumerTest.java | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/CommonDataApiConsumerTest.java diff --git a/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/CommonDataApiConsumerTest.java b/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/CommonDataApiConsumerTest.java new file mode 100644 index 000000000..ceec1f5ee --- /dev/null +++ b/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/CommonDataApiConsumerTest.java @@ -0,0 +1,156 @@ +package uk.gov.hmcts.reform.cwrdapi; + +import au.com.dius.pact.consumer.MockServer; +import au.com.dius.pact.consumer.dsl.DslPart; +import au.com.dius.pact.consumer.dsl.PactDslWithProvider; +import au.com.dius.pact.consumer.junit5.PactConsumerTestExt; +import au.com.dius.pact.core.model.RequestResponsePact; +import com.google.common.collect.Maps; +import groovy.util.logging.Slf4j; +import io.restassured.http.ContentType; +import net.serenitybdd.rest.SerenityRest; +import org.apache.http.client.fluent.Executor; +import org.jetbrains.annotations.NotNull; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.Map; + +import static io.pactfoundation.consumer.dsl.LambdaDsl.newJsonBody; +import static org.junit.Assert.assertNotNull; + +@Slf4j +@ExtendWith(PactConsumerTestExt.class) +@ExtendWith(SpringExtension.class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +//@PactTestFor(providerName = "referenceData_location") +//@PactFolder("pacts") +public class CommonDataApiConsumerTest { + + private static final String CRD_URL = "/refdata/commondata/lov/categories"; + private static final String CRD_CASE_FLAG_URL = "/refdata/commondata/caseflags/service-id"; + + @BeforeEach + public void setUpEachTest() throws InterruptedException { + Thread.sleep(2000); + } + + @AfterEach + void teardown() { + Executor.closeIdleConnections(); + } + + + //@Pact(provider = "referenceData_commondata", consumer = "crd_api_for_list_of_category") + public RequestResponsePact executeListOfCategoryValues(PactDslWithProvider builder) { + + return builder + .given("ListOfCategories Details Exist") + .uponReceiving("valid request to retrieve list of category values") + .path(CRD_URL + "/HearingChannel") + .method(HttpMethod.GET.toString()) + .willRespondWith() + .status(HttpStatus.OK.value()) + .headers(getResponseHeaders()) + .body(createCrdListOfValuesResponse()) + .toPact(); + } + + //@Test + //@PactTestFor(pactMethod = "executeListOfCategoryValues") + void getExecuteListOfCategoryValues(MockServer mockServer) throws JSONException { + String actualResponseBody = + SerenityRest + .given() + .headers(getHttpHeaders()) + .contentType(ContentType.JSON) + .get(mockServer.getUrl() + CRD_URL + "/HearingChannel") + .then() + .log().all().extract().asString(); + JSONObject jsonArray = new JSONObject(actualResponseBody); + assertNotNull(jsonArray); + } + + + + //@Pact(provider = "referenceData_commondata", consumer = "crd_api_for_list_of_case_flag") + public RequestResponsePact executeCaseFlagDetailsForServiceID(PactDslWithProvider builder) { + + return builder + .given("Case Flag Details Exist") + .uponReceiving("valid request to retrieve list of case flag for given servie Id") + .path(CRD_CASE_FLAG_URL + "=HearingChannel") + .method(HttpMethod.GET.toString()) + .willRespondWith() + .status(HttpStatus.OK.value()) + .headers(getResponseHeaders()) + .body(createCrdListOfCaseFlagResponse()) + .toPact(); + } + + //@Test + //@PactTestFor(pactMethod = "executeCaseFlagDetailsForServiceID") + void getExecuteCaseFlagDetailsForServiceID(MockServer mockServer) throws JSONException { + String actualResponseBody = + SerenityRest + .given() + .headers(getHttpHeaders()) + .contentType(ContentType.JSON) + .get(mockServer.getUrl() + CRD_CASE_FLAG_URL + "=HearingChannel") + .then() + .log().all().extract().asString(); + JSONObject jsonArray = new JSONObject(actualResponseBody); + assertNotNull(jsonArray); + } + + private DslPart createCrdListOfValuesResponse() { + + return newJsonBody(o -> o + .minArrayLike("list_of_values",1,obj -> obj + .stringType("category_key","HearingChannel") + .stringType("key","video") + .stringType("value_en","Video") + .stringType("value_cy",null) + .stringType("hint_text_en",null) + .stringType("hint_text_cy",null) + .stringType("parent_category",null) + .stringType("active_flag","Y") + ) + ).build(); + } + + private DslPart createCrdListOfCaseFlagResponse() { + + return newJsonBody(o -> o + .minArrayLike("flags",1,obj -> obj + .minArrayLike("FlagDetails",1,ob -> ob + .stringType("flagCode","RA0001") + ) + ) + ).build(); + } + + + @NotNull + private Map getResponseHeaders() { + Map responseHeaders = Maps.newHashMap(); + responseHeaders.put("Content-Type", + "application/json"); + return responseHeaders; + } + + private HttpHeaders getHttpHeaders() { + HttpHeaders headers = new HttpHeaders(); + headers.add("ServiceAuthorization", "Bearer " + "1234"); + headers.add("Authorization", "Bearer " + "2345"); + return headers; + } +} From d8ea22bb88b93f432dba54143b60a61dfa9b2592 Mon Sep 17 00:00:00 2001 From: manukundloo-hmcts Date: Mon, 30 Oct 2023 15:03:59 +0000 Subject: [PATCH 61/67] [RDCC-7096]Commented the pact annotation --- .../reform/cwrdapi/LocationReferenceDataConsumerTest.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/LocationReferenceDataConsumerTest.java b/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/LocationReferenceDataConsumerTest.java index ece39dabc..478b4e951 100644 --- a/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/LocationReferenceDataConsumerTest.java +++ b/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/LocationReferenceDataConsumerTest.java @@ -4,9 +4,7 @@ import au.com.dius.pact.consumer.dsl.DslPart; import au.com.dius.pact.consumer.dsl.PactDslWithProvider; import au.com.dius.pact.consumer.junit5.PactConsumerTestExt; -import au.com.dius.pact.consumer.junit5.PactTestFor; import au.com.dius.pact.core.model.RequestResponsePact; -import au.com.dius.pact.core.model.annotations.PactFolder; import com.google.common.collect.Maps; import groovy.util.logging.Slf4j; import io.restassured.http.ContentType; @@ -35,8 +33,8 @@ @ExtendWith(PactConsumerTestExt.class) @ExtendWith(SpringExtension.class) @TestInstance(TestInstance.Lifecycle.PER_CLASS) -@PactTestFor(providerName = "referenceData_location") -@PactFolder("pacts") +//@PactTestFor(providerName = "referenceData_location") +//@PactFolder("pacts") public class LocationReferenceDataConsumerTest { private static final String LRD_URL = "/refdata/location/orgServices"; From 3a7fd8f716cd339c00480b4763e3438b7b56c2d0 Mon Sep 17 00:00:00 2001 From: prudhvi-maddineni <111352154+prudhvi-maddineni@users.noreply.github.com> Date: Tue, 31 Oct 2023 14:17:05 +0000 Subject: [PATCH 62/67] Staff : Block Staff upload Template (#792) upload block template --- charts/rd-caseworker-ref-api/Chart.yaml | 2 +- .../values.preview.template.yaml | 2 + charts/rd-caseworker-ref-api/values.yaml | 2 + .../cwrdapi/CaseWorkerRefFunctionalTest.java | 171 ++++++++++++------ .../resources/application-functional.yaml | 6 +- .../resources/application-test.yml | 3 + .../impl/CaseWorkerServiceFacadeImpl.java | 20 ++ .../cwrdapi/util/CaseWorkerConstants.java | 5 + src/main/resources/application.yaml | 3 + .../impl/CaseWorkerServiceFacadeImplTest.java | 36 +++- 10 files changed, 194 insertions(+), 56 deletions(-) diff --git a/charts/rd-caseworker-ref-api/Chart.yaml b/charts/rd-caseworker-ref-api/Chart.yaml index 04057ff4d..a87c22592 100644 --- a/charts/rd-caseworker-ref-api/Chart.yaml +++ b/charts/rd-caseworker-ref-api/Chart.yaml @@ -3,7 +3,7 @@ appVersion: "1.0" description: A Helm chart for rd-caseworker-ref-api name: rd-caseworker-ref-api home: https://github.com/hmcts/rd-caseworker-ref-api -version: 1.0.3 +version: 1.0.4 maintainers: - name: Reference Data Team dependencies: diff --git a/charts/rd-caseworker-ref-api/values.preview.template.yaml b/charts/rd-caseworker-ref-api/values.preview.template.yaml index 318e6911a..c07fc3ffa 100644 --- a/charts/rd-caseworker-ref-api/values.preview.template.yaml +++ b/charts/rd-caseworker-ref-api/values.preview.template.yaml @@ -22,6 +22,8 @@ java: LAUNCH_DARKLY_ENV: "preview" ENVIRONMENT_NAME: "preview" EMAIL_DOMAIN_LIST: "justice.gov.uk,dwp.gov.uk,hmrc.gov.uk,hmcts.net,dfcni.gov.uk" + STAFF_UPLOAD_FILE: false # false: Block the File, true: Able to Upload the File + IDAM_ROLE_MAPPING_FILE: true # false: Block the File, true: Able to Upload the File postgresql: enabled: true auth: diff --git a/charts/rd-caseworker-ref-api/values.yaml b/charts/rd-caseworker-ref-api/values.yaml index b10596958..13df55f0b 100644 --- a/charts/rd-caseworker-ref-api/values.yaml +++ b/charts/rd-caseworker-ref-api/values.yaml @@ -23,6 +23,8 @@ java: OPEN_ID_API_BASE_URI: https://idam-web-public.{{ .Values.global.environment }}.platform.hmcts.net/o REFRESH_PAGE_SIZE: 20 REFRESH_SORT_COLUMN: caseWorkerId + STAFF_UPLOAD_FILE: false # false: Block the File, true: Able to Upload the File + IDAM_ROLE_MAPPING_FILE: true # false: Block the File, true: Able to Upload the File keyVaults: rd: secrets: diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/CaseWorkerRefFunctionalTest.java b/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/CaseWorkerRefFunctionalTest.java index c27b5d607..4eae2beea 100644 --- a/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/CaseWorkerRefFunctionalTest.java +++ b/src/functionalTest/java/uk/gov/hmcts/reform/cwrdapi/CaseWorkerRefFunctionalTest.java @@ -19,6 +19,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.ComponentScan; import org.springframework.http.HttpStatus; @@ -53,7 +54,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.springframework.http.HttpStatus.FORBIDDEN; import static org.springframework.http.HttpStatus.NO_CONTENT; @@ -74,6 +74,12 @@ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class CaseWorkerRefFunctionalTest extends AuthorizationFunctionalTest { + @Value("${staff_upload_file}") + public boolean staffUploadFile; + + @Value("${idam_role_mapping_file}") + public boolean idamRoleMappingFile; + public static final String CREATE_CASEWORKER_PROFILE = "CaseWorkerRefUsersController.createCaseWorkerProfiles"; public static final String FETCH_BY_CASEWORKER_ID = "CaseWorkerRefUsersController.fetchCaseworkersById"; public static final String CASEWORKER_FILE_UPLOAD = "CaseWorkerRefController.caseWorkerFileUpload"; @@ -88,7 +94,7 @@ public class CaseWorkerRefFunctionalTest extends AuthorizationFunctionalTest { @ToggleEnable(mapKey = CREATE_CASEWORKER_PROFILE, withFeature = true) @ExtendWith(FeatureToggleConditionExtension.class) //this test verifies new User profile is created - public void createCwProfileWhenUserNotExistsInCrdAndSidamAndUp_Ac1() { + void createCwProfileWhenUserNotExistsInCrdAndSidamAndUp_Ac1() { List roleRequests = new ArrayList(); roleRequests.add(new CaseWorkerRoleRequest("National Business Centre Team Leader", true)); roleRequests.add(new CaseWorkerRoleRequest("Regional Centre Team Leader", false)); @@ -291,87 +297,132 @@ public void shouldThrowForbiddenExceptionForNonCompliantRole() { @Test @ToggleEnable(mapKey = CASEWORKER_FILE_UPLOAD, withFeature = true) @ExtendWith(FeatureToggleConditionExtension.class) - public void shouldUploadXlsxFileSuccessfully() throws IOException { - ExtractableResponse uploadCaseWorkerFileResponse = + void shouldUploadXlsxFileSuccessfully() throws IOException { + if (staffUploadFile) { + ExtractableResponse uploadCaseWorkerFileResponse = uploadCaseWorkerFile("src/functionalTest/resources/Staff Data Upload.xlsx", - 200, REQUEST_COMPLETED_SUCCESSFULLY, TYPE_XLSX, ROLE_CWD_ADMIN); + 200, REQUEST_COMPLETED_SUCCESSFULLY, TYPE_XLSX, ROLE_CWD_ADMIN); - CaseWorkerFileCreationResponse caseWorkerFileCreationResponse = uploadCaseWorkerFileResponse + CaseWorkerFileCreationResponse caseWorkerFileCreationResponse = uploadCaseWorkerFileResponse .as(CaseWorkerFileCreationResponse.class); - assertTrue(caseWorkerFileCreationResponse.getMessage().contains(REQUEST_COMPLETED_SUCCESSFULLY)); - assertTrue(caseWorkerFileCreationResponse.getDetailedMessage().contains(format(RECORDS_UPLOADED, 6))); + assertTrue(caseWorkerFileCreationResponse.getMessage().contains(REQUEST_COMPLETED_SUCCESSFULLY)); + assertTrue(caseWorkerFileCreationResponse.getDetailedMessage().contains(format(RECORDS_UPLOADED, 6))); + } else { + ExtractableResponse uploadCaseWorkerFileResponse = + uploadCaseWorkerFile("src/functionalTest/resources/Staff Data Upload.xlsx", + 403, REQUEST_COMPLETED_SUCCESSFULLY, TYPE_XLSX, ROLE_CWD_ADMIN); + assertTrue(true); + } } @Test @ToggleEnable(mapKey = CASEWORKER_FILE_UPLOAD, withFeature = true) @ExtendWith(FeatureToggleConditionExtension.class) - public void shouldUploadXlsFileSuccessfully() throws IOException { - ExtractableResponse uploadCaseWorkerFileResponse = + void shouldUploadXlsFileSuccessfully() throws IOException { + if (staffUploadFile) { + ExtractableResponse uploadCaseWorkerFileResponse = uploadCaseWorkerFile("src/functionalTest/resources/Staff Data Upload.xls", - 200, REQUEST_COMPLETED_SUCCESSFULLY, TYPE_XLS, - ROLE_CWD_ADMIN); + 200, REQUEST_COMPLETED_SUCCESSFULLY, TYPE_XLS, + ROLE_CWD_ADMIN); - CaseWorkerFileCreationResponse caseWorkerFileCreationResponse = uploadCaseWorkerFileResponse + CaseWorkerFileCreationResponse caseWorkerFileCreationResponse = uploadCaseWorkerFileResponse .as(CaseWorkerFileCreationResponse.class); - assertTrue(caseWorkerFileCreationResponse.getMessage() + assertTrue(caseWorkerFileCreationResponse.getMessage() .contains(REQUEST_COMPLETED_SUCCESSFULLY)); - assertTrue(caseWorkerFileCreationResponse.getDetailedMessage() + assertTrue(caseWorkerFileCreationResponse.getDetailedMessage() .contains(format(RECORDS_UPLOADED, 3))); + } else { + + ExtractableResponse uploadCaseWorkerFileResponse = + uploadCaseWorkerFile("src/functionalTest/resources/Staff Data Upload.xls", + 403, REQUEST_COMPLETED_SUCCESSFULLY, TYPE_XLS, + ROLE_CWD_ADMIN); + assertTrue(true); + + } } @Test @ToggleEnable(mapKey = CASEWORKER_FILE_UPLOAD, withFeature = true) @ExtendWith(FeatureToggleConditionExtension.class) @Order(1) - public void shouldUploadServiceRoleMappingXlsxFileSuccessfully() throws IOException { - ExtractableResponse uploadCaseWorkerFileResponse = + void shouldUploadServiceRoleMappingXlsxFileSuccessfully() throws IOException { + if (idamRoleMappingFile) { + ExtractableResponse uploadCaseWorkerFileResponse = uploadCaseWorkerFile("src/functionalTest/resources/ServiceRoleMapping_BBA9.xlsx", - 200, IDAM_ROLE_MAPPINGS_SUCCESS, TYPE_XLS, - ROLE_CWD_ADMIN); + 200, IDAM_ROLE_MAPPINGS_SUCCESS, TYPE_XLS, + ROLE_CWD_ADMIN); - CaseWorkerFileCreationResponse caseWorkerFileCreationResponse = uploadCaseWorkerFileResponse + CaseWorkerFileCreationResponse caseWorkerFileCreationResponse = uploadCaseWorkerFileResponse .as(CaseWorkerFileCreationResponse.class); - assertTrue(caseWorkerFileCreationResponse.getMessage() + assertTrue(caseWorkerFileCreationResponse.getMessage() .contains(REQUEST_COMPLETED_SUCCESSFULLY)); - assertTrue(caseWorkerFileCreationResponse.getDetailedMessage() + assertTrue(caseWorkerFileCreationResponse.getDetailedMessage() .contains(format(RECORDS_UPLOADED, 4))); + } else { + + ExtractableResponse uploadCaseWorkerFileResponse = + uploadCaseWorkerFile("src/functionalTest/resources/ServiceRoleMapping_BBA9.xlsx", + 403, IDAM_ROLE_MAPPINGS_SUCCESS, TYPE_XLS, + ROLE_CWD_ADMIN); + assertTrue(true); + + } } @Test @ToggleEnable(mapKey = CASEWORKER_FILE_UPLOAD, withFeature = true) @ExtendWith(FeatureToggleConditionExtension.class) @Order(2) - public void shouldUploadServiceRoleMappingXlsFileSuccessfully() throws IOException { - ExtractableResponse uploadCaseWorkerFileResponse = + void shouldUploadServiceRoleMappingXlsFileSuccessfully() throws IOException { + if (idamRoleMappingFile) { + ExtractableResponse uploadCaseWorkerFileResponse = uploadCaseWorkerFile("src/functionalTest/resources/ServiceRoleMapping_BBA9.xls", - 200, IDAM_ROLE_MAPPINGS_SUCCESS, TYPE_XLS, - ROLE_CWD_ADMIN); + 200, IDAM_ROLE_MAPPINGS_SUCCESS, TYPE_XLS, + ROLE_CWD_ADMIN); - CaseWorkerFileCreationResponse caseWorkerProfileCreationResponse = uploadCaseWorkerFileResponse + CaseWorkerFileCreationResponse caseWorkerProfileCreationResponse = uploadCaseWorkerFileResponse .as(CaseWorkerFileCreationResponse.class); - assertTrue(caseWorkerProfileCreationResponse.getMessage() + assertTrue(caseWorkerProfileCreationResponse.getMessage() .contains(REQUEST_COMPLETED_SUCCESSFULLY)); - assertTrue(caseWorkerProfileCreationResponse.getDetailedMessage() + assertTrue(caseWorkerProfileCreationResponse.getDetailedMessage() .contains(format(RECORDS_UPLOADED, 4))); + } else { + + ExtractableResponse uploadCaseWorkerFileResponse = + uploadCaseWorkerFile("src/functionalTest/resources/ServiceRoleMapping_BBA9.xls", + 403, IDAM_ROLE_MAPPINGS_SUCCESS, TYPE_XLS, + ROLE_CWD_ADMIN); + assertTrue(true); + + } } @Test @ToggleEnable(mapKey = CASEWORKER_FILE_UPLOAD, withFeature = true) @ExtendWith(FeatureToggleConditionExtension.class) @Order(3) - public void shouldUploadServiceRoleMappingAba1XlsFileSuccessfully() throws IOException { - ExtractableResponse uploadCaseWorkerFileResponse = + void shouldUploadServiceRoleMappingAba1XlsFileSuccessfully() throws IOException { + if (idamRoleMappingFile) { + ExtractableResponse uploadCaseWorkerFileResponse = uploadCaseWorkerFile("src/functionalTest/resources/ServiceRoleMapping_ABA1.xls", - 200, IDAM_ROLE_MAPPINGS_SUCCESS, TYPE_XLS, - ROLE_CWD_ADMIN); + 200, IDAM_ROLE_MAPPINGS_SUCCESS, TYPE_XLS, + ROLE_CWD_ADMIN); - CaseWorkerFileCreationResponse caseWorkerProfileCreationResponse = uploadCaseWorkerFileResponse + CaseWorkerFileCreationResponse caseWorkerProfileCreationResponse = uploadCaseWorkerFileResponse .as(CaseWorkerFileCreationResponse.class); - assertTrue(caseWorkerProfileCreationResponse.getMessage() + assertTrue(caseWorkerProfileCreationResponse.getMessage() .contains(REQUEST_COMPLETED_SUCCESSFULLY)); - assertTrue(caseWorkerProfileCreationResponse.getDetailedMessage() + assertTrue(caseWorkerProfileCreationResponse.getDetailedMessage() .contains(format(RECORDS_UPLOADED, 4))); + } else { + ExtractableResponse uploadCaseWorkerFileResponse = + uploadCaseWorkerFile("src/functionalTest/resources/ServiceRoleMapping_ABA1.xls", + 403, IDAM_ROLE_MAPPINGS_SUCCESS, TYPE_XLS, + ROLE_CWD_ADMIN); + assertTrue(true); + } } @Test @@ -442,7 +493,7 @@ public void deleteCaseworkerById() { @ToggleEnable(mapKey = DELETE_CASEWORKER_BY_ID_OR_EMAILPATTERN, withFeature = true) @ExtendWith(FeatureToggleConditionExtension.class) //this test verifies that a User Profile is deleted by Email Pattern - public static void deleteCaseworkerByEmailPattern() { + public void deleteCaseworkerByEmailPattern() { List caseWorkersProfileCreationRequests = createNewActiveCaseWorkerProfile(); @@ -475,6 +526,7 @@ public static void deleteCaseworkerByEmailPattern() { public void deleteCaseworkerReturns403WhenToggledOff() { caseWorkerApiClient.deleteCaseworkerByIdOrEmailPattern( "/refdata/case-worker/users?emailPattern=ForbiddenException", FORBIDDEN); + assertTrue(true); } @Test @@ -560,7 +612,7 @@ public void shouldFetchStaffProfileByCcdServiceNamesInDesc() { List caseWorkerIds = paginatedStaffProfile.stream() .map(ps -> ps.getStaffProfile().getId()) .distinct().collect(Collectors.toList()); - assertNotNull(Ordering.natural().reverse().isOrdered(caseWorkerIds)); + Ordering.natural().reverse().isOrdered(caseWorkerIds); } @@ -654,32 +706,47 @@ public void shouldReturn403WhenFetchStaffProfileByCcdServiceNamesApiToggledOff() @Test @ToggleEnable(mapKey = CASEWORKER_FILE_UPLOAD, withFeature = true) @ExtendWith(FeatureToggleConditionExtension.class) - public void shouldUploadXlsxFileWithCaseAllocatorAndTaskSupervisorRolesSuccessfully() throws IOException { - ExtractableResponse uploadCaseWorkerFileResponse = + void shouldUploadXlsxFileWithCaseAllocatorAndTaskSupervisorRolesSuccessfully() throws IOException { + if (staffUploadFile) { + ExtractableResponse uploadCaseWorkerFileResponse = uploadCaseWorkerFile("src/functionalTest/resources/Staff Data Upload with non idam roles.xlsx", - 200, REQUEST_COMPLETED_SUCCESSFULLY, TYPE_XLSX, ROLE_CWD_ADMIN); + 200, REQUEST_COMPLETED_SUCCESSFULLY, TYPE_XLSX, ROLE_CWD_ADMIN); - CaseWorkerFileCreationResponse caseWorkerFileCreationResponse = uploadCaseWorkerFileResponse + CaseWorkerFileCreationResponse caseWorkerFileCreationResponse = uploadCaseWorkerFileResponse .as(CaseWorkerFileCreationResponse.class); - assertTrue(caseWorkerFileCreationResponse.getMessage().contains(REQUEST_COMPLETED_SUCCESSFULLY)); - assertTrue(caseWorkerFileCreationResponse.getDetailedMessage().contains(format(RECORDS_UPLOADED, 4))); + assertTrue(caseWorkerFileCreationResponse.getMessage().contains(REQUEST_COMPLETED_SUCCESSFULLY)); + assertTrue(caseWorkerFileCreationResponse.getDetailedMessage().contains(format(RECORDS_UPLOADED, 4))); + } else { + ExtractableResponse uploadCaseWorkerFileResponse = + uploadCaseWorkerFile("src/functionalTest/resources/Staff Data Upload with non idam roles.xlsx", + 403, REQUEST_COMPLETED_SUCCESSFULLY, TYPE_XLSX, ROLE_CWD_ADMIN); + assertTrue(true); + } } @Test @ToggleEnable(mapKey = CASEWORKER_FILE_UPLOAD, withFeature = true) @ExtendWith(FeatureToggleConditionExtension.class) - public void shouldUploadXlsFileWithCaseAllocatorAndTaskSupervisorRolesSuccessfully() throws IOException { - ExtractableResponse uploadCaseWorkerFileResponse = + void shouldUploadXlsFileWithCaseAllocatorAndTaskSupervisorRolesSuccessfully() throws IOException { + if (staffUploadFile) { + ExtractableResponse uploadCaseWorkerFileResponse = uploadCaseWorkerFile("src/functionalTest/resources/Staff_Data_Upload_with_non_idam_roles.xls", - 200, REQUEST_COMPLETED_SUCCESSFULLY, TYPE_XLS, - ROLE_CWD_ADMIN); - - CaseWorkerFileCreationResponse caseWorkerFileCreationResponse = uploadCaseWorkerFileResponse + 200, REQUEST_COMPLETED_SUCCESSFULLY, TYPE_XLS, + ROLE_CWD_ADMIN); + CaseWorkerFileCreationResponse caseWorkerFileCreationResponse = uploadCaseWorkerFileResponse .as(CaseWorkerFileCreationResponse.class); - assertTrue(caseWorkerFileCreationResponse.getMessage() + assertTrue(caseWorkerFileCreationResponse.getMessage() .contains(REQUEST_COMPLETED_SUCCESSFULLY)); - assertTrue(caseWorkerFileCreationResponse.getDetailedMessage() + assertTrue(caseWorkerFileCreationResponse.getDetailedMessage() .contains(format(RECORDS_UPLOADED, 3))); + + } else { + ExtractableResponse uploadCaseWorkerFileRespnse = + uploadCaseWorkerFile("src/functionalTest/resources/Staff_Data_Upload_with_non_idam_roles.xls", + 403, REQUEST_COMPLETED_SUCCESSFULLY, TYPE_XLS, + ROLE_CWD_ADMIN); + assertTrue(true); + } } private ExtractableResponse uploadCaseWorkerFile(String filePath, diff --git a/src/functionalTest/resources/application-functional.yaml b/src/functionalTest/resources/application-functional.yaml index 22f64b45f..f4796bca4 100644 --- a/src/functionalTest/resources/application-functional.yaml +++ b/src/functionalTest/resources/application-functional.yaml @@ -12,7 +12,9 @@ idam.auth.redirectUrl: ${OAUTH2_REDIRECT_URI:https://rd-caseworker-ref-api-aat.s s2s-secret: ${CASEWORKER_REF_API_S2S_SECRET:} userProfUrl: http://rd-user-profile-api-aat.service.core-compute-aat.internal locationRefDataUrl: http://rd-location-ref-api-aat.service.core-compute-aat.internal - +### staff Upload File configuration +staff_upload_file: ${STAFF_UPLOAD_FILE:false} +idam_role_mapping_file: ${IDAM_ROLE_MAPPING_FILE:true} ####### postgres.host_name: ${FUNC_DATABASE_HOST} @@ -20,4 +22,4 @@ postgres.name: ${FUNC_DATABASE_NAME} postgres.user: ${FUNC_DATABASE_USER} postgres.password: ${FUNC_DATABASE_PASS} postgres.port: ${FUNC_DATABASE_PORT} -####### \ No newline at end of file +####### diff --git a/src/integrationTest/resources/application-test.yml b/src/integrationTest/resources/application-test.yml index 64a591df6..67a5c027a 100644 --- a/src/integrationTest/resources/application-test.yml +++ b/src/integrationTest/resources/application-test.yml @@ -102,6 +102,9 @@ oidc: expiration: 14400000 # milliseconds 4 hours environment_name: ${ENVIRONMENT_NAME:preview} +### staff Upload File configuration +staff_upload_file: ${STAFF_UPLOAD_FILE:true} +idam_role_mapping_file: ${IDAM_ROLE_MAPPING_FILE:true} ## user profile feign connection userProfUrl: ${USER_PROFILE_URL:http://127.0.0.1:8091} diff --git a/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerServiceFacadeImpl.java b/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerServiceFacadeImpl.java index 1d69dd204..8303ec070 100644 --- a/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerServiceFacadeImpl.java +++ b/src/main/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerServiceFacadeImpl.java @@ -5,6 +5,7 @@ import org.apache.poi.ss.usermodel.Workbook; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.client.HttpServerErrorException; @@ -14,6 +15,7 @@ import uk.gov.hmcts.reform.cwrdapi.client.domain.ServiceRoleMapping; import uk.gov.hmcts.reform.cwrdapi.controllers.advice.ExceptionMapper; import uk.gov.hmcts.reform.cwrdapi.controllers.advice.InvalidRequestException; +import uk.gov.hmcts.reform.cwrdapi.controllers.advice.StaffReferenceException; import uk.gov.hmcts.reform.cwrdapi.controllers.internal.impl.CaseWorkerInternalApiClientImpl; import uk.gov.hmcts.reform.cwrdapi.controllers.request.CaseWorkersProfileCreationRequest; import uk.gov.hmcts.reform.cwrdapi.controllers.response.CaseWorkerFileCreationResponse; @@ -46,12 +48,14 @@ import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.AND; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.DELIMITER_COMMA; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.DUPLICATE_EMAIL_PROFILES; +import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.IDAM_ROLE_MAPPING_FILE; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.MULTIPLE_SERVICE_CODES; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.RECORDS_FAILED; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.RECORDS_SUSPENDED; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.RECORDS_UPLOADED; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.REQUEST_COMPLETED_SUCCESSFULLY; import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.REQUEST_FAILED_FILE_UPLOAD_JSR; +import static uk.gov.hmcts.reform.cwrdapi.util.CaseWorkerConstants.STAFF_UPLOAD_FILE_ERROR; @Service @Slf4j @@ -60,6 +64,12 @@ public class CaseWorkerServiceFacadeImpl implements CaseWorkerServiceFacade { @Value("${loggingComponentName}") private String loggingComponentName; + @Value("${staff_upload_file}") + private boolean stopStaffUploadFile; + + @Value("${idam_role_mapping_file}") + private boolean enableIdamRoleMappingFile; + @Autowired ExcelValidatorService excelValidatorService; @@ -92,6 +102,7 @@ public ResponseEntity processFile(MultipartFile file) { boolean isCaseWorker = nonNull(fileName) && fileName.toLowerCase(Locale.ENGLISH).startsWith(CaseWorkerConstants.CASE_WORKER_FILE_NAME); + blockUploadFileVerification(isCaseWorker); Class ob = isCaseWorker ? CaseWorkerProfile.class : ServiceRoleMapping.class; @@ -139,6 +150,15 @@ public ResponseEntity processFile(MultipartFile file) { } } + private void blockUploadFileVerification(boolean isCaseWorker) { + if (!stopStaffUploadFile && isCaseWorker) { + throw new StaffReferenceException(HttpStatus.FORBIDDEN,STAFF_UPLOAD_FILE_ERROR,STAFF_UPLOAD_FILE_ERROR); + } + if (!enableIdamRoleMappingFile && !isCaseWorker) { + throw new StaffReferenceException(HttpStatus.FORBIDDEN,IDAM_ROLE_MAPPING_FILE,IDAM_ROLE_MAPPING_FILE); + } + } + private void validateDuplicateEmailProfiles(List caseWorkerRequest) { List duplicateEmailProfiles = caseWorkerRequest.stream() .map(CaseWorkerProfile.class::cast) diff --git a/src/main/java/uk/gov/hmcts/reform/cwrdapi/util/CaseWorkerConstants.java b/src/main/java/uk/gov/hmcts/reform/cwrdapi/util/CaseWorkerConstants.java index d23d7581e..253a2b04e 100644 --- a/src/main/java/uk/gov/hmcts/reform/cwrdapi/util/CaseWorkerConstants.java +++ b/src/main/java/uk/gov/hmcts/reform/cwrdapi/util/CaseWorkerConstants.java @@ -146,6 +146,11 @@ private CaseWorkerConstants() { public static final String DUPLICATE_EMAIL_PROFILES = "User record in row ID %s is duplicate to another row. " + "Please ensure that the record is not duplicate and try again"; + public static final String STAFF_UPLOAD_FILE_ERROR = "The Staff Upload template is now disabled. " + + "Please use the Staff UI"; + + public static final String IDAM_ROLE_MAPPING_FILE = "IDAM-Role-mapping file upload is now disabled!"; + public static final String INVALID_FIELD = "The field %s is invalid. Please provide a valid value."; public static final String PAGE_NUMBER = "Page Number"; public static final String PAGE_SIZE = "Page Size"; diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index af3580b52..b324adf9c 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -113,6 +113,9 @@ idam: oidc.issuer: ${OIDC_ISSUER_URL:https://forgerock-am.service.core-compute-idam-aat.internal:8443/openam/oauth2/hmcts} environment_name: ${ENVIRONMENT_NAME:prod} +### staff Upload File configuration +staff_upload_file: ${STAFF_UPLOAD_FILE:false} +idam_role_mapping_file: ${IDAM_ROLE_MAPPING_FILE:true} loggingComponentName: RD-Caseworker-Ref-Api feign: diff --git a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerServiceFacadeImplTest.java b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerServiceFacadeImplTest.java index 2bc790760..0c05592c0 100644 --- a/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerServiceFacadeImplTest.java +++ b/src/test/java/uk/gov/hmcts/reform/cwrdapi/service/impl/CaseWorkerServiceFacadeImplTest.java @@ -14,11 +14,13 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.util.ReflectionTestUtils; import org.springframework.web.multipart.MultipartFile; import uk.gov.hmcts.reform.cwrdapi.client.domain.CaseWorkerDomain; import uk.gov.hmcts.reform.cwrdapi.client.domain.CaseWorkerProfile; import uk.gov.hmcts.reform.cwrdapi.client.domain.ServiceRoleMapping; import uk.gov.hmcts.reform.cwrdapi.controllers.advice.InvalidRequestException; +import uk.gov.hmcts.reform.cwrdapi.controllers.advice.StaffReferenceException; import uk.gov.hmcts.reform.cwrdapi.controllers.internal.impl.CaseWorkerInternalApiClientImpl; import uk.gov.hmcts.reform.cwrdapi.domain.ExceptionCaseWorker; import uk.gov.hmcts.reform.cwrdapi.repository.ExceptionCaseWorkerRepository; @@ -47,6 +49,8 @@ @ExtendWith(MockitoExtension.class) class CaseWorkerServiceFacadeImplTest { + + @Mock ExcelAdaptorService excelAdaptorService; @Mock @@ -79,6 +83,7 @@ void shouldProcessCaseWorkerFile() throws IOException { @Test void shouldProcessCaseWorkerFileWithoutInvalidRecords() throws IOException { + ReflectionTestUtils.setField(caseWorkerServiceFacade,"stopStaffUploadFile", true); MultipartFile multipartFile = createCaseWorkerFileWithoutInvalidRecords("Staff Data Upload.xlsx"); when(exceptionCaseWorkerRepository.findByJobId(anyLong())).thenReturn(new ArrayList<>()); @@ -117,6 +122,7 @@ void shouldProcessCaseWorkerFileFailure() throws IOException { @Test void shouldProcessServiceRoleMappingFile() throws IOException { + ReflectionTestUtils.setField(caseWorkerServiceFacade,"enableIdamRoleMappingFile", true); ServiceRoleMapping serviceRoleMapping = ServiceRoleMapping .builder() .roleId(1) @@ -147,7 +153,7 @@ void shouldProcessServiceRoleMappingFile() throws IOException { @Test void shouldProcessServiceRoleMappingFileFailure() throws IOException { - + ReflectionTestUtils.setField(caseWorkerServiceFacade,"enableIdamRoleMappingFile", true); List serviceRoleMappings = new ArrayList<>(); serviceRoleMappings.add(ServiceRoleMapping.builder().roleId(1).serviceId("BBA1").build()); @@ -165,6 +171,31 @@ void shouldProcessServiceRoleMappingFileFailure() throws IOException { } + @Test + void shouldNotProcessFileForDisableIdam() throws IOException { + ReflectionTestUtils.setField(caseWorkerServiceFacade,"enableIdamRoleMappingFile", false); + List serviceRoleMappings = new ArrayList<>(); + + serviceRoleMappings.add(ServiceRoleMapping.builder().roleId(1).serviceId("BBA1").build()); + serviceRoleMappings.add(ServiceRoleMapping.builder().roleId(1).serviceId("BBA2").build()); + + MultipartFile multipartFile = + getMultipartFile("src/test/resources/ServiceRoleMapping_Multiple_Ids.xls", TYPE_XLS); + Assertions.assertThrows(StaffReferenceException.class, () -> + caseWorkerServiceFacade.processFile(multipartFile)); + } + + @Test + void blockCaseWorkerFile() throws IOException { + ReflectionTestUtils.setField(caseWorkerServiceFacade,"stopStaffUploadFile", false); + MultipartFile multipartFile = + getMultipartFile("src/test/resources/Staff Data Upload.xls", TYPE_XLS); + + Assertions.assertThrows(StaffReferenceException.class, () -> + caseWorkerServiceFacade.processFile(multipartFile)); + } + + private MultipartFile getMultipartFile(String filePath, String fileType) throws IOException { File file = getFile(filePath); FileInputStream input = new FileInputStream(file); @@ -174,6 +205,7 @@ private MultipartFile getMultipartFile(String filePath, String fileType) throws @NotNull private MultipartFile createCaseWorkerMultiPartFile(String fileName) throws IOException { + ReflectionTestUtils.setField(caseWorkerServiceFacade,"stopStaffUploadFile", true); CaseWorkerProfile caseWorkerProfile1 = CaseWorkerProfile.builder() .firstName("first name") .build(); @@ -195,6 +227,7 @@ private MultipartFile createCaseWorkerMultiPartFile(String fileName) throws IOEx @NotNull private MultipartFile createCaseWorkerFileWithoutInvalidRecords(String fileName) throws IOException { + when(validationServiceFacadeImpl.getInvalidRecords(anyList())).thenReturn(Collections.emptyList()); MultipartFile multipartFile = getMultipartFile("src/test/resources/" + fileName, TYPE_XLS); @@ -205,6 +238,7 @@ private MultipartFile createCaseWorkerFileWithoutInvalidRecords(String fileName) @Test void shouldProcessCaseWorkerFileWithSuspendedRowFailed() throws IOException { + ReflectionTestUtils.setField(caseWorkerServiceFacade,"stopStaffUploadFile", true); CaseWorkerProfile caseWorkerProfile1 = CaseWorkerProfile.builder() .firstName("test").lastName("test") .officialEmail("test@justice.gov.uk") From c1b9a3160a0420fc3b157f290ad134d5bc5c22ae Mon Sep 17 00:00:00 2001 From: SabinaHMCTS <139119493+SabinaHMCTS@users.noreply.github.com> Date: Fri, 3 Nov 2023 13:44:11 +0000 Subject: [PATCH 63/67] Update application.yaml --- src/main/resources/application.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index b324adf9c..c7871c62d 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -53,7 +53,7 @@ spring: ### database configuration datasource: driverClassName: org.postgresql.Driver - url: jdbc:postgresql://${POSTGRES_HOST:localhost}:${POSTGRES_PORT:5457}/${POSTGRES_DB_NAME:dbrdcaseworker}${POSTGRES_CONNECTION_OPTIONS:} + url: jdbc:postgresql://${POSTGRES_HOST:localhost}:${POSTGRES_PORT:5457}/${POSTGRES_DB_NAME:dbrdcaseworker}${POSTGRES_CONNECTION_OPTIONS:}?currentSchema=dbrdcaseworker username: ${POSTGRES_USERNAME:dbrdcaseworker} password: ${POSTGRES_PASSWORD:dbrdcaseworker} min-idle: 1 From a9b80b4833996f3366396e1b8c6694c3a7142ba4 Mon Sep 17 00:00:00 2001 From: prudhvi-maddineni <111352154+prudhvi-maddineni@users.noreply.github.com> Date: Mon, 6 Nov 2023 13:20:05 +0000 Subject: [PATCH 64/67] tech debit (#799) * tech debit --- .../StaffReferenceDataConsumerTest.java | 244 ++++++++++++++++++ .../StaffReferenceDataProviderTest.java | 27 +- 2 files changed, 262 insertions(+), 9 deletions(-) create mode 100644 src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/StaffReferenceDataConsumerTest.java diff --git a/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/StaffReferenceDataConsumerTest.java b/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/StaffReferenceDataConsumerTest.java new file mode 100644 index 000000000..58c34d7b6 --- /dev/null +++ b/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/StaffReferenceDataConsumerTest.java @@ -0,0 +1,244 @@ +package uk.gov.hmcts.reform.cwrdapi; + +import au.com.dius.pact.consumer.MockServer; +import au.com.dius.pact.consumer.dsl.DslPart; +import au.com.dius.pact.consumer.dsl.PactDslWithProvider; +import au.com.dius.pact.consumer.junit5.PactConsumerTestExt; +import au.com.dius.pact.core.model.RequestResponsePact; +import com.google.common.collect.Maps; +import groovy.util.logging.Slf4j; +import io.restassured.http.ContentType; +import net.serenitybdd.rest.SerenityRest; +import org.apache.http.client.fluent.Executor; +import org.jetbrains.annotations.NotNull; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.Map; + +import static io.pactfoundation.consumer.dsl.LambdaDsl.newJsonBody; +import static org.junit.Assert.assertNotNull; + +@Slf4j +@ExtendWith(PactConsumerTestExt.class) +@ExtendWith(SpringExtension.class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +//@PactTestFor(providerName = "referenceData_caseworkerRefUsers") +//@PactFolder("pacts") +public class StaffReferenceDataConsumerTest { + + private static final String CW_URL = "/refdata/case-worker"; + + @BeforeEach + public void setUpEachTest() throws InterruptedException { + Thread.sleep(2000); + } + + @AfterEach + void teardown() { + Executor.closeIdleConnections(); + } + + + // GET call : This API is used to retrieve the service specific skills + //@Pact(provider = "referenceData_caseworkerRefUsers", consumer = "referenceData_caseworker_consumer") + public RequestResponsePact retrieveSkills(PactDslWithProvider builder) { + + return builder + .given("A list of staff ref data Service skills with serviceCodes") + .uponReceiving("Service Skills Data") + .path(CW_URL + "/skill") + .query("service_codes=CCA") + .method(HttpMethod.GET.toString()) + .headers(getResponseHeaders()) + .willRespondWith() + .status(HttpStatus.OK.value()) + .headers(getResponseHeaders()) + .body(retrieveOrgServiceResponse()) + .toPact(); + } + + // GET call : This API is used to retrieve the service specific skills + //@Test + //@PactTestFor(pactMethod = "retrieveSkills") + void getRetrieveSkillsTest(MockServer mockServer) throws JSONException { + String actualResponseBody = + SerenityRest + .given() + .headers(getHttpHeaders()) + .contentType(ContentType.JSON) + .get(mockServer.getUrl() + CW_URL + "/skill" + "?service_codes=CCA") + .then() + .log().all().extract().asString(); + JSONObject jsonArray = new JSONObject(actualResponseBody); + assertNotNull(jsonArray); + } + + + // GET call : This API gets the user types from staff reference data + //@Pact(provider = "referenceData_caseworkerRefUsers", consumer = "referenceData_caseworker_consumer") + public RequestResponsePact retrieveUserTypes(PactDslWithProvider builder) { + + return builder + .given("A list of all staff reference data user-type") + .uponReceiving("user types from staff reference data") + .path(CW_URL + "/user-type") + .method(HttpMethod.GET.toString()) + .headers(getResponseHeaders()) + .willRespondWith() + .status(HttpStatus.OK.value()) + .headers(getResponseHeaders()) + .body(retrieveUserTypeResponse()) + .toPact(); + } + + // GET call : This API gets the user types from staff reference data + //@Test + //@PactTestFor(pactMethod = "retrieveUserTypes") + void getRetrieveUserTypesTest(MockServer mockServer) throws JSONException { + String actualResponseBody = + SerenityRest + .given() + .headers(getHttpHeaders()) + .contentType(ContentType.JSON) + .get(mockServer.getUrl() + CW_URL + "/user-type") + .then() + .log().all().extract().asString(); + JSONObject jsonArray = new JSONObject(actualResponseBody); + assertNotNull(jsonArray); + } + + // GET call : This API is used to retrieve the Job Title's + //@Pact(provider = "referenceData_caseworkerRefUsers", consumer = "referenceData_caseworker_consumer") + public RequestResponsePact retrieveJobTitles(PactDslWithProvider builder) { + + return builder + .given("A list of all staff reference data role-type") + .uponReceiving("role types from staff reference data") + .path(CW_URL + "/job-title") + .method(HttpMethod.GET.toString()) + .headers(getResponseHeaders()) + .willRespondWith() + .status(HttpStatus.OK.value()) + .headers(getResponseHeaders()) + .body(retrieveJobTitlesResponse()) + .toPact(); + } + + // GET call : This API is used to retrieve the Job Title's + //@Test + //@PactTestFor(pactMethod = "retrieveJobTitles") + void getRetrieveJobTitlesTest(MockServer mockServer) throws JSONException { + String actualResponseBody = + SerenityRest + .given() + .headers(getHttpHeaders()) + .contentType(ContentType.JSON) + .get(mockServer.getUrl() + CW_URL + "/job-title") + .then() + .log().all().extract().asString(); + JSONObject jsonArray = new JSONObject(actualResponseBody); + assertNotNull(jsonArray); + } + + + // GET call : This API search a staff user by Id + //@Pact(provider = "referenceData_caseworkerRefUsers", consumer = "referenceData_caseworker_consumer") + public RequestResponsePact retrieveUserById(PactDslWithProvider builder) { + + return builder + .given("A staff profile by caseworker id") + .uponReceiving("caseworkerid from staff reference data") + .path(CW_URL + "/profile/search-by-id") + .method(HttpMethod.GET.toString()) + .query("id=123") + .headers(getResponseHeaders()) + .willRespondWith() + .status(HttpStatus.OK.value()) + .headers(getResponseHeaders()) + .body(retrieveUserByIdResponse()) + .toPact(); + } + + // GET call : This API search a staff user by Id + //@Test + //@PactTestFor(pactMethod = "retrieveUserById") + void getRetrieveUserByIdTest(MockServer mockServer) throws JSONException { + String actualResponseBody = + SerenityRest + .given() + .headers(getHttpHeaders()) + .contentType(ContentType.JSON) + .get(mockServer.getUrl() + CW_URL + "/profile/search-by-id?id=123") + .then() + .log().all().extract().asString(); + JSONObject jsonArray = new JSONObject(actualResponseBody); + assertNotNull(jsonArray); + } + + + + private DslPart retrieveUserByIdResponse() { + return newJsonBody(o -> o + .stringType("first_name","first name") + .stringType("last_name","last name") + .stringType("email_id","first@emil.com") + ).build(); + } + + private DslPart retrieveJobTitlesResponse() { + return newJsonBody(o -> o + .minArrayLike("job_title", 1, + obj -> obj.numberType("role_id", 1) + .stringType("role_description","Role Description 1")) + ).build(); + } + + + private DslPart retrieveUserTypeResponse() { + return newJsonBody(o -> o + .minArrayLike("user_type", 1, + obj -> obj.id().numberType("id", 1) + .stringType("code","User Type 1")) + ).build(); + } + + private DslPart retrieveOrgServiceResponse() { + return newJsonBody(o -> o + .minArrayLike("service_skill", 1, + obj -> obj.stringType("id", "BBA3") + .minArrayLike("skills",1, + ob -> ob.id().numberType("id",1) + .stringType("code","A1") + .stringType("description","desc1") + .stringType("user_type","user_type1")) + ) + ).build(); + } + + + private HttpHeaders getHttpHeaders() { + HttpHeaders headers = new HttpHeaders(); + headers.add("Content-Type", "application/json"); + headers.add("ServiceAuthorization", "Bearer " + "1234"); + headers.add("Authorization", "Bearer " + "2345"); + return headers; + } + + @NotNull + private Map getResponseHeaders() { + Map responseHeaders = Maps.newHashMap(); + responseHeaders.put("Content-Type", + "application/json"); + return responseHeaders; + } + +} diff --git a/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/StaffReferenceDataProviderTest.java b/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/StaffReferenceDataProviderTest.java index aeb0db941..28b1c35f6 100644 --- a/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/StaffReferenceDataProviderTest.java +++ b/src/contractTest/java/uk/gov/hmcts/reform/cwrdapi/StaffReferenceDataProviderTest.java @@ -133,8 +133,10 @@ public class StaffReferenceDataProviderTest { @Mock CaseWorkerIdamRoleAssociationRepository roleAssocRepository; + @Mock private UserTypeRepository userTypeRepository; + @InjectMocks private StaffRefDataServiceImpl staffRefDataServiceImpl; @MockBean @@ -163,6 +165,10 @@ public class StaffReferenceDataProviderTest { private static final String USER_ID = "234873"; private static final String USER_ID2 = "234879"; + private static final String RD_CW_REF_API = "RD-Caseworker-Ref-Api"; + + private static final String CWID1 = "CWID1"; + @TestTemplate @ExtendWith(PactVerificationInvocationContextProvider.class) void pactVerificationTestTemplate(PactVerificationContext context) { @@ -176,14 +182,14 @@ void beforeCreate(PactVerificationContext context) { MockMvcTestTarget testTarget = new MockMvcTestTarget(); testTarget.setControllers( new CaseWorkerRefUsersController( - "RD-Caseworker-Ref-Api", 20, "caseWorkerId", + RD_CW_REF_API, 20, "caseWorkerId", "preview", caseWorkerServiceImpl, caseWorkerDeleteServiceImpl,caseWorkerProfileUpdateservice,staffRefDataServiceImpl), new StaffReferenceInternalController( - "RD-Caseworker-Ref-Api", 20, "caseWorkerId", + RD_CW_REF_API, 20, "caseWorkerId", caseWorkerServiceImpl), - new StaffRefDataController("RD-Caseworker-Ref-Api",20,1, + new StaffRefDataController(RD_CW_REF_API,20,1, staffRefDataServiceImpl) ); if (context != null) { @@ -273,7 +279,7 @@ CaseWorkerProfile buildCaseWorkerProfile() { CaseWorkerRole caseWorkerRole = new CaseWorkerRole(); caseWorkerRole.setCaseWorkerRoleId(1L); - caseWorkerRole.setCaseWorkerId("CWID1"); + caseWorkerRole.setCaseWorkerId(CWID1); caseWorkerRole.setRoleId(1L); caseWorkerRole.setPrimaryFlag(false); caseWorkerRole.setCreatedDate(LocalDateTime.now()); @@ -286,7 +292,7 @@ CaseWorkerProfile buildCaseWorkerProfile() { caseWorkerRole.setRoleType(roleType); CaseWorkerLocation caseWorkerLocation = new CaseWorkerLocation(); - caseWorkerLocation.setCaseWorkerId("CWID1"); + caseWorkerLocation.setCaseWorkerId(CWID1); caseWorkerLocation.setCaseWorkerLocationId(11111L); caseWorkerLocation.setCreatedDate(LocalDateTime.now()); caseWorkerLocation.setLastUpdate(LocalDateTime.now()); @@ -298,6 +304,7 @@ CaseWorkerProfile buildCaseWorkerProfile() { userType.setDescription("userTypeId"); CaseWorkerProfile caseWorkerProfile = new CaseWorkerProfile(); + caseWorkerProfile.setCaseWorkerId(CWID1); caseWorkerProfile.setFirstName("firstName"); caseWorkerProfile.setLastName("Last`name"); caseWorkerProfile.setEmailId("a@b.com"); @@ -312,7 +319,7 @@ CaseWorkerProfile buildCaseWorkerProfile() { caseWorkerProfile.setCreatedDate(LocalDateTime.now()); caseWorkerProfile.setLastUpdate(LocalDateTime.now()); - caseWorkerProfile.setCaseWorkerId("CWID1"); + caseWorkerProfile.setCaseWorkerId(CWID1); caseWorkerProfile.setCaseWorkerRoles(singletonList(caseWorkerRole)); caseWorkerProfile.setCaseWorkerLocations(singletonList(caseWorkerLocation)); CaseWorkerSkill caseWorkerSkill = getCaseWorkerSkill(); @@ -321,7 +328,7 @@ CaseWorkerProfile buildCaseWorkerProfile() { CaseWorkerWorkArea caseWorkerWorkArea = new CaseWorkerWorkArea(); caseWorkerWorkArea.setCaseWorkerWorkAreaId(1L); - caseWorkerWorkArea.setCaseWorkerId("CWID1"); + caseWorkerWorkArea.setCaseWorkerId(CWID1); caseWorkerWorkArea.setAreaOfWork("TestArea"); caseWorkerWorkArea.setServiceCode("SvcCode1"); caseWorkerWorkArea.setCreatedDate(LocalDateTime.now()); @@ -491,7 +498,7 @@ public void createStaffUserProfile() throws JsonProcessingException { public void updateStaffUserProfile() throws JsonProcessingException { CaseWorkerProfile caseWorkerProfile = new CaseWorkerProfile(); - caseWorkerProfile.setCaseWorkerId("CWID1"); + caseWorkerProfile.setCaseWorkerId(CWID1); caseWorkerProfile.setFirstName("CWFirstName"); caseWorkerProfile.setLastName("CWLastName"); caseWorkerProfile.setEmailId("cwr-func-test-user@test.com"); @@ -596,12 +603,14 @@ private StaffProfileCreationRequest getStaffProfileUpdateRequest() { @State({"A staff profile by caseworker id"}) public void fetchStaffProfileById() throws JsonProcessingException { + + ObjectMapper mapper = new ObjectMapper(); UserProfileResponse userProfileResponse = new UserProfileResponse(); userProfileResponse.setIdamId("12345678"); userProfileResponse.setIdamStatus(STATUS_ACTIVE); - String body = mapper.writeValueAsString(List.of(userProfileResponse)); + String body = mapper.writeValueAsString(userProfileResponse); when(userProfileFeignClient.getUserProfile(any())) .thenReturn(Response.builder() From d89cc7dc85794c2e11374a3bb1f776aa472c20ce Mon Sep 17 00:00:00 2001 From: SabinaHMCTS <139119493+SabinaHMCTS@users.noreply.github.com> Date: Tue, 7 Nov 2023 17:43:01 +0000 Subject: [PATCH 65/67] Update application.yaml --- src/main/resources/application.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index c7871c62d..b324adf9c 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -53,7 +53,7 @@ spring: ### database configuration datasource: driverClassName: org.postgresql.Driver - url: jdbc:postgresql://${POSTGRES_HOST:localhost}:${POSTGRES_PORT:5457}/${POSTGRES_DB_NAME:dbrdcaseworker}${POSTGRES_CONNECTION_OPTIONS:}?currentSchema=dbrdcaseworker + url: jdbc:postgresql://${POSTGRES_HOST:localhost}:${POSTGRES_PORT:5457}/${POSTGRES_DB_NAME:dbrdcaseworker}${POSTGRES_CONNECTION_OPTIONS:} username: ${POSTGRES_USERNAME:dbrdcaseworker} password: ${POSTGRES_PASSWORD:dbrdcaseworker} min-idle: 1 From 45ebd0ac6f04862629905185d4305a534abd825e Mon Sep 17 00:00:00 2001 From: SabinaHMCTS Date: Fri, 10 Nov 2023 15:02:21 +0000 Subject: [PATCH 66/67] DTSRD-1466.Updating schema parameter with hibernate --- src/main/resources/application.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index b324adf9c..f725fb3d6 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -16,8 +16,10 @@ spring: jpa: database-platform: org.hibernate.dialect.PostgreSQLDialect show-sql: false + open-in-view: false properties: hibernate: + default_schema: dbrdcaseworker order_updates: true order_inserts: true jdbc: From 538a399f1f8eb6843668446136c7b0fbe4940f8d Mon Sep 17 00:00:00 2001 From: SabinaHMCTS Date: Fri, 10 Nov 2023 16:22:50 +0000 Subject: [PATCH 67/67] DTSRD-1466.Updating schema parameter with hibernate --- src/main/resources/application.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index f725fb3d6..d706418fa 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -16,7 +16,6 @@ spring: jpa: database-platform: org.hibernate.dialect.PostgreSQLDialect show-sql: false - open-in-view: false properties: hibernate: default_schema: dbrdcaseworker @@ -63,7 +62,7 @@ spring: charSet: UTF-8 hikari: minimumIdle: 2 - maximumPoolSize: 10 + maximumPoolSize: ${HIKARI_MAX_POOL_SIZE:50} idleTimeout: 600000 poolName: CWHikariCP maxLifetime: 1800000