From 444364d531580481aabd4c852e49a6fc6dcd895b Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Thu, 21 Sep 2023 18:07:46 +0100 Subject: [PATCH 01/70] VUU25: Initialise module --- layout-server/.gitignore | 33 ++ layout-server/.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 62547 bytes .../.mvn/wrapper/maven-wrapper.properties | 2 + layout-server/mvnw | 308 ++++++++++++++++++ layout-server/mvnw.cmd | 205 ++++++++++++ layout-server/pom.xml | 54 +++ .../layoutserver/LayoutServerApplication.java | 13 + .../src/main/resources/application.properties | 1 + .../LayoutServerApplicationTests.java | 13 + pom.xml | 1 + 10 files changed, 630 insertions(+) create mode 100644 layout-server/.gitignore create mode 100644 layout-server/.mvn/wrapper/maven-wrapper.jar create mode 100644 layout-server/.mvn/wrapper/maven-wrapper.properties create mode 100644 layout-server/mvnw create mode 100644 layout-server/mvnw.cmd create mode 100644 layout-server/pom.xml create mode 100644 layout-server/src/main/java/org/finos/vuu/layoutserver/LayoutServerApplication.java create mode 100644 layout-server/src/main/resources/application.properties create mode 100644 layout-server/src/test/java/org/finos/vuu/layoutserver/LayoutServerApplicationTests.java diff --git a/layout-server/.gitignore b/layout-server/.gitignore new file mode 100644 index 000000000..549e00a2a --- /dev/null +++ b/layout-server/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/layout-server/.mvn/wrapper/maven-wrapper.jar b/layout-server/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..cb28b0e37c7d206feb564310fdeec0927af4123a GIT binary patch literal 62547 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 A?*IS* literal 0 HcmV?d00001 diff --git a/layout-server/.mvn/wrapper/maven-wrapper.properties b/layout-server/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..2e76e189d --- /dev/null +++ b/layout-server/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.4/apache-maven-3.9.4-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/layout-server/mvnw b/layout-server/mvnw new file mode 100644 index 000000000..66df28542 --- /dev/null +++ b/layout-server/mvnw @@ -0,0 +1,308 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "$(uname)" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin ; then + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "\"$javaExecutable\"")" + fi + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + 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" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/layout-server/mvnw.cmd b/layout-server/mvnw.cmd new file mode 100644 index 000000000..95ba6f54a --- /dev/null +++ b/layout-server/mvnw.cmd @@ -0,0 +1,205 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/layout-server/pom.xml b/layout-server/pom.xml new file mode 100644 index 000000000..bfb06416b --- /dev/null +++ b/layout-server/pom.xml @@ -0,0 +1,54 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.1.3 + + + org.finos.vuu + layout-server + 0.0.1-SNAPSHOT + layout-server + layout-server + + 17 + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-test + test + + + org.projectlombok + lombok + true + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/LayoutServerApplication.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/LayoutServerApplication.java new file mode 100644 index 000000000..f0a1d10c7 --- /dev/null +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/LayoutServerApplication.java @@ -0,0 +1,13 @@ +package org.finos.vuu.layoutserver; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class LayoutServerApplication { + + public static void main(String[] args) { + SpringApplication.run(LayoutServerApplication.class, args); + } + +} diff --git a/layout-server/src/main/resources/application.properties b/layout-server/src/main/resources/application.properties new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/layout-server/src/main/resources/application.properties @@ -0,0 +1 @@ + diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/LayoutServerApplicationTests.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/LayoutServerApplicationTests.java new file mode 100644 index 000000000..0e56bd365 --- /dev/null +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/LayoutServerApplicationTests.java @@ -0,0 +1,13 @@ +package org.finos.vuu.layoutserver; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class LayoutServerApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/pom.xml b/pom.xml index 06993cb6e..31a023435 100644 --- a/pom.xml +++ b/pom.xml @@ -82,6 +82,7 @@ toolbox vuu vuu-ui + layout-server benchmark From 3e8e7386fb3314a969eaf1a6cbd5c68906a3047a Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Thu, 21 Sep 2023 20:24:14 +0100 Subject: [PATCH 02/70] VUU25: Basic Controller infrastructure --- .../controller/LayoutController.java | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java new file mode 100644 index 000000000..68cfc9367 --- /dev/null +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java @@ -0,0 +1,72 @@ +package org.finos.vuu.layoutserver.controller; + +import org.springframework.web.bind.annotation.*; + +@RestController("/layout") +public class LayoutController { + + /** + * Gets all layouts if no IDs are specified, otherwise gets the specified layouts + * + * @param ids IDs of the layouts to get + * @return the layouts + */ + @GetMapping + public String getLayouts(@RequestBody(required = false) String[] ids) { + return "Hello World"; + } + + /** + * Gets the specified layout + * + * @param id ID of the layout to get + * @return the layout + */ + @GetMapping("/{id}") + public String getLayout(@PathVariable String id) { + return "Hello World"; + } + + /** + * Gets metadata for all layouts if no IDs are specified, otherwise gets the metadata for specified layouts + * + * @param ids IDs of the layouts to get metadata for + * @return the metadata + */ + @GetMapping("/metadata") + public String getMetadata(@RequestBody(required = false) String[] ids) { + return "Hello World"; + } + + /** + * Creates a new layout + * + * @return the ID of the new layout + */ + @PostMapping + public String createLayout() { + return "Hello World"; + } + + /** + * Updates the specified layout + * + * @param id ID of the layout to update + * @return the ID of the updated layout + */ + @PutMapping("/{id}") + public String updateLayout(@PathVariable String id) { + return "Hello World"; + } + + /** + * Deletes the specified layout + * + * @param id ID of the layout to delete + * @return the ID of the deleted layout + */ + @DeleteMapping("/{id}") + public String deleteLayout(@PathVariable String id) { + return "Hello World"; + } +} From 7061d64174539568d3ab965e84b47779c8968fab Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Thu, 21 Sep 2023 20:50:41 +0100 Subject: [PATCH 03/70] VUU25: Implement Layout & Metadata models --- layout-server/pom.xml | 8 ++++++ .../controller/LayoutController.java | 19 ++++++++----- .../finos/vuu/layoutserver/model/Layout.java | 21 ++++++++++++++ .../vuu/layoutserver/model/Metadata.java | 28 +++++++++++++++++++ 4 files changed, 69 insertions(+), 7 deletions(-) create mode 100644 layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java create mode 100644 layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java diff --git a/layout-server/pom.xml b/layout-server/pom.xml index bfb06416b..e714cc33c 100644 --- a/layout-server/pom.xml +++ b/layout-server/pom.xml @@ -32,6 +32,14 @@ lombok true + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-data-jpa + diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java index 68cfc9367..9583c10e4 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java @@ -1,7 +1,12 @@ package org.finos.vuu.layoutserver.controller; +import org.finos.vuu.layoutserver.model.Layout; +import org.finos.vuu.layoutserver.model.Metadata; import org.springframework.web.bind.annotation.*; +import java.util.ArrayList; +import java.util.List; + @RestController("/layout") public class LayoutController { @@ -12,8 +17,8 @@ public class LayoutController { * @return the layouts */ @GetMapping - public String getLayouts(@RequestBody(required = false) String[] ids) { - return "Hello World"; + public List getLayouts(@RequestBody(required = false) String[] ids) { + return new ArrayList<>(); } /** @@ -23,8 +28,8 @@ public String getLayouts(@RequestBody(required = false) String[] ids) { * @return the layout */ @GetMapping("/{id}") - public String getLayout(@PathVariable String id) { - return "Hello World"; + public Layout getLayout(@PathVariable String id) { + return Layout.builder().build(); } /** @@ -34,14 +39,14 @@ public String getLayout(@PathVariable String id) { * @return the metadata */ @GetMapping("/metadata") - public String getMetadata(@RequestBody(required = false) String[] ids) { - return "Hello World"; + public List getMetadata(@RequestBody(required = false) String[] ids) { + return new ArrayList<>(); } /** * Creates a new layout * - * @return the ID of the new layout + * @return the ID of the new layout` */ @PostMapping public String createLayout() { diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java new file mode 100644 index 000000000..cb5aebe62 --- /dev/null +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java @@ -0,0 +1,21 @@ +package org.finos.vuu.layoutserver.model; + +import jakarta.persistence.*; +import lombok.Builder; +import lombok.Data; + +@Entity +@Data +@Builder +public class Layout { + @Id + @GeneratedValue(strategy= GenerationType.AUTO) + private String id; + + private String definition; + + @OneToOne(cascade = CascadeType.ALL) + private Metadata metadata; + + protected Layout() {} +} diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java new file mode 100644 index 000000000..7df0c2fa0 --- /dev/null +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java @@ -0,0 +1,28 @@ +package org.finos.vuu.layoutserver.model; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import lombok.Builder; +import lombok.Data; + +import java.util.Date; + +@Entity +@Data +@Builder +public class Metadata { + + @Id + @GeneratedValue + private Long id; + + private String name; + private String group; + private String screenshot; + private String user; + private Date date; + + protected Metadata() { + } +} From d444cad38cd0371cdfde0ccd929f762f97f64ad4 Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Fri, 22 Sep 2023 16:33:24 +0100 Subject: [PATCH 04/70] VUU25: Add OpenAPI docs (Swagger) --- layout-server/pom.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/layout-server/pom.xml b/layout-server/pom.xml index e714cc33c..32570e8ee 100644 --- a/layout-server/pom.xml +++ b/layout-server/pom.xml @@ -37,8 +37,9 @@ spring-boot-starter-web - org.springframework.boot - spring-boot-starter-data-jpa + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.1.0 From 2bb9c0aa3b322ed4dd19e08e74394a3c267158d6 Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Fri, 22 Sep 2023 16:34:01 +0100 Subject: [PATCH 05/70] VUU25: Add basic H2 database --- layout-server/pom.xml | 17 +++++++++++++---- .../src/main/resources/application.properties | 8 +++++++- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/layout-server/pom.xml b/layout-server/pom.xml index 32570e8ee..f1ab238a2 100644 --- a/layout-server/pom.xml +++ b/layout-server/pom.xml @@ -28,13 +28,22 @@ test - org.projectlombok - lombok - true + org.springframework.boot + spring-boot-starter-web org.springframework.boot - spring-boot-starter-web + spring-boot-starter-data-jpa + + + com.h2database + h2 + runtime + + + org.projectlombok + lombok + true org.springdoc diff --git a/layout-server/src/main/resources/application.properties b/layout-server/src/main/resources/application.properties index 8b1378917..f95f5b025 100644 --- a/layout-server/src/main/resources/application.properties +++ b/layout-server/src/main/resources/application.properties @@ -1 +1,7 @@ - +spring.datasource.url=jdbc:h2:file:/data/layout +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password=password +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.h2.console.enabled=true +server.port=8081 \ No newline at end of file From 55aa6327e23d1ada3dcd16249cf58c12de6c03da Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Fri, 22 Sep 2023 17:34:49 +0100 Subject: [PATCH 06/70] VUU25: Implement DTOs --- layout-server/pom.xml | 17 +--- .../controller/LayoutController.java | 77 ++++++++++++------- .../layoutserver/dto/response/LayoutDTO.java | 24 ++++++ .../dto/response/MetadataDTO.java | 31 ++++++++ .../finos/vuu/layoutserver/model/Layout.java | 14 ++-- .../vuu/layoutserver/model/Metadata.java | 24 +++--- .../src/main/resources/application.properties | 1 + 7 files changed, 128 insertions(+), 60 deletions(-) create mode 100644 layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/LayoutDTO.java create mode 100644 layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/MetadataDTO.java diff --git a/layout-server/pom.xml b/layout-server/pom.xml index f1ab238a2..5b4cb720c 100644 --- a/layout-server/pom.xml +++ b/layout-server/pom.xml @@ -40,16 +40,15 @@ h2 runtime - - org.projectlombok - lombok - true - org.springdoc springdoc-openapi-starter-webmvc-ui 2.1.0 + + org.projectlombok + lombok + @@ -57,14 +56,6 @@ org.springframework.boot spring-boot-maven-plugin - - - - org.projectlombok - lombok - - - diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java index 9583c10e4..50dca326a 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java @@ -1,24 +1,42 @@ package org.finos.vuu.layoutserver.controller; +import org.finos.vuu.layoutserver.dto.response.LayoutDTO; +import org.finos.vuu.layoutserver.dto.response.MetadataDTO; import org.finos.vuu.layoutserver.model.Layout; import org.finos.vuu.layoutserver.model.Metadata; -import org.springframework.web.bind.annotation.*; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +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.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; -import java.util.ArrayList; import java.util.List; -@RestController("/layout") +@RestController +@RequestMapping("/layout") public class LayoutController { - /** - * Gets all layouts if no IDs are specified, otherwise gets the specified layouts - * - * @param ids IDs of the layouts to get - * @return the layouts - */ - @GetMapping - public List getLayouts(@RequestBody(required = false) String[] ids) { - return new ArrayList<>(); + public static final String LAYOUT_ID = "testLayoutId"; + + // TODO: Delete dummy data + private Layout createDummyLayout(String id) { + Layout layout = new Layout(); + layout.setId(id); + layout.setDefinition("testDefinition"); + Metadata metadata = new Metadata(); + metadata.setId("testMetadataId"); + metadata.setLayout(layout); + metadata.setName("testName"); + metadata.setGroup("testGroup"); + metadata.setScreenshot("testScreenshot"); + metadata.setUser("testUser"); + layout.setMetadata(metadata); + return layout; } /** @@ -28,50 +46,51 @@ public List getLayouts(@RequestBody(required = false) String[] ids) { * @return the layout */ @GetMapping("/{id}") - public Layout getLayout(@PathVariable String id) { - return Layout.builder().build(); + public LayoutDTO getLayout(@PathVariable String id) { + Layout layout = createDummyLayout(id); + return LayoutDTO.fromEntity(layout); } /** - * Gets metadata for all layouts if no IDs are specified, otherwise gets the metadata for specified layouts + * Gets metadata for all layouts * - * @param ids IDs of the layouts to get metadata for * @return the metadata */ @GetMapping("/metadata") - public List getMetadata(@RequestBody(required = false) String[] ids) { - return new ArrayList<>(); + public List getMetadata() { + Layout layout = createDummyLayout(LAYOUT_ID); + return List.of(MetadataDTO.fromEntity(layout.getMetadata())); } /** * Creates a new layout * - * @return the ID of the new layout` + * @return the ID of the new layout */ + @ResponseStatus(HttpStatus.CREATED) @PostMapping - public String createLayout() { - return "Hello World"; + public String createLayout(@RequestBody LayoutDTO layoutDTO) { + return createDummyLayout(LAYOUT_ID).getId(); } /** * Updates the specified layout * - * @param id ID of the layout to update - * @return the ID of the updated layout + * @param id ID of the layout to update + * @param layoutDTO the new data to overwrite the layout with */ + @ResponseStatus(HttpStatus.ACCEPTED) @PutMapping("/{id}") - public String updateLayout(@PathVariable String id) { - return "Hello World"; + public void updateLayout(@PathVariable String id, @RequestBody LayoutDTO layoutDTO) { + createDummyLayout(LAYOUT_ID); } /** * Deletes the specified layout * * @param id ID of the layout to delete - * @return the ID of the deleted layout */ + @ResponseStatus(HttpStatus.ACCEPTED) @DeleteMapping("/{id}") - public String deleteLayout(@PathVariable String id) { - return "Hello World"; - } + public void deleteLayout(@PathVariable String id) {} } diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/LayoutDTO.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/LayoutDTO.java new file mode 100644 index 000000000..d7c64edb5 --- /dev/null +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/LayoutDTO.java @@ -0,0 +1,24 @@ +package org.finos.vuu.layoutserver.dto.response; + +import lombok.Builder; +import lombok.Data; +import org.finos.vuu.layoutserver.model.Layout; + +import java.io.Serializable; + +@Data +@Builder +public class LayoutDTO implements Serializable { + + private String id; + private String definition; + private MetadataDTO metadata; + + public static LayoutDTO fromEntity(Layout layout) { + return LayoutDTO.builder() + .id(layout.getId()) + .definition(layout.getDefinition()) + .metadata(MetadataDTO.fromEntity(layout.getMetadata())) + .build(); + } +} diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/MetadataDTO.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/MetadataDTO.java new file mode 100644 index 000000000..bd253a86a --- /dev/null +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/MetadataDTO.java @@ -0,0 +1,31 @@ +package org.finos.vuu.layoutserver.dto.response; + +import lombok.Builder; +import lombok.Data; +import org.finos.vuu.layoutserver.model.Metadata; + +import java.io.Serializable; +import java.util.Date; + +@Data +@Builder +public class MetadataDTO implements Serializable { + + private String layoutId; + private String name; + private String group; + private String screenshot; + private String user; + private Date date; + + public static MetadataDTO fromEntity(Metadata metadata) { + return MetadataDTO.builder() + .layoutId(metadata.getLayout().getId()) + .name(metadata.getName()) + .group(metadata.getGroup()) + .screenshot(metadata.getScreenshot()) + .user(metadata.getUser()) + .date(metadata.getDate()) + .build(); + } +} diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java index cb5aebe62..9f1efef61 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java @@ -1,21 +1,23 @@ package org.finos.vuu.layoutserver.model; import jakarta.persistence.*; -import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.NonNull; -@Entity @Data -@Builder +@Entity +@NoArgsConstructor public class Layout { @Id - @GeneratedValue(strategy= GenerationType.AUTO) + @GeneratedValue(strategy = GenerationType.AUTO) private String id; + @NonNull private String definition; @OneToOne(cascade = CascadeType.ALL) + @JoinColumn(name = "metadata_id", referencedColumnName = "id") + @NonNull private Metadata metadata; - - protected Layout() {} } diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java index 7df0c2fa0..2a0eec777 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java @@ -1,28 +1,28 @@ package org.finos.vuu.layoutserver.model; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; -import lombok.Builder; +import jakarta.persistence.*; import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.NonNull; import java.util.Date; -@Entity @Data -@Builder +@NoArgsConstructor +@Entity public class Metadata { @Id - @GeneratedValue - private Long id; + @GeneratedValue(strategy = GenerationType.AUTO) + private String id; + + @OneToOne(mappedBy = "metadata") + @NonNull + private Layout layout; private String name; private String group; private String screenshot; private String user; - private Date date; - - protected Metadata() { - } + private Date date = new Date(); } diff --git a/layout-server/src/main/resources/application.properties b/layout-server/src/main/resources/application.properties index f95f5b025..fb9c7e613 100644 --- a/layout-server/src/main/resources/application.properties +++ b/layout-server/src/main/resources/application.properties @@ -1,3 +1,4 @@ +server.servlet.contextPath=/api spring.datasource.url=jdbc:h2:file:/data/layout spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa From e0d6728170b90302c6bd1ec64071c51ca6ba5f15 Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Tue, 26 Sep 2023 13:11:40 +0100 Subject: [PATCH 07/70] VUU25: Change server port and swagger URL --- layout-server/src/main/resources/application.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/layout-server/src/main/resources/application.properties b/layout-server/src/main/resources/application.properties index fb9c7e613..7511c604a 100644 --- a/layout-server/src/main/resources/application.properties +++ b/layout-server/src/main/resources/application.properties @@ -1,8 +1,9 @@ +server.port=8081 server.servlet.contextPath=/api +springdoc.swagger-ui.path=/swagger spring.datasource.url=jdbc:h2:file:/data/layout spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password=password spring.jpa.database-platform=org.hibernate.dialect.H2Dialect spring.h2.console.enabled=true -server.port=8081 \ No newline at end of file From 2cbb5e271fe69b9be2ec018eb9b05b21b653ca35 Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Tue, 26 Sep 2023 13:17:41 +0100 Subject: [PATCH 08/70] VUU25: Wire up Controller, Service and Repository for full implementation - Also amend DTOs to differentiate request/response data --- .../controller/LayoutController.java | 58 +++++++++---------- .../dto/request/LayoutRequestDTO.java | 20 +++++++ .../dto/request/MetadataRequestDTO.java | 26 +++++++++ .../layoutserver/dto/response/LayoutDTO.java | 24 -------- .../dto/response/LayoutResponseDTO.java | 21 +++++++ ...adataDTO.java => MetadataResponseDTO.java} | 11 ++-- .../finos/vuu/layoutserver/model/Layout.java | 16 +++-- .../vuu/layoutserver/model/Metadata.java | 8 ++- .../repository/LayoutRepository.java | 10 ++++ .../layoutserver/service/LayoutService.java | 44 ++++++++++++++ .../src/main/resources/application.properties | 4 +- 11 files changed, 173 insertions(+), 69 deletions(-) create mode 100644 layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/LayoutRequestDTO.java create mode 100644 layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/MetadataRequestDTO.java delete mode 100644 layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/LayoutDTO.java create mode 100644 layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/LayoutResponseDTO.java rename layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/{MetadataDTO.java => MetadataResponseDTO.java} (69%) create mode 100644 layout-server/src/main/java/org/finos/vuu/layoutserver/repository/LayoutRepository.java create mode 100644 layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java index 50dca326a..81f75156b 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java @@ -1,9 +1,11 @@ package org.finos.vuu.layoutserver.controller; -import org.finos.vuu.layoutserver.dto.response.LayoutDTO; -import org.finos.vuu.layoutserver.dto.response.MetadataDTO; +import lombok.RequiredArgsConstructor; +import org.finos.vuu.layoutserver.dto.request.LayoutRequestDTO; +import org.finos.vuu.layoutserver.dto.response.LayoutResponseDTO; +import org.finos.vuu.layoutserver.dto.response.MetadataResponseDTO; import org.finos.vuu.layoutserver.model.Layout; -import org.finos.vuu.layoutserver.model.Metadata; +import org.finos.vuu.layoutserver.service.LayoutService; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -16,28 +18,14 @@ import org.springframework.web.bind.annotation.RestController; import java.util.List; +import java.util.UUID; +@RequiredArgsConstructor @RestController @RequestMapping("/layout") public class LayoutController { - public static final String LAYOUT_ID = "testLayoutId"; - - // TODO: Delete dummy data - private Layout createDummyLayout(String id) { - Layout layout = new Layout(); - layout.setId(id); - layout.setDefinition("testDefinition"); - Metadata metadata = new Metadata(); - metadata.setId("testMetadataId"); - metadata.setLayout(layout); - metadata.setName("testName"); - metadata.setGroup("testGroup"); - metadata.setScreenshot("testScreenshot"); - metadata.setUser("testUser"); - layout.setMetadata(metadata); - return layout; - } + private final LayoutService layoutService; /** * Gets the specified layout @@ -46,9 +34,8 @@ private Layout createDummyLayout(String id) { * @return the layout */ @GetMapping("/{id}") - public LayoutDTO getLayout(@PathVariable String id) { - Layout layout = createDummyLayout(id); - return LayoutDTO.fromEntity(layout); + public LayoutResponseDTO getLayout(@PathVariable UUID id) { + return LayoutResponseDTO.fromEntity(layoutService.getLayout(id)); } /** @@ -57,9 +44,8 @@ public LayoutDTO getLayout(@PathVariable String id) { * @return the metadata */ @GetMapping("/metadata") - public List getMetadata() { - Layout layout = createDummyLayout(LAYOUT_ID); - return List.of(MetadataDTO.fromEntity(layout.getMetadata())); + public List getMetadata() { + return layoutService.getMetadata().stream().map(MetadataResponseDTO::fromEntity).toList(); } /** @@ -69,20 +55,26 @@ public List getMetadata() { */ @ResponseStatus(HttpStatus.CREATED) @PostMapping - public String createLayout(@RequestBody LayoutDTO layoutDTO) { - return createDummyLayout(LAYOUT_ID).getId(); + public UUID createLayout(@RequestBody LayoutRequestDTO layoutToCreate) { + Layout layout = layoutToCreate.toEntity(); + + // TODO: Layout already created, updating instead + + return layoutService.createLayout(layout); } /** * Updates the specified layout * * @param id ID of the layout to update - * @param layoutDTO the new data to overwrite the layout with + * @param layoutToUpdate the new data to overwrite the layout with */ @ResponseStatus(HttpStatus.ACCEPTED) @PutMapping("/{id}") - public void updateLayout(@PathVariable String id, @RequestBody LayoutDTO layoutDTO) { - createDummyLayout(LAYOUT_ID); + public void updateLayout(@PathVariable UUID id, @RequestBody LayoutRequestDTO layoutToUpdate) { + Layout layout = layoutToUpdate.toEntity(); + + layoutService.updateLayout(id, layout); } /** @@ -92,5 +84,7 @@ public void updateLayout(@PathVariable String id, @RequestBody LayoutDTO layoutD */ @ResponseStatus(HttpStatus.ACCEPTED) @DeleteMapping("/{id}") - public void deleteLayout(@PathVariable String id) {} + public void deleteLayout(@PathVariable UUID id) { + layoutService.deleteLayout(id); + } } diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/LayoutRequestDTO.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/LayoutRequestDTO.java new file mode 100644 index 000000000..b6a94eec3 --- /dev/null +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/LayoutRequestDTO.java @@ -0,0 +1,20 @@ +package org.finos.vuu.layoutserver.dto.request; + +import lombok.Data; +import org.finos.vuu.layoutserver.model.Layout; + +import java.io.Serializable; + +@Data +public class LayoutRequestDTO implements Serializable { + + private String definition; + private MetadataRequestDTO metadata; + + public Layout toEntity() { + Layout layout = new Layout(); + layout.setDefinition(definition); + layout.setMetadata(metadata.toEntity()); + return layout; + } +} diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/MetadataRequestDTO.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/MetadataRequestDTO.java new file mode 100644 index 000000000..821933275 --- /dev/null +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/MetadataRequestDTO.java @@ -0,0 +1,26 @@ +package org.finos.vuu.layoutserver.dto.request; + +import lombok.Data; +import org.finos.vuu.layoutserver.model.Metadata; + +import java.io.Serializable; +import java.util.Date; + +@Data +public class MetadataRequestDTO implements Serializable { + + private String name; + private String group; + private String screenshot; + private String user; + + public Metadata toEntity() { + Metadata metadata = new Metadata(); + metadata.setName(name); + metadata.setGroup(group); + metadata.setScreenshot(screenshot); + metadata.setUser(user); + metadata.setUpdated(new Date()); + return metadata; + } +} diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/LayoutDTO.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/LayoutDTO.java deleted file mode 100644 index d7c64edb5..000000000 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/LayoutDTO.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.finos.vuu.layoutserver.dto.response; - -import lombok.Builder; -import lombok.Data; -import org.finos.vuu.layoutserver.model.Layout; - -import java.io.Serializable; - -@Data -@Builder -public class LayoutDTO implements Serializable { - - private String id; - private String definition; - private MetadataDTO metadata; - - public static LayoutDTO fromEntity(Layout layout) { - return LayoutDTO.builder() - .id(layout.getId()) - .definition(layout.getDefinition()) - .metadata(MetadataDTO.fromEntity(layout.getMetadata())) - .build(); - } -} diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/LayoutResponseDTO.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/LayoutResponseDTO.java new file mode 100644 index 000000000..1c64a82cf --- /dev/null +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/LayoutResponseDTO.java @@ -0,0 +1,21 @@ +package org.finos.vuu.layoutserver.dto.response; + +import lombok.Builder; +import lombok.Data; +import org.finos.vuu.layoutserver.model.Layout; + +import java.io.Serializable; +import java.util.UUID; + +@Data +@Builder +public class LayoutResponseDTO implements Serializable { + + private UUID id; + private String definition; + private MetadataResponseDTO metadata; + + public static LayoutResponseDTO fromEntity(Layout layout) { + return LayoutResponseDTO.builder().id(layout.getId()).definition(layout.getDefinition()).metadata(MetadataResponseDTO.fromEntity(layout.getMetadata())).build(); + } +} diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/MetadataDTO.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/MetadataResponseDTO.java similarity index 69% rename from layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/MetadataDTO.java rename to layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/MetadataResponseDTO.java index bd253a86a..ec11e5ce5 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/MetadataDTO.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/MetadataResponseDTO.java @@ -6,26 +6,27 @@ import java.io.Serializable; import java.util.Date; +import java.util.UUID; @Data @Builder -public class MetadataDTO implements Serializable { +public class MetadataResponseDTO implements Serializable { - private String layoutId; + private UUID layoutId; private String name; private String group; private String screenshot; private String user; private Date date; - public static MetadataDTO fromEntity(Metadata metadata) { - return MetadataDTO.builder() + public static MetadataResponseDTO fromEntity(Metadata metadata) { + return MetadataResponseDTO.builder() .layoutId(metadata.getLayout().getId()) .name(metadata.getName()) .group(metadata.getGroup()) .screenshot(metadata.getScreenshot()) .user(metadata.getUser()) - .date(metadata.getDate()) + .date(metadata.getCreated()) .build(); } } diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java index 9f1efef61..2a98ad3cd 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java @@ -1,17 +1,25 @@ package org.finos.vuu.layoutserver.model; -import jakarta.persistence.*; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; import lombok.Data; import lombok.NoArgsConstructor; import lombok.NonNull; +import java.util.UUID; + @Data -@Entity @NoArgsConstructor +@Entity public class Layout { @Id - @GeneratedValue(strategy = GenerationType.AUTO) - private String id; + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; @NonNull private String definition; diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java index 2a0eec777..8d44d1b07 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java @@ -6,6 +6,7 @@ import lombok.NonNull; import java.util.Date; +import java.util.UUID; @Data @NoArgsConstructor @@ -13,8 +14,8 @@ public class Metadata { @Id - @GeneratedValue(strategy = GenerationType.AUTO) - private String id; + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; @OneToOne(mappedBy = "metadata") @NonNull @@ -24,5 +25,6 @@ public class Metadata { private String group; private String screenshot; private String user; - private Date date = new Date(); + private Date created = new Date(); + private Date updated; } diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/repository/LayoutRepository.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/repository/LayoutRepository.java new file mode 100644 index 000000000..d57af4897 --- /dev/null +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/repository/LayoutRepository.java @@ -0,0 +1,10 @@ +package org.finos.vuu.layoutserver.repository; + +import org.finos.vuu.layoutserver.model.Layout; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +import java.util.UUID; + +@Repository +public interface LayoutRepository extends CrudRepository {} diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java new file mode 100644 index 000000000..77451e0e8 --- /dev/null +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java @@ -0,0 +1,44 @@ +package org.finos.vuu.layoutserver.service; + +import lombok.RequiredArgsConstructor; +import org.finos.vuu.layoutserver.model.Layout; +import org.finos.vuu.layoutserver.model.Metadata; +import org.finos.vuu.layoutserver.repository.LayoutRepository; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@RequiredArgsConstructor +@Service +public class LayoutService { + + private final LayoutRepository layoutRepository; + + public Layout getLayout(UUID id) { + return layoutRepository.findById(id).orElseThrow(); + } + + public List getMetadata() { + List metadata = new ArrayList<>(); + layoutRepository.findAll().forEach(layout -> metadata.add(layout.getMetadata())); + return metadata; + } + + public UUID createLayout(Layout layout) { + return layoutRepository.save(layout).getId(); + } + + public void updateLayout(UUID id, Layout updatedLayout) { + Layout oldLayout = getLayout(id); + oldLayout.setDefinition(updatedLayout.getDefinition()); + oldLayout.setMetadata(updatedLayout.getMetadata()); + + layoutRepository.save(oldLayout); + } + + public void deleteLayout(UUID id) { + layoutRepository.deleteById(id); + } +} diff --git a/layout-server/src/main/resources/application.properties b/layout-server/src/main/resources/application.properties index 7511c604a..f85ed2a9c 100644 --- a/layout-server/src/main/resources/application.properties +++ b/layout-server/src/main/resources/application.properties @@ -1,9 +1,11 @@ server.port=8081 server.servlet.contextPath=/api springdoc.swagger-ui.path=/swagger -spring.datasource.url=jdbc:h2:file:/data/layout +spring.datasource.url=jdbc:h2:file:/data/layout;NON_KEYWORDS=GROUP,USER spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password=password spring.jpa.database-platform=org.hibernate.dialect.H2Dialect spring.h2.console.enabled=true +spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.generate-ddl=true From 4f3f4cb619f8e5e7c9fef21c54fbd17b2235070a Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Tue, 26 Sep 2023 17:23:48 +0100 Subject: [PATCH 09/70] VUU25: Implement model mapper to replace manual entity<->DTO conversion --- layout-server/pom.xml | 5 +++ .../controller/LayoutController.java | 16 +++++++--- .../vuu/layoutserver/dto/MappingConfig.java | 31 +++++++++++++++++++ .../dto/request/LayoutRequestDTO.java | 12 +------ .../dto/request/MetadataRequestDTO.java | 16 +++------- .../dto/response/LayoutResponseDTO.java | 10 +----- .../dto/response/MetadataResponseDTO.java | 20 ++---------- .../finos/vuu/layoutserver/model/Layout.java | 2 +- .../vuu/layoutserver/model/Metadata.java | 2 +- .../src/main/resources/application.properties | 3 +- 10 files changed, 60 insertions(+), 57 deletions(-) create mode 100644 layout-server/src/main/java/org/finos/vuu/layoutserver/dto/MappingConfig.java diff --git a/layout-server/pom.xml b/layout-server/pom.xml index 5b4cb720c..ace79292f 100644 --- a/layout-server/pom.xml +++ b/layout-server/pom.xml @@ -49,6 +49,11 @@ org.projectlombok lombok + + org.modelmapper + modelmapper + 3.1.0 + diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java index 81f75156b..32f49e6e4 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java @@ -6,6 +6,7 @@ import org.finos.vuu.layoutserver.dto.response.MetadataResponseDTO; import org.finos.vuu.layoutserver.model.Layout; import org.finos.vuu.layoutserver.service.LayoutService; +import org.modelmapper.ModelMapper; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -26,6 +27,7 @@ public class LayoutController { private final LayoutService layoutService; + private final ModelMapper mapper; /** * Gets the specified layout @@ -35,7 +37,7 @@ public class LayoutController { */ @GetMapping("/{id}") public LayoutResponseDTO getLayout(@PathVariable UUID id) { - return LayoutResponseDTO.fromEntity(layoutService.getLayout(id)); + return mapper.map(layoutService.getLayout(id), LayoutResponseDTO.class); } /** @@ -45,7 +47,11 @@ public LayoutResponseDTO getLayout(@PathVariable UUID id) { */ @GetMapping("/metadata") public List getMetadata() { - return layoutService.getMetadata().stream().map(MetadataResponseDTO::fromEntity).toList(); + + return layoutService.getMetadata() + .stream() + .map(metadata -> mapper.map(metadata, MetadataResponseDTO.class)) + .toList(); } /** @@ -56,7 +62,7 @@ public List getMetadata() { @ResponseStatus(HttpStatus.CREATED) @PostMapping public UUID createLayout(@RequestBody LayoutRequestDTO layoutToCreate) { - Layout layout = layoutToCreate.toEntity(); + Layout layout = mapper.map(layoutToCreate, Layout.class); // TODO: Layout already created, updating instead @@ -66,13 +72,13 @@ public UUID createLayout(@RequestBody LayoutRequestDTO layoutToCreate) { /** * Updates the specified layout * - * @param id ID of the layout to update + * @param id ID of the layout to update * @param layoutToUpdate the new data to overwrite the layout with */ @ResponseStatus(HttpStatus.ACCEPTED) @PutMapping("/{id}") public void updateLayout(@PathVariable UUID id, @RequestBody LayoutRequestDTO layoutToUpdate) { - Layout layout = layoutToUpdate.toEntity(); + Layout layout = mapper.map(layoutToUpdate, Layout.class); layoutService.updateLayout(id, layout); } diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/MappingConfig.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/MappingConfig.java new file mode 100644 index 000000000..f20a93003 --- /dev/null +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/MappingConfig.java @@ -0,0 +1,31 @@ +package org.finos.vuu.layoutserver.dto; + +import org.finos.vuu.layoutserver.dto.request.LayoutRequestDTO; +import org.finos.vuu.layoutserver.dto.request.MetadataRequestDTO; +import org.finos.vuu.layoutserver.dto.response.MetadataResponseDTO; +import org.finos.vuu.layoutserver.model.Layout; +import org.finos.vuu.layoutserver.model.Metadata; +import org.modelmapper.ModelMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class MappingConfig { + + @Bean + public ModelMapper modelMapper() { + ModelMapper mapper = new ModelMapper(); + + // LayoutRequestDTO to Layout + mapper.typeMap(LayoutRequestDTO.class, Layout.class).addMappings(m -> m.skip(Layout::setId)); + + // Metadata to MetadataResponseDTO + mapper.typeMap(Metadata.class, MetadataResponseDTO.class) + .addMappings(m -> m.map(metadata -> metadata.getLayout().getId(), MetadataResponseDTO::setLayoutId)); + + // MetadataRequestDTO to Metadata + mapper.typeMap(MetadataRequestDTO.class, Metadata.class).addMappings(m -> m.skip(Metadata::setId)); + + return mapper; + } +} diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/LayoutRequestDTO.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/LayoutRequestDTO.java index b6a94eec3..a3001f4f7 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/LayoutRequestDTO.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/LayoutRequestDTO.java @@ -1,20 +1,10 @@ package org.finos.vuu.layoutserver.dto.request; import lombok.Data; -import org.finos.vuu.layoutserver.model.Layout; - -import java.io.Serializable; @Data -public class LayoutRequestDTO implements Serializable { +public class LayoutRequestDTO { private String definition; private MetadataRequestDTO metadata; - - public Layout toEntity() { - Layout layout = new Layout(); - layout.setDefinition(definition); - layout.setMetadata(metadata.toEntity()); - return layout; - } } diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/MetadataRequestDTO.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/MetadataRequestDTO.java index 821933275..cef3205e5 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/MetadataRequestDTO.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/MetadataRequestDTO.java @@ -1,26 +1,18 @@ package org.finos.vuu.layoutserver.dto.request; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; -import org.finos.vuu.layoutserver.model.Metadata; -import java.io.Serializable; import java.util.Date; @Data -public class MetadataRequestDTO implements Serializable { +public class MetadataRequestDTO { private String name; private String group; private String screenshot; private String user; - public Metadata toEntity() { - Metadata metadata = new Metadata(); - metadata.setName(name); - metadata.setGroup(group); - metadata.setScreenshot(screenshot); - metadata.setUser(user); - metadata.setUpdated(new Date()); - return metadata; - } + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + private Date updated = new Date(); } diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/LayoutResponseDTO.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/LayoutResponseDTO.java index 1c64a82cf..835bcbdd3 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/LayoutResponseDTO.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/LayoutResponseDTO.java @@ -1,21 +1,13 @@ package org.finos.vuu.layoutserver.dto.response; -import lombok.Builder; import lombok.Data; -import org.finos.vuu.layoutserver.model.Layout; -import java.io.Serializable; import java.util.UUID; @Data -@Builder -public class LayoutResponseDTO implements Serializable { +public class LayoutResponseDTO { private UUID id; private String definition; private MetadataResponseDTO metadata; - - public static LayoutResponseDTO fromEntity(Layout layout) { - return LayoutResponseDTO.builder().id(layout.getId()).definition(layout.getDefinition()).metadata(MetadataResponseDTO.fromEntity(layout.getMetadata())).build(); - } } diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/MetadataResponseDTO.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/MetadataResponseDTO.java index ec11e5ce5..79ecf6c6c 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/MetadataResponseDTO.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/MetadataResponseDTO.java @@ -1,32 +1,18 @@ package org.finos.vuu.layoutserver.dto.response; -import lombok.Builder; import lombok.Data; -import org.finos.vuu.layoutserver.model.Metadata; -import java.io.Serializable; import java.util.Date; import java.util.UUID; @Data -@Builder -public class MetadataResponseDTO implements Serializable { +public class MetadataResponseDTO { private UUID layoutId; private String name; private String group; private String screenshot; private String user; - private Date date; - - public static MetadataResponseDTO fromEntity(Metadata metadata) { - return MetadataResponseDTO.builder() - .layoutId(metadata.getLayout().getId()) - .name(metadata.getName()) - .group(metadata.getGroup()) - .screenshot(metadata.getScreenshot()) - .user(metadata.getUser()) - .date(metadata.getCreated()) - .build(); - } + private Date created; + private Date updated; } diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java index 2a98ad3cd..3dff00dd7 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java @@ -24,7 +24,7 @@ public class Layout { @NonNull private String definition; - @OneToOne(cascade = CascadeType.ALL) + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "metadata_id", referencedColumnName = "id") @NonNull private Metadata metadata; diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java index 8d44d1b07..1f24e6ad4 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java @@ -17,7 +17,7 @@ public class Metadata { @GeneratedValue(strategy = GenerationType.UUID) private UUID id; - @OneToOne(mappedBy = "metadata") + @OneToOne(mappedBy = "metadata", cascade = CascadeType.ALL) @NonNull private Layout layout; diff --git a/layout-server/src/main/resources/application.properties b/layout-server/src/main/resources/application.properties index f85ed2a9c..81e7a32e3 100644 --- a/layout-server/src/main/resources/application.properties +++ b/layout-server/src/main/resources/application.properties @@ -7,5 +7,6 @@ spring.datasource.username=sa spring.datasource.password=password spring.jpa.database-platform=org.hibernate.dialect.H2Dialect spring.h2.console.enabled=true -spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.hibernate.ddl-auto=update spring.jpa.generate-ddl=true +spring.jpa.show-sql=true From 9b2f6ff22b2999fbf99b90004901397b24579ee4 Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Thu, 28 Sep 2023 17:41:33 +0100 Subject: [PATCH 10/70] VUU25: Fix one-to-one entity persistence issue --- .../layoutserver/controller/LayoutController.java | 11 ++++++----- .../org/finos/vuu/layoutserver/model/Layout.java | 4 +--- .../finos/vuu/layoutserver/model/Metadata.java | 10 ++++++---- .../repository/MetadataRepository.java | 10 ++++++++++ .../vuu/layoutserver/service/LayoutService.java | 15 +++++++++------ 5 files changed, 32 insertions(+), 18 deletions(-) create mode 100644 layout-server/src/main/java/org/finos/vuu/layoutserver/repository/MetadataRepository.java diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java index 32f49e6e4..7032d868f 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java @@ -73,14 +73,15 @@ public UUID createLayout(@RequestBody LayoutRequestDTO layoutToCreate) { * Updates the specified layout * * @param id ID of the layout to update - * @param layoutToUpdate the new data to overwrite the layout with + * @param layoutToUpdate the updated layout */ - @ResponseStatus(HttpStatus.ACCEPTED) + @ResponseStatus(HttpStatus.OK) @PutMapping("/{id}") public void updateLayout(@PathVariable UUID id, @RequestBody LayoutRequestDTO layoutToUpdate) { - Layout layout = mapper.map(layoutToUpdate, Layout.class); + Layout layout = layoutService.getLayout(id); + mapper.map(layoutToUpdate, layout); - layoutService.updateLayout(id, layout); + layoutService.updateLayout(layout); } /** @@ -88,7 +89,7 @@ public void updateLayout(@PathVariable UUID id, @RequestBody LayoutRequestDTO la * * @param id ID of the layout to delete */ - @ResponseStatus(HttpStatus.ACCEPTED) + @ResponseStatus(HttpStatus.OK) @DeleteMapping("/{id}") public void deleteLayout(@PathVariable UUID id) { layoutService.deleteLayout(id); diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java index 3dff00dd7..54c93224a 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java @@ -9,7 +9,6 @@ import jakarta.persistence.OneToOne; import lombok.Data; import lombok.NoArgsConstructor; -import lombok.NonNull; import java.util.UUID; @@ -17,15 +16,14 @@ @NoArgsConstructor @Entity public class Layout { + @Id @GeneratedValue(strategy = GenerationType.UUID) private UUID id; - @NonNull private String definition; @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "metadata_id", referencedColumnName = "id") - @NonNull private Metadata metadata; } diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java index 1f24e6ad4..798dde66d 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java @@ -1,9 +1,12 @@ package org.finos.vuu.layoutserver.model; -import jakarta.persistence.*; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToOne; import lombok.Data; import lombok.NoArgsConstructor; -import lombok.NonNull; import java.util.Date; import java.util.UUID; @@ -17,8 +20,7 @@ public class Metadata { @GeneratedValue(strategy = GenerationType.UUID) private UUID id; - @OneToOne(mappedBy = "metadata", cascade = CascadeType.ALL) - @NonNull + @OneToOne(mappedBy = "metadata") private Layout layout; private String name; diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/repository/MetadataRepository.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/repository/MetadataRepository.java new file mode 100644 index 000000000..03f81b108 --- /dev/null +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/repository/MetadataRepository.java @@ -0,0 +1,10 @@ +package org.finos.vuu.layoutserver.repository; + +import org.finos.vuu.layoutserver.model.Metadata; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +import java.util.UUID; + +@Repository +public interface MetadataRepository extends CrudRepository {} diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java index 77451e0e8..b73c440de 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java @@ -4,7 +4,9 @@ import org.finos.vuu.layoutserver.model.Layout; import org.finos.vuu.layoutserver.model.Metadata; import org.finos.vuu.layoutserver.repository.LayoutRepository; +import org.finos.vuu.layoutserver.repository.MetadataRepository; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.List; @@ -15,6 +17,7 @@ public class LayoutService { private final LayoutRepository layoutRepository; + private final MetadataRepository metadataRepository; public Layout getLayout(UUID id) { return layoutRepository.findById(id).orElseThrow(); @@ -26,16 +29,16 @@ public List getMetadata() { return metadata; } + @Transactional public UUID createLayout(Layout layout) { + Metadata metadata = metadataRepository.save(layout.getMetadata()); + metadata.setLayout(layout); + layout.setMetadata(metadata); return layoutRepository.save(layout).getId(); } - public void updateLayout(UUID id, Layout updatedLayout) { - Layout oldLayout = getLayout(id); - oldLayout.setDefinition(updatedLayout.getDefinition()); - oldLayout.setMetadata(updatedLayout.getMetadata()); - - layoutRepository.save(oldLayout); + public void updateLayout(Layout updatedLayout) { + layoutRepository.save(updatedLayout); } public void deleteLayout(UUID id) { From a7ed2bdb69715f0331addab73c9b88d3bd793314 Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Wed, 4 Oct 2023 17:29:42 +0100 Subject: [PATCH 11/70] VUU25: Pluralise layout endpoint resource --- .../org/finos/vuu/layoutserver/controller/LayoutController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java index 7032d868f..9c6347246 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java @@ -23,7 +23,7 @@ @RequiredArgsConstructor @RestController -@RequestMapping("/layout") +@RequestMapping("/layouts") public class LayoutController { private final LayoutService layoutService; From ed7f15172a8485f08b67d062aee80457f7f1f6d8 Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Wed, 4 Oct 2023 17:45:14 +0100 Subject: [PATCH 12/70] VUU25: Amend annotations, javadocs, and updateLayout signature in the Controller --- .../controller/LayoutController.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java index 9c6347246..7125e75ed 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java @@ -57,29 +57,28 @@ public List getMetadata() { /** * Creates a new layout * - * @return the ID of the new layout + * @param layoutToCreate the layout to be created + * @return the generated ID of the new layout */ @ResponseStatus(HttpStatus.CREATED) @PostMapping public UUID createLayout(@RequestBody LayoutRequestDTO layoutToCreate) { Layout layout = mapper.map(layoutToCreate, Layout.class); - // TODO: Layout already created, updating instead - return layoutService.createLayout(layout); } /** * Updates the specified layout * - * @param id ID of the layout to update - * @param layoutToUpdate the updated layout + * @param id ID of the layout to update + * @param newLayout the new layout */ - @ResponseStatus(HttpStatus.OK) + @ResponseStatus(HttpStatus.NO_CONTENT) @PutMapping("/{id}") - public void updateLayout(@PathVariable UUID id, @RequestBody LayoutRequestDTO layoutToUpdate) { + public void updateLayout(@PathVariable UUID id, @RequestBody LayoutRequestDTO newLayout) { Layout layout = layoutService.getLayout(id); - mapper.map(layoutToUpdate, layout); + mapper.map(newLayout, layout); layoutService.updateLayout(layout); } @@ -89,7 +88,7 @@ public void updateLayout(@PathVariable UUID id, @RequestBody LayoutRequestDTO la * * @param id ID of the layout to delete */ - @ResponseStatus(HttpStatus.OK) + @ResponseStatus(HttpStatus.NO_CONTENT) @DeleteMapping("/{id}") public void deleteLayout(@PathVariable UUID id) { layoutService.deleteLayout(id); From 7d72461f88149d20eb03d295f870a28d2bde424d Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Wed, 4 Oct 2023 17:57:02 +0100 Subject: [PATCH 13/70] VUU25: Add javadocs to definition field in LayoutDTOs --- .../finos/vuu/layoutserver/dto/request/LayoutRequestDTO.java | 4 ++++ .../vuu/layoutserver/dto/response/LayoutResponseDTO.java | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/LayoutRequestDTO.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/LayoutRequestDTO.java index a3001f4f7..dafce63b5 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/LayoutRequestDTO.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/LayoutRequestDTO.java @@ -5,6 +5,10 @@ @Data public class LayoutRequestDTO { + /** + * The definition of the layout as a string (e.g. stringified JSON structure containing components) + */ private String definition; + private MetadataRequestDTO metadata; } diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/LayoutResponseDTO.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/LayoutResponseDTO.java index 835bcbdd3..9e1077063 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/LayoutResponseDTO.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/LayoutResponseDTO.java @@ -8,6 +8,11 @@ public class LayoutResponseDTO { private UUID id; + + /** + * The definition of the layout as a string (e.g. stringified JSON structure containing components) + */ private String definition; + private MetadataResponseDTO metadata; } From 2653ab1fcd17aa423b591c842e7a31d4b23bb70d Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Wed, 4 Oct 2023 18:21:59 +0100 Subject: [PATCH 14/70] VUU25: Remove no args constructor on entities --- .../src/main/java/org/finos/vuu/layoutserver/model/Layout.java | 1 - .../src/main/java/org/finos/vuu/layoutserver/model/Metadata.java | 1 - 2 files changed, 2 deletions(-) diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java index 54c93224a..6d1f3aee6 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java @@ -13,7 +13,6 @@ import java.util.UUID; @Data -@NoArgsConstructor @Entity public class Layout { diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java index 798dde66d..531b31ddb 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java @@ -12,7 +12,6 @@ import java.util.UUID; @Data -@NoArgsConstructor @Entity public class Metadata { From 29a71ce5c7cf764df7c18b62d8803fbfc1965007 Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Wed, 4 Oct 2023 18:29:52 +0100 Subject: [PATCH 15/70] VUU25: Replace layoutRepository with metadataRepository in LayoutService.getMetadata --- .../org/finos/vuu/layoutserver/service/LayoutService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java index b73c440de..ae4411ba6 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java @@ -25,7 +25,9 @@ public Layout getLayout(UUID id) { public List getMetadata() { List metadata = new ArrayList<>(); - layoutRepository.findAll().forEach(layout -> metadata.add(layout.getMetadata())); + + metadataRepository.findAll().forEach(metadata::add); + return metadata; } From 42f48634974416a1642b6eb62ed252c62c977b51 Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Wed, 4 Oct 2023 18:44:33 +0100 Subject: [PATCH 16/70] VUU25: Add date to create layout HTTP response body --- .../controller/LayoutController.java | 7 +++++-- .../vuu/layoutserver/dto/MappingConfig.java | 6 ++++++ .../dto/response/CreateLayoutResponseDTO.java | 17 +++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/CreateLayoutResponseDTO.java diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java index 7125e75ed..77efd8fcb 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java @@ -2,6 +2,7 @@ import lombok.RequiredArgsConstructor; import org.finos.vuu.layoutserver.dto.request.LayoutRequestDTO; +import org.finos.vuu.layoutserver.dto.response.CreateLayoutResponseDTO; import org.finos.vuu.layoutserver.dto.response.LayoutResponseDTO; import org.finos.vuu.layoutserver.dto.response.MetadataResponseDTO; import org.finos.vuu.layoutserver.model.Layout; @@ -62,10 +63,12 @@ public List getMetadata() { */ @ResponseStatus(HttpStatus.CREATED) @PostMapping - public UUID createLayout(@RequestBody LayoutRequestDTO layoutToCreate) { + public CreateLayoutResponseDTO createLayout(@RequestBody LayoutRequestDTO layoutToCreate) { Layout layout = mapper.map(layoutToCreate, Layout.class); - return layoutService.createLayout(layout); + Layout createdLayout = layoutService.getLayout(layoutService.createLayout(layout)); + + return mapper.map(createdLayout, CreateLayoutResponseDTO.class); } /** diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/MappingConfig.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/MappingConfig.java index f20a93003..5d4f8ec34 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/MappingConfig.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/MappingConfig.java @@ -2,6 +2,7 @@ import org.finos.vuu.layoutserver.dto.request.LayoutRequestDTO; import org.finos.vuu.layoutserver.dto.request.MetadataRequestDTO; +import org.finos.vuu.layoutserver.dto.response.CreateLayoutResponseDTO; import org.finos.vuu.layoutserver.dto.response.MetadataResponseDTO; import org.finos.vuu.layoutserver.model.Layout; import org.finos.vuu.layoutserver.model.Metadata; @@ -19,6 +20,11 @@ public ModelMapper modelMapper() { // LayoutRequestDTO to Layout mapper.typeMap(LayoutRequestDTO.class, Layout.class).addMappings(m -> m.skip(Layout::setId)); + // Layout to CreateLayoutResponseDTO + mapper.typeMap(Layout.class, CreateLayoutResponseDTO.class) + .addMappings(m -> m.map(layout -> layout.getMetadata().getCreated(), + CreateLayoutResponseDTO::setCreated)); + // Metadata to MetadataResponseDTO mapper.typeMap(Metadata.class, MetadataResponseDTO.class) .addMappings(m -> m.map(metadata -> metadata.getLayout().getId(), MetadataResponseDTO::setLayoutId)); diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/CreateLayoutResponseDTO.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/CreateLayoutResponseDTO.java new file mode 100644 index 000000000..8ff792674 --- /dev/null +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/CreateLayoutResponseDTO.java @@ -0,0 +1,17 @@ +package org.finos.vuu.layoutserver.dto.response; + +import lombok.Data; + +import java.util.Date; +import java.util.UUID; + +@Data +public class CreateLayoutResponseDTO { + + private UUID id; + + /** + * The generated creation date of the created layout + */ + private Date created; +} From 10e53ad4a75a47e1ab3f9e7c259aa01e2f6d99ea Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Wed, 4 Oct 2023 18:53:13 +0100 Subject: [PATCH 17/70] VUU25: Introduce MetadataService --- .../controller/LayoutController.java | 2 ++ .../layoutserver/service/LayoutService.java | 12 ++------ .../layoutserver/service/MetadataService.java | 28 +++++++++++++++++++ 3 files changed, 33 insertions(+), 9 deletions(-) create mode 100644 layout-server/src/main/java/org/finos/vuu/layoutserver/service/MetadataService.java diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java index 77efd8fcb..cc7814e80 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java @@ -80,7 +80,9 @@ public CreateLayoutResponseDTO createLayout(@RequestBody LayoutRequestDTO layout @ResponseStatus(HttpStatus.NO_CONTENT) @PutMapping("/{id}") public void updateLayout(@PathVariable UUID id, @RequestBody LayoutRequestDTO newLayout) { + // TODO I'm not sure about this, I think layoutService should be doing more logic, not the mapper here Layout layout = layoutService.getLayout(id); + mapper.map(newLayout, layout); layoutService.updateLayout(layout); diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java index ae4411ba6..5aed4a625 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java @@ -4,11 +4,9 @@ import org.finos.vuu.layoutserver.model.Layout; import org.finos.vuu.layoutserver.model.Metadata; import org.finos.vuu.layoutserver.repository.LayoutRepository; -import org.finos.vuu.layoutserver.repository.MetadataRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -17,23 +15,19 @@ public class LayoutService { private final LayoutRepository layoutRepository; - private final MetadataRepository metadataRepository; + private final MetadataService metadataService; public Layout getLayout(UUID id) { return layoutRepository.findById(id).orElseThrow(); } public List getMetadata() { - List metadata = new ArrayList<>(); - - metadataRepository.findAll().forEach(metadata::add); - - return metadata; + return metadataService.getMetadata(); } @Transactional public UUID createLayout(Layout layout) { - Metadata metadata = metadataRepository.save(layout.getMetadata()); + Metadata metadata = metadataService.createMetadata(layout.getMetadata()); metadata.setLayout(layout); layout.setMetadata(metadata); return layoutRepository.save(layout).getId(); diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/MetadataService.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/MetadataService.java new file mode 100644 index 000000000..0103e3a4a --- /dev/null +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/MetadataService.java @@ -0,0 +1,28 @@ +package org.finos.vuu.layoutserver.service; + +import lombok.RequiredArgsConstructor; +import org.finos.vuu.layoutserver.model.Metadata; +import org.finos.vuu.layoutserver.repository.MetadataRepository; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +@RequiredArgsConstructor +@Service +public class MetadataService { + + private final MetadataRepository metadataRepository; + + public List getMetadata() { + List metadata = new ArrayList<>(); + + metadataRepository.findAll().forEach(metadata::add); + + return metadata; + } + + public Metadata createMetadata(Metadata metadata) { + return metadataRepository.save(metadata); + } +} From 715cbacccb943b6060f06d1d5464cc61451635cc Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Tue, 3 Oct 2023 15:10:14 +0100 Subject: [PATCH 18/70] VUU25: Create layout controller unit tests (backend) --- .../controller/LayoutController.java | 5 +- .../dto/request/LayoutRequestDTO.java | 6 + .../dto/request/MetadataRequestDTO.java | 4 + .../vuu/layoutserver/model/Metadata.java | 2 + .../layoutserver/service/LayoutService.java | 2 + .../controller/LayoutControllerTest.java | 158 ++++++++++++++++++ 6 files changed, 175 insertions(+), 2 deletions(-) create mode 100644 layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java index cc7814e80..d6c8000a8 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java @@ -1,5 +1,6 @@ package org.finos.vuu.layoutserver.controller; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.finos.vuu.layoutserver.dto.request.LayoutRequestDTO; import org.finos.vuu.layoutserver.dto.response.CreateLayoutResponseDTO; @@ -63,7 +64,7 @@ public List getMetadata() { */ @ResponseStatus(HttpStatus.CREATED) @PostMapping - public CreateLayoutResponseDTO createLayout(@RequestBody LayoutRequestDTO layoutToCreate) { + public CreateLayoutResponseDTO createLayout(@RequestBody @Valid LayoutRequestDTO layoutToCreate) { Layout layout = mapper.map(layoutToCreate, Layout.class); Layout createdLayout = layoutService.getLayout(layoutService.createLayout(layout)); @@ -79,7 +80,7 @@ public CreateLayoutResponseDTO createLayout(@RequestBody LayoutRequestDTO layout */ @ResponseStatus(HttpStatus.NO_CONTENT) @PutMapping("/{id}") - public void updateLayout(@PathVariable UUID id, @RequestBody LayoutRequestDTO newLayout) { + public void updateLayout(@PathVariable UUID id, @RequestBody @Valid LayoutRequestDTO newLayout) { // TODO I'm not sure about this, I think layoutService should be doing more logic, not the mapper here Layout layout = layoutService.getLayout(id); diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/LayoutRequestDTO.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/LayoutRequestDTO.java index dafce63b5..79455260b 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/LayoutRequestDTO.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/LayoutRequestDTO.java @@ -1,5 +1,7 @@ package org.finos.vuu.layoutserver.dto.request; +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.NotNull; import lombok.Data; @Data @@ -8,7 +10,11 @@ public class LayoutRequestDTO { /** * The definition of the layout as a string (e.g. stringified JSON structure containing components) */ + @JsonProperty(value = "definition", required = true) + @NotNull(message = "Please provide a valid definition") private String definition; + @JsonProperty(value = "metadata", required = true) + @NotNull(message = "Please provide valid metadata") private MetadataRequestDTO metadata; } diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/MetadataRequestDTO.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/MetadataRequestDTO.java index cef3205e5..d78ef7c36 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/MetadataRequestDTO.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/MetadataRequestDTO.java @@ -1,6 +1,7 @@ package org.finos.vuu.layoutserver.dto.request; import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.NotNull; import lombok.Data; import java.util.Date; @@ -8,7 +9,10 @@ @Data public class MetadataRequestDTO { + @JsonProperty(value = "name", required = true) + @NotNull(message = "Please provide a valid name") private String name; + private String group; private String screenshot; private String user; diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java index 531b31ddb..e9bf280c8 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java @@ -7,6 +7,7 @@ import jakarta.persistence.OneToOne; import lombok.Data; import lombok.NoArgsConstructor; +import lombok.ToString; import java.util.Date; import java.util.UUID; @@ -20,6 +21,7 @@ public class Metadata { private UUID id; @OneToOne(mappedBy = "metadata") + @ToString.Exclude private Layout layout; private String name; diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java index 5aed4a625..c0185ed18 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java @@ -21,6 +21,8 @@ public Layout getLayout(UUID id) { return layoutRepository.findById(id).orElseThrow(); } + + // TODO Refactor to use a metadatarepository.findAll method public List getMetadata() { return metadataService.getMetadata(); } diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java new file mode 100644 index 000000000..aed588352 --- /dev/null +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java @@ -0,0 +1,158 @@ +package org.finos.vuu.layoutserver.controller; + +import org.finos.vuu.layoutserver.dto.request.LayoutRequestDTO; +import org.finos.vuu.layoutserver.dto.request.MetadataRequestDTO; +import org.finos.vuu.layoutserver.dto.response.LayoutResponseDTO; +import org.finos.vuu.layoutserver.dto.response.MetadataResponseDTO; +import org.finos.vuu.layoutserver.model.Layout; +import org.finos.vuu.layoutserver.model.Metadata; +import org.finos.vuu.layoutserver.repository.LayoutRepository; +import org.finos.vuu.layoutserver.service.LayoutService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import org.modelmapper.ModelMapper; + +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class LayoutControllerTest { + + @Mock + private LayoutService layoutService; + @Mock + private LayoutRepository layoutRepository; + // TODO Should modelmapper be mocked out? + @Spy + private ModelMapper modelMapper; + @InjectMocks + private LayoutController layoutController; + + private UUID validLayoutId; + private String invalidLayoutId; + private UUID doesNotExistLayoutId; + private Layout layout; + private Metadata metadata; + private LayoutRequestDTO layoutRequest; + private LayoutResponseDTO expectedLayoutResponse; + private List expectedMetadataResponse; + + @BeforeEach + public void setup() { + validLayoutId = UUID.randomUUID(); + invalidLayoutId = "invalidId"; + doesNotExistLayoutId = UUID.randomUUID(); + UUID metadataId = UUID.randomUUID(); + String layoutDefinition = "Test Definition"; + + metadata = new Metadata(); + metadata.setId(metadataId); + metadata.setName("Test Layout"); + metadata.setUser("Test User"); + metadata.setGroup("Test Group"); + metadata.setScreenshot("Test Screenshot"); + + layout = new Layout(); + layout.setId(validLayoutId); + layout.setDefinition(layoutDefinition); + layout.setMetadata(metadata); + metadata.setLayout(layout); + + layoutRequest = new LayoutRequestDTO(); + MetadataRequestDTO metadataRequestDTO = new MetadataRequestDTO(); + metadataRequestDTO.setName(metadata.getName()); + metadataRequestDTO.setUser(metadata.getUser()); + metadataRequestDTO.setGroup(metadata.getGroup()); + metadataRequestDTO.setScreenshot(metadata.getScreenshot()); + layoutRequest.setDefinition(layout.getDefinition()); + layoutRequest.setMetadata(metadataRequestDTO); + + expectedLayoutResponse = new LayoutResponseDTO(); + expectedLayoutResponse.setId(layout.getId()); + expectedLayoutResponse.setDefinition(layout.getDefinition()); + + MetadataResponseDTO metadataResponse = getMetadataResponseDTO(); + expectedLayoutResponse.setMetadata(metadataResponse); + + expectedMetadataResponse = new ArrayList<>(); + expectedMetadataResponse.add(metadataResponse); + } + + + @Test + void getLayout_validIdAndLayoutExists_returnsLayout() { + when(layoutService.getLayout(validLayoutId)).thenReturn(layout); + assertThat(layoutController.getLayout(validLayoutId)).isEqualTo(expectedLayoutResponse); + } + + @Test + void getLayout_layoutDoesNotExist_throwsNotFoundAndReturns404() { + when(layoutService.getLayout(doesNotExistLayoutId)).thenThrow(NoSuchElementException.class); + assertThrows(NoSuchElementException.class, () -> { + layoutController.getLayout(doesNotExistLayoutId); + }); + } + + @Test + void getMetadata_metadataExists_returnsMetadata() { + when(layoutService.getMetadata()).thenReturn(List.of(metadata)); + assertThat(layoutController.getMetadata()).isEqualTo(expectedMetadataResponse); + } + + @Test + void getMetadata_noMetadataExists_returnsEmptyArray() { + when(layoutService.getMetadata()).thenReturn(List.of()); + assertThat(layoutController.getMetadata()).isEmpty(); + } + + @Test + void createLayout_validLayout_createsLayout() { + when(layoutService.createLayout(any(Layout.class))).thenReturn(layout.getId()); + assertThat(layoutController.createLayout(layoutRequest)).isEqualTo(layout.getId()); + } + + // TODO I don't think this is a valid / worthwhile unit test? + @Test + void updateLayout_validLayout_doesNothing() { + } + + @Test + void updateLayout_layoutDoesNotExist_returnsInvalidRequest() { + when(layoutService.getLayout(layout.getId())).thenThrow(NoSuchElementException.class); + assertThrows(NoSuchElementException.class, () -> layoutController.updateLayout(layout.getId(), layoutRequest)); + } + + // TODO I don't think this is a valid / worthwhile unit test? + @Test + void deleteLayout_validId_returnsSuccess() { + } + + // TODO I don't think this is a valid / worthwhile unit test? + @Test + void deleteLayout_layoutDoesNotExist_doesNothing() { + } + + private MetadataResponseDTO getMetadataResponseDTO() { + MetadataResponseDTO metadataResponse = new MetadataResponseDTO(); + metadataResponse.setLayoutId(layout.getId()); + metadataResponse.setName(layout.getMetadata().getName()); + metadataResponse.setUser(layout.getMetadata().getUser()); + metadataResponse.setGroup(layout.getMetadata().getGroup()); + metadataResponse.setScreenshot(layout.getMetadata().getScreenshot()); + metadataResponse.setCreated(layout.getMetadata().getCreated()); + metadataResponse.setUpdated(layout.getMetadata().getUpdated()); + return metadataResponse; + } +} From 9895e100ddee422ada0c4f29afeb2d9784330ca1 Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Thu, 5 Oct 2023 15:25:05 +0100 Subject: [PATCH 19/70] VUU25: Create layout integration tests (backend) --- .../controller/LayoutController.java | 5 +- .../layoutserver/service/LayoutService.java | 9 +- .../controller/LayoutControllerTest.java | 24 +- .../integration/LayoutIntegrationTest.java | 279 ++++++++++++++++++ .../resources/application-test.properties | 5 + 5 files changed, 302 insertions(+), 20 deletions(-) create mode 100644 layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java create mode 100644 layout-server/src/test/resources/application-test.properties diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java index d6c8000a8..62d18841a 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java @@ -1,6 +1,8 @@ package org.finos.vuu.layoutserver.controller; import jakarta.validation.Valid; +import java.util.List; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.finos.vuu.layoutserver.dto.request.LayoutRequestDTO; import org.finos.vuu.layoutserver.dto.response.CreateLayoutResponseDTO; @@ -20,9 +22,6 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; -import java.util.List; -import java.util.UUID; - @RequiredArgsConstructor @RestController @RequestMapping("/layouts") diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java index c0185ed18..eef37d39c 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java @@ -1,5 +1,8 @@ package org.finos.vuu.layoutserver.service; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.finos.vuu.layoutserver.model.Layout; import org.finos.vuu.layoutserver.model.Metadata; @@ -7,9 +10,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.List; -import java.util.UUID; - @RequiredArgsConstructor @Service public class LayoutService { @@ -18,7 +18,8 @@ public class LayoutService { private final MetadataService metadataService; public Layout getLayout(UUID id) { - return layoutRepository.findById(id).orElseThrow(); + return layoutRepository.findById(id) + .orElseThrow(() -> new NoSuchElementException("Layout with ID '" + id + "' not found")); } diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java index aed588352..27a8489fe 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java @@ -1,5 +1,14 @@ package org.finos.vuu.layoutserver.controller; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; import org.finos.vuu.layoutserver.dto.request.LayoutRequestDTO; import org.finos.vuu.layoutserver.dto.request.MetadataRequestDTO; import org.finos.vuu.layoutserver.dto.response.LayoutResponseDTO; @@ -17,16 +26,6 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.modelmapper.ModelMapper; -import java.util.ArrayList; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.UUID; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - @ExtendWith(MockitoExtension.class) class LayoutControllerTest { @@ -41,7 +40,6 @@ class LayoutControllerTest { private LayoutController layoutController; private UUID validLayoutId; - private String invalidLayoutId; private UUID doesNotExistLayoutId; private Layout layout; private Metadata metadata; @@ -52,7 +50,6 @@ class LayoutControllerTest { @BeforeEach public void setup() { validLayoutId = UUID.randomUUID(); - invalidLayoutId = "invalidId"; doesNotExistLayoutId = UUID.randomUUID(); UUID metadataId = UUID.randomUUID(); String layoutDefinition = "Test Definition"; @@ -131,7 +128,8 @@ void updateLayout_validLayout_doesNothing() { @Test void updateLayout_layoutDoesNotExist_returnsInvalidRequest() { when(layoutService.getLayout(layout.getId())).thenThrow(NoSuchElementException.class); - assertThrows(NoSuchElementException.class, () -> layoutController.updateLayout(layout.getId(), layoutRequest)); + assertThrows(NoSuchElementException.class, + () -> layoutController.updateLayout(layout.getId(), layoutRequest)); } // TODO I don't think this is a valid / worthwhile unit test? diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java new file mode 100644 index 000000000..1efd18082 --- /dev/null +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java @@ -0,0 +1,279 @@ +package org.finos.vuu.layoutserver.integration; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.Date; +import java.util.UUID; +import org.finos.vuu.layoutserver.dto.request.LayoutRequestDTO; +import org.finos.vuu.layoutserver.dto.request.MetadataRequestDTO; +import org.finos.vuu.layoutserver.model.Layout; +import org.finos.vuu.layoutserver.model.Metadata; +import org.finos.vuu.layoutserver.repository.LayoutRepository; +import org.finos.vuu.layoutserver.repository.MetadataRepository; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@AutoConfigureMockMvc +@Transactional +@ActiveProfiles("test") +public class LayoutIntegrationTest { + + private static String defaultDefinition; + private static String defaultName; + private static String defaultGroup; + private static String defaultScreenshot; + private static String defaultUser; + private final ObjectMapper objectMapper = new ObjectMapper(); + @Autowired + private MockMvc mockMvc; + @Autowired + private LayoutRepository layoutRepository; + @Autowired + private MetadataRepository metadataRepository; + + @BeforeAll + public static void setup() { + defaultDefinition = "Default layout definition"; + defaultName = "Default layout name"; + defaultGroup = "Default layout group"; + defaultScreenshot = "Default layout screenshot"; + defaultUser = "Default layout user"; + } + + @Test + void getLayout_validIDAndLayoutExists_returns200WithLayout() throws Exception { + Layout layout = createDefaultLayoutInDatabase(); + + mockMvc.perform(get("/layouts/{id}", layout.getId())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.definition", is(layout.getDefinition()))) + .andExpect(jsonPath("$.metadata.name", is(layout.getMetadata().getName()))) + .andExpect(jsonPath("$.metadata.group", is(layout.getMetadata().getGroup()))) + .andExpect(jsonPath("$.metadata.screenshot", is(layout.getMetadata().getScreenshot()))) + .andExpect(jsonPath("$.metadata.user", is(layout.getMetadata().getUser()))); + } + + @Test + void getLayout_validIDButLayoutDoesNotExist_returns404() throws Exception { + UUID layoutID = UUID.randomUUID(); + + mockMvc.perform(get("/layouts/{id}", layoutID)).andExpect(status().isNotFound()); + } + + @Test + void getLayout_invalidId_returns400() throws Exception { + String layoutID = "invalidUUID"; + + mockMvc.perform(get("/layouts/{id}", layoutID)).andExpect(status().isBadRequest()); + } + + @Test + void getMetadata_metadataExists_returnsMetadata() throws Exception { + Layout layout = createDefaultLayoutInDatabase(); + + mockMvc.perform(get("/layouts/metadata")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].name", is(layout.getMetadata().getName()))) + .andExpect(jsonPath("$[0].group", is(layout.getMetadata().getGroup()))) + .andExpect(jsonPath("$[0].screenshot", is(layout.getMetadata().getScreenshot()))) + .andExpect(jsonPath("$[0].user", is(layout.getMetadata().getUser()))); + } + + @Test + void getMetadata_metadataDoesNotExist_returnsEmptyList() throws Exception { + mockMvc.perform(get("/layouts/metadata")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isEmpty()); + } + + @Test + void createLayout_validLayout_returnsLayoutCreatedWithIDAndCreatedDate() throws Exception { + MetadataRequestDTO metadataRequest = new MetadataRequestDTO(); + metadataRequest.setName(defaultName); + metadataRequest.setGroup(defaultGroup); + metadataRequest.setScreenshot(defaultScreenshot); + metadataRequest.setUser(defaultUser); + + LayoutRequestDTO layoutRequest = new LayoutRequestDTO(); + layoutRequest.setDefinition(defaultDefinition); + layoutRequest.setMetadata(metadataRequest); + + mockMvc.perform(post("/layouts") + .content(objectMapper.writeValueAsString(layoutRequest)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.id").isNotEmpty()) + .andExpect(jsonPath("$.created").isNotEmpty()); + } + + @Test + void createLayout_invalidLayout_returns400() throws Exception { + String invalidLayout = "invalidLayout"; + + mockMvc.perform(post("/layouts") + .content(invalidLayout) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + + @Test + void updateLayout_validIDAndValidRequest_returns204AndLayoutHasChanged() throws Exception { + Layout layout = createDefaultLayoutInDatabase(); + LayoutRequestDTO layoutRequest = createValidUpdateRequest(); + + mockMvc.perform(put("/layouts/{id}", layout.getId()) + .content(objectMapper.writeValueAsString(layoutRequest)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNoContent()) + .andExpect(jsonPath("$").doesNotExist()); + + Layout updatedLayout = layoutRepository.findById(layout.getId()).orElseThrow(); + + assertThat(updatedLayout.getDefinition()) + .isEqualTo(layoutRequest.getDefinition()); + assertThat(updatedLayout.getMetadata().getName()) + .isEqualTo(layoutRequest.getMetadata().getName()); + assertThat(updatedLayout.getMetadata().getGroup()) + .isEqualTo(layoutRequest.getMetadata().getGroup()); + assertThat(updatedLayout.getMetadata().getScreenshot()) + .isEqualTo(layoutRequest.getMetadata().getScreenshot()); + assertThat(updatedLayout.getMetadata().getUser()) + .isEqualTo(layoutRequest.getMetadata().getUser()); + } + + @Test + void updateLayout_invalidRequestBodyDefinitionIsBlankAndMetadataIsNull_returns400AndLayoutDoesNotChange() + throws Exception { + Layout layout = createDefaultLayoutInDatabase(); + + LayoutRequestDTO request = new LayoutRequestDTO(); + request.setDefinition(""); + request.setMetadata(null); + + mockMvc.perform(put("/layouts/{id}", layout.getId()) + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + assertThat(layoutRepository.findById(layout.getId()).orElseThrow()).isEqualTo(layout); + } + + @Test + void updateLayout_invalidRequestBodyUnexpectedFormat_returns400AndLayoutDoesNotChange() + throws Exception { + Layout layout = createDefaultLayoutInDatabase(); + String request = "invalidRequest"; + + mockMvc.perform(put("/layouts/{id}", layout.getId()) + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + assertThat(layoutRepository.findById(layout.getId()).orElseThrow()).isEqualTo(layout); + } + + // TODO Update layout, invalid ID, returns 400 + @Test + void updateLayout_validIdButLayoutDoesNotExist_returnsNotFound() throws Exception { + UUID layoutID = UUID.randomUUID(); + LayoutRequestDTO layoutRequest = createValidUpdateRequest(); + + mockMvc.perform(put("/layouts/{id}", layoutID) + .content(objectMapper.writeValueAsString(layoutRequest)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + void updateLayout_invalidId_returns400() throws Exception { + String layoutID = "invalidUUID"; + LayoutRequestDTO layoutRequest = createValidUpdateRequest(); + + mockMvc.perform(put("/layouts/{id}", layoutID) + .content(objectMapper.writeValueAsString(layoutRequest)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + + @Test + void deleteLayout_validIDLayoutExists_returnsSuccessAndLayoutIsDeleted() throws Exception { + Layout layout = createDefaultLayoutInDatabase(); + + mockMvc.perform(get("/layouts/{id}", layout.getId())) + .andExpect(status().isOk()); + + mockMvc.perform(delete("/layouts/{id}", layout.getId())) + .andExpect(status().isNoContent()); + + mockMvc.perform(get("/layouts/{id}", layout.getId())) + .andExpect(status().isNotFound()); + } + + @Test + void deleteLayout_validIDLayoutDoesNotExist_returnsNotFound() throws Exception { + UUID layoutID = UUID.randomUUID(); + + mockMvc.perform(delete("/layouts/{id}", layoutID)) + .andExpect(status().isNotFound()); + } + + @Test + void deleteLayout_invalidId_returns400() throws Exception { + String layoutID = "invalidUUID"; + + mockMvc.perform(delete("/layouts/{id}", layoutID)) + .andExpect(status().isBadRequest()); + } + + private Layout createDefaultLayoutInDatabase() { + Layout layout = new Layout(); + Metadata metadata = new Metadata(); + + layout.setDefinition(defaultDefinition); + layout.setMetadata(metadata); + + metadata.setLayout(layout); + metadata.setName(defaultName); + metadata.setGroup(defaultGroup); + metadata.setScreenshot(defaultScreenshot); + metadata.setUser(defaultUser); + + metadataRepository.save(metadata); + Layout createdLayout = layoutRepository.save(layout); + + assertThat(layoutRepository.findById(createdLayout.getId()).orElseThrow()) + .isEqualTo(layout); + + return createdLayout; + } + + private LayoutRequestDTO createValidUpdateRequest() { + MetadataRequestDTO metadataRequest = new MetadataRequestDTO(); + metadataRequest.setName("Updated name"); + metadataRequest.setGroup("Updated group"); + metadataRequest.setScreenshot("Updated screenshot"); + metadataRequest.setUser("Updated user"); + + LayoutRequestDTO layoutRequest = new LayoutRequestDTO(); + layoutRequest.setDefinition("Updated definition"); + layoutRequest.setMetadata(metadataRequest); + return layoutRequest; + } + +} diff --git a/layout-server/src/test/resources/application-test.properties b/layout-server/src/test/resources/application-test.properties new file mode 100644 index 000000000..2722b4aca --- /dev/null +++ b/layout-server/src/test/resources/application-test.properties @@ -0,0 +1,5 @@ +spring.datasource.url=jdbc:h2:mem:testdb;NON_KEYWORDS=GROUP,USER +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password=password +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect \ No newline at end of file From 399040841ec43b6c88a03df74e5afed9dd3abac3 Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Thu, 5 Oct 2023 17:26:43 +0100 Subject: [PATCH 20/70] VUU25: Add global exception handling to give appropriate HTTP responses --- layout-server/pom.xml | 4 ++++ .../controller/GlobalExceptionHandler.java | 23 +++++++++++++++++++ .../controller/LayoutController.java | 2 ++ .../dto/request/LayoutRequestDTO.java | 6 +++-- 4 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 layout-server/src/main/java/org/finos/vuu/layoutserver/controller/GlobalExceptionHandler.java diff --git a/layout-server/pom.xml b/layout-server/pom.xml index ace79292f..490ac4a5e 100644 --- a/layout-server/pom.xml +++ b/layout-server/pom.xml @@ -27,6 +27,10 @@ spring-boot-starter-test test + + org.springframework.boot + spring-boot-starter-validation + org.springframework.boot spring-boot-starter-web diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/GlobalExceptionHandler.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/GlobalExceptionHandler.java new file mode 100644 index 000000000..a41a76ffa --- /dev/null +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/GlobalExceptionHandler.java @@ -0,0 +1,23 @@ +package org.finos.vuu.layoutserver.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; + +import java.util.NoSuchElementException; + +@ControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(NoSuchElementException.class) + public ResponseEntity handleNotFound(NoSuchElementException ex) { + return new ResponseEntity<>(ex.getMessage(), org.springframework.http.HttpStatus.NOT_FOUND); + } + + @ExceptionHandler({MethodArgumentNotValidException.class, MethodArgumentTypeMismatchException.class}) + public ResponseEntity handleBadRequest(MethodArgumentNotValidException ex) { + return new ResponseEntity<>(ex.getMessage(), org.springframework.http.HttpStatus.BAD_REQUEST); + } +} diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java index 62d18841a..eb70be1b5 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java @@ -12,6 +12,7 @@ import org.finos.vuu.layoutserver.service.LayoutService; import org.modelmapper.ModelMapper; import org.springframework.http.HttpStatus; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -25,6 +26,7 @@ @RequiredArgsConstructor @RestController @RequestMapping("/layouts") +@Validated public class LayoutController { private final LayoutService layoutService; diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/LayoutRequestDTO.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/LayoutRequestDTO.java index 79455260b..a91f9baac 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/LayoutRequestDTO.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/LayoutRequestDTO.java @@ -1,6 +1,7 @@ package org.finos.vuu.layoutserver.dto.request; import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Data; @@ -8,10 +9,11 @@ public class LayoutRequestDTO { /** - * The definition of the layout as a string (e.g. stringified JSON structure containing components) + * The definition of the layout as a string (e.g. stringified JSON structure containing + * components) */ @JsonProperty(value = "definition", required = true) - @NotNull(message = "Please provide a valid definition") + @NotBlank(message = "Please provide a valid definition") private String definition; @JsonProperty(value = "metadata", required = true) From 13142bb7b0d0d7ba0927faef753cfc8b2defb9ce Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Thu, 5 Oct 2023 20:36:11 +0100 Subject: [PATCH 21/70] VUU25: Rename LayoutResponseDTO to GetLayoutResponseDTO --- .../finos/vuu/layoutserver/controller/LayoutController.java | 6 +++--- .../{LayoutResponseDTO.java => GetLayoutResponseDTO.java} | 2 +- .../vuu/layoutserver/controller/LayoutControllerTest.java | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) rename layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/{LayoutResponseDTO.java => GetLayoutResponseDTO.java} (89%) diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java index eb70be1b5..c24bd8d71 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java @@ -6,7 +6,7 @@ import lombok.RequiredArgsConstructor; import org.finos.vuu.layoutserver.dto.request.LayoutRequestDTO; import org.finos.vuu.layoutserver.dto.response.CreateLayoutResponseDTO; -import org.finos.vuu.layoutserver.dto.response.LayoutResponseDTO; +import org.finos.vuu.layoutserver.dto.response.GetLayoutResponseDTO; import org.finos.vuu.layoutserver.dto.response.MetadataResponseDTO; import org.finos.vuu.layoutserver.model.Layout; import org.finos.vuu.layoutserver.service.LayoutService; @@ -39,8 +39,8 @@ public class LayoutController { * @return the layout */ @GetMapping("/{id}") - public LayoutResponseDTO getLayout(@PathVariable UUID id) { - return mapper.map(layoutService.getLayout(id), LayoutResponseDTO.class); + public GetLayoutResponseDTO getLayout(@PathVariable UUID id) { + return mapper.map(layoutService.getLayout(id), GetLayoutResponseDTO.class); } /** diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/LayoutResponseDTO.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/GetLayoutResponseDTO.java similarity index 89% rename from layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/LayoutResponseDTO.java rename to layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/GetLayoutResponseDTO.java index 9e1077063..e07d77565 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/LayoutResponseDTO.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/GetLayoutResponseDTO.java @@ -5,7 +5,7 @@ import java.util.UUID; @Data -public class LayoutResponseDTO { +public class GetLayoutResponseDTO { private UUID id; diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java index 27a8489fe..7acb32067 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java @@ -11,7 +11,7 @@ import java.util.UUID; import org.finos.vuu.layoutserver.dto.request.LayoutRequestDTO; import org.finos.vuu.layoutserver.dto.request.MetadataRequestDTO; -import org.finos.vuu.layoutserver.dto.response.LayoutResponseDTO; +import org.finos.vuu.layoutserver.dto.response.GetLayoutResponseDTO; import org.finos.vuu.layoutserver.dto.response.MetadataResponseDTO; import org.finos.vuu.layoutserver.model.Layout; import org.finos.vuu.layoutserver.model.Metadata; @@ -44,7 +44,7 @@ class LayoutControllerTest { private Layout layout; private Metadata metadata; private LayoutRequestDTO layoutRequest; - private LayoutResponseDTO expectedLayoutResponse; + private GetLayoutResponseDTO expectedLayoutResponse; private List expectedMetadataResponse; @BeforeEach @@ -76,7 +76,7 @@ public void setup() { layoutRequest.setDefinition(layout.getDefinition()); layoutRequest.setMetadata(metadataRequestDTO); - expectedLayoutResponse = new LayoutResponseDTO(); + expectedLayoutResponse = new GetLayoutResponseDTO(); expectedLayoutResponse.setId(layout.getId()); expectedLayoutResponse.setDefinition(layout.getDefinition()); From 681dd938ecf1a110e1063f99e8c563ef54d66c42 Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Thu, 5 Oct 2023 20:46:12 +0100 Subject: [PATCH 22/70] VUU25: Reformatting --- .../layoutserver/controller/LayoutController.java | 6 +++--- .../finos/vuu/layoutserver/dto/MappingConfig.java | 13 ++++++++----- .../org/finos/vuu/layoutserver/model/Layout.java | 4 +--- .../org/finos/vuu/layoutserver/model/Metadata.java | 6 ++---- .../layoutserver/repository/LayoutRepository.java | 3 +-- .../layoutserver/repository/MetadataRepository.java | 3 +-- .../vuu/layoutserver/service/MetadataService.java | 5 ++--- 7 files changed, 18 insertions(+), 22 deletions(-) diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java index c24bd8d71..90068d5fd 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java @@ -52,9 +52,9 @@ public GetLayoutResponseDTO getLayout(@PathVariable UUID id) { public List getMetadata() { return layoutService.getMetadata() - .stream() - .map(metadata -> mapper.map(metadata, MetadataResponseDTO.class)) - .toList(); + .stream() + .map(metadata -> mapper.map(metadata, MetadataResponseDTO.class)) + .toList(); } /** diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/MappingConfig.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/MappingConfig.java index 5d4f8ec34..ff69a08f8 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/MappingConfig.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/MappingConfig.java @@ -18,19 +18,22 @@ public ModelMapper modelMapper() { ModelMapper mapper = new ModelMapper(); // LayoutRequestDTO to Layout - mapper.typeMap(LayoutRequestDTO.class, Layout.class).addMappings(m -> m.skip(Layout::setId)); + mapper.typeMap(LayoutRequestDTO.class, Layout.class) + .addMappings(m -> m.skip(Layout::setId)); // Layout to CreateLayoutResponseDTO mapper.typeMap(Layout.class, CreateLayoutResponseDTO.class) - .addMappings(m -> m.map(layout -> layout.getMetadata().getCreated(), - CreateLayoutResponseDTO::setCreated)); + .addMappings(m -> m.map(layout -> layout.getMetadata().getCreated(), + CreateLayoutResponseDTO::setCreated)); // Metadata to MetadataResponseDTO mapper.typeMap(Metadata.class, MetadataResponseDTO.class) - .addMappings(m -> m.map(metadata -> metadata.getLayout().getId(), MetadataResponseDTO::setLayoutId)); + .addMappings(m -> m.map(metadata -> metadata.getLayout().getId(), + MetadataResponseDTO::setLayoutId)); // MetadataRequestDTO to Metadata - mapper.typeMap(MetadataRequestDTO.class, Metadata.class).addMappings(m -> m.skip(Metadata::setId)); + mapper.typeMap(MetadataRequestDTO.class, Metadata.class) + .addMappings(m -> m.skip(Metadata::setId)); return mapper; } diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java index 6d1f3aee6..3fc6a6dcc 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java @@ -7,10 +7,8 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToOne; -import lombok.Data; -import lombok.NoArgsConstructor; - import java.util.UUID; +import lombok.Data; @Data @Entity diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java index e9bf280c8..b5ae0bec2 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java @@ -5,12 +5,10 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.OneToOne; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.ToString; - import java.util.Date; import java.util.UUID; +import lombok.Data; +import lombok.ToString; @Data @Entity diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/repository/LayoutRepository.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/repository/LayoutRepository.java index d57af4897..6cbae4025 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/repository/LayoutRepository.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/repository/LayoutRepository.java @@ -1,10 +1,9 @@ package org.finos.vuu.layoutserver.repository; +import java.util.UUID; import org.finos.vuu.layoutserver.model.Layout; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; -import java.util.UUID; - @Repository public interface LayoutRepository extends CrudRepository {} diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/repository/MetadataRepository.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/repository/MetadataRepository.java index 03f81b108..50cbe6288 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/repository/MetadataRepository.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/repository/MetadataRepository.java @@ -1,10 +1,9 @@ package org.finos.vuu.layoutserver.repository; +import java.util.UUID; import org.finos.vuu.layoutserver.model.Metadata; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; -import java.util.UUID; - @Repository public interface MetadataRepository extends CrudRepository {} diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/MetadataService.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/MetadataService.java index 0103e3a4a..fb9696ac5 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/MetadataService.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/MetadataService.java @@ -1,13 +1,12 @@ package org.finos.vuu.layoutserver.service; +import java.util.ArrayList; +import java.util.List; import lombok.RequiredArgsConstructor; import org.finos.vuu.layoutserver.model.Metadata; import org.finos.vuu.layoutserver.repository.MetadataRepository; import org.springframework.stereotype.Service; -import java.util.ArrayList; -import java.util.List; - @RequiredArgsConstructor @Service public class MetadataService { From 7b5dc5afba85dffc00559be722a20f36eb5231be Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Thu, 5 Oct 2023 20:46:43 +0100 Subject: [PATCH 23/70] VUU25: Make deleteLayout generate 404 if layout does not exist (backend) --- .../org/finos/vuu/layoutserver/controller/LayoutController.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java index 90068d5fd..cdf67d3b3 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java @@ -98,6 +98,8 @@ public void updateLayout(@PathVariable UUID id, @RequestBody @Valid LayoutReques @ResponseStatus(HttpStatus.NO_CONTENT) @DeleteMapping("/{id}") public void deleteLayout(@PathVariable UUID id) { + // Generate a 404 if layout doesn't exist + layoutService.getLayout(id); layoutService.deleteLayout(id); } } From 09b9e73c1c17de5ae87fdbe3c2529433203d2316 Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Mon, 9 Oct 2023 12:00:02 +0100 Subject: [PATCH 24/70] VUU25: Downgrade Java 17 -> 11 and Springboot 3 -> 2 --- layout-server/pom.xml | 8 ++++---- .../{dto => config}/MappingConfig.java | 2 +- .../controller/LayoutController.java | 14 +++++++------- .../finos/vuu/layoutserver/model/Layout.java | 18 ++++++++---------- .../finos/vuu/layoutserver/model/Metadata.java | 13 ++++++------- 5 files changed, 26 insertions(+), 29 deletions(-) rename layout-server/src/main/java/org/finos/vuu/layoutserver/{dto => config}/MappingConfig.java (97%) diff --git a/layout-server/pom.xml b/layout-server/pom.xml index ace79292f..11dce5f7c 100644 --- a/layout-server/pom.xml +++ b/layout-server/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 3.1.3 + 2.7.16 org.finos.vuu @@ -14,7 +14,7 @@ layout-server layout-server - 17 + 11 @@ -42,8 +42,8 @@ org.springdoc - springdoc-openapi-starter-webmvc-ui - 2.1.0 + springdoc-openapi-ui + 1.6.12 org.projectlombok diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/MappingConfig.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/config/MappingConfig.java similarity index 97% rename from layout-server/src/main/java/org/finos/vuu/layoutserver/dto/MappingConfig.java rename to layout-server/src/main/java/org/finos/vuu/layoutserver/config/MappingConfig.java index 5d4f8ec34..0e08053b4 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/MappingConfig.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/config/MappingConfig.java @@ -1,4 +1,4 @@ -package org.finos.vuu.layoutserver.dto; +package org.finos.vuu.layoutserver.config; import org.finos.vuu.layoutserver.dto.request.LayoutRequestDTO; import org.finos.vuu.layoutserver.dto.request.MetadataRequestDTO; diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java index cc7814e80..f6a557bbe 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java @@ -1,5 +1,7 @@ package org.finos.vuu.layoutserver.controller; +import java.util.List; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.finos.vuu.layoutserver.dto.request.LayoutRequestDTO; import org.finos.vuu.layoutserver.dto.response.CreateLayoutResponseDTO; @@ -19,9 +21,6 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; -import java.util.List; -import java.util.UUID; - @RequiredArgsConstructor @RestController @RequestMapping("/layouts") @@ -50,9 +49,9 @@ public LayoutResponseDTO getLayout(@PathVariable UUID id) { public List getMetadata() { return layoutService.getMetadata() - .stream() - .map(metadata -> mapper.map(metadata, MetadataResponseDTO.class)) - .toList(); + .stream() + .map(metadata -> mapper.map(metadata, MetadataResponseDTO.class)) + .collect(java.util.stream.Collectors.toList()); } /** @@ -80,7 +79,8 @@ public CreateLayoutResponseDTO createLayout(@RequestBody LayoutRequestDTO layout @ResponseStatus(HttpStatus.NO_CONTENT) @PutMapping("/{id}") public void updateLayout(@PathVariable UUID id, @RequestBody LayoutRequestDTO newLayout) { - // TODO I'm not sure about this, I think layoutService should be doing more logic, not the mapper here + // TODO I'm not sure about this, I think layoutService should be doing more logic, not + // the mapper here Layout layout = layoutService.getLayout(id); mapper.map(newLayout, layout); diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java index 6d1f3aee6..7c1be01c4 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java @@ -1,15 +1,13 @@ package org.finos.vuu.layoutserver.model; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.OneToOne; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; import lombok.Data; -import lombok.NoArgsConstructor; - import java.util.UUID; @Data @@ -17,7 +15,7 @@ public class Layout { @Id - @GeneratedValue(strategy = GenerationType.UUID) + @GeneratedValue(strategy = GenerationType.AUTO) private UUID id; private String definition; diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java index 531b31ddb..16c1fc61b 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java @@ -1,12 +1,11 @@ package org.finos.vuu.layoutserver.model; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.OneToOne; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.OneToOne; import lombok.Data; -import lombok.NoArgsConstructor; import java.util.Date; import java.util.UUID; @@ -16,7 +15,7 @@ public class Metadata { @Id - @GeneratedValue(strategy = GenerationType.UUID) + @GeneratedValue(strategy = GenerationType.AUTO) private UUID id; @OneToOne(mappedBy = "metadata") From dd5c120fe54a0aafb7fffd4a75af0b22eff11094 Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Mon, 9 Oct 2023 12:30:48 +0100 Subject: [PATCH 25/70] VUU25: Change DB persistence from file to in-memory --- layout-server/src/main/resources/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/layout-server/src/main/resources/application.properties b/layout-server/src/main/resources/application.properties index 81e7a32e3..2cf2389e5 100644 --- a/layout-server/src/main/resources/application.properties +++ b/layout-server/src/main/resources/application.properties @@ -1,7 +1,7 @@ server.port=8081 server.servlet.contextPath=/api springdoc.swagger-ui.path=/swagger -spring.datasource.url=jdbc:h2:file:/data/layout;NON_KEYWORDS=GROUP,USER +spring.datasource.url=jdbc:h2:mem:layoutdb;NON_KEYWORDS=GROUP,USER spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password=password From 07c179a4500a185fae471cfc90048aa447e51332 Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Mon, 9 Oct 2023 12:50:53 +0100 Subject: [PATCH 26/70] VUU25: Fix issues caused by downgrading Java & Springboot --- layout-server/pom.xml | 6 ++++++ .../vuu/layoutserver/controller/LayoutController.java | 10 +++++----- .../vuu/layoutserver/dto/request/LayoutRequestDTO.java | 4 ++-- .../layoutserver/dto/request/MetadataRequestDTO.java | 2 +- .../java/org/finos/vuu/layoutserver/model/Layout.java | 2 +- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/layout-server/pom.xml b/layout-server/pom.xml index 458c7cd69..7d9eab55a 100644 --- a/layout-server/pom.xml +++ b/layout-server/pom.xml @@ -58,6 +58,12 @@ modelmapper 3.1.0 + + org.jetbrains + annotations + 13.0 + compile + diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java index 10d50fdc2..7e5f8c5a5 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java @@ -2,9 +2,7 @@ import java.util.List; import java.util.UUID; -import jakarta.validation.Valid; -import java.util.List; -import java.util.UUID; +import javax.validation.Valid; import lombok.RequiredArgsConstructor; import org.finos.vuu.layoutserver.dto.request.LayoutRequestDTO; import org.finos.vuu.layoutserver.dto.response.CreateLayoutResponseDTO; @@ -67,7 +65,8 @@ public List getMetadata() { */ @ResponseStatus(HttpStatus.CREATED) @PostMapping - public CreateLayoutResponseDTO createLayout(@RequestBody @Valid LayoutRequestDTO layoutToCreate) { + public CreateLayoutResponseDTO createLayout( + @RequestBody @Valid LayoutRequestDTO layoutToCreate) { Layout layout = mapper.map(layoutToCreate, Layout.class); Layout createdLayout = layoutService.getLayout(layoutService.createLayout(layout)); @@ -83,7 +82,8 @@ public CreateLayoutResponseDTO createLayout(@RequestBody @Valid LayoutRequestDTO */ @ResponseStatus(HttpStatus.NO_CONTENT) @PutMapping("/{id}") - public void updateLayout(@PathVariable UUID id, @RequestBody @Valid LayoutRequestDTO newLayout) { + public void updateLayout(@PathVariable UUID id, + @RequestBody @Valid LayoutRequestDTO newLayout) { // TODO I'm not sure about this, I think layoutService should be doing more logic, not // the mapper here Layout layout = layoutService.getLayout(id); diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/LayoutRequestDTO.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/LayoutRequestDTO.java index a91f9baac..8b93e6343 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/LayoutRequestDTO.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/LayoutRequestDTO.java @@ -1,8 +1,8 @@ package org.finos.vuu.layoutserver.dto.request; import com.fasterxml.jackson.annotation.JsonProperty; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; import lombok.Data; @Data diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/MetadataRequestDTO.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/MetadataRequestDTO.java index d78ef7c36..01e62128f 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/MetadataRequestDTO.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/MetadataRequestDTO.java @@ -1,7 +1,7 @@ package org.finos.vuu.layoutserver.dto.request; import com.fasterxml.jackson.annotation.JsonProperty; -import jakarta.validation.constraints.NotNull; +import javax.validation.constraints.NotNull; import lombok.Data; import java.util.Date; diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java index 7c1be01c4..0178e82b8 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java @@ -1,5 +1,6 @@ package org.finos.vuu.layoutserver.model; +import java.util.UUID; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.GeneratedValue; @@ -8,7 +9,6 @@ import javax.persistence.JoinColumn; import javax.persistence.OneToOne; import lombok.Data; -import java.util.UUID; @Data @Entity From 5fcb3283a88c0b7d5dd35fb480aa87c8612edf85 Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Tue, 10 Oct 2023 10:42:04 +0100 Subject: [PATCH 27/70] VUU25: Write description for layout-server pom.xml --- layout-server/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/layout-server/pom.xml b/layout-server/pom.xml index 11dce5f7c..f617a4999 100644 --- a/layout-server/pom.xml +++ b/layout-server/pom.xml @@ -12,7 +12,7 @@ layout-server 0.0.1-SNAPSHOT layout-server - layout-server + A remote server to persist layouts for the Vuu client 11 From 1c05d6be05804b403b965cc7fab68e6d8ef046d4 Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Tue, 10 Oct 2023 10:57:00 +0100 Subject: [PATCH 28/70] VUU25: Implement interface for Metadata DTOs --- .../vuu/layoutserver/dto/MetadataDTO.java | 21 +++++++++++++++++++ .../dto/request/MetadataRequestDTO.java | 3 ++- .../dto/response/MetadataResponseDTO.java | 6 +++--- 3 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 layout-server/src/main/java/org/finos/vuu/layoutserver/dto/MetadataDTO.java diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/MetadataDTO.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/MetadataDTO.java new file mode 100644 index 000000000..23f82a691 --- /dev/null +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/MetadataDTO.java @@ -0,0 +1,21 @@ +package org.finos.vuu.layoutserver.dto; + +public interface MetadataDTO { + + String getName(); + + void setName(String name); + + String getGroup(); + + void setGroup(String group); + + String getScreenshot(); + + void setScreenshot(String screenshot); + + String getUser(); + + void setUser(String user); + +} diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/MetadataRequestDTO.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/MetadataRequestDTO.java index cef3205e5..26c31ca83 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/MetadataRequestDTO.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/MetadataRequestDTO.java @@ -4,9 +4,10 @@ import lombok.Data; import java.util.Date; +import org.finos.vuu.layoutserver.dto.MetadataDTO; @Data -public class MetadataRequestDTO { +public class MetadataRequestDTO implements MetadataDTO { private String name; private String group; diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/MetadataResponseDTO.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/MetadataResponseDTO.java index 79ecf6c6c..21dc8082d 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/MetadataResponseDTO.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/MetadataResponseDTO.java @@ -1,12 +1,12 @@ package org.finos.vuu.layoutserver.dto.response; -import lombok.Data; - import java.util.Date; import java.util.UUID; +import lombok.Data; +import org.finos.vuu.layoutserver.dto.MetadataDTO; @Data -public class MetadataResponseDTO { +public class MetadataResponseDTO implements MetadataDTO { private UUID layoutId; private String name; From 3c32c6ac47e410230b36d4835f56ee04f71c33b5 Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Tue, 10 Oct 2023 18:12:06 +0100 Subject: [PATCH 29/70] VUU25: Create layout service unit tests (backend) --- .../layoutserver/service/LayoutService.java | 2 - .../service/LayoutServiceTest.java | 93 +++++++++++++++++++ 2 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java index eef37d39c..ac7d354c9 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java @@ -22,8 +22,6 @@ public Layout getLayout(UUID id) { .orElseThrow(() -> new NoSuchElementException("Layout with ID '" + id + "' not found")); } - - // TODO Refactor to use a metadatarepository.findAll method public List getMetadata() { return metadataService.getMetadata(); } diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java new file mode 100644 index 000000000..734c7efe9 --- /dev/null +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java @@ -0,0 +1,93 @@ +package org.finos.vuu.layoutserver.service; + +import java.util.Date; +import java.util.List; +import org.finos.vuu.layoutserver.model.Metadata; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Optional; +import java.util.UUID; +import org.finos.vuu.layoutserver.model.Layout; +import org.finos.vuu.layoutserver.repository.LayoutRepository; +import org.junit.jupiter.api.BeforeEach; +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; + +@ExtendWith(MockitoExtension.class) +class LayoutServiceTest { + + @Mock + private LayoutRepository layoutRepository; + + @Mock + private MetadataService metadataService; + + @InjectMocks + private LayoutService layoutService; + + private Layout layout; + private Metadata metadata; + private UUID layoutId; + + @BeforeEach + public void setup() { + layoutId = UUID.randomUUID(); + UUID metadataId = UUID.randomUUID(); + layout = new Layout(); + metadata = new Metadata(); + layout.setId(layoutId); + layout.setDefinition(""); + layout.setMetadata(metadata); + metadata.setId(metadataId); + metadata.setLayout(layout); + metadata.setName(""); + metadata.setGroup(""); + metadata.setScreenshot(""); + metadata.setUser(""); + metadata.setCreated(new Date()); + metadata.setUpdated(new Date()); + } + + @Test + void getLayout_returnsLayout() { + when(layoutRepository.findById(layoutId)).thenReturn(Optional.of(layout)); + + assertThat(layoutService.getLayout(layoutId)).isEqualTo(layout); + } + + @Test + void getMetadata_returnsMetadata() { + when(metadataService.getMetadata()).thenReturn(List.of(metadata)); + + assertThat(layoutService.getMetadata()).isEqualTo(List.of(metadata)); + } + + @Test + void createLayout() { + when(metadataService.createMetadata(metadata)).thenReturn(metadata); + when(layoutRepository.save(layout)).thenReturn(layout); + + assertThat(layoutService.createLayout(layout)).isEqualTo(layoutId); + } + + @Test + void updateLayout_returnsNothing() { + layoutService.updateLayout(layout); + + verify(layoutRepository, times(1)).save(layout); + } + + @Test + void deleteLayout_returnsNothing() { + layoutService.deleteLayout(layoutId); + + verify(layoutRepository, times(1)).deleteById(layoutId); + } +} \ No newline at end of file From 3d5697b5a8967f951b9981af0b3a5c54c312960f Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Tue, 10 Oct 2023 18:38:23 +0100 Subject: [PATCH 30/70] VUU25: Add test for transactional nature of LayoutService.createLayout() --- .../integration/LayoutIntegrationTest.java | 55 ++++++++++++------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java index 1efd18082..d3725e5f7 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java @@ -1,6 +1,6 @@ package org.finos.vuu.layoutserver.integration; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -10,7 +10,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.Date; import java.util.UUID; import org.finos.vuu.layoutserver.dto.request.LayoutRequestDTO; import org.finos.vuu.layoutserver.dto.request.MetadataRequestDTO; @@ -104,15 +103,7 @@ void getMetadata_metadataDoesNotExist_returnsEmptyList() throws Exception { @Test void createLayout_validLayout_returnsLayoutCreatedWithIDAndCreatedDate() throws Exception { - MetadataRequestDTO metadataRequest = new MetadataRequestDTO(); - metadataRequest.setName(defaultName); - metadataRequest.setGroup(defaultGroup); - metadataRequest.setScreenshot(defaultScreenshot); - metadataRequest.setUser(defaultUser); - - LayoutRequestDTO layoutRequest = new LayoutRequestDTO(); - layoutRequest.setDefinition(defaultDefinition); - layoutRequest.setMetadata(metadataRequest); + LayoutRequestDTO layoutRequest = createValidCreateRequest(); mockMvc.perform(post("/layouts") .content(objectMapper.writeValueAsString(layoutRequest)) @@ -122,6 +113,7 @@ void createLayout_validLayout_returnsLayoutCreatedWithIDAndCreatedDate() throws .andExpect(jsonPath("$.created").isNotEmpty()); } + @Test void createLayout_invalidLayout_returns400() throws Exception { String invalidLayout = "invalidLayout"; @@ -132,6 +124,20 @@ void createLayout_invalidLayout_returns400() throws Exception { .andExpect(status().isBadRequest()); } + @Test + void createLayout_validLayoutButInvalidMetadata_returns400AndDoesNotCreateLayout() + throws Exception { + LayoutRequestDTO layoutRequest = createValidCreateRequest(); + layoutRequest.setMetadata(null); + + mockMvc.perform(post("/layouts") + .content(objectMapper.writeValueAsString(layoutRequest)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + assertThat(layoutRepository.findAll()).isEmpty(); + } + @Test void updateLayout_validIDAndValidRequest_returns204AndLayoutHasChanged() throws Exception { Layout layout = createDefaultLayoutInDatabase(); @@ -215,30 +221,25 @@ void updateLayout_invalidId_returns400() throws Exception { void deleteLayout_validIDLayoutExists_returnsSuccessAndLayoutIsDeleted() throws Exception { Layout layout = createDefaultLayoutInDatabase(); - mockMvc.perform(get("/layouts/{id}", layout.getId())) - .andExpect(status().isOk()); + mockMvc.perform(get("/layouts/{id}", layout.getId())).andExpect(status().isOk()); - mockMvc.perform(delete("/layouts/{id}", layout.getId())) - .andExpect(status().isNoContent()); + mockMvc.perform(delete("/layouts/{id}", layout.getId())).andExpect(status().isNoContent()); - mockMvc.perform(get("/layouts/{id}", layout.getId())) - .andExpect(status().isNotFound()); + mockMvc.perform(get("/layouts/{id}", layout.getId())).andExpect(status().isNotFound()); } @Test void deleteLayout_validIDLayoutDoesNotExist_returnsNotFound() throws Exception { UUID layoutID = UUID.randomUUID(); - mockMvc.perform(delete("/layouts/{id}", layoutID)) - .andExpect(status().isNotFound()); + mockMvc.perform(delete("/layouts/{id}", layoutID)).andExpect(status().isNotFound()); } @Test void deleteLayout_invalidId_returns400() throws Exception { String layoutID = "invalidUUID"; - mockMvc.perform(delete("/layouts/{id}", layoutID)) - .andExpect(status().isBadRequest()); + mockMvc.perform(delete("/layouts/{id}", layoutID)).andExpect(status().isBadRequest()); } private Layout createDefaultLayoutInDatabase() { @@ -276,4 +277,16 @@ private LayoutRequestDTO createValidUpdateRequest() { return layoutRequest; } + private LayoutRequestDTO createValidCreateRequest() { + MetadataRequestDTO metadataRequest = new MetadataRequestDTO(); + metadataRequest.setName(defaultName); + metadataRequest.setGroup(defaultGroup); + metadataRequest.setScreenshot(defaultScreenshot); + metadataRequest.setUser(defaultUser); + + LayoutRequestDTO layoutRequest = new LayoutRequestDTO(); + layoutRequest.setDefinition(defaultDefinition); + layoutRequest.setMetadata(metadataRequest); + return layoutRequest; + } } From 3d25ff86252091fc903a3bf9d130ae69531310f3 Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Tue, 10 Oct 2023 18:49:54 +0100 Subject: [PATCH 31/70] VUU25: Remove unnecessary LayoutController unit tests --- .../controller/LayoutControllerTest.java | 26 +++---------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java index 7acb32067..e2274a214 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java @@ -15,7 +15,6 @@ import org.finos.vuu.layoutserver.dto.response.MetadataResponseDTO; import org.finos.vuu.layoutserver.model.Layout; import org.finos.vuu.layoutserver.model.Metadata; -import org.finos.vuu.layoutserver.repository.LayoutRepository; import org.finos.vuu.layoutserver.service.LayoutService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -31,8 +30,6 @@ class LayoutControllerTest { @Mock private LayoutService layoutService; - @Mock - private LayoutRepository layoutRepository; // TODO Should modelmapper be mocked out? @Spy private ModelMapper modelMapper; @@ -97,9 +94,8 @@ void getLayout_validIdAndLayoutExists_returnsLayout() { @Test void getLayout_layoutDoesNotExist_throwsNotFoundAndReturns404() { when(layoutService.getLayout(doesNotExistLayoutId)).thenThrow(NoSuchElementException.class); - assertThrows(NoSuchElementException.class, () -> { - layoutController.getLayout(doesNotExistLayoutId); - }); + assertThrows(NoSuchElementException.class, + () -> layoutController.getLayout(doesNotExistLayoutId)); } @Test @@ -117,12 +113,8 @@ void getMetadata_noMetadataExists_returnsEmptyArray() { @Test void createLayout_validLayout_createsLayout() { when(layoutService.createLayout(any(Layout.class))).thenReturn(layout.getId()); - assertThat(layoutController.createLayout(layoutRequest)).isEqualTo(layout.getId()); - } - - // TODO I don't think this is a valid / worthwhile unit test? - @Test - void updateLayout_validLayout_doesNothing() { + when(layoutService.getLayout(layout.getId())).thenReturn(layout); + assertThat(layoutController.createLayout(layoutRequest).getId()).isEqualTo(layout.getId()); } @Test @@ -132,16 +124,6 @@ void updateLayout_layoutDoesNotExist_returnsInvalidRequest() { () -> layoutController.updateLayout(layout.getId(), layoutRequest)); } - // TODO I don't think this is a valid / worthwhile unit test? - @Test - void deleteLayout_validId_returnsSuccess() { - } - - // TODO I don't think this is a valid / worthwhile unit test? - @Test - void deleteLayout_layoutDoesNotExist_doesNothing() { - } - private MetadataResponseDTO getMetadataResponseDTO() { MetadataResponseDTO metadataResponse = new MetadataResponseDTO(); metadataResponse.setLayoutId(layout.getId()); From b9ec7caf0e1ffc74fb6aa0a4b797c71133f65896 Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Tue, 10 Oct 2023 19:09:08 +0100 Subject: [PATCH 32/70] VUU25: Mock out model mapper in LayoutController tests --- .../controller/LayoutControllerTest.java | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java index e2274a214..309de1ab7 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java @@ -2,7 +2,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; import java.util.ArrayList; @@ -11,6 +10,7 @@ import java.util.UUID; import org.finos.vuu.layoutserver.dto.request.LayoutRequestDTO; import org.finos.vuu.layoutserver.dto.request.MetadataRequestDTO; +import org.finos.vuu.layoutserver.dto.response.CreateLayoutResponseDTO; import org.finos.vuu.layoutserver.dto.response.GetLayoutResponseDTO; import org.finos.vuu.layoutserver.dto.response.MetadataResponseDTO; import org.finos.vuu.layoutserver.model.Layout; @@ -21,7 +21,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.modelmapper.ModelMapper; @@ -30,9 +29,10 @@ class LayoutControllerTest { @Mock private LayoutService layoutService; - // TODO Should modelmapper be mocked out? - @Spy + + @Mock private ModelMapper modelMapper; + @InjectMocks private LayoutController layoutController; @@ -88,6 +88,8 @@ public void setup() { @Test void getLayout_validIdAndLayoutExists_returnsLayout() { when(layoutService.getLayout(validLayoutId)).thenReturn(layout); + when(modelMapper.map(layout, GetLayoutResponseDTO.class)).thenReturn( + expectedLayoutResponse); assertThat(layoutController.getLayout(validLayoutId)).isEqualTo(expectedLayoutResponse); } @@ -101,6 +103,8 @@ void getLayout_layoutDoesNotExist_throwsNotFoundAndReturns404() { @Test void getMetadata_metadataExists_returnsMetadata() { when(layoutService.getMetadata()).thenReturn(List.of(metadata)); + when(modelMapper.map(metadata, MetadataResponseDTO.class)).thenReturn( + getMetadataResponseDTO()); assertThat(layoutController.getMetadata()).isEqualTo(expectedMetadataResponse); } @@ -112,9 +116,23 @@ void getMetadata_noMetadataExists_returnsEmptyArray() { @Test void createLayout_validLayout_createsLayout() { - when(layoutService.createLayout(any(Layout.class))).thenReturn(layout.getId()); + Layout layoutWithoutIds = layout; + layoutWithoutIds.setId(null); + layoutWithoutIds.getMetadata().setId(null); + + CreateLayoutResponseDTO expectedResponse = new CreateLayoutResponseDTO(); + expectedResponse.setId(layout.getId()); + expectedResponse.setCreated(layout.getMetadata().getCreated()); + + when(modelMapper.map(layoutRequest, Layout.class)).thenReturn(layoutWithoutIds); + when(layoutService.createLayout(layoutWithoutIds)).thenReturn(layout.getId()); when(layoutService.getLayout(layout.getId())).thenReturn(layout); - assertThat(layoutController.createLayout(layoutRequest).getId()).isEqualTo(layout.getId()); + when(modelMapper.map(layout, CreateLayoutResponseDTO.class)).thenReturn(expectedResponse); + + assertThat(layoutController.createLayout(layoutRequest).getId()) + .isEqualTo(layout.getId()); + assertThat(layoutController.createLayout(layoutRequest).getCreated()) + .isEqualTo(layout.getMetadata().getCreated()); } @Test From bee7f43cf0a01babcebd9af99937307f3a7e02b4 Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Wed, 11 Oct 2023 11:18:38 +0100 Subject: [PATCH 33/70] VUU25: Remove TODO --- .../vuu/layoutserver/integration/LayoutIntegrationTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java index d3725e5f7..73185d84a 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java @@ -194,7 +194,6 @@ void updateLayout_invalidRequestBodyUnexpectedFormat_returns400AndLayoutDoesNotC assertThat(layoutRepository.findById(layout.getId()).orElseThrow()).isEqualTo(layout); } - // TODO Update layout, invalid ID, returns 400 @Test void updateLayout_validIdButLayoutDoesNotExist_returnsNotFound() throws Exception { UUID layoutID = UUID.randomUUID(); From 6629880471319bf2abcf0ca11d2d973f77500d7f Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Wed, 11 Oct 2023 11:29:53 +0100 Subject: [PATCH 34/70] VUU25: Move 'updated date' logic from DTO to service for update layout requests --- .../vuu/layoutserver/dto/request/MetadataRequestDTO.java | 6 ------ .../org/finos/vuu/layoutserver/service/LayoutService.java | 7 ++++--- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/MetadataRequestDTO.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/MetadataRequestDTO.java index 26c31ca83..35137c4d7 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/MetadataRequestDTO.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/MetadataRequestDTO.java @@ -1,9 +1,6 @@ package org.finos.vuu.layoutserver.dto.request; -import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; - -import java.util.Date; import org.finos.vuu.layoutserver.dto.MetadataDTO; @Data @@ -13,7 +10,4 @@ public class MetadataRequestDTO implements MetadataDTO { private String group; private String screenshot; private String user; - - @JsonProperty(access = JsonProperty.Access.READ_ONLY) - private Date updated = new Date(); } diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java index 5aed4a625..3ee2f12d7 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java @@ -1,5 +1,8 @@ package org.finos.vuu.layoutserver.service; +import java.util.Date; +import java.util.List; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.finos.vuu.layoutserver.model.Layout; import org.finos.vuu.layoutserver.model.Metadata; @@ -7,9 +10,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.List; -import java.util.UUID; - @RequiredArgsConstructor @Service public class LayoutService { @@ -34,6 +34,7 @@ public UUID createLayout(Layout layout) { } public void updateLayout(Layout updatedLayout) { + updatedLayout.getMetadata().setUpdated(new Date()); layoutRepository.save(updatedLayout); } From 90545b0e60677fdf1c6834154f5f807cec0983df Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Wed, 11 Oct 2023 11:39:15 +0100 Subject: [PATCH 35/70] VUU25: Move updateLayout logic from mapper in controller to setters in service --- .../layoutserver/controller/LayoutController.java | 12 ++++-------- .../vuu/layoutserver/service/LayoutService.java | 15 ++++++++++++--- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java index f6a557bbe..abd8dc88a 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java @@ -74,18 +74,14 @@ public CreateLayoutResponseDTO createLayout(@RequestBody LayoutRequestDTO layout * Updates the specified layout * * @param id ID of the layout to update - * @param newLayout the new layout + * @param layout the new layout */ @ResponseStatus(HttpStatus.NO_CONTENT) @PutMapping("/{id}") - public void updateLayout(@PathVariable UUID id, @RequestBody LayoutRequestDTO newLayout) { - // TODO I'm not sure about this, I think layoutService should be doing more logic, not - // the mapper here - Layout layout = layoutService.getLayout(id); + public void updateLayout(@PathVariable UUID id, @RequestBody LayoutRequestDTO layout) { + Layout newLayout = mapper.map(layout, Layout.class); - mapper.map(newLayout, layout); - - layoutService.updateLayout(layout); + layoutService.updateLayout(id, newLayout); } /** diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java index 3ee2f12d7..6f77417a5 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java @@ -33,9 +33,18 @@ public UUID createLayout(Layout layout) { return layoutRepository.save(layout).getId(); } - public void updateLayout(Layout updatedLayout) { - updatedLayout.getMetadata().setUpdated(new Date()); - layoutRepository.save(updatedLayout); + public void updateLayout(UUID layoutId, Layout newLayout) { + Layout layoutToUpdate = getLayout(layoutId); + layoutToUpdate.setDefinition(newLayout.getDefinition()); + + Metadata metadataToUpdate = layoutToUpdate.getMetadata(); + metadataToUpdate.setName(newLayout.getMetadata().getName()); + metadataToUpdate.setGroup(newLayout.getMetadata().getGroup()); + metadataToUpdate.setScreenshot(newLayout.getMetadata().getScreenshot()); + metadataToUpdate.setUser(newLayout.getMetadata().getUser()); + metadataToUpdate.setUpdated(new Date()); + + layoutRepository.save(layoutToUpdate); } public void deleteLayout(UUID id) { From 6f97b5d678fff71e7b2a6c7e34b08370684cdc30 Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Wed, 11 Oct 2023 11:56:07 +0100 Subject: [PATCH 36/70] VUU25: Fix tests for recent changes in layout server - Update and delete layout in LayoutControllerTest and LayoutServiceTest --- .../controller/LayoutControllerTest.java | 22 +++++++++++++++---- .../service/LayoutServiceTest.java | 17 +++++++++++--- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java index 309de1ab7..2e69498ad 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.ArrayList; @@ -136,10 +137,23 @@ void createLayout_validLayout_createsLayout() { } @Test - void updateLayout_layoutDoesNotExist_returnsInvalidRequest() { - when(layoutService.getLayout(layout.getId())).thenThrow(NoSuchElementException.class); - assertThrows(NoSuchElementException.class, - () -> layoutController.updateLayout(layout.getId(), layoutRequest)); + void updateLayout_callsLayoutService() { + layout.setId(null); + layout.getMetadata().setId(null); + + when(modelMapper.map(layoutRequest, Layout.class)).thenReturn(layout); + + layoutController.updateLayout(validLayoutId, layoutRequest); + + verify(layoutService).updateLayout(validLayoutId, layout); + } + + @Test + void deleteLayout_callsLayoutService() { + layoutController.deleteLayout(validLayoutId); + + verify(layoutService).getLayout(validLayoutId); + verify(layoutService).deleteLayout(validLayoutId); } private MetadataResponseDTO getMetadataResponseDTO() { diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java index 734c7efe9..0edcc1086 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java @@ -2,9 +2,11 @@ import java.util.Date; import java.util.List; +import java.util.NoSuchElementException; import org.finos.vuu.layoutserver.model.Metadata; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -78,14 +80,23 @@ void createLayout() { } @Test - void updateLayout_returnsNothing() { - layoutService.updateLayout(layout); + void updateLayout_layoutExists_callsRepository() { + when(layoutRepository.findById(layoutId)).thenReturn(Optional.of(layout)); + + layoutService.updateLayout(layoutId, layout); verify(layoutRepository, times(1)).save(layout); } @Test - void deleteLayout_returnsNothing() { + void updateLayout_layoutDoesNotExist_throwsNoSuchElementException() { + when(layoutRepository.findById(layoutId)).thenReturn(Optional.empty()); + + assertThrows(NoSuchElementException.class, () -> layoutService.updateLayout(layoutId, layout)); + } + + @Test + void deleteLayout_callsRepository() { layoutService.deleteLayout(layoutId); verify(layoutRepository, times(1)).deleteById(layoutId); From 6116b28af6ed884f96db81485613c556b2f3c318 Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Wed, 11 Oct 2023 12:06:30 +0100 Subject: [PATCH 37/70] VUU25: Remove unnecessary autogenerated content - maven wrapper files - gitignore --- layout-server/.gitignore | 33 -- layout-server/.mvn/wrapper/maven-wrapper.jar | Bin 62547 -> 0 bytes .../.mvn/wrapper/maven-wrapper.properties | 2 - layout-server/mvnw | 308 ------------------ layout-server/mvnw.cmd | 205 ------------ 5 files changed, 548 deletions(-) delete mode 100644 layout-server/.gitignore delete mode 100644 layout-server/.mvn/wrapper/maven-wrapper.jar delete mode 100644 layout-server/.mvn/wrapper/maven-wrapper.properties delete mode 100644 layout-server/mvnw delete mode 100644 layout-server/mvnw.cmd diff --git a/layout-server/.gitignore b/layout-server/.gitignore deleted file mode 100644 index 549e00a2a..000000000 --- a/layout-server/.gitignore +++ /dev/null @@ -1,33 +0,0 @@ -HELP.md -target/ -!.mvn/wrapper/maven-wrapper.jar -!**/src/main/**/target/ -!**/src/test/**/target/ - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ -build/ -!**/src/main/**/build/ -!**/src/test/**/build/ - -### VS Code ### -.vscode/ diff --git a/layout-server/.mvn/wrapper/maven-wrapper.jar b/layout-server/.mvn/wrapper/maven-wrapper.jar deleted file mode 100644 index cb28b0e37c7d206feb564310fdeec0927af4123a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62547 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 A?*IS* diff --git a/layout-server/.mvn/wrapper/maven-wrapper.properties b/layout-server/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index 2e76e189d..000000000 --- a/layout-server/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,2 +0,0 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.4/apache-maven-3.9.4-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/layout-server/mvnw b/layout-server/mvnw deleted file mode 100644 index 66df28542..000000000 --- a/layout-server/mvnw +++ /dev/null @@ -1,308 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.2.0 -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir -# -# Optional ENV vars -# ----------------- -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files -# ---------------------------------------------------------------------------- - -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /usr/local/etc/mavenrc ] ; then - . /usr/local/etc/mavenrc - fi - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi - - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi - -fi - -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false -case "$(uname)" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME - else - JAVA_HOME="/Library/Java/Home"; export JAVA_HOME - fi - fi - ;; -esac - -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=$(java-config --jre-home) - fi -fi - -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$JAVA_HOME" ] && - JAVA_HOME=$(cygpath --unix "$JAVA_HOME") - [ -n "$CLASSPATH" ] && - CLASSPATH=$(cygpath --path --unix "$CLASSPATH") -fi - -# For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && - JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" -fi - -if [ -z "$JAVA_HOME" ]; then - javaExecutable="$(which javac)" - if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=$(which readlink) - if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then - if $darwin ; then - javaHome="$(dirname "\"$javaExecutable\"")" - javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" - else - javaExecutable="$(readlink -f "\"$javaExecutable\"")" - fi - javaHome="$(dirname "\"$javaExecutable\"")" - javaHome=$(expr "$javaHome" : '\(.*\)/bin') - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi - -if [ -z "$JAVACMD" ] ; then - 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" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - else - JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" - fi -fi - -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi - -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." -fi - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 - fi - - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=$(cd "$wdir/.." || exit 1; pwd) - fi - # end of workaround - done - printf '%s' "$(cd "$basedir" || exit 1; pwd)" -} - -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - # Remove \r in case we run on Windows within Git Bash - # and check out the repository with auto CRLF management - # enabled. Otherwise, we may read lines that are delimited with - # \r\n and produce $'-Xarg\r' rather than -Xarg due to word - # splitting rules. - tr -s '\r\n' ' ' < "$1" - fi -} - -log() { - if [ "$MVNW_VERBOSE" = true ]; then - printf '%s\n' "$1" - fi -} - -BASE_DIR=$(find_maven_basedir "$(dirname "$0")") -if [ -z "$BASE_DIR" ]; then - exit 1; -fi - -MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR -log "$MAVEN_PROJECTBASEDIR" - -########################################################################################## -# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -# This allows using the maven wrapper in projects that prohibit checking in binary data. -########################################################################################## -wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" -if [ -r "$wrapperJarPath" ]; then - log "Found $wrapperJarPath" -else - log "Couldn't find $wrapperJarPath, downloading it ..." - - if [ -n "$MVNW_REPOURL" ]; then - wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - else - wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - fi - while IFS="=" read -r key value; do - # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) - safeValue=$(echo "$value" | tr -d '\r') - case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; - esac - done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" - log "Downloading from: $wrapperUrl" - - if $cygwin; then - wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") - fi - - if command -v wget > /dev/null; then - log "Found wget ... using wget" - [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" - else - wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" - fi - elif command -v curl > /dev/null; then - log "Found curl ... using curl" - [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" - else - curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" - fi - else - log "Falling back to using Java to download" - javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" - javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" - # For Cygwin, switch paths to Windows format before running javac - if $cygwin; then - javaSource=$(cygpath --path --windows "$javaSource") - javaClass=$(cygpath --path --windows "$javaClass") - fi - if [ -e "$javaSource" ]; then - if [ ! -e "$javaClass" ]; then - log " - Compiling MavenWrapperDownloader.java ..." - ("$JAVA_HOME/bin/javac" "$javaSource") - fi - if [ -e "$javaClass" ]; then - log " - Running MavenWrapperDownloader.java ..." - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" - fi - fi - fi -fi -########################################################################################## -# End of extension -########################################################################################## - -# If specified, validate the SHA-256 sum of the Maven wrapper jar file -wrapperSha256Sum="" -while IFS="=" read -r key value; do - case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; - esac -done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" -if [ -n "$wrapperSha256Sum" ]; then - wrapperSha256Result=false - if command -v sha256sum > /dev/null; then - if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then - wrapperSha256Result=true - fi - elif command -v shasum > /dev/null; then - if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then - wrapperSha256Result=true - fi - else - echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." - echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." - exit 1 - fi - if [ $wrapperSha256Result = false ]; then - echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 - echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 - echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 - exit 1 - fi -fi - -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$JAVA_HOME" ] && - JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") - [ -n "$CLASSPATH" ] && - CLASSPATH=$(cygpath --path --windows "$CLASSPATH") - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") -fi - -# Provide a "standardized" way to retrieve the CLI args that will -# work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" -export MAVEN_CMD_LINE_ARGS - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -# shellcheck disable=SC2086 # safe args -exec "$JAVACMD" \ - $MAVEN_OPTS \ - $MAVEN_DEBUG_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/layout-server/mvnw.cmd b/layout-server/mvnw.cmd deleted file mode 100644 index 95ba6f54a..000000000 --- a/layout-server/mvnw.cmd +++ /dev/null @@ -1,205 +0,0 @@ -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM https://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.2.0 -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@REM -@REM Optional ENV vars -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files -@REM ---------------------------------------------------------------------------- - -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM set title of command window -title %0 -@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* -if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - -FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B -) - -@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -@REM This allows using the maven wrapper in projects that prohibit checking in binary data. -if exist %WRAPPER_JAR% ( - if "%MVNW_VERBOSE%" == "true" ( - echo Found %WRAPPER_JAR% - ) -) else ( - if not "%MVNW_REPOURL%" == "" ( - SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - ) - if "%MVNW_VERBOSE%" == "true" ( - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %WRAPPER_URL% - ) - - powershell -Command "&{"^ - "$webclient = new-object System.Net.WebClient;"^ - "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ - "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ - "}"^ - "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ - "}" - if "%MVNW_VERBOSE%" == "true" ( - echo Finished downloading %WRAPPER_JAR% - ) -) -@REM End of extension - -@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file -SET WRAPPER_SHA_256_SUM="" -FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B -) -IF NOT %WRAPPER_SHA_256_SUM%=="" ( - powershell -Command "&{"^ - "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ - "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ - " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ - " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ - " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ - " exit 1;"^ - "}"^ - "}" - if ERRORLEVEL 1 goto error -) - -@REM Provide a "standardized" way to retrieve the CLI args that will -@REM work with both Windows and non-Windows executions. -set MAVEN_CMD_LINE_ARGS=%* - -%MAVEN_JAVA_EXE% ^ - %JVM_CONFIG_MAVEN_PROPS% ^ - %MAVEN_OPTS% ^ - %MAVEN_DEBUG_OPTS% ^ - -classpath %WRAPPER_JAR% ^ - "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ - %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" -if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%"=="on" pause - -if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% - -cmd /C exit /B %ERROR_CODE% From 461310346c9eb8be663a25bf7b221b1ccd2c36b2 Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Wed, 11 Oct 2023 12:45:31 +0100 Subject: [PATCH 38/70] VUU25: Make create layout (with a valid layout) integration test more robust --- .../src/main/resources/application.properties | 3 -- .../integration/LayoutIntegrationTest.java | 29 +++++++++++++++++-- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/layout-server/src/main/resources/application.properties b/layout-server/src/main/resources/application.properties index 2cf2389e5..afee88372 100644 --- a/layout-server/src/main/resources/application.properties +++ b/layout-server/src/main/resources/application.properties @@ -7,6 +7,3 @@ spring.datasource.username=sa spring.datasource.password=password spring.jpa.database-platform=org.hibernate.dialect.H2Dialect spring.h2.console.enabled=true -spring.jpa.hibernate.ddl-auto=update -spring.jpa.generate-ddl=true -spring.jpa.show-sql=true diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java index 73185d84a..30e2786a3 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java @@ -10,6 +10,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; +import com.jayway.jsonpath.JsonPath; import java.util.UUID; import org.finos.vuu.layoutserver.dto.request.LayoutRequestDTO; import org.finos.vuu.layoutserver.dto.request.MetadataRequestDTO; @@ -25,6 +26,7 @@ import org.springframework.http.MediaType; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; import org.springframework.transaction.annotation.Transactional; @SpringBootTest @@ -102,15 +104,36 @@ void getMetadata_metadataDoesNotExist_returnsEmptyList() throws Exception { } @Test - void createLayout_validLayout_returnsLayoutCreatedWithIDAndCreatedDate() throws Exception { + void createLayout_validLayout_returnsLayoutCreatedWithIDAndCreatedDateAndLayoutIsCreated() + throws Exception { LayoutRequestDTO layoutRequest = createValidCreateRequest(); - mockMvc.perform(post("/layouts") + MvcResult result = mockMvc.perform(post("/layouts") .content(objectMapper.writeValueAsString(layoutRequest)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) .andExpect(jsonPath("$.id").isNotEmpty()) - .andExpect(jsonPath("$.created").isNotEmpty()); + .andExpect(jsonPath("$.created").isNotEmpty()) + .andReturn(); + + UUID createdLayoutId = UUID.fromString(JsonPath.read(result.getResponse().getContentAsString(), "$.id")); + Layout createdLayout = layoutRepository.findById(createdLayoutId).orElseThrow(); + Metadata createdMetadata = metadataRepository.findById(createdLayout.getMetadata().getId()).orElseThrow(); + + // Check that the one-to-one relationship isn't causing duplicate/unexpected entries in the DB + assertThat(layoutRepository.findAll()).containsExactly(createdLayout); + assertThat(metadataRepository.findAll()).containsExactly(createdMetadata); + + assertThat(createdLayout.getDefinition()) + .isEqualTo(layoutRequest.getDefinition()); + assertThat(createdMetadata.getName()) + .isEqualTo(layoutRequest.getMetadata().getName()); + assertThat(createdMetadata.getGroup()) + .isEqualTo(layoutRequest.getMetadata().getGroup()); + assertThat(createdMetadata.getScreenshot()) + .isEqualTo(layoutRequest.getMetadata().getScreenshot()); + assertThat(createdMetadata.getUser()) + .isEqualTo(layoutRequest.getMetadata().getUser()); } From 6c0de9aac12db0a245e6176ff7aeeaaec41a6c94 Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Mon, 16 Oct 2023 10:56:17 +0100 Subject: [PATCH 39/70] VUU25: Increase max length of screenshot column --- .../org/finos/vuu/layoutserver/model/Metadata.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java index 16c1fc61b..c94d90a97 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java @@ -1,5 +1,8 @@ package org.finos.vuu.layoutserver.model; +import java.util.Date; +import java.util.UUID; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; @@ -7,9 +10,6 @@ import javax.persistence.OneToOne; import lombok.Data; -import java.util.Date; -import java.util.UUID; - @Data @Entity public class Metadata { @@ -22,9 +22,15 @@ public class Metadata { private Layout layout; private String name; + private String group; + + @Column(length = 16384) private String screenshot; + private String user; + private Date created = new Date(); + private Date updated; } From ae2d2cc2d1193c7fece1847655a8b22e8c462e09 Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Mon, 16 Oct 2023 12:20:19 +0100 Subject: [PATCH 40/70] VUU25: Change create layout endpoint to return whole layout object --- .../vuu/layoutserver/config/MappingConfig.java | 11 ++--------- .../controller/LayoutController.java | 7 +++---- .../dto/response/CreateLayoutResponseDTO.java | 17 ----------------- 3 files changed, 5 insertions(+), 30 deletions(-) delete mode 100644 layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/CreateLayoutResponseDTO.java diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/config/MappingConfig.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/config/MappingConfig.java index 0e08053b4..66311db40 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/config/MappingConfig.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/config/MappingConfig.java @@ -2,7 +2,6 @@ import org.finos.vuu.layoutserver.dto.request.LayoutRequestDTO; import org.finos.vuu.layoutserver.dto.request.MetadataRequestDTO; -import org.finos.vuu.layoutserver.dto.response.CreateLayoutResponseDTO; import org.finos.vuu.layoutserver.dto.response.MetadataResponseDTO; import org.finos.vuu.layoutserver.model.Layout; import org.finos.vuu.layoutserver.model.Metadata; @@ -17,19 +16,13 @@ public class MappingConfig { public ModelMapper modelMapper() { ModelMapper mapper = new ModelMapper(); - // LayoutRequestDTO to Layout + // Layout mapper.typeMap(LayoutRequestDTO.class, Layout.class).addMappings(m -> m.skip(Layout::setId)); - // Layout to CreateLayoutResponseDTO - mapper.typeMap(Layout.class, CreateLayoutResponseDTO.class) - .addMappings(m -> m.map(layout -> layout.getMetadata().getCreated(), - CreateLayoutResponseDTO::setCreated)); - - // Metadata to MetadataResponseDTO + // Metadata mapper.typeMap(Metadata.class, MetadataResponseDTO.class) .addMappings(m -> m.map(metadata -> metadata.getLayout().getId(), MetadataResponseDTO::setLayoutId)); - // MetadataRequestDTO to Metadata mapper.typeMap(MetadataRequestDTO.class, Metadata.class).addMappings(m -> m.skip(Metadata::setId)); return mapper; diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java index abd8dc88a..0576af68a 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java @@ -4,7 +4,6 @@ import java.util.UUID; import lombok.RequiredArgsConstructor; import org.finos.vuu.layoutserver.dto.request.LayoutRequestDTO; -import org.finos.vuu.layoutserver.dto.response.CreateLayoutResponseDTO; import org.finos.vuu.layoutserver.dto.response.LayoutResponseDTO; import org.finos.vuu.layoutserver.dto.response.MetadataResponseDTO; import org.finos.vuu.layoutserver.model.Layout; @@ -58,16 +57,16 @@ public List getMetadata() { * Creates a new layout * * @param layoutToCreate the layout to be created - * @return the generated ID of the new layout + * @return the layout that has been created */ @ResponseStatus(HttpStatus.CREATED) @PostMapping - public CreateLayoutResponseDTO createLayout(@RequestBody LayoutRequestDTO layoutToCreate) { + public LayoutResponseDTO createLayout(@RequestBody LayoutRequestDTO layoutToCreate) { Layout layout = mapper.map(layoutToCreate, Layout.class); Layout createdLayout = layoutService.getLayout(layoutService.createLayout(layout)); - return mapper.map(createdLayout, CreateLayoutResponseDTO.class); + return mapper.map(createdLayout, LayoutResponseDTO.class); } /** diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/CreateLayoutResponseDTO.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/CreateLayoutResponseDTO.java deleted file mode 100644 index 8ff792674..000000000 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/CreateLayoutResponseDTO.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.finos.vuu.layoutserver.dto.response; - -import lombok.Data; - -import java.util.Date; -import java.util.UUID; - -@Data -public class CreateLayoutResponseDTO { - - private UUID id; - - /** - * The generated creation date of the created layout - */ - private Date created; -} From 4d463716c85061da10b1879730f7d9d05f960ead Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Mon, 16 Oct 2023 12:39:35 +0100 Subject: [PATCH 41/70] VUU25: Fix tests for new create layout response --- .../controller/LayoutController.java | 6 ++--- ...esponseDTO.java => LayoutResponseDTO.java} | 2 +- .../controller/LayoutControllerTest.java | 23 +++++++------------ .../integration/LayoutIntegrationTest.java | 14 +++++++---- 4 files changed, 21 insertions(+), 24 deletions(-) rename layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/{GetLayoutResponseDTO.java => LayoutResponseDTO.java} (89%) diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java index d74f95c24..aeaa409da 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java @@ -5,7 +5,7 @@ import javax.validation.Valid; import lombok.RequiredArgsConstructor; import org.finos.vuu.layoutserver.dto.request.LayoutRequestDTO; -import org.finos.vuu.layoutserver.dto.response.GetLayoutResponseDTO; +import org.finos.vuu.layoutserver.dto.response.LayoutResponseDTO; import org.finos.vuu.layoutserver.dto.response.MetadataResponseDTO; import org.finos.vuu.layoutserver.model.Layout; import org.finos.vuu.layoutserver.service.LayoutService; @@ -38,8 +38,8 @@ public class LayoutController { * @return the layout */ @GetMapping("/{id}") - public GetLayoutResponseDTO getLayout(@PathVariable UUID id) { - return mapper.map(layoutService.getLayout(id), GetLayoutResponseDTO.class); + public LayoutResponseDTO getLayout(@PathVariable UUID id) { + return mapper.map(layoutService.getLayout(id), LayoutResponseDTO.class); } /** diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/GetLayoutResponseDTO.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/LayoutResponseDTO.java similarity index 89% rename from layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/GetLayoutResponseDTO.java rename to layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/LayoutResponseDTO.java index e07d77565..9e1077063 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/GetLayoutResponseDTO.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/LayoutResponseDTO.java @@ -5,7 +5,7 @@ import java.util.UUID; @Data -public class GetLayoutResponseDTO { +public class LayoutResponseDTO { private UUID id; diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java index 2e69498ad..14fd1dd24 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java @@ -11,8 +11,7 @@ import java.util.UUID; import org.finos.vuu.layoutserver.dto.request.LayoutRequestDTO; import org.finos.vuu.layoutserver.dto.request.MetadataRequestDTO; -import org.finos.vuu.layoutserver.dto.response.CreateLayoutResponseDTO; -import org.finos.vuu.layoutserver.dto.response.GetLayoutResponseDTO; +import org.finos.vuu.layoutserver.dto.response.LayoutResponseDTO; import org.finos.vuu.layoutserver.dto.response.MetadataResponseDTO; import org.finos.vuu.layoutserver.model.Layout; import org.finos.vuu.layoutserver.model.Metadata; @@ -42,7 +41,7 @@ class LayoutControllerTest { private Layout layout; private Metadata metadata; private LayoutRequestDTO layoutRequest; - private GetLayoutResponseDTO expectedLayoutResponse; + private LayoutResponseDTO expectedLayoutResponse; private List expectedMetadataResponse; @BeforeEach @@ -74,7 +73,7 @@ public void setup() { layoutRequest.setDefinition(layout.getDefinition()); layoutRequest.setMetadata(metadataRequestDTO); - expectedLayoutResponse = new GetLayoutResponseDTO(); + expectedLayoutResponse = new LayoutResponseDTO(); expectedLayoutResponse.setId(layout.getId()); expectedLayoutResponse.setDefinition(layout.getDefinition()); @@ -89,7 +88,7 @@ public void setup() { @Test void getLayout_validIdAndLayoutExists_returnsLayout() { when(layoutService.getLayout(validLayoutId)).thenReturn(layout); - when(modelMapper.map(layout, GetLayoutResponseDTO.class)).thenReturn( + when(modelMapper.map(layout, LayoutResponseDTO.class)).thenReturn( expectedLayoutResponse); assertThat(layoutController.getLayout(validLayoutId)).isEqualTo(expectedLayoutResponse); } @@ -116,24 +115,18 @@ void getMetadata_noMetadataExists_returnsEmptyArray() { } @Test - void createLayout_validLayout_createsLayout() { + void createLayout_validLayout_returnsCreatedLayout() { Layout layoutWithoutIds = layout; layoutWithoutIds.setId(null); layoutWithoutIds.getMetadata().setId(null); - CreateLayoutResponseDTO expectedResponse = new CreateLayoutResponseDTO(); - expectedResponse.setId(layout.getId()); - expectedResponse.setCreated(layout.getMetadata().getCreated()); - when(modelMapper.map(layoutRequest, Layout.class)).thenReturn(layoutWithoutIds); when(layoutService.createLayout(layoutWithoutIds)).thenReturn(layout.getId()); when(layoutService.getLayout(layout.getId())).thenReturn(layout); - when(modelMapper.map(layout, CreateLayoutResponseDTO.class)).thenReturn(expectedResponse); + when(modelMapper.map(layout, LayoutResponseDTO.class)).thenReturn(expectedLayoutResponse); - assertThat(layoutController.createLayout(layoutRequest).getId()) - .isEqualTo(layout.getId()); - assertThat(layoutController.createLayout(layoutRequest).getCreated()) - .isEqualTo(layout.getMetadata().getCreated()); + assertThat(layoutController.createLayout(layoutRequest)) + .isEqualTo(expectedLayoutResponse); } @Test diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java index 30e2786a3..c9bd1ae8e 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java @@ -104,16 +104,20 @@ void getMetadata_metadataDoesNotExist_returnsEmptyList() throws Exception { } @Test - void createLayout_validLayout_returnsLayoutCreatedWithIDAndCreatedDateAndLayoutIsCreated() + void createLayout_validLayout_returnsCreatedLayoutAndLayoutIsPersisted() throws Exception { - LayoutRequestDTO layoutRequest = createValidCreateRequest(); + LayoutRequestDTO layoutRequest = createValidCreateLayoutRequest(); MvcResult result = mockMvc.perform(post("/layouts") .content(objectMapper.writeValueAsString(layoutRequest)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) .andExpect(jsonPath("$.id").isNotEmpty()) - .andExpect(jsonPath("$.created").isNotEmpty()) + .andExpect(jsonPath("$.definition", is(layoutRequest.getDefinition()))) + .andExpect(jsonPath("$.metadata.name", is(layoutRequest.getMetadata().getName()))) + .andExpect(jsonPath("$.metadata.group", is(layoutRequest.getMetadata().getGroup()))) + .andExpect(jsonPath("$.metadata.screenshot", is(layoutRequest.getMetadata().getScreenshot()))) + .andExpect(jsonPath("$.metadata.user", is(layoutRequest.getMetadata().getUser()))) .andReturn(); UUID createdLayoutId = UUID.fromString(JsonPath.read(result.getResponse().getContentAsString(), "$.id")); @@ -150,7 +154,7 @@ void createLayout_invalidLayout_returns400() throws Exception { @Test void createLayout_validLayoutButInvalidMetadata_returns400AndDoesNotCreateLayout() throws Exception { - LayoutRequestDTO layoutRequest = createValidCreateRequest(); + LayoutRequestDTO layoutRequest = createValidCreateLayoutRequest(); layoutRequest.setMetadata(null); mockMvc.perform(post("/layouts") @@ -299,7 +303,7 @@ private LayoutRequestDTO createValidUpdateRequest() { return layoutRequest; } - private LayoutRequestDTO createValidCreateRequest() { + private LayoutRequestDTO createValidCreateLayoutRequest() { MetadataRequestDTO metadataRequest = new MetadataRequestDTO(); metadataRequest.setName(defaultName); metadataRequest.setGroup(defaultGroup); From 8a6d60a12595af9ea228cbec8dfc8e55d5884716 Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Mon, 16 Oct 2023 12:44:33 +0100 Subject: [PATCH 42/70] VUU25: Simplify LayoutControllerTest test name - `getLayout_validIdAndLayoutExists_returnsLayout` -> `getLayout_layoutExists_returnsLayout` --- .../finos/vuu/layoutserver/controller/LayoutControllerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java index 14fd1dd24..7c9dd764f 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java @@ -86,7 +86,7 @@ public void setup() { @Test - void getLayout_validIdAndLayoutExists_returnsLayout() { + void getLayout_layoutExists_returnsLayout() { when(layoutService.getLayout(validLayoutId)).thenReturn(layout); when(modelMapper.map(layout, LayoutResponseDTO.class)).thenReturn( expectedLayoutResponse); From d0ca229223e56e469611aa655cd3e95af0bd6a11 Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Mon, 16 Oct 2023 17:15:26 +0100 Subject: [PATCH 43/70] VUU25: Fix tests for changed unidirectional Layout<->Metadata relationship --- .../org/finos/vuu/layoutserver/config/MappingConfig.java | 7 ------- .../main/java/org/finos/vuu/layoutserver/model/Layout.java | 2 ++ .../java/org/finos/vuu/layoutserver/model/Metadata.java | 7 +------ .../org/finos/vuu/layoutserver/service/LayoutService.java | 5 ----- .../finos/vuu/layoutserver/service/MetadataService.java | 4 ---- .../vuu/layoutserver/controller/LayoutControllerTest.java | 1 - .../layoutserver/integration/LayoutIntegrationTest.java | 1 - .../finos/vuu/layoutserver/service/LayoutServiceTest.java | 2 -- 8 files changed, 3 insertions(+), 26 deletions(-) diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/config/MappingConfig.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/config/MappingConfig.java index 1809371d1..fad55680c 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/config/MappingConfig.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/config/MappingConfig.java @@ -2,7 +2,6 @@ import org.finos.vuu.layoutserver.dto.request.LayoutRequestDTO; import org.finos.vuu.layoutserver.dto.request.MetadataRequestDTO; -import org.finos.vuu.layoutserver.dto.response.MetadataResponseDTO; import org.finos.vuu.layoutserver.model.Layout; import org.finos.vuu.layoutserver.model.Metadata; import org.modelmapper.ModelMapper; @@ -16,15 +15,9 @@ public class MappingConfig { public ModelMapper modelMapper() { ModelMapper mapper = new ModelMapper(); - // Layout mapper.typeMap(LayoutRequestDTO.class, Layout.class) .addMappings(m -> m.skip(Layout::setId)); - // Metadata - mapper.typeMap(Metadata.class, MetadataResponseDTO.class) - .addMappings(m -> m.map(metadata -> metadata.getLayout().getId(), - MetadataResponseDTO::setLayoutId)); - mapper.typeMap(MetadataRequestDTO.class, Metadata.class) .addMappings(m -> m.skip(Metadata::setId)); diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java index 0178e82b8..6251cbf2a 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java @@ -2,6 +2,7 @@ import java.util.UUID; import javax.persistence.CascadeType; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; @@ -16,6 +17,7 @@ public class Layout { @Id @GeneratedValue(strategy = GenerationType.AUTO) + @Column(columnDefinition = "BINARY(16)") private UUID id; private String definition; diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java index 8d5d9cd9e..7e81b9030 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java @@ -5,12 +5,10 @@ import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; -import javax.persistence.OneToOne; import java.util.Date; import java.util.UUID; import lombok.Data; -import lombok.ToString; @Data @Entity @@ -18,12 +16,9 @@ public class Metadata { @Id @GeneratedValue(strategy = GenerationType.AUTO) + @Column(columnDefinition = "BINARY(16)") private UUID id; - @OneToOne(mappedBy = "metadata") - @ToString.Exclude - private Layout layout; - private String name; private String group; diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java index 127333aed..42bb4d3f3 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java @@ -9,7 +9,6 @@ import org.finos.vuu.layoutserver.model.Metadata; import org.finos.vuu.layoutserver.repository.LayoutRepository; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; @RequiredArgsConstructor @Service @@ -27,11 +26,7 @@ public List getMetadata() { return metadataService.getMetadata(); } - @Transactional public UUID createLayout(Layout layout) { - Metadata metadata = metadataService.createMetadata(layout.getMetadata()); - metadata.setLayout(layout); - layout.setMetadata(metadata); return layoutRepository.save(layout).getId(); } diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/MetadataService.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/MetadataService.java index fb9696ac5..de3eb095e 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/MetadataService.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/MetadataService.java @@ -20,8 +20,4 @@ public List getMetadata() { return metadata; } - - public Metadata createMetadata(Metadata metadata) { - return metadataRepository.save(metadata); - } } diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java index 7c9dd764f..03a9cf640 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java @@ -62,7 +62,6 @@ public void setup() { layout.setId(validLayoutId); layout.setDefinition(layoutDefinition); layout.setMetadata(metadata); - metadata.setLayout(layout); layoutRequest = new LayoutRequestDTO(); MetadataRequestDTO metadataRequestDTO = new MetadataRequestDTO(); diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java index c9bd1ae8e..d0297ad5f 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java @@ -275,7 +275,6 @@ private Layout createDefaultLayoutInDatabase() { layout.setDefinition(defaultDefinition); layout.setMetadata(metadata); - metadata.setLayout(layout); metadata.setName(defaultName); metadata.setGroup(defaultGroup); metadata.setScreenshot(defaultScreenshot); diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java index 0edcc1086..079347059 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java @@ -48,7 +48,6 @@ public void setup() { layout.setDefinition(""); layout.setMetadata(metadata); metadata.setId(metadataId); - metadata.setLayout(layout); metadata.setName(""); metadata.setGroup(""); metadata.setScreenshot(""); @@ -73,7 +72,6 @@ void getMetadata_returnsMetadata() { @Test void createLayout() { - when(metadataService.createMetadata(metadata)).thenReturn(metadata); when(layoutRepository.save(layout)).thenReturn(layout); assertThat(layoutService.createLayout(layout)).isEqualTo(layoutId); From 300b89754b05833cd1e66413313566eceabb8a6a Mon Sep 17 00:00:00 2001 From: Cara <99646608+cfisher-scottlogic@users.noreply.github.com> Date: Mon, 23 Oct 2023 16:21:48 +0100 Subject: [PATCH 44/70] VUU25: Fix tests since merging in main with BaseMetadata --- .../layoutserver/config/MappingConfig.java | 12 +- .../controller/LayoutController.java | 2 +- .../vuu/layoutserver/model/Metadata.java | 2 +- .../controller/LayoutControllerTest.java | 32 +++-- .../integration/LayoutIntegrationTest.java | 131 ++++++++++-------- .../service/LayoutServiceTest.java | 31 +++-- 6 files changed, 110 insertions(+), 100 deletions(-) diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/config/MappingConfig.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/config/MappingConfig.java index 017470ede..855392f6c 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/config/MappingConfig.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/config/MappingConfig.java @@ -2,7 +2,7 @@ import lombok.RequiredArgsConstructor; import org.finos.vuu.layoutserver.dto.request.LayoutRequestDTO; -import org.finos.vuu.layoutserver.dto.request.MetadataRequestDTO; +import org.finos.vuu.layoutserver.dto.response.MetadataResponseDTO; import org.finos.vuu.layoutserver.model.Layout; import org.finos.vuu.layoutserver.model.Metadata; import org.finos.vuu.layoutserver.service.LayoutService; @@ -28,16 +28,6 @@ public ModelMapper modelMapper() { metadata -> layoutService.getLayoutByMetadataId(metadata.getId()), MetadataResponseDTO::setLayoutId)); - mapper.typeMap(MetadataRequestDTO.class, Metadata.class) - .addMappings(m -> m.map( - MetadataRequestDTO::getBaseMetadata, - Metadata::setBaseMetadata)); - - mapper.typeMap(Metadata.class, MetadataResponseDTO.class) - .addMappings(m -> m.map( - Metadata::getBaseMetadata, - MetadataResponseDTO::setBaseMetadata)); - return mapper; } } \ No newline at end of file diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java index 35395dd3a..9b10f46e9 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java @@ -84,7 +84,7 @@ public LayoutResponseDTO createLayout(@RequestBody @Valid LayoutRequestDTO layou */ @ResponseStatus(HttpStatus.NO_CONTENT) @PutMapping("/{id}") - public void updateLayout(@PathVariable UUID id, @RequestBody LayoutRequestDTO layout) { + public void updateLayout(@PathVariable UUID id, @Valid @RequestBody LayoutRequestDTO layout) { Layout newLayout = mapper.map(layout, Layout.class); layoutService.updateLayout(id, newLayout); diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java index 9ff2e8f0a..4a01354ee 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java @@ -30,7 +30,7 @@ public class Metadata { @Embedded private BaseMetadata baseMetadata; - private Date created = new Date(); + private final Date created = new Date(); private Date updated; } diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java index 03a9cf640..bb57395da 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java @@ -13,9 +13,11 @@ import org.finos.vuu.layoutserver.dto.request.MetadataRequestDTO; import org.finos.vuu.layoutserver.dto.response.LayoutResponseDTO; import org.finos.vuu.layoutserver.dto.response.MetadataResponseDTO; +import org.finos.vuu.layoutserver.model.BaseMetadata; import org.finos.vuu.layoutserver.model.Layout; import org.finos.vuu.layoutserver.model.Metadata; import org.finos.vuu.layoutserver.service.LayoutService; +import org.finos.vuu.layoutserver.service.MetadataService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -30,6 +32,9 @@ class LayoutControllerTest { @Mock private LayoutService layoutService; + @Mock + private MetadataService metadataService; + @Mock private ModelMapper modelMapper; @@ -40,6 +45,7 @@ class LayoutControllerTest { private UUID doesNotExistLayoutId; private Layout layout; private Metadata metadata; + private BaseMetadata baseMetadata; private LayoutRequestDTO layoutRequest; private LayoutResponseDTO expectedLayoutResponse; private List expectedMetadataResponse; @@ -51,12 +57,15 @@ public void setup() { UUID metadataId = UUID.randomUUID(); String layoutDefinition = "Test Definition"; + baseMetadata = new BaseMetadata(); + baseMetadata.setName("Test Layout"); + baseMetadata.setUser("Test User"); + baseMetadata.setGroup("Test Group"); + baseMetadata.setScreenshot("Test Screenshot"); + metadata = new Metadata(); metadata.setId(metadataId); - metadata.setName("Test Layout"); - metadata.setUser("Test User"); - metadata.setGroup("Test Group"); - metadata.setScreenshot("Test Screenshot"); + metadata.setBaseMetadata(baseMetadata); layout = new Layout(); layout.setId(validLayoutId); @@ -65,10 +74,7 @@ public void setup() { layoutRequest = new LayoutRequestDTO(); MetadataRequestDTO metadataRequestDTO = new MetadataRequestDTO(); - metadataRequestDTO.setName(metadata.getName()); - metadataRequestDTO.setUser(metadata.getUser()); - metadataRequestDTO.setGroup(metadata.getGroup()); - metadataRequestDTO.setScreenshot(metadata.getScreenshot()); + metadataRequestDTO.setBaseMetadata(baseMetadata); layoutRequest.setDefinition(layout.getDefinition()); layoutRequest.setMetadata(metadataRequestDTO); @@ -101,15 +107,16 @@ void getLayout_layoutDoesNotExist_throwsNotFoundAndReturns404() { @Test void getMetadata_metadataExists_returnsMetadata() { - when(layoutService.getMetadata()).thenReturn(List.of(metadata)); + when(metadataService.getMetadata()).thenReturn(List.of(metadata)); when(modelMapper.map(metadata, MetadataResponseDTO.class)).thenReturn( getMetadataResponseDTO()); + assertThat(layoutController.getMetadata()).isEqualTo(expectedMetadataResponse); } @Test void getMetadata_noMetadataExists_returnsEmptyArray() { - when(layoutService.getMetadata()).thenReturn(List.of()); + when(metadataService.getMetadata()).thenReturn(List.of()); assertThat(layoutController.getMetadata()).isEmpty(); } @@ -151,10 +158,7 @@ void deleteLayout_callsLayoutService() { private MetadataResponseDTO getMetadataResponseDTO() { MetadataResponseDTO metadataResponse = new MetadataResponseDTO(); metadataResponse.setLayoutId(layout.getId()); - metadataResponse.setName(layout.getMetadata().getName()); - metadataResponse.setUser(layout.getMetadata().getUser()); - metadataResponse.setGroup(layout.getMetadata().getGroup()); - metadataResponse.setScreenshot(layout.getMetadata().getScreenshot()); + metadataResponse.setBaseMetadata(baseMetadata); metadataResponse.setCreated(layout.getMetadata().getCreated()); metadataResponse.setUpdated(layout.getMetadata().getUpdated()); return metadataResponse; diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java index d0297ad5f..e9bbd61fd 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java @@ -14,6 +14,7 @@ import java.util.UUID; import org.finos.vuu.layoutserver.dto.request.LayoutRequestDTO; import org.finos.vuu.layoutserver.dto.request.MetadataRequestDTO; +import org.finos.vuu.layoutserver.model.BaseMetadata; import org.finos.vuu.layoutserver.model.Layout; import org.finos.vuu.layoutserver.model.Metadata; import org.finos.vuu.layoutserver.repository.LayoutRepository; @@ -40,7 +41,9 @@ public class LayoutIntegrationTest { private static String defaultGroup; private static String defaultScreenshot; private static String defaultUser; + private final ObjectMapper objectMapper = new ObjectMapper(); + @Autowired private MockMvc mockMvc; @Autowired @@ -63,11 +66,16 @@ void getLayout_validIDAndLayoutExists_returns200WithLayout() throws Exception { mockMvc.perform(get("/layouts/{id}", layout.getId())) .andExpect(status().isOk()) - .andExpect(jsonPath("$.definition", is(layout.getDefinition()))) - .andExpect(jsonPath("$.metadata.name", is(layout.getMetadata().getName()))) - .andExpect(jsonPath("$.metadata.group", is(layout.getMetadata().getGroup()))) - .andExpect(jsonPath("$.metadata.screenshot", is(layout.getMetadata().getScreenshot()))) - .andExpect(jsonPath("$.metadata.user", is(layout.getMetadata().getUser()))); + .andExpect(jsonPath("$.definition", + is(layout.getDefinition()))) + .andExpect(jsonPath("$.metadata.name", + is(layout.getMetadata().getBaseMetadata().getName()))) + .andExpect(jsonPath("$.metadata.group", + is(layout.getMetadata().getBaseMetadata().getGroup()))) + .andExpect(jsonPath("$.metadata.screenshot", + is(layout.getMetadata().getBaseMetadata().getScreenshot()))) + .andExpect(jsonPath("$.metadata.user", + is(layout.getMetadata().getBaseMetadata().getUser()))); } @Test @@ -90,10 +98,14 @@ void getMetadata_metadataExists_returnsMetadata() throws Exception { mockMvc.perform(get("/layouts/metadata")) .andExpect(status().isOk()) - .andExpect(jsonPath("$[0].name", is(layout.getMetadata().getName()))) - .andExpect(jsonPath("$[0].group", is(layout.getMetadata().getGroup()))) - .andExpect(jsonPath("$[0].screenshot", is(layout.getMetadata().getScreenshot()))) - .andExpect(jsonPath("$[0].user", is(layout.getMetadata().getUser()))); + .andExpect(jsonPath("$[0].name", + is(layout.getMetadata().getBaseMetadata().getName()))) + .andExpect(jsonPath("$[0].group", + is(layout.getMetadata().getBaseMetadata().getGroup()))) + .andExpect(jsonPath("$[0].screenshot", + is(layout.getMetadata().getBaseMetadata().getScreenshot()))) + .andExpect(jsonPath("$[0].user", + is(layout.getMetadata().getBaseMetadata().getUser()))); } @Test @@ -106,7 +118,7 @@ void getMetadata_metadataDoesNotExist_returnsEmptyList() throws Exception { @Test void createLayout_validLayout_returnsCreatedLayoutAndLayoutIsPersisted() throws Exception { - LayoutRequestDTO layoutRequest = createValidCreateLayoutRequest(); + LayoutRequestDTO layoutRequest = createValidLayoutRequest(); MvcResult result = mockMvc.perform(post("/layouts") .content(objectMapper.writeValueAsString(layoutRequest)) @@ -114,30 +126,37 @@ void createLayout_validLayout_returnsCreatedLayoutAndLayoutIsPersisted() .andExpect(status().isCreated()) .andExpect(jsonPath("$.id").isNotEmpty()) .andExpect(jsonPath("$.definition", is(layoutRequest.getDefinition()))) - .andExpect(jsonPath("$.metadata.name", is(layoutRequest.getMetadata().getName()))) - .andExpect(jsonPath("$.metadata.group", is(layoutRequest.getMetadata().getGroup()))) - .andExpect(jsonPath("$.metadata.screenshot", is(layoutRequest.getMetadata().getScreenshot()))) - .andExpect(jsonPath("$.metadata.user", is(layoutRequest.getMetadata().getUser()))) + .andExpect(jsonPath("$.metadata.name", + is(layoutRequest.getMetadata().getBaseMetadata().getName()))) + .andExpect(jsonPath("$.metadata.group", + is(layoutRequest.getMetadata().getBaseMetadata().getGroup()))) + .andExpect(jsonPath("$.metadata.screenshot", + is(layoutRequest.getMetadata().getBaseMetadata().getScreenshot()))) + .andExpect(jsonPath("$.metadata.user", + is(layoutRequest.getMetadata().getBaseMetadata().getUser()))) .andReturn(); - UUID createdLayoutId = UUID.fromString(JsonPath.read(result.getResponse().getContentAsString(), "$.id")); + UUID createdLayoutId = UUID.fromString( + JsonPath.read(result.getResponse().getContentAsString(), "$.id")); Layout createdLayout = layoutRepository.findById(createdLayoutId).orElseThrow(); - Metadata createdMetadata = metadataRepository.findById(createdLayout.getMetadata().getId()).orElseThrow(); + Metadata createdMetadata = metadataRepository.findById(createdLayout.getMetadata().getId()) + .orElseThrow(); - // Check that the one-to-one relationship isn't causing duplicate/unexpected entries in the DB + // Check that the one-to-one relationship isn't causing duplicate/unexpected entries in + // the DB assertThat(layoutRepository.findAll()).containsExactly(createdLayout); assertThat(metadataRepository.findAll()).containsExactly(createdMetadata); assertThat(createdLayout.getDefinition()) .isEqualTo(layoutRequest.getDefinition()); - assertThat(createdMetadata.getName()) - .isEqualTo(layoutRequest.getMetadata().getName()); - assertThat(createdMetadata.getGroup()) - .isEqualTo(layoutRequest.getMetadata().getGroup()); - assertThat(createdMetadata.getScreenshot()) - .isEqualTo(layoutRequest.getMetadata().getScreenshot()); - assertThat(createdMetadata.getUser()) - .isEqualTo(layoutRequest.getMetadata().getUser()); + assertThat(createdMetadata.getBaseMetadata().getName()) + .isEqualTo(layoutRequest.getMetadata().getBaseMetadata().getName()); + assertThat(createdMetadata.getBaseMetadata().getGroup()) + .isEqualTo(layoutRequest.getMetadata().getBaseMetadata().getGroup()); + assertThat(createdMetadata.getBaseMetadata().getScreenshot()) + .isEqualTo(layoutRequest.getMetadata().getBaseMetadata().getScreenshot()); + assertThat(createdMetadata.getBaseMetadata().getUser()) + .isEqualTo(layoutRequest.getMetadata().getBaseMetadata().getUser()); } @@ -154,7 +173,7 @@ void createLayout_invalidLayout_returns400() throws Exception { @Test void createLayout_validLayoutButInvalidMetadata_returns400AndDoesNotCreateLayout() throws Exception { - LayoutRequestDTO layoutRequest = createValidCreateLayoutRequest(); + LayoutRequestDTO layoutRequest = createValidLayoutRequest(); layoutRequest.setMetadata(null); mockMvc.perform(post("/layouts") @@ -168,7 +187,7 @@ void createLayout_validLayoutButInvalidMetadata_returns400AndDoesNotCreateLayout @Test void updateLayout_validIDAndValidRequest_returns204AndLayoutHasChanged() throws Exception { Layout layout = createDefaultLayoutInDatabase(); - LayoutRequestDTO layoutRequest = createValidUpdateRequest(); + LayoutRequestDTO layoutRequest = createValidLayoutRequest(); mockMvc.perform(put("/layouts/{id}", layout.getId()) .content(objectMapper.writeValueAsString(layoutRequest)) @@ -180,14 +199,14 @@ void updateLayout_validIDAndValidRequest_returns204AndLayoutHasChanged() throws assertThat(updatedLayout.getDefinition()) .isEqualTo(layoutRequest.getDefinition()); - assertThat(updatedLayout.getMetadata().getName()) - .isEqualTo(layoutRequest.getMetadata().getName()); - assertThat(updatedLayout.getMetadata().getGroup()) - .isEqualTo(layoutRequest.getMetadata().getGroup()); - assertThat(updatedLayout.getMetadata().getScreenshot()) - .isEqualTo(layoutRequest.getMetadata().getScreenshot()); - assertThat(updatedLayout.getMetadata().getUser()) - .isEqualTo(layoutRequest.getMetadata().getUser()); + assertThat(updatedLayout.getMetadata().getBaseMetadata().getName()) + .isEqualTo(layoutRequest.getMetadata().getBaseMetadata().getName()); + assertThat(updatedLayout.getMetadata().getBaseMetadata().getGroup()) + .isEqualTo(layoutRequest.getMetadata().getBaseMetadata().getGroup()); + assertThat(updatedLayout.getMetadata().getBaseMetadata().getScreenshot()) + .isEqualTo(layoutRequest.getMetadata().getBaseMetadata().getScreenshot()); + assertThat(updatedLayout.getMetadata().getBaseMetadata().getUser()) + .isEqualTo(layoutRequest.getMetadata().getBaseMetadata().getUser()); } @Test @@ -224,7 +243,7 @@ void updateLayout_invalidRequestBodyUnexpectedFormat_returns400AndLayoutDoesNotC @Test void updateLayout_validIdButLayoutDoesNotExist_returnsNotFound() throws Exception { UUID layoutID = UUID.randomUUID(); - LayoutRequestDTO layoutRequest = createValidUpdateRequest(); + LayoutRequestDTO layoutRequest = createValidLayoutRequest(); mockMvc.perform(put("/layouts/{id}", layoutID) .content(objectMapper.writeValueAsString(layoutRequest)) @@ -235,7 +254,7 @@ void updateLayout_validIdButLayoutDoesNotExist_returnsNotFound() throws Exceptio @Test void updateLayout_invalidId_returns400() throws Exception { String layoutID = "invalidUUID"; - LayoutRequestDTO layoutRequest = createValidUpdateRequest(); + LayoutRequestDTO layoutRequest = createValidLayoutRequest(); mockMvc.perform(put("/layouts/{id}", layoutID) .content(objectMapper.writeValueAsString(layoutRequest)) @@ -271,15 +290,18 @@ void deleteLayout_invalidId_returns400() throws Exception { private Layout createDefaultLayoutInDatabase() { Layout layout = new Layout(); Metadata metadata = new Metadata(); + BaseMetadata baseMetadata = new BaseMetadata(); + + baseMetadata.setName(defaultName); + baseMetadata.setGroup(defaultGroup); + baseMetadata.setScreenshot(defaultScreenshot); + baseMetadata.setUser(defaultUser); + + metadata.setBaseMetadata(baseMetadata); layout.setDefinition(defaultDefinition); layout.setMetadata(metadata); - metadata.setName(defaultName); - metadata.setGroup(defaultGroup); - metadata.setScreenshot(defaultScreenshot); - metadata.setUser(defaultUser); - metadataRepository.save(metadata); Layout createdLayout = layoutRepository.save(layout); @@ -289,29 +311,20 @@ private Layout createDefaultLayoutInDatabase() { return createdLayout; } - private LayoutRequestDTO createValidUpdateRequest() { - MetadataRequestDTO metadataRequest = new MetadataRequestDTO(); - metadataRequest.setName("Updated name"); - metadataRequest.setGroup("Updated group"); - metadataRequest.setScreenshot("Updated screenshot"); - metadataRequest.setUser("Updated user"); - - LayoutRequestDTO layoutRequest = new LayoutRequestDTO(); - layoutRequest.setDefinition("Updated definition"); - layoutRequest.setMetadata(metadataRequest); - return layoutRequest; - } + private LayoutRequestDTO createValidLayoutRequest() { + BaseMetadata baseMetadata = new BaseMetadata(); + baseMetadata.setName(defaultName); + baseMetadata.setGroup(defaultGroup); + baseMetadata.setScreenshot(defaultScreenshot); + baseMetadata.setUser(defaultUser); - private LayoutRequestDTO createValidCreateLayoutRequest() { MetadataRequestDTO metadataRequest = new MetadataRequestDTO(); - metadataRequest.setName(defaultName); - metadataRequest.setGroup(defaultGroup); - metadataRequest.setScreenshot(defaultScreenshot); - metadataRequest.setUser(defaultUser); + metadataRequest.setBaseMetadata(baseMetadata); LayoutRequestDTO layoutRequest = new LayoutRequestDTO(); layoutRequest.setDefinition(defaultDefinition); layoutRequest.setMetadata(metadataRequest); + return layoutRequest; } } diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java index 079347059..3b8461b1a 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java @@ -1,19 +1,18 @@ package org.finos.vuu.layoutserver.service; -import java.util.Date; -import java.util.List; -import java.util.NoSuchElementException; -import org.finos.vuu.layoutserver.model.Metadata; - import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.util.List; +import java.util.NoSuchElementException; import java.util.Optional; import java.util.UUID; +import org.finos.vuu.layoutserver.model.BaseMetadata; import org.finos.vuu.layoutserver.model.Layout; +import org.finos.vuu.layoutserver.model.Metadata; import org.finos.vuu.layoutserver.repository.LayoutRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -42,18 +41,21 @@ class LayoutServiceTest { public void setup() { layoutId = UUID.randomUUID(); UUID metadataId = UUID.randomUUID(); - layout = new Layout(); + BaseMetadata baseMetadata = new BaseMetadata(); metadata = new Metadata(); + layout = new Layout(); + + baseMetadata.setName("Test Name"); + baseMetadata.setGroup("Test Group"); + baseMetadata.setScreenshot("Test Screenshot"); + baseMetadata.setUser("Test User"); + + metadata.setId(metadataId); + metadata.setBaseMetadata(baseMetadata); + layout.setId(layoutId); layout.setDefinition(""); layout.setMetadata(metadata); - metadata.setId(metadataId); - metadata.setName(""); - metadata.setGroup(""); - metadata.setScreenshot(""); - metadata.setUser(""); - metadata.setCreated(new Date()); - metadata.setUpdated(new Date()); } @Test @@ -90,7 +92,8 @@ void updateLayout_layoutExists_callsRepository() { void updateLayout_layoutDoesNotExist_throwsNoSuchElementException() { when(layoutRepository.findById(layoutId)).thenReturn(Optional.empty()); - assertThrows(NoSuchElementException.class, () -> layoutService.updateLayout(layoutId, layout)); + assertThrows(NoSuchElementException.class, + () -> layoutService.updateLayout(layoutId, layout)); } @Test From e36c635d77e6c9448f32d5a2c130db92625eb05b Mon Sep 17 00:00:00 2001 From: cfisher-scottlogic Date: Mon, 23 Oct 2023 16:27:41 +0100 Subject: [PATCH 45/70] VUU25: Fix exception not being resolved due to invalid parameters --- .../vuu/layoutserver/controller/GlobalExceptionHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/GlobalExceptionHandler.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/GlobalExceptionHandler.java index a41a76ffa..9c4ba025e 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/GlobalExceptionHandler.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/GlobalExceptionHandler.java @@ -17,7 +17,7 @@ public ResponseEntity handleNotFound(NoSuchElementException ex) { } @ExceptionHandler({MethodArgumentNotValidException.class, MethodArgumentTypeMismatchException.class}) - public ResponseEntity handleBadRequest(MethodArgumentNotValidException ex) { + public ResponseEntity handleBadRequest(Exception ex) { return new ResponseEntity<>(ex.getMessage(), org.springframework.http.HttpStatus.BAD_REQUEST); } } From 436d437b974304a0dd32b0bf8cd7e80384589f23 Mon Sep 17 00:00:00 2001 From: Cara Fisher Date: Mon, 23 Oct 2023 17:20:09 +0100 Subject: [PATCH 46/70] VUU25: Add response body content message assertion for non-200 test requests --- .../controller/GlobalExceptionHandler.java | 12 +- .../integration/LayoutIntegrationTest.java | 107 ++++++++++++++++-- 2 files changed, 106 insertions(+), 13 deletions(-) diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/GlobalExceptionHandler.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/GlobalExceptionHandler.java index 9c4ba025e..5aa509079 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/GlobalExceptionHandler.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/GlobalExceptionHandler.java @@ -1,13 +1,13 @@ package org.finos.vuu.layoutserver.controller; +import java.util.NoSuchElementException; import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; -import java.util.NoSuchElementException; - @ControllerAdvice public class GlobalExceptionHandler { @@ -16,8 +16,12 @@ public ResponseEntity handleNotFound(NoSuchElementException ex) { return new ResponseEntity<>(ex.getMessage(), org.springframework.http.HttpStatus.NOT_FOUND); } - @ExceptionHandler({MethodArgumentNotValidException.class, MethodArgumentTypeMismatchException.class}) + @ExceptionHandler({ + MethodArgumentNotValidException.class, + MethodArgumentTypeMismatchException.class, + HttpMessageNotReadableException.class}) public ResponseEntity handleBadRequest(Exception ex) { - return new ResponseEntity<>(ex.getMessage(), org.springframework.http.HttpStatus.BAD_REQUEST); + return new ResponseEntity<>(ex.getMessage(), + org.springframework.http.HttpStatus.BAD_REQUEST); } } diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java index e9bbd61fd..764254cf6 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java @@ -1,11 +1,14 @@ package org.finos.vuu.layoutserver.integration; import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.anyOf; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -89,7 +92,12 @@ void getLayout_validIDButLayoutDoesNotExist_returns404() throws Exception { void getLayout_invalidId_returns400() throws Exception { String layoutID = "invalidUUID"; - mockMvc.perform(get("/layouts/{id}", layoutID)).andExpect(status().isBadRequest()); + mockMvc.perform(get("/layouts/{id}", layoutID)) + .andExpect(status().isBadRequest()) + .andExpect(content().string( + "Failed to convert value of type 'java.lang.String' to required type 'java.util" + + ".UUID'; nested exception is java.lang.IllegalArgumentException: Invalid " + + "UUID string: invalidUUID")); } @Test @@ -167,7 +175,15 @@ void createLayout_invalidLayout_returns400() throws Exception { mockMvc.perform(post("/layouts") .content(invalidLayout) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()); + .andExpect(status().isBadRequest()) + .andExpect(content().string( + "JSON parse error: Unrecognized token 'invalidLayout': was expecting (JSON " + + "String, Number, Array, Object or token 'null', 'true' or 'false'); nested " + + "exception is com.fasterxml.jackson.core.JsonParseException: Unrecognized " + + "token 'invalidLayout': was expecting (JSON String, Number, Array, Object " + + "or token 'null', 'true' or 'false')\n" + + " at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream);" + + " line: 1, column: 14]")); } @Test @@ -179,7 +195,18 @@ void createLayout_validLayoutButInvalidMetadata_returns400AndDoesNotCreateLayout mockMvc.perform(post("/layouts") .content(objectMapper.writeValueAsString(layoutRequest)) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()); + .andExpect(status().isBadRequest()) + .andExpect(content().string( + "Validation failed for argument [0] in public org.finos.vuu.layoutserver.dto" + + ".response.LayoutResponseDTO org.finos.vuu.layoutserver.controller" + + ".LayoutController.createLayout(org.finos.vuu.layoutserver.dto.request" + + ".LayoutRequestDTO): [Field error in object 'layoutRequestDTO' on field " + + "'metadata': rejected value [null]; codes [NotNull.layoutRequestDTO" + + ".metadata,NotNull.metadata,NotNull.org.finos.vuu.layoutserver.dto.request" + + ".MetadataRequestDTO,NotNull]; arguments [org.springframework.context" + + ".support.DefaultMessageSourceResolvable: codes [layoutRequestDTO.metadata," + + "metadata]; arguments []; default message [metadata]]; default message " + + "[Please provide valid metadata]] ")); assertThat(layoutRepository.findAll()).isEmpty(); } @@ -221,7 +248,48 @@ void updateLayout_invalidRequestBodyDefinitionIsBlankAndMetadataIsNull_returns40 mockMvc.perform(put("/layouts/{id}", layout.getId()) .content(objectMapper.writeValueAsString(request)) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()); + .andExpect(status().isBadRequest()) + .andExpect(content().string(anyOf( + equalTo( + "Validation failed for argument [1] in public void org.finos.vuu.layoutserver" + + ".controller.LayoutController.updateLayout(java.util.UUID,org.finos.vuu" + + ".layoutserver.dto.request.LayoutRequestDTO) with 2 errors: [Field " + + "error in" + + " object 'layoutRequestDTO' on field 'metadata': rejected value [null]; " + + "codes [NotNull.layoutRequestDTO.metadata,NotNull.metadata,NotNull.org" + + ".finos.vuu.layoutserver.dto.request.MetadataRequestDTO,NotNull]; " + + "arguments" + + " [org.springframework.context.support.DefaultMessageSourceResolvable: " + + "codes [layoutRequestDTO.metadata,metadata]; arguments []; default " + + "message " + + "[metadata]]; default message [Please provide valid metadata]] [Field " + + "error" + + " in object 'layoutRequestDTO' on field 'definition': rejected value []; " + + "codes [NotBlank.layoutRequestDTO.definition,NotBlank.definition,NotBlank" + + ".java.lang.String,NotBlank]; arguments [org.springframework.context" + + ".support.DefaultMessageSourceResolvable: codes [layoutRequestDTO" + + ".definition,definition]; arguments []; default message [definition]]; " + + "default message [Please provide a valid definition]] "), + equalTo( + "Validation failed for argument [1] in public void org.finos.vuu.layoutserver" + + ".controller.LayoutController.updateLayout(java.util.UUID,org.finos.vuu" + + ".layoutserver.dto.request.LayoutRequestDTO) with 2 errors: [Field " + + "error in" + + " object 'layoutRequestDTO' on field 'metadata': rejected value [null]; " + + "codes [NotNull.layoutRequestDTO.metadata,NotNull.metadata,NotNull.org" + + ".finos.vuu.layoutserver.dto.request.MetadataRequestDTO,NotNull]; " + + "arguments" + + " [org.springframework.context.support.DefaultMessageSourceResolvable: " + + "codes [layoutRequestDTO.metadata,metadata]; arguments []; default " + + "message " + + "[metadata]]; default message [Please provide valid metadata]] [Field " + + "error" + + " in object 'layoutRequestDTO' on field 'definition': rejected value []; " + + "codes [NotBlank.layoutRequestDTO.definition,NotBlank.definition,NotBlank" + + ".java.lang.String,NotBlank]; arguments [org.springframework.context" + + ".support.DefaultMessageSourceResolvable: codes [layoutRequestDTO" + + ".definition,definition]; arguments []; default message [definition]]; " + + "default message [Please provide a valid definition]] ")))); assertThat(layoutRepository.findById(layout.getId()).orElseThrow()).isEqualTo(layout); } @@ -235,9 +303,21 @@ void updateLayout_invalidRequestBodyUnexpectedFormat_returns400AndLayoutDoesNotC mockMvc.perform(put("/layouts/{id}", layout.getId()) .content(objectMapper.writeValueAsString(request)) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()); - - assertThat(layoutRepository.findById(layout.getId()).orElseThrow()).isEqualTo(layout); + .andExpect(status().isBadRequest()) + .andExpect(content().string( + "JSON parse error: Cannot construct instance of `org.finos.vuu.layoutserver.dto" + + ".request.LayoutRequestDTO` (although at least one Creator exists): no " + + "String-argument constructor/factory method to deserialize from String " + + "value ('invalidRequest'); nested exception is com.fasterxml.jackson" + + ".databind.exc.MismatchedInputException: Cannot construct instance of `org" + + ".finos.vuu.layoutserver.dto.request.LayoutRequestDTO` (although at least " + + "one Creator exists): no String-argument constructor/factory method to " + + "deserialize from String value ('invalidRequest')\n" + + " at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream);" + + " line: 1, column: 1]")); + + assertThat(layoutRepository.findById(layout.getId()).orElseThrow()).isEqualTo( + layout); } @Test @@ -259,7 +339,11 @@ void updateLayout_invalidId_returns400() throws Exception { mockMvc.perform(put("/layouts/{id}", layoutID) .content(objectMapper.writeValueAsString(layoutRequest)) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()); + .andExpect(status().isBadRequest()) + .andExpect(content().string( + "Failed to convert value of type 'java.lang.String' to required type 'java.util" + + ".UUID'; nested exception is java.lang.IllegalArgumentException: Invalid " + + "UUID string: invalidUUID")); } @Test @@ -284,7 +368,12 @@ void deleteLayout_validIDLayoutDoesNotExist_returnsNotFound() throws Exception { void deleteLayout_invalidId_returns400() throws Exception { String layoutID = "invalidUUID"; - mockMvc.perform(delete("/layouts/{id}", layoutID)).andExpect(status().isBadRequest()); + mockMvc.perform(delete("/layouts/{id}", layoutID)) + .andExpect(status().isBadRequest()) + .andExpect(content().string( + "Failed to convert value of type 'java.lang.String' to required type 'java.util" + + ".UUID'; nested exception is java.lang.IllegalArgumentException: Invalid " + + "UUID string: invalidUUID")); } private Layout createDefaultLayoutInDatabase() { From 333a244842e36baed6b7cd6e4cb778aca2180fc8 Mon Sep 17 00:00:00 2001 From: Cara Fisher Date: Tue, 24 Oct 2023 10:35:12 +0100 Subject: [PATCH 47/70] VUU25: Cleanup conflict resolution mistakes from merge commit --- .../controller/LayoutController.java | 2 +- .../vuu/layoutserver/dto/MetadataDTO.java | 21 ------------------- 2 files changed, 1 insertion(+), 22 deletions(-) delete mode 100644 layout-server/src/main/java/org/finos/vuu/layoutserver/dto/MetadataDTO.java diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java index 9b10f46e9..265889857 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java @@ -68,7 +68,7 @@ public List getMetadata() { */ @ResponseStatus(HttpStatus.CREATED) @PostMapping - public LayoutResponseDTO createLayout(@RequestBody @Valid LayoutRequestDTO layoutToCreate) { + public LayoutResponseDTO createLayout(@Valid @RequestBody LayoutRequestDTO layoutToCreate) { Layout layout = mapper.map(layoutToCreate, Layout.class); Layout createdLayout = layoutService.getLayout(layoutService.createLayout(layout)); diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/MetadataDTO.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/MetadataDTO.java deleted file mode 100644 index 23f82a691..000000000 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/MetadataDTO.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.finos.vuu.layoutserver.dto; - -public interface MetadataDTO { - - String getName(); - - void setName(String name); - - String getGroup(); - - void setGroup(String group); - - String getScreenshot(); - - void setScreenshot(String screenshot); - - String getUser(); - - void setUser(String user); - -} From 42515027c003bd7aaac4b5f65b65ca3c90f1f9b2 Mon Sep 17 00:00:00 2001 From: Cara Fisher Date: Tue, 24 Oct 2023 12:04:58 +0100 Subject: [PATCH 48/70] VUU25: Modify exception handling for deleteLayout --- .../finos/vuu/layoutserver/controller/LayoutController.java | 2 -- .../org/finos/vuu/layoutserver/service/LayoutService.java | 6 +++++- .../vuu/layoutserver/controller/LayoutControllerTest.java | 1 - 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java index 265889857..3de15fa14 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java @@ -98,8 +98,6 @@ public void updateLayout(@PathVariable UUID id, @Valid @RequestBody LayoutReques @ResponseStatus(HttpStatus.NO_CONTENT) @DeleteMapping("/{id}") public void deleteLayout(@PathVariable UUID id) { - // Generate a 404 if layout doesn't exist - layoutService.getLayout(id); layoutService.deleteLayout(id); } } diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java index 7dd105e59..f813d8ecd 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java @@ -50,6 +50,10 @@ public void updateLayout(UUID layoutId, Layout newLayout) { } public void deleteLayout(UUID id) { - layoutRepository.deleteById(id); + try { + layoutRepository.deleteById(id); + } catch (Exception e) { + throw new NoSuchElementException("Layout with ID '" + id + "' not found"); + } } } diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java index bb57395da..04cb33c0a 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java @@ -151,7 +151,6 @@ void updateLayout_callsLayoutService() { void deleteLayout_callsLayoutService() { layoutController.deleteLayout(validLayoutId); - verify(layoutService).getLayout(validLayoutId); verify(layoutService).deleteLayout(validLayoutId); } From 7dd8233feeb0442ac1db22ded9df90f86f54b6e0 Mon Sep 17 00:00:00 2001 From: Cara Fisher Date: Wed, 25 Oct 2023 18:10:29 +0100 Subject: [PATCH 49/70] VUU25: Improve non-200 response messages --- .../controller/GlobalExceptionHandler.java | 29 +++++++--- .../dto/request/LayoutRequestDTO.java | 4 +- .../integration/LayoutIntegrationTest.java | 54 ++----------------- 3 files changed, 28 insertions(+), 59 deletions(-) diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/GlobalExceptionHandler.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/GlobalExceptionHandler.java index 5aa509079..f543ba473 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/GlobalExceptionHandler.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/GlobalExceptionHandler.java @@ -1,6 +1,8 @@ package org.finos.vuu.layoutserver.controller; +import java.util.List; import java.util.NoSuchElementException; +import java.util.stream.Collectors; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.web.bind.MethodArgumentNotValidException; @@ -12,15 +14,30 @@ public class GlobalExceptionHandler { @ExceptionHandler(NoSuchElementException.class) - public ResponseEntity handleNotFound(NoSuchElementException ex) { + public ResponseEntity handleNotFound(NoSuchElementException ex) { return new ResponseEntity<>(ex.getMessage(), org.springframework.http.HttpStatus.NOT_FOUND); } - @ExceptionHandler({ - MethodArgumentNotValidException.class, - MethodArgumentTypeMismatchException.class, - HttpMessageNotReadableException.class}) - public ResponseEntity handleBadRequest(Exception ex) { + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException ex) { + List errors = ex.getFieldErrors().stream() + .map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage()) + .collect(Collectors.toList()); + + return new ResponseEntity<>(errors.toString(), + org.springframework.http.HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public ResponseEntity handleMethodArgumentTypeMismatchException( + MethodArgumentTypeMismatchException ex) { + return new ResponseEntity<>(ex.getMessage(), + org.springframework.http.HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(HttpMessageNotReadableException.class) + public ResponseEntity handleHttpMessageNotReadableException( + HttpMessageNotReadableException ex) { return new ResponseEntity<>(ex.getMessage(), org.springframework.http.HttpStatus.BAD_REQUEST); } diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/LayoutRequestDTO.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/LayoutRequestDTO.java index 8b93e6343..60d50af11 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/LayoutRequestDTO.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/request/LayoutRequestDTO.java @@ -13,10 +13,10 @@ public class LayoutRequestDTO { * components) */ @JsonProperty(value = "definition", required = true) - @NotBlank(message = "Please provide a valid definition") + @NotBlank(message = "Definition must not be blank") private String definition; @JsonProperty(value = "metadata", required = true) - @NotNull(message = "Please provide valid metadata") + @NotNull(message = "Metadata must not be null") private MetadataRequestDTO metadata; } diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java index 764254cf6..e72518497 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java @@ -167,7 +167,6 @@ void createLayout_validLayout_returnsCreatedLayoutAndLayoutIsPersisted() .isEqualTo(layoutRequest.getMetadata().getBaseMetadata().getUser()); } - @Test void createLayout_invalidLayout_returns400() throws Exception { String invalidLayout = "invalidLayout"; @@ -197,16 +196,7 @@ void createLayout_validLayoutButInvalidMetadata_returns400AndDoesNotCreateLayout .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isBadRequest()) .andExpect(content().string( - "Validation failed for argument [0] in public org.finos.vuu.layoutserver.dto" - + ".response.LayoutResponseDTO org.finos.vuu.layoutserver.controller" - + ".LayoutController.createLayout(org.finos.vuu.layoutserver.dto.request" - + ".LayoutRequestDTO): [Field error in object 'layoutRequestDTO' on field " - + "'metadata': rejected value [null]; codes [NotNull.layoutRequestDTO" - + ".metadata,NotNull.metadata,NotNull.org.finos.vuu.layoutserver.dto.request" - + ".MetadataRequestDTO,NotNull]; arguments [org.springframework.context" - + ".support.DefaultMessageSourceResolvable: codes [layoutRequestDTO.metadata," - + "metadata]; arguments []; default message [metadata]]; default message " - + "[Please provide valid metadata]] ")); + "[metadata: Metadata must not be null]")); assertThat(layoutRepository.findAll()).isEmpty(); } @@ -250,46 +240,8 @@ void updateLayout_invalidRequestBodyDefinitionIsBlankAndMetadataIsNull_returns40 .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isBadRequest()) .andExpect(content().string(anyOf( - equalTo( - "Validation failed for argument [1] in public void org.finos.vuu.layoutserver" - + ".controller.LayoutController.updateLayout(java.util.UUID,org.finos.vuu" - + ".layoutserver.dto.request.LayoutRequestDTO) with 2 errors: [Field " - + "error in" - + " object 'layoutRequestDTO' on field 'metadata': rejected value [null]; " - + "codes [NotNull.layoutRequestDTO.metadata,NotNull.metadata,NotNull.org" - + ".finos.vuu.layoutserver.dto.request.MetadataRequestDTO,NotNull]; " - + "arguments" - + " [org.springframework.context.support.DefaultMessageSourceResolvable: " - + "codes [layoutRequestDTO.metadata,metadata]; arguments []; default " - + "message " - + "[metadata]]; default message [Please provide valid metadata]] [Field " - + "error" - + " in object 'layoutRequestDTO' on field 'definition': rejected value []; " - + "codes [NotBlank.layoutRequestDTO.definition,NotBlank.definition,NotBlank" - + ".java.lang.String,NotBlank]; arguments [org.springframework.context" - + ".support.DefaultMessageSourceResolvable: codes [layoutRequestDTO" - + ".definition,definition]; arguments []; default message [definition]]; " - + "default message [Please provide a valid definition]] "), - equalTo( - "Validation failed for argument [1] in public void org.finos.vuu.layoutserver" - + ".controller.LayoutController.updateLayout(java.util.UUID,org.finos.vuu" - + ".layoutserver.dto.request.LayoutRequestDTO) with 2 errors: [Field " - + "error in" - + " object 'layoutRequestDTO' on field 'metadata': rejected value [null]; " - + "codes [NotNull.layoutRequestDTO.metadata,NotNull.metadata,NotNull.org" - + ".finos.vuu.layoutserver.dto.request.MetadataRequestDTO,NotNull]; " - + "arguments" - + " [org.springframework.context.support.DefaultMessageSourceResolvable: " - + "codes [layoutRequestDTO.metadata,metadata]; arguments []; default " - + "message " - + "[metadata]]; default message [Please provide valid metadata]] [Field " - + "error" - + " in object 'layoutRequestDTO' on field 'definition': rejected value []; " - + "codes [NotBlank.layoutRequestDTO.definition,NotBlank.definition,NotBlank" - + ".java.lang.String,NotBlank]; arguments [org.springframework.context" - + ".support.DefaultMessageSourceResolvable: codes [layoutRequestDTO" - + ".definition,definition]; arguments []; default message [definition]]; " - + "default message [Please provide a valid definition]] ")))); + equalTo("[definition: Definition must not be blank, metadata: Metadata must not be null]"), + equalTo("[metadata: Metadata must not be null, definition: Definition must not be blank]")))); assertThat(layoutRepository.findById(layout.getId()).orElseThrow()).isEqualTo(layout); } From 32372f36eb00b770b47ab999c44bb457b4284e46 Mon Sep 17 00:00:00 2001 From: Cara Fisher Date: Tue, 31 Oct 2023 11:32:21 +0000 Subject: [PATCH 50/70] VUU86: ID -> Id in test names --- .../vuu/layoutserver/integration/LayoutIntegrationTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java index e72518497..1b9627e76 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java @@ -202,7 +202,7 @@ void createLayout_validLayoutButInvalidMetadata_returns400AndDoesNotCreateLayout } @Test - void updateLayout_validIDAndValidRequest_returns204AndLayoutHasChanged() throws Exception { + void updateLayout_validIdAndValidRequest_returns204AndLayoutHasChanged() throws Exception { Layout layout = createDefaultLayoutInDatabase(); LayoutRequestDTO layoutRequest = createValidLayoutRequest(); @@ -299,7 +299,7 @@ void updateLayout_invalidId_returns400() throws Exception { } @Test - void deleteLayout_validIDLayoutExists_returnsSuccessAndLayoutIsDeleted() throws Exception { + void deleteLayout_validIdLayoutExists_returnsSuccessAndLayoutIsDeleted() throws Exception { Layout layout = createDefaultLayoutInDatabase(); mockMvc.perform(get("/layouts/{id}", layout.getId())).andExpect(status().isOk()); @@ -310,7 +310,7 @@ void deleteLayout_validIDLayoutExists_returnsSuccessAndLayoutIsDeleted() throws } @Test - void deleteLayout_validIDLayoutDoesNotExist_returnsNotFound() throws Exception { + void deleteLayout_validIdLayoutDoesNotExist_returnsNotFound() throws Exception { UUID layoutID = UUID.randomUUID(); mockMvc.perform(delete("/layouts/{id}", layoutID)).andExpect(status().isNotFound()); From bf7278e3202694a62712feabaf406c379640e73a Mon Sep 17 00:00:00 2001 From: Cara Fisher Date: Tue, 31 Oct 2023 18:07:57 +0000 Subject: [PATCH 51/70] VUU86: Add assertion that no metadata is saved upon invalid create layout request --- .../vuu/layoutserver/integration/LayoutIntegrationTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java index 1b9627e76..b61d3e8d1 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java @@ -199,6 +199,7 @@ void createLayout_validLayoutButInvalidMetadata_returns400AndDoesNotCreateLayout "[metadata: Metadata must not be null]")); assertThat(layoutRepository.findAll()).isEmpty(); + assertThat(metadataRepository.findAll()).isEmpty(); } @Test From 45ff96496048a1aee5b42cd1547c8e50e1f8a1ac Mon Sep 17 00:00:00 2001 From: Cara Fisher Date: Tue, 31 Oct 2023 18:12:53 +0000 Subject: [PATCH 52/70] VUU86: Add test case for blank layout definition in create layout request --- .../integration/LayoutIntegrationTest.java | 41 +++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java index b61d3e8d1..b45c679eb 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java @@ -168,26 +168,24 @@ void createLayout_validLayout_returnsCreatedLayoutAndLayoutIsPersisted() } @Test - void createLayout_invalidLayout_returns400() throws Exception { - String invalidLayout = "invalidLayout"; + void createLayout_invalidRequestBodyDefinitionsIsBlank_returns400AndDoesNotCreateLayout() + throws Exception { + LayoutRequestDTO layoutRequest = createValidLayoutRequest(); + layoutRequest.setDefinition(""); mockMvc.perform(post("/layouts") - .content(invalidLayout) + .content(objectMapper.writeValueAsString(layoutRequest)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isBadRequest()) .andExpect(content().string( - "JSON parse error: Unrecognized token 'invalidLayout': was expecting (JSON " - + "String, Number, Array, Object or token 'null', 'true' or 'false'); nested " - + "exception is com.fasterxml.jackson.core.JsonParseException: Unrecognized " - + "token 'invalidLayout': was expecting (JSON String, Number, Array, Object " - + "or token 'null', 'true' or 'false')\n" - + " at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream);" - + " line: 1, column: 14]")); + "[definition: Definition must not be blank]")); + + assertThat(layoutRepository.findAll()).isEmpty(); + assertThat(metadataRepository.findAll()).isEmpty(); } @Test - void createLayout_validLayoutButInvalidMetadata_returns400AndDoesNotCreateLayout() - throws Exception { + void createLayout_invalidRequestBodyMetadataIsNull_returns400AndDoesNotCreateLayout() throws Exception { LayoutRequestDTO layoutRequest = createValidLayoutRequest(); layoutRequest.setMetadata(null); @@ -202,6 +200,25 @@ void createLayout_validLayoutButInvalidMetadata_returns400AndDoesNotCreateLayout assertThat(metadataRepository.findAll()).isEmpty(); } + + @Test + void createLayout_invalidRequestBodyUnexpectedFormat_returns400() throws Exception { + String invalidLayout = "invalidLayout"; + + mockMvc.perform(post("/layouts") + .content(invalidLayout) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andExpect(content().string( + "JSON parse error: Unrecognized token 'invalidLayout': was expecting (JSON " + + "String, Number, Array, Object or token 'null', 'true' or 'false'); nested " + + "exception is com.fasterxml.jackson.core.JsonParseException: Unrecognized " + + "token 'invalidLayout': was expecting (JSON String, Number, Array, Object " + + "or token 'null', 'true' or 'false')\n" + + " at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream);" + + " line: 1, column: 14]")); + } + @Test void updateLayout_validIdAndValidRequest_returns204AndLayoutHasChanged() throws Exception { Layout layout = createDefaultLayoutInDatabase(); From 71401dc968b802a331a7338627758080f1a6d676 Mon Sep 17 00:00:00 2001 From: Cara Fisher Date: Wed, 1 Nov 2023 11:30:49 +0000 Subject: [PATCH 53/70] VUU86: Refactor odd formatting --- .../java/org/finos/vuu/layoutserver/model/Metadata.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java index 4a01354ee..65340ad0f 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java @@ -1,18 +1,16 @@ package org.finos.vuu.layoutserver.model; -import javax.persistence.Column; import java.util.Date; import java.util.UUID; +import javax.persistence.Column; import javax.persistence.Embedded; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; - -import lombok.Data; - import lombok.AllArgsConstructor; import lombok.Builder; +import lombok.Data; import lombok.NoArgsConstructor; @Data From fc3cd391e14234de0cc3b6f1c49adbe5d488fec7 Mon Sep 17 00:00:00 2001 From: Cara Fisher Date: Wed, 1 Nov 2023 11:45:57 +0000 Subject: [PATCH 54/70] VUU86: Remove `LayoutService.getMetadata()` --- .../layoutserver/service/LayoutService.java | 7 +------ .../service/LayoutServiceTest.java | 20 ++++--------------- 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java index f813d8ecd..ada5d3b9a 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java @@ -1,9 +1,8 @@ package org.finos.vuu.layoutserver.service; import java.util.Date; -import java.util.UUID; -import java.util.List; import java.util.NoSuchElementException; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.finos.vuu.layoutserver.model.Layout; import org.finos.vuu.layoutserver.model.Metadata; @@ -26,10 +25,6 @@ public Layout getLayoutByMetadataId(UUID id) { return layoutRepository.findLayoutByMetadataId(id); } - public List getMetadata() { - return metadataService.getMetadata(); - } - public UUID createLayout(Layout layout) { return layoutRepository.save(layout).getId(); } diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java index 3b8461b1a..1f4cba754 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java @@ -6,7 +6,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import java.util.List; import java.util.NoSuchElementException; import java.util.Optional; import java.util.UUID; @@ -27,14 +26,10 @@ class LayoutServiceTest { @Mock private LayoutRepository layoutRepository; - @Mock - private MetadataService metadataService; - @InjectMocks private LayoutService layoutService; private Layout layout; - private Metadata metadata; private UUID layoutId; @BeforeEach @@ -42,7 +37,7 @@ public void setup() { layoutId = UUID.randomUUID(); UUID metadataId = UUID.randomUUID(); BaseMetadata baseMetadata = new BaseMetadata(); - metadata = new Metadata(); + Metadata metadata = new Metadata(); layout = new Layout(); baseMetadata.setName("Test Name"); @@ -66,21 +61,14 @@ void getLayout_returnsLayout() { } @Test - void getMetadata_returnsMetadata() { - when(metadataService.getMetadata()).thenReturn(List.of(metadata)); - - assertThat(layoutService.getMetadata()).isEqualTo(List.of(metadata)); - } - - @Test - void createLayout() { + void createLayout_anyLayout_returnsLayoutId() { when(layoutRepository.save(layout)).thenReturn(layout); assertThat(layoutService.createLayout(layout)).isEqualTo(layoutId); } @Test - void updateLayout_layoutExists_callsRepository() { + void updateLayout_layoutExists_callsRepositorySave() { when(layoutRepository.findById(layoutId)).thenReturn(Optional.of(layout)); layoutService.updateLayout(layoutId, layout); @@ -97,7 +85,7 @@ void updateLayout_layoutDoesNotExist_throwsNoSuchElementException() { } @Test - void deleteLayout_callsRepository() { + void deleteLayout_anyUUID_callsRepositoryDeleteById() { layoutService.deleteLayout(layoutId); verify(layoutRepository, times(1)).deleteById(layoutId); From 44d9719668602a2b7ea576d916ad47364ac090cf Mon Sep 17 00:00:00 2001 From: Cara Fisher Date: Wed, 1 Nov 2023 11:53:00 +0000 Subject: [PATCH 55/70] VUU86: Move GlobalExceptionHandler to new `exceptions` package --- .../{controller => exceptions}/GlobalExceptionHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename layout-server/src/main/java/org/finos/vuu/layoutserver/{controller => exceptions}/GlobalExceptionHandler.java (97%) diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/GlobalExceptionHandler.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/exceptions/GlobalExceptionHandler.java similarity index 97% rename from layout-server/src/main/java/org/finos/vuu/layoutserver/controller/GlobalExceptionHandler.java rename to layout-server/src/main/java/org/finos/vuu/layoutserver/exceptions/GlobalExceptionHandler.java index f543ba473..1418b2dc0 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/GlobalExceptionHandler.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/exceptions/GlobalExceptionHandler.java @@ -1,4 +1,4 @@ -package org.finos.vuu.layoutserver.controller; +package org.finos.vuu.layoutserver.exceptions; import java.util.List; import java.util.NoSuchElementException; From 510900d10f2e384e421a6961f16a78d1705d517c Mon Sep 17 00:00:00 2001 From: Cara Fisher Date: Wed, 1 Nov 2023 12:09:04 +0000 Subject: [PATCH 56/70] VUU86: Create generic `handleBadRequest` exception handler --- .../exceptions/GlobalExceptionHandler.java | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/exceptions/GlobalExceptionHandler.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/exceptions/GlobalExceptionHandler.java index 1418b2dc0..b9838c681 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/exceptions/GlobalExceptionHandler.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/exceptions/GlobalExceptionHandler.java @@ -15,30 +15,26 @@ public class GlobalExceptionHandler { @ExceptionHandler(NoSuchElementException.class) public ResponseEntity handleNotFound(NoSuchElementException ex) { - return new ResponseEntity<>(ex.getMessage(), org.springframework.http.HttpStatus.NOT_FOUND); + return new ResponseEntity<>(ex.getMessage(), + org.springframework.http.HttpStatus.NOT_FOUND); + } + + @ExceptionHandler({ + HttpMessageNotReadableException.class, + MethodArgumentTypeMismatchException.class}) + public ResponseEntity handleBadRequest(Exception ex) { + return new ResponseEntity<>(ex.getMessage(), + org.springframework.http.HttpStatus.BAD_REQUEST); } @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException ex) { - List errors = ex.getFieldErrors().stream() + List errors = ex.getFieldErrors() + .stream() .map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage()) .collect(Collectors.toList()); return new ResponseEntity<>(errors.toString(), org.springframework.http.HttpStatus.BAD_REQUEST); } - - @ExceptionHandler(MethodArgumentTypeMismatchException.class) - public ResponseEntity handleMethodArgumentTypeMismatchException( - MethodArgumentTypeMismatchException ex) { - return new ResponseEntity<>(ex.getMessage(), - org.springframework.http.HttpStatus.BAD_REQUEST); - } - - @ExceptionHandler(HttpMessageNotReadableException.class) - public ResponseEntity handleHttpMessageNotReadableException( - HttpMessageNotReadableException ex) { - return new ResponseEntity<>(ex.getMessage(), - org.springframework.http.HttpStatus.BAD_REQUEST); - } } From 474c23e4820e5fb6ce30703df3446f5ba4726161 Mon Sep 17 00:00:00 2001 From: Cara Fisher Date: Wed, 1 Nov 2023 12:26:28 +0000 Subject: [PATCH 57/70] VUU86: Refactor test variables as constants --- .../controller/LayoutControllerTest.java | 53 ++++++++++--------- .../integration/LayoutIntegrationTest.java | 50 ++++++++--------- .../service/LayoutServiceTest.java | 28 +++++----- 3 files changed, 64 insertions(+), 67 deletions(-) diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java index 04cb33c0a..2734e4200 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java @@ -29,6 +29,15 @@ @ExtendWith(MockitoExtension.class) class LayoutControllerTest { + private static final String LAYOUT_DEFINITION = "Test Definition"; + private static final String LAYOUT_GROUP = "Test Group"; + private static final String LAYOUT_NAME = "Test Layout"; + private static final String LAYOUT_SCREENSHOT = "Test Screenshot"; + private static final String LAYOUT_USER = "Test User"; + private static final UUID VALID_LAYOUT_ID = UUID.randomUUID(); + private static final UUID VALID_METADATA_ID = UUID.randomUUID(); + private static final UUID DOES_NOT_EXIST_LAYOUT_ID = UUID.randomUUID(); + @Mock private LayoutService layoutService; @@ -41,8 +50,6 @@ class LayoutControllerTest { @InjectMocks private LayoutController layoutController; - private UUID validLayoutId; - private UUID doesNotExistLayoutId; private Layout layout; private Metadata metadata; private BaseMetadata baseMetadata; @@ -52,24 +59,19 @@ class LayoutControllerTest { @BeforeEach public void setup() { - validLayoutId = UUID.randomUUID(); - doesNotExistLayoutId = UUID.randomUUID(); - UUID metadataId = UUID.randomUUID(); - String layoutDefinition = "Test Definition"; - baseMetadata = new BaseMetadata(); - baseMetadata.setName("Test Layout"); - baseMetadata.setUser("Test User"); - baseMetadata.setGroup("Test Group"); - baseMetadata.setScreenshot("Test Screenshot"); + baseMetadata.setName(LAYOUT_NAME); + baseMetadata.setUser(LAYOUT_USER); + baseMetadata.setGroup(LAYOUT_GROUP); + baseMetadata.setScreenshot(LAYOUT_SCREENSHOT); metadata = new Metadata(); - metadata.setId(metadataId); + metadata.setId(VALID_METADATA_ID); metadata.setBaseMetadata(baseMetadata); layout = new Layout(); - layout.setId(validLayoutId); - layout.setDefinition(layoutDefinition); + layout.setId(VALID_LAYOUT_ID); + layout.setDefinition(LAYOUT_DEFINITION); layout.setMetadata(metadata); layoutRequest = new LayoutRequestDTO(); @@ -92,17 +94,17 @@ public void setup() { @Test void getLayout_layoutExists_returnsLayout() { - when(layoutService.getLayout(validLayoutId)).thenReturn(layout); - when(modelMapper.map(layout, LayoutResponseDTO.class)).thenReturn( - expectedLayoutResponse); - assertThat(layoutController.getLayout(validLayoutId)).isEqualTo(expectedLayoutResponse); + when(layoutService.getLayout(VALID_LAYOUT_ID)).thenReturn(layout); + when(modelMapper.map(layout, LayoutResponseDTO.class)).thenReturn(expectedLayoutResponse); + assertThat(layoutController.getLayout(VALID_LAYOUT_ID)).isEqualTo(expectedLayoutResponse); } @Test void getLayout_layoutDoesNotExist_throwsNotFoundAndReturns404() { - when(layoutService.getLayout(doesNotExistLayoutId)).thenThrow(NoSuchElementException.class); + when(layoutService.getLayout(DOES_NOT_EXIST_LAYOUT_ID)).thenThrow( + NoSuchElementException.class); assertThrows(NoSuchElementException.class, - () -> layoutController.getLayout(doesNotExistLayoutId)); + () -> layoutController.getLayout(DOES_NOT_EXIST_LAYOUT_ID)); } @Test @@ -131,8 +133,7 @@ void createLayout_validLayout_returnsCreatedLayout() { when(layoutService.getLayout(layout.getId())).thenReturn(layout); when(modelMapper.map(layout, LayoutResponseDTO.class)).thenReturn(expectedLayoutResponse); - assertThat(layoutController.createLayout(layoutRequest)) - .isEqualTo(expectedLayoutResponse); + assertThat(layoutController.createLayout(layoutRequest)).isEqualTo(expectedLayoutResponse); } @Test @@ -142,16 +143,16 @@ void updateLayout_callsLayoutService() { when(modelMapper.map(layoutRequest, Layout.class)).thenReturn(layout); - layoutController.updateLayout(validLayoutId, layoutRequest); + layoutController.updateLayout(VALID_LAYOUT_ID, layoutRequest); - verify(layoutService).updateLayout(validLayoutId, layout); + verify(layoutService).updateLayout(VALID_LAYOUT_ID, layout); } @Test void deleteLayout_callsLayoutService() { - layoutController.deleteLayout(validLayoutId); + layoutController.deleteLayout(VALID_LAYOUT_ID); - verify(layoutService).deleteLayout(validLayoutId); + verify(layoutService).deleteLayout(VALID_LAYOUT_ID); } private MetadataResponseDTO getMetadataResponseDTO() { diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java index b45c679eb..042206c77 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java @@ -22,7 +22,6 @@ import org.finos.vuu.layoutserver.model.Metadata; import org.finos.vuu.layoutserver.repository.LayoutRepository; import org.finos.vuu.layoutserver.repository.MetadataRepository; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -39,11 +38,11 @@ @ActiveProfiles("test") public class LayoutIntegrationTest { - private static String defaultDefinition; - private static String defaultName; - private static String defaultGroup; - private static String defaultScreenshot; - private static String defaultUser; + private static final String DEFAULT_LAYOUT_DEFINITION = "Default layout definition"; + private static final String DEFAULT_LAYOUT_NAME = "Default layout name"; + private static final String DEFAULT_LAYOUT_GROUP = "Default layout group"; + private static final String DEFAULT_LAYOUT_SCREENSHOT = "Default layout screenshot"; + private static final String DEFAULT_LAYOUT_USER = "Default layout user"; private final ObjectMapper objectMapper = new ObjectMapper(); @@ -54,14 +53,6 @@ public class LayoutIntegrationTest { @Autowired private MetadataRepository metadataRepository; - @BeforeAll - public static void setup() { - defaultDefinition = "Default layout definition"; - defaultName = "Default layout name"; - defaultGroup = "Default layout group"; - defaultScreenshot = "Default layout screenshot"; - defaultUser = "Default layout user"; - } @Test void getLayout_validIDAndLayoutExists_returns200WithLayout() throws Exception { @@ -185,7 +176,8 @@ void createLayout_invalidRequestBodyDefinitionsIsBlank_returns400AndDoesNotCreat } @Test - void createLayout_invalidRequestBodyMetadataIsNull_returns400AndDoesNotCreateLayout() throws Exception { + void createLayout_invalidRequestBodyMetadataIsNull_returns400AndDoesNotCreateLayout() + throws Exception { LayoutRequestDTO layoutRequest = createValidLayoutRequest(); layoutRequest.setMetadata(null); @@ -258,8 +250,12 @@ void updateLayout_invalidRequestBodyDefinitionIsBlankAndMetadataIsNull_returns40 .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isBadRequest()) .andExpect(content().string(anyOf( - equalTo("[definition: Definition must not be blank, metadata: Metadata must not be null]"), - equalTo("[metadata: Metadata must not be null, definition: Definition must not be blank]")))); + equalTo( + "[definition: Definition must not be blank, metadata: Metadata must not be " + + "null]"), + equalTo( + "[metadata: Metadata must not be null, definition: Definition must not be " + + "blank]")))); assertThat(layoutRepository.findById(layout.getId()).orElseThrow()).isEqualTo(layout); } @@ -351,14 +347,14 @@ private Layout createDefaultLayoutInDatabase() { Metadata metadata = new Metadata(); BaseMetadata baseMetadata = new BaseMetadata(); - baseMetadata.setName(defaultName); - baseMetadata.setGroup(defaultGroup); - baseMetadata.setScreenshot(defaultScreenshot); - baseMetadata.setUser(defaultUser); + baseMetadata.setName(DEFAULT_LAYOUT_NAME); + baseMetadata.setGroup(DEFAULT_LAYOUT_GROUP); + baseMetadata.setScreenshot(DEFAULT_LAYOUT_SCREENSHOT); + baseMetadata.setUser(DEFAULT_LAYOUT_USER); metadata.setBaseMetadata(baseMetadata); - layout.setDefinition(defaultDefinition); + layout.setDefinition(DEFAULT_LAYOUT_DEFINITION); layout.setMetadata(metadata); metadataRepository.save(metadata); @@ -372,16 +368,16 @@ private Layout createDefaultLayoutInDatabase() { private LayoutRequestDTO createValidLayoutRequest() { BaseMetadata baseMetadata = new BaseMetadata(); - baseMetadata.setName(defaultName); - baseMetadata.setGroup(defaultGroup); - baseMetadata.setScreenshot(defaultScreenshot); - baseMetadata.setUser(defaultUser); + baseMetadata.setName(DEFAULT_LAYOUT_NAME); + baseMetadata.setGroup(DEFAULT_LAYOUT_GROUP); + baseMetadata.setScreenshot(DEFAULT_LAYOUT_SCREENSHOT); + baseMetadata.setUser(DEFAULT_LAYOUT_USER); MetadataRequestDTO metadataRequest = new MetadataRequestDTO(); metadataRequest.setBaseMetadata(baseMetadata); LayoutRequestDTO layoutRequest = new LayoutRequestDTO(); - layoutRequest.setDefinition(defaultDefinition); + layoutRequest.setDefinition(DEFAULT_LAYOUT_DEFINITION); layoutRequest.setMetadata(metadataRequest); return layoutRequest; diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java index 1f4cba754..b87dbbac1 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java @@ -23,6 +23,9 @@ @ExtendWith(MockitoExtension.class) class LayoutServiceTest { + private static final UUID LAYOUT_ID = UUID.randomUUID(); + public static final UUID METADATA_ID = UUID.randomUUID(); + @Mock private LayoutRepository layoutRepository; @@ -30,12 +33,9 @@ class LayoutServiceTest { private LayoutService layoutService; private Layout layout; - private UUID layoutId; @BeforeEach public void setup() { - layoutId = UUID.randomUUID(); - UUID metadataId = UUID.randomUUID(); BaseMetadata baseMetadata = new BaseMetadata(); Metadata metadata = new Metadata(); layout = new Layout(); @@ -45,49 +45,49 @@ public void setup() { baseMetadata.setScreenshot("Test Screenshot"); baseMetadata.setUser("Test User"); - metadata.setId(metadataId); + metadata.setId(METADATA_ID); metadata.setBaseMetadata(baseMetadata); - layout.setId(layoutId); + layout.setId(LAYOUT_ID); layout.setDefinition(""); layout.setMetadata(metadata); } @Test void getLayout_returnsLayout() { - when(layoutRepository.findById(layoutId)).thenReturn(Optional.of(layout)); + when(layoutRepository.findById(LAYOUT_ID)).thenReturn(Optional.of(layout)); - assertThat(layoutService.getLayout(layoutId)).isEqualTo(layout); + assertThat(layoutService.getLayout(LAYOUT_ID)).isEqualTo(layout); } @Test void createLayout_anyLayout_returnsLayoutId() { when(layoutRepository.save(layout)).thenReturn(layout); - assertThat(layoutService.createLayout(layout)).isEqualTo(layoutId); + assertThat(layoutService.createLayout(layout)).isEqualTo(LAYOUT_ID); } @Test void updateLayout_layoutExists_callsRepositorySave() { - when(layoutRepository.findById(layoutId)).thenReturn(Optional.of(layout)); + when(layoutRepository.findById(LAYOUT_ID)).thenReturn(Optional.of(layout)); - layoutService.updateLayout(layoutId, layout); + layoutService.updateLayout(LAYOUT_ID, layout); verify(layoutRepository, times(1)).save(layout); } @Test void updateLayout_layoutDoesNotExist_throwsNoSuchElementException() { - when(layoutRepository.findById(layoutId)).thenReturn(Optional.empty()); + when(layoutRepository.findById(LAYOUT_ID)).thenReturn(Optional.empty()); assertThrows(NoSuchElementException.class, - () -> layoutService.updateLayout(layoutId, layout)); + () -> layoutService.updateLayout(LAYOUT_ID, layout)); } @Test void deleteLayout_anyUUID_callsRepositoryDeleteById() { - layoutService.deleteLayout(layoutId); + layoutService.deleteLayout(LAYOUT_ID); - verify(layoutRepository, times(1)).deleteById(layoutId); + verify(layoutRepository, times(1)).deleteById(LAYOUT_ID); } } \ No newline at end of file From d8fd1b52b335f6491ec8ee24b7d75e0a9177877a Mon Sep 17 00:00:00 2001 From: Cara Fisher Date: Wed, 1 Nov 2023 12:29:03 +0000 Subject: [PATCH 58/70] VUU86: Remove incorrect `returns404` on test name --- .../layoutserver/controller/LayoutControllerTest.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java index 2734e4200..71e8088c9 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java @@ -100,9 +100,10 @@ void getLayout_layoutExists_returnsLayout() { } @Test - void getLayout_layoutDoesNotExist_throwsNotFoundAndReturns404() { - when(layoutService.getLayout(DOES_NOT_EXIST_LAYOUT_ID)).thenThrow( - NoSuchElementException.class); + void getLayout_layoutDoesNotExist_throwsNoSuchElementException() { + when(layoutService.getLayout(DOES_NOT_EXIST_LAYOUT_ID)) + .thenThrow(NoSuchElementException.class); + assertThrows(NoSuchElementException.class, () -> layoutController.getLayout(DOES_NOT_EXIST_LAYOUT_ID)); } @@ -110,8 +111,8 @@ void getLayout_layoutDoesNotExist_throwsNotFoundAndReturns404() { @Test void getMetadata_metadataExists_returnsMetadata() { when(metadataService.getMetadata()).thenReturn(List.of(metadata)); - when(modelMapper.map(metadata, MetadataResponseDTO.class)).thenReturn( - getMetadataResponseDTO()); + when(modelMapper.map(metadata, MetadataResponseDTO.class)) + .thenReturn(getMetadataResponseDTO()); assertThat(layoutController.getMetadata()).isEqualTo(expectedMetadataResponse); } From 62d3a90df4c79f75d807fac430caf3b89962bd7a Mon Sep 17 00:00:00 2001 From: Cara Fisher Date: Wed, 1 Nov 2023 12:31:47 +0000 Subject: [PATCH 59/70] VUU86: Refactor abstraction in `LayoutController.getMetadata_metadataExists_returnsMetadata()` --- .../controller/LayoutControllerTest.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java index 71e8088c9..519b3cb2b 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java @@ -5,7 +5,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; import java.util.UUID; @@ -55,7 +54,7 @@ class LayoutControllerTest { private BaseMetadata baseMetadata; private LayoutRequestDTO layoutRequest; private LayoutResponseDTO expectedLayoutResponse; - private List expectedMetadataResponse; + private MetadataResponseDTO metadataResponse; @BeforeEach public void setup() { @@ -80,15 +79,13 @@ public void setup() { layoutRequest.setDefinition(layout.getDefinition()); layoutRequest.setMetadata(metadataRequestDTO); + metadataResponse = getMetadataResponseDTO(); + expectedLayoutResponse = new LayoutResponseDTO(); expectedLayoutResponse.setId(layout.getId()); expectedLayoutResponse.setDefinition(layout.getDefinition()); - - MetadataResponseDTO metadataResponse = getMetadataResponseDTO(); expectedLayoutResponse.setMetadata(metadataResponse); - expectedMetadataResponse = new ArrayList<>(); - expectedMetadataResponse.add(metadataResponse); } @@ -103,18 +100,20 @@ void getLayout_layoutExists_returnsLayout() { void getLayout_layoutDoesNotExist_throwsNoSuchElementException() { when(layoutService.getLayout(DOES_NOT_EXIST_LAYOUT_ID)) .thenThrow(NoSuchElementException.class); - + assertThrows(NoSuchElementException.class, () -> layoutController.getLayout(DOES_NOT_EXIST_LAYOUT_ID)); } @Test void getMetadata_metadataExists_returnsMetadata() { - when(metadataService.getMetadata()).thenReturn(List.of(metadata)); + List metadataList = List.of(metadata); + + when(metadataService.getMetadata()).thenReturn(metadataList); when(modelMapper.map(metadata, MetadataResponseDTO.class)) - .thenReturn(getMetadataResponseDTO()); + .thenReturn(metadataResponse); - assertThat(layoutController.getMetadata()).isEqualTo(expectedMetadataResponse); + assertThat(layoutController.getMetadata()).isEqualTo(List.of(metadataResponse)); } @Test From bc6e526d1977b492035df0dcb17445881d108486 Mon Sep 17 00:00:00 2001 From: Cara Fisher Date: Wed, 1 Nov 2023 12:44:20 +0000 Subject: [PATCH 60/70] VUU86: Implement test naming convention for 'stateUnderTest' --- .../vuu/layoutserver/controller/LayoutControllerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java index 519b3cb2b..53b2ae3a0 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java @@ -137,7 +137,7 @@ void createLayout_validLayout_returnsCreatedLayout() { } @Test - void updateLayout_callsLayoutService() { + void updateLayout_validLayout_callsLayoutService() { layout.setId(null); layout.getMetadata().setId(null); @@ -149,7 +149,7 @@ void updateLayout_callsLayoutService() { } @Test - void deleteLayout_callsLayoutService() { + void deleteLayout__validId_callsLayoutService() { layoutController.deleteLayout(VALID_LAYOUT_ID); verify(layoutService).deleteLayout(VALID_LAYOUT_ID); From ad5ddeb682c1bc6307916f1be7b52ef26e734187 Mon Sep 17 00:00:00 2001 From: Cara Fisher Date: Wed, 1 Nov 2023 12:53:49 +0000 Subject: [PATCH 61/70] VUU86: Use Metadata builder in tests --- .../vuu/layoutserver/controller/LayoutControllerTest.java | 4 +--- .../finos/vuu/layoutserver/service/LayoutServiceTest.java | 7 ++----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java index 53b2ae3a0..6c9ecb75f 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/LayoutControllerTest.java @@ -64,9 +64,7 @@ public void setup() { baseMetadata.setGroup(LAYOUT_GROUP); baseMetadata.setScreenshot(LAYOUT_SCREENSHOT); - metadata = new Metadata(); - metadata.setId(VALID_METADATA_ID); - metadata.setBaseMetadata(baseMetadata); + metadata = Metadata.builder().id(VALID_METADATA_ID).baseMetadata(baseMetadata).build(); layout = new Layout(); layout.setId(VALID_LAYOUT_ID); diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java index b87dbbac1..4fab9de16 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java @@ -37,17 +37,14 @@ class LayoutServiceTest { @BeforeEach public void setup() { BaseMetadata baseMetadata = new BaseMetadata(); - Metadata metadata = new Metadata(); - layout = new Layout(); - baseMetadata.setName("Test Name"); baseMetadata.setGroup("Test Group"); baseMetadata.setScreenshot("Test Screenshot"); baseMetadata.setUser("Test User"); - metadata.setId(METADATA_ID); - metadata.setBaseMetadata(baseMetadata); + Metadata metadata = Metadata.builder().id(METADATA_ID).baseMetadata(baseMetadata).build(); + layout = new Layout(); layout.setId(LAYOUT_ID); layout.setDefinition(""); layout.setMetadata(metadata); From 3009319ce36c9443bca2966cbab6fc3dc7a32a97 Mon Sep 17 00:00:00 2001 From: Cara Fisher Date: Wed, 1 Nov 2023 13:08:23 +0000 Subject: [PATCH 62/70] VUU86: Add test cases for when repo returns empty --- .../service/LayoutServiceTest.java | 10 ++++- .../service/MetadataServiceTest.java | 37 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 layout-server/src/test/java/org/finos/vuu/layoutserver/service/MetadataServiceTest.java diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java index 4fab9de16..3dddb39cb 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java @@ -51,12 +51,20 @@ public void setup() { } @Test - void getLayout_returnsLayout() { + void getLayout_layoutExists_returnsLayout() { when(layoutRepository.findById(LAYOUT_ID)).thenReturn(Optional.of(layout)); assertThat(layoutService.getLayout(LAYOUT_ID)).isEqualTo(layout); } + @Test + void getLayout_noLayoutsExist_throwsNotFoundException() { + when(layoutRepository.findById(LAYOUT_ID)).thenReturn(Optional.empty()); + + assertThrows(NoSuchElementException.class, + () -> layoutService.getLayout(LAYOUT_ID)); + } + @Test void createLayout_anyLayout_returnsLayoutId() { when(layoutRepository.save(layout)).thenReturn(layout); diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/service/MetadataServiceTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/service/MetadataServiceTest.java new file mode 100644 index 000000000..1539a9237 --- /dev/null +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/service/MetadataServiceTest.java @@ -0,0 +1,37 @@ +package org.finos.vuu.layoutserver.service; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.when; + +import java.util.List; +import org.finos.vuu.layoutserver.model.Metadata; +import org.finos.vuu.layoutserver.repository.MetadataRepository; +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; + +@ExtendWith(MockitoExtension.class) +class MetadataServiceTest { + + @Mock + private MetadataRepository metadataRepository; + + @InjectMocks + private MetadataService metadataService; + + @Test + void getMetadata_metadataExists_returnsMetadata() { + Metadata metadata = Metadata.builder().build(); + + when(metadataRepository.findAll()).thenReturn(List.of(metadata)); + assertThat(metadataService.getMetadata()).isEqualTo(List.of(metadata)); + } + + @Test + void getMetadata_noMetadataExists_returnsEmptyList() { + when(metadataRepository.findAll()).thenReturn(List.of()); + assertThat(metadataService.getMetadata()).isEqualTo(List.of()); + } +} \ No newline at end of file From 5493de82563369f5e88935123c2e78d91d284373 Mon Sep 17 00:00:00 2001 From: Cara Fisher Date: Wed, 1 Nov 2023 14:46:43 +0000 Subject: [PATCH 63/70] VUU86: Remove assertion from helper method --- .../integration/LayoutIntegrationTest.java | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java index 042206c77..9fb4970e5 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java @@ -57,6 +57,8 @@ public class LayoutIntegrationTest { @Test void getLayout_validIDAndLayoutExists_returns200WithLayout() throws Exception { Layout layout = createDefaultLayoutInDatabase(); + assertThat(layoutRepository.findById(layout.getId()).orElseThrow()) + .isEqualTo(layout); mockMvc.perform(get("/layouts/{id}", layout.getId())) .andExpect(status().isOk()) @@ -94,6 +96,8 @@ void getLayout_invalidId_returns400() throws Exception { @Test void getMetadata_metadataExists_returnsMetadata() throws Exception { Layout layout = createDefaultLayoutInDatabase(); + assertThat(layoutRepository.findById(layout.getId()).orElseThrow()) + .isEqualTo(layout); mockMvc.perform(get("/layouts/metadata")) .andExpect(status().isOk()) @@ -214,6 +218,9 @@ void createLayout_invalidRequestBodyUnexpectedFormat_returns400() throws Excepti @Test void updateLayout_validIdAndValidRequest_returns204AndLayoutHasChanged() throws Exception { Layout layout = createDefaultLayoutInDatabase(); + assertThat(layoutRepository.findById(layout.getId()).orElseThrow()) + .isEqualTo(layout); + LayoutRequestDTO layoutRequest = createValidLayoutRequest(); mockMvc.perform(put("/layouts/{id}", layout.getId()) @@ -240,6 +247,8 @@ void updateLayout_validIdAndValidRequest_returns204AndLayoutHasChanged() throws void updateLayout_invalidRequestBodyDefinitionIsBlankAndMetadataIsNull_returns400AndLayoutDoesNotChange() throws Exception { Layout layout = createDefaultLayoutInDatabase(); + assertThat(layoutRepository.findById(layout.getId()).orElseThrow()) + .isEqualTo(layout); LayoutRequestDTO request = new LayoutRequestDTO(); request.setDefinition(""); @@ -264,6 +273,9 @@ void updateLayout_invalidRequestBodyDefinitionIsBlankAndMetadataIsNull_returns40 void updateLayout_invalidRequestBodyUnexpectedFormat_returns400AndLayoutDoesNotChange() throws Exception { Layout layout = createDefaultLayoutInDatabase(); + assertThat(layoutRepository.findById(layout.getId()).orElseThrow()) + .isEqualTo(layout); + String request = "invalidRequest"; mockMvc.perform(put("/layouts/{id}", layout.getId()) @@ -315,12 +327,12 @@ void updateLayout_invalidId_returns400() throws Exception { @Test void deleteLayout_validIdLayoutExists_returnsSuccessAndLayoutIsDeleted() throws Exception { Layout layout = createDefaultLayoutInDatabase(); - - mockMvc.perform(get("/layouts/{id}", layout.getId())).andExpect(status().isOk()); + assertThat(layoutRepository.findById(layout.getId()).orElseThrow()) + .isEqualTo(layout); mockMvc.perform(delete("/layouts/{id}", layout.getId())).andExpect(status().isNoContent()); - mockMvc.perform(get("/layouts/{id}", layout.getId())).andExpect(status().isNotFound()); + assertThat(layoutRepository.findById(layout.getId())).isEmpty(); } @Test @@ -358,12 +370,9 @@ private Layout createDefaultLayoutInDatabase() { layout.setMetadata(metadata); metadataRepository.save(metadata); - Layout createdLayout = layoutRepository.save(layout); - - assertThat(layoutRepository.findById(createdLayout.getId()).orElseThrow()) - .isEqualTo(layout); + layoutRepository.save(layout); - return createdLayout; + return layout; } private LayoutRequestDTO createValidLayoutRequest() { From 92ebfabbc9fee12fae503cdf8b5808028ad4f63e Mon Sep 17 00:00:00 2001 From: Cara Fisher Date: Wed, 1 Nov 2023 14:51:04 +0000 Subject: [PATCH 64/70] VUU86: Add integration test for multiple metadata --- .../integration/LayoutIntegrationTest.java | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java index 9fb4970e5..bd5395be7 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java @@ -94,7 +94,7 @@ void getLayout_invalidId_returns400() throws Exception { } @Test - void getMetadata_metadataExists_returnsMetadata() throws Exception { + void getMetadata_singleMetadataExists_returnsMetadata() throws Exception { Layout layout = createDefaultLayoutInDatabase(); assertThat(layoutRepository.findById(layout.getId()).orElseThrow()) .isEqualTo(layout); @@ -111,6 +111,36 @@ void getMetadata_metadataExists_returnsMetadata() throws Exception { is(layout.getMetadata().getBaseMetadata().getUser()))); } + @Test + void getMetadata_multipleMetadataExists_returnsAllMetadata() throws Exception { + Layout layout1 = createDefaultLayoutInDatabase(); + Layout layout2 = createDefaultLayoutInDatabase(); + + assertThat(layoutRepository.findById(layout1.getId()).orElseThrow()) + .isEqualTo(layout1); + assertThat(layoutRepository.findById(layout2.getId()).orElseThrow()) + .isEqualTo(layout2); + + mockMvc.perform(get("/layouts/metadata")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].name", + is(layout1.getMetadata().getBaseMetadata().getName()))) + .andExpect(jsonPath("$[0].group", + is(layout1.getMetadata().getBaseMetadata().getGroup()))) + .andExpect(jsonPath("$[0].screenshot", + is(layout1.getMetadata().getBaseMetadata().getScreenshot()))) + .andExpect(jsonPath("$[0].user", + is(layout1.getMetadata().getBaseMetadata().getUser()))) + .andExpect(jsonPath("$[0].name", + is(layout2.getMetadata().getBaseMetadata().getName()))) + .andExpect(jsonPath("$[0].group", + is(layout2.getMetadata().getBaseMetadata().getGroup()))) + .andExpect(jsonPath("$[0].screenshot", + is(layout2.getMetadata().getBaseMetadata().getScreenshot()))) + .andExpect(jsonPath("$[0].user", + is(layout2.getMetadata().getBaseMetadata().getUser()))); + } + @Test void getMetadata_metadataDoesNotExist_returnsEmptyList() throws Exception { mockMvc.perform(get("/layouts/metadata")) From 5c23160fbbff8b7f94125de8e799518981b9e0d3 Mon Sep 17 00:00:00 2001 From: Cara Fisher Date: Wed, 1 Nov 2023 14:51:42 +0000 Subject: [PATCH 65/70] VUU86: _validLayout -> _validRequest_ --- .../vuu/layoutserver/integration/LayoutIntegrationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java index bd5395be7..b341a4418 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java @@ -149,7 +149,7 @@ void getMetadata_metadataDoesNotExist_returnsEmptyList() throws Exception { } @Test - void createLayout_validLayout_returnsCreatedLayoutAndLayoutIsPersisted() + void createLayout_validRequest_returnsCreatedLayoutAndLayoutIsPersisted() throws Exception { LayoutRequestDTO layoutRequest = createValidLayoutRequest(); From 324444ef4fb336ebe577ddc16bb4dd74818cbd6a Mon Sep 17 00:00:00 2001 From: Cara Fisher Date: Wed, 1 Nov 2023 14:53:40 +0000 Subject: [PATCH 66/70] VUU86: Refactor `updateLayout_validIdAndValidRequest_returns204AndLayoutHasChanged` - layout -> initialLayout - assertion that updatedLayout != initialLayout --- .../integration/LayoutIntegrationTest.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java index b341a4418..14dc93e75 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java @@ -247,19 +247,19 @@ void createLayout_invalidRequestBodyUnexpectedFormat_returns400() throws Excepti @Test void updateLayout_validIdAndValidRequest_returns204AndLayoutHasChanged() throws Exception { - Layout layout = createDefaultLayoutInDatabase(); - assertThat(layoutRepository.findById(layout.getId()).orElseThrow()) - .isEqualTo(layout); + Layout initialLayout = createDefaultLayoutInDatabase(); + assertThat(layoutRepository.findById(initialLayout.getId()).orElseThrow()) + .isEqualTo(initialLayout); LayoutRequestDTO layoutRequest = createValidLayoutRequest(); - mockMvc.perform(put("/layouts/{id}", layout.getId()) + mockMvc.perform(put("/layouts/{id}", initialLayout.getId()) .content(objectMapper.writeValueAsString(layoutRequest)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isNoContent()) .andExpect(jsonPath("$").doesNotExist()); - Layout updatedLayout = layoutRepository.findById(layout.getId()).orElseThrow(); + Layout updatedLayout = layoutRepository.findById(initialLayout.getId()).orElseThrow(); assertThat(updatedLayout.getDefinition()) .isEqualTo(layoutRequest.getDefinition()); @@ -271,6 +271,8 @@ void updateLayout_validIdAndValidRequest_returns204AndLayoutHasChanged() throws .isEqualTo(layoutRequest.getMetadata().getBaseMetadata().getScreenshot()); assertThat(updatedLayout.getMetadata().getBaseMetadata().getUser()) .isEqualTo(layoutRequest.getMetadata().getBaseMetadata().getUser()); + + assertThat(updatedLayout).isNotEqualTo(initialLayout); } @Test From 0ccaee48ab7fa5ad97a978f7a1aa3659d2051f26 Mon Sep 17 00:00:00 2001 From: Cara Fisher Date: Wed, 1 Nov 2023 14:59:59 +0000 Subject: [PATCH 67/70] VUU86: Separate _definitionIsBlankAndMetadataIsNull_ into two tests --- .../integration/LayoutIntegrationTest.java | 58 +++++++++++-------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java index 14dc93e75..4bad8e776 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java @@ -1,8 +1,6 @@ package org.finos.vuu.layoutserver.integration; import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.Matchers.anyOf; -import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -57,8 +55,7 @@ public class LayoutIntegrationTest { @Test void getLayout_validIDAndLayoutExists_returns200WithLayout() throws Exception { Layout layout = createDefaultLayoutInDatabase(); - assertThat(layoutRepository.findById(layout.getId()).orElseThrow()) - .isEqualTo(layout); + assertThat(layoutRepository.findById(layout.getId()).orElseThrow()).isEqualTo(layout); mockMvc.perform(get("/layouts/{id}", layout.getId())) .andExpect(status().isOk()) @@ -96,8 +93,7 @@ void getLayout_invalidId_returns400() throws Exception { @Test void getMetadata_singleMetadataExists_returnsMetadata() throws Exception { Layout layout = createDefaultLayoutInDatabase(); - assertThat(layoutRepository.findById(layout.getId()).orElseThrow()) - .isEqualTo(layout); + assertThat(layoutRepository.findById(layout.getId()).orElseThrow()).isEqualTo(layout); mockMvc.perform(get("/layouts/metadata")) .andExpect(status().isOk()) @@ -116,10 +112,8 @@ void getMetadata_multipleMetadataExists_returnsAllMetadata() throws Exception { Layout layout1 = createDefaultLayoutInDatabase(); Layout layout2 = createDefaultLayoutInDatabase(); - assertThat(layoutRepository.findById(layout1.getId()).orElseThrow()) - .isEqualTo(layout1); - assertThat(layoutRepository.findById(layout2.getId()).orElseThrow()) - .isEqualTo(layout2); + assertThat(layoutRepository.findById(layout1.getId()).orElseThrow()).isEqualTo(layout1); + assertThat(layoutRepository.findById(layout2.getId()).orElseThrow()).isEqualTo(layout2); mockMvc.perform(get("/layouts/metadata")) .andExpect(status().isOk()) @@ -248,8 +242,8 @@ void createLayout_invalidRequestBodyUnexpectedFormat_returns400() throws Excepti @Test void updateLayout_validIdAndValidRequest_returns204AndLayoutHasChanged() throws Exception { Layout initialLayout = createDefaultLayoutInDatabase(); - assertThat(layoutRepository.findById(initialLayout.getId()).orElseThrow()) - .isEqualTo(initialLayout); + assertThat(layoutRepository.findById(initialLayout.getId()).orElseThrow()).isEqualTo( + initialLayout); LayoutRequestDTO layoutRequest = createValidLayoutRequest(); @@ -276,27 +270,43 @@ void updateLayout_validIdAndValidRequest_returns204AndLayoutHasChanged() throws } @Test - void updateLayout_invalidRequestBodyDefinitionIsBlankAndMetadataIsNull_returns400AndLayoutDoesNotChange() + void updateLayout_invalidRequestBodyDefinitionIsBlank_returns400AndLayoutDoesNotChange() throws Exception { Layout layout = createDefaultLayoutInDatabase(); - assertThat(layoutRepository.findById(layout.getId()).orElseThrow()) - .isEqualTo(layout); + assertThat(layoutRepository.findById(layout.getId()).orElseThrow()).isEqualTo(layout); LayoutRequestDTO request = new LayoutRequestDTO(); request.setDefinition(""); - request.setMetadata(null); mockMvc.perform(put("/layouts/{id}", layout.getId()) .content(objectMapper.writeValueAsString(request)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isBadRequest()) - .andExpect(content().string(anyOf( - equalTo( + .andExpect(content() + .string( "[definition: Definition must not be blank, metadata: Metadata must not be " - + "null]"), - equalTo( + + "null]")); + + assertThat(layoutRepository.findById(layout.getId()).orElseThrow()).isEqualTo(layout); + } + + @Test + void updateLayout_invalidRequestBodyMetadataIsNull_returns400AndLayoutDoesNotChange() + throws Exception { + Layout layout = createDefaultLayoutInDatabase(); + assertThat(layoutRepository.findById(layout.getId()).orElseThrow()).isEqualTo(layout); + + LayoutRequestDTO request = new LayoutRequestDTO(); + request.setMetadata(null); + + mockMvc.perform(put("/layouts/{id}", layout.getId()) + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andExpect(content() + .string( "[metadata: Metadata must not be null, definition: Definition must not be " - + "blank]")))); + + "blank]")); assertThat(layoutRepository.findById(layout.getId()).orElseThrow()).isEqualTo(layout); } @@ -305,8 +315,7 @@ void updateLayout_invalidRequestBodyDefinitionIsBlankAndMetadataIsNull_returns40 void updateLayout_invalidRequestBodyUnexpectedFormat_returns400AndLayoutDoesNotChange() throws Exception { Layout layout = createDefaultLayoutInDatabase(); - assertThat(layoutRepository.findById(layout.getId()).orElseThrow()) - .isEqualTo(layout); + assertThat(layoutRepository.findById(layout.getId()).orElseThrow()).isEqualTo(layout); String request = "invalidRequest"; @@ -359,8 +368,7 @@ void updateLayout_invalidId_returns400() throws Exception { @Test void deleteLayout_validIdLayoutExists_returnsSuccessAndLayoutIsDeleted() throws Exception { Layout layout = createDefaultLayoutInDatabase(); - assertThat(layoutRepository.findById(layout.getId()).orElseThrow()) - .isEqualTo(layout); + assertThat(layoutRepository.findById(layout.getId()).orElseThrow()).isEqualTo(layout); mockMvc.perform(delete("/layouts/{id}", layout.getId())).andExpect(status().isNoContent()); From f2ca86873608dc39362b3aa6748e8a9e9de4fb85 Mon Sep 17 00:00:00 2001 From: Cara Fisher Date: Wed, 1 Nov 2023 16:58:50 +0000 Subject: [PATCH 68/70] VUU86: Remove `@Transactional` on LayoutIntegrationTest --- .../integration/LayoutIntegrationTest.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java index 4bad8e776..ae23d4e6b 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java @@ -20,6 +20,7 @@ import org.finos.vuu.layoutserver.model.Metadata; import org.finos.vuu.layoutserver.repository.LayoutRepository; import org.finos.vuu.layoutserver.repository.MetadataRepository; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -28,11 +29,9 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; -import org.springframework.transaction.annotation.Transactional; @SpringBootTest @AutoConfigureMockMvc -@Transactional @ActiveProfiles("test") public class LayoutIntegrationTest { @@ -51,6 +50,11 @@ public class LayoutIntegrationTest { @Autowired private MetadataRepository metadataRepository; + @BeforeEach + void tearDown() { + layoutRepository.deleteAll(); + metadataRepository.deleteAll(); + } @Test void getLayout_validIDAndLayoutExists_returns200WithLayout() throws Exception { @@ -246,6 +250,11 @@ void updateLayout_validIdAndValidRequest_returns204AndLayoutHasChanged() throws initialLayout); LayoutRequestDTO layoutRequest = createValidLayoutRequest(); + layoutRequest.setDefinition("Updated definition"); + layoutRequest.getMetadata().getBaseMetadata().setName("Updated name"); + layoutRequest.getMetadata().getBaseMetadata().setGroup("Updated group"); + layoutRequest.getMetadata().getBaseMetadata().setScreenshot("Updated screenshot"); + layoutRequest.getMetadata().getBaseMetadata().setUser("Updated user"); mockMvc.perform(put("/layouts/{id}", initialLayout.getId()) .content(objectMapper.writeValueAsString(layoutRequest)) @@ -275,17 +284,14 @@ void updateLayout_invalidRequestBodyDefinitionIsBlank_returns400AndLayoutDoesNot Layout layout = createDefaultLayoutInDatabase(); assertThat(layoutRepository.findById(layout.getId()).orElseThrow()).isEqualTo(layout); - LayoutRequestDTO request = new LayoutRequestDTO(); + LayoutRequestDTO request = createValidLayoutRequest(); request.setDefinition(""); mockMvc.perform(put("/layouts/{id}", layout.getId()) .content(objectMapper.writeValueAsString(request)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isBadRequest()) - .andExpect(content() - .string( - "[definition: Definition must not be blank, metadata: Metadata must not be " - + "null]")); + .andExpect(content().string("[definition: Definition must not be blank]")); assertThat(layoutRepository.findById(layout.getId()).orElseThrow()).isEqualTo(layout); } @@ -296,17 +302,14 @@ void updateLayout_invalidRequestBodyMetadataIsNull_returns400AndLayoutDoesNotCha Layout layout = createDefaultLayoutInDatabase(); assertThat(layoutRepository.findById(layout.getId()).orElseThrow()).isEqualTo(layout); - LayoutRequestDTO request = new LayoutRequestDTO(); + LayoutRequestDTO request = createValidLayoutRequest(); request.setMetadata(null); mockMvc.perform(put("/layouts/{id}", layout.getId()) .content(objectMapper.writeValueAsString(request)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isBadRequest()) - .andExpect(content() - .string( - "[metadata: Metadata must not be null, definition: Definition must not be " - + "blank]")); + .andExpect(content().string("[metadata: Metadata must not be null]")); assertThat(layoutRepository.findById(layout.getId()).orElseThrow()).isEqualTo(layout); } @@ -409,10 +412,7 @@ private Layout createDefaultLayoutInDatabase() { layout.setDefinition(DEFAULT_LAYOUT_DEFINITION); layout.setMetadata(metadata); - metadataRepository.save(metadata); - layoutRepository.save(layout); - - return layout; + return layoutRepository.save(layout); } private LayoutRequestDTO createValidLayoutRequest() { From c1692d727138689e2e51f9732d15fa8158d06523 Mon Sep 17 00:00:00 2001 From: Cara Fisher Date: Wed, 1 Nov 2023 17:03:22 +0000 Subject: [PATCH 69/70] VUU86: Date -> LocalDate --- .../vuu/layoutserver/dto/response/MetadataResponseDTO.java | 6 +++--- .../java/org/finos/vuu/layoutserver/model/Metadata.java | 6 +++--- .../org/finos/vuu/layoutserver/service/LayoutService.java | 5 ++--- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/MetadataResponseDTO.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/MetadataResponseDTO.java index 8312a4758..5efa5abaa 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/MetadataResponseDTO.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/MetadataResponseDTO.java @@ -1,7 +1,7 @@ package org.finos.vuu.layoutserver.dto.response; import com.fasterxml.jackson.annotation.JsonUnwrapped; -import java.util.Date; +import java.time.LocalDate; import java.util.UUID; import lombok.Data; import org.finos.vuu.layoutserver.model.BaseMetadata; @@ -14,6 +14,6 @@ public class MetadataResponseDTO { @JsonUnwrapped BaseMetadata baseMetadata; - private Date created; - private Date updated; + private LocalDate created; + private LocalDate updated; } diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java index 65340ad0f..604779f23 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java @@ -1,6 +1,6 @@ package org.finos.vuu.layoutserver.model; -import java.util.Date; +import java.time.LocalDate; import java.util.UUID; import javax.persistence.Column; import javax.persistence.Embedded; @@ -28,7 +28,7 @@ public class Metadata { @Embedded private BaseMetadata baseMetadata; - private final Date created = new Date(); + private final LocalDate created = LocalDate.now(); - private Date updated; + private LocalDate updated; } diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java index ada5d3b9a..26dca49f5 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/LayoutService.java @@ -1,6 +1,6 @@ package org.finos.vuu.layoutserver.service; -import java.util.Date; +import java.time.LocalDate; import java.util.NoSuchElementException; import java.util.UUID; import lombok.RequiredArgsConstructor; @@ -14,7 +14,6 @@ public class LayoutService { private final LayoutRepository layoutRepository; - private final MetadataService metadataService; public Layout getLayout(UUID id) { return layoutRepository.findById(id) @@ -35,7 +34,7 @@ public void updateLayout(UUID layoutId, Layout newLayout) { Metadata updatedMetadata = Metadata.builder() .baseMetadata(newMetadata.getBaseMetadata()) - .updated(new Date()) + .updated(LocalDate.now()) .build(); layoutToUpdate.setDefinition(newLayout.getDefinition()); From f4dce71bac644aa686307af5a4bca80eae3a1917 Mon Sep 17 00:00:00 2001 From: Cara Fisher Date: Fri, 3 Nov 2023 15:19:21 +0000 Subject: [PATCH 70/70] VUU86: Improve tests --- .../integration/LayoutIntegrationTest.java | 14 ++++++++++---- .../layoutserver/service/LayoutServiceTest.java | 13 +++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java index ae23d4e6b..a1689c9e4 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/LayoutIntegrationTest.java @@ -115,6 +115,12 @@ void getMetadata_singleMetadataExists_returnsMetadata() throws Exception { void getMetadata_multipleMetadataExists_returnsAllMetadata() throws Exception { Layout layout1 = createDefaultLayoutInDatabase(); Layout layout2 = createDefaultLayoutInDatabase(); + layout2.setDefinition("Different definition"); + layout2.getMetadata().getBaseMetadata().setName("Different name"); + layout2.getMetadata().getBaseMetadata().setGroup("Different group"); + layout2.getMetadata().getBaseMetadata().setScreenshot("Different screenshot"); + layout2.getMetadata().getBaseMetadata().setUser("Different user"); + layoutRepository.save(layout2); assertThat(layoutRepository.findById(layout1.getId()).orElseThrow()).isEqualTo(layout1); assertThat(layoutRepository.findById(layout2.getId()).orElseThrow()).isEqualTo(layout2); @@ -129,13 +135,13 @@ void getMetadata_multipleMetadataExists_returnsAllMetadata() throws Exception { is(layout1.getMetadata().getBaseMetadata().getScreenshot()))) .andExpect(jsonPath("$[0].user", is(layout1.getMetadata().getBaseMetadata().getUser()))) - .andExpect(jsonPath("$[0].name", + .andExpect(jsonPath("$[1].name", is(layout2.getMetadata().getBaseMetadata().getName()))) - .andExpect(jsonPath("$[0].group", + .andExpect(jsonPath("$[1].group", is(layout2.getMetadata().getBaseMetadata().getGroup()))) - .andExpect(jsonPath("$[0].screenshot", + .andExpect(jsonPath("$[1].screenshot", is(layout2.getMetadata().getBaseMetadata().getScreenshot()))) - .andExpect(jsonPath("$[0].user", + .andExpect(jsonPath("$[1].user", is(layout2.getMetadata().getBaseMetadata().getUser()))); } diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java index 3dddb39cb..5ec50e5d7 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/service/LayoutServiceTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -19,6 +20,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.dao.EmptyResultDataAccessException; @ExtendWith(MockitoExtension.class) class LayoutServiceTest { @@ -95,4 +97,15 @@ void deleteLayout_anyUUID_callsRepositoryDeleteById() { verify(layoutRepository, times(1)).deleteById(LAYOUT_ID); } + + @Test + void deleteLayout_noLayoutExists_throwsNoSuchElementException() { + doThrow(new EmptyResultDataAccessException(1)) + .when(layoutRepository).deleteById(LAYOUT_ID); + + assertThrows(NoSuchElementException.class, + () -> layoutService.deleteLayout(LAYOUT_ID)); + + verify(layoutRepository, times(1)).deleteById(LAYOUT_ID); + } } \ No newline at end of file