From f1c11281dc67e5c61a5d0c66cfdb6d6aca9c1eba Mon Sep 17 00:00:00 2001 From: Tyson Gern Date: Fri, 21 Jul 2017 09:26:37 -0600 Subject: [PATCH 01/24] Initial commit --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..4fa332f5c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +build +.gradle +.idea +*.iml +ci/variables.yml From 6d7a9045680998161034bc32d28549a89ec166d3 Mon Sep 17 00:00:00 2001 From: Subbu Perugu Date: Tue, 17 Oct 2017 10:31:24 -0400 Subject: [PATCH 02/24] Simple Spring Boot app --- settings.gradle | 1 + src/main/java/PalTrackerApplication.java | 12 ++++++++++++ .../io/pivotal/pal/tracker/WelcomeController.java | 13 +++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 settings.gradle create mode 100644 src/main/java/PalTrackerApplication.java create mode 100644 src/main/java/io/pivotal/pal/tracker/WelcomeController.java diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 000000000..ef961960e --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = "pal-tracker" \ No newline at end of file diff --git a/src/main/java/PalTrackerApplication.java b/src/main/java/PalTrackerApplication.java new file mode 100644 index 000000000..80f2a72a5 --- /dev/null +++ b/src/main/java/PalTrackerApplication.java @@ -0,0 +1,12 @@ +package io.pivotal.pal.tracker; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class PalTrackerApplication { + + public static void main(String[] args) { + SpringApplication.run(PalTrackerApplication.class, args); + } +} \ No newline at end of file diff --git a/src/main/java/io/pivotal/pal/tracker/WelcomeController.java b/src/main/java/io/pivotal/pal/tracker/WelcomeController.java new file mode 100644 index 000000000..2d477ae71 --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/WelcomeController.java @@ -0,0 +1,13 @@ +package io.pivotal.pal.tracker; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class WelcomeController { + + @GetMapping("/") + public String sayHello() { + return "hello"; + } +} \ No newline at end of file From b7ead59a309696e12ff0ddf7970e922d18d789a1 Mon Sep 17 00:00:00 2001 From: Tyson Gern Date: Thu, 20 Jul 2017 13:56:50 -0600 Subject: [PATCH 03/24] Add tests for deployment lab --- .../pal/tracker/EnvControllerTest.java | 28 +++++++++++++++++++ .../pal/tracker/WelcomeControllerTest.java | 16 +++++++++++ .../pal/trackerapi/WelcomeApiTest.java | 26 +++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 src/test/java/test/pivotal/pal/tracker/EnvControllerTest.java create mode 100644 src/test/java/test/pivotal/pal/tracker/WelcomeControllerTest.java create mode 100644 src/test/java/test/pivotal/pal/trackerapi/WelcomeApiTest.java diff --git a/src/test/java/test/pivotal/pal/tracker/EnvControllerTest.java b/src/test/java/test/pivotal/pal/tracker/EnvControllerTest.java new file mode 100644 index 000000000..fda0f0f34 --- /dev/null +++ b/src/test/java/test/pivotal/pal/tracker/EnvControllerTest.java @@ -0,0 +1,28 @@ +package test.pivotal.pal.tracker; + +import org.junit.Test; + +import java.util.Map; +import io.pivotal.pal.tracker.EnvController; + +import static org.assertj.core.api.Assertions.assertThat; + +public class EnvControllerTest { + @Test + public void getEnv() throws Exception { + EnvController controller = new EnvController( + "8675", + "12G", + "34", + "123.sesame.street" + ); + + Map env = controller.getEnv(); + + assertThat(env.get("PORT")).isEqualTo("8675"); + assertThat(env.get("MEMORY_LIMIT")).isEqualTo("12G"); + assertThat(env.get("CF_INSTANCE_INDEX")).isEqualTo("34"); + assertThat(env.get("CF_INSTANCE_ADDR")).isEqualTo("123.sesame.street"); + } + +} diff --git a/src/test/java/test/pivotal/pal/tracker/WelcomeControllerTest.java b/src/test/java/test/pivotal/pal/tracker/WelcomeControllerTest.java new file mode 100644 index 000000000..bfa8271a0 --- /dev/null +++ b/src/test/java/test/pivotal/pal/tracker/WelcomeControllerTest.java @@ -0,0 +1,16 @@ +package test.pivotal.pal.tracker; + +import io.pivotal.pal.tracker.WelcomeController; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class WelcomeControllerTest { + + @Test + public void itSaysHello() throws Exception { + WelcomeController controller = new WelcomeController("A welcome message"); + + assertThat(controller.sayHello()).isEqualTo("A welcome message"); + } +} diff --git a/src/test/java/test/pivotal/pal/trackerapi/WelcomeApiTest.java b/src/test/java/test/pivotal/pal/trackerapi/WelcomeApiTest.java new file mode 100644 index 000000000..cc7091ed4 --- /dev/null +++ b/src/test/java/test/pivotal/pal/trackerapi/WelcomeApiTest.java @@ -0,0 +1,26 @@ +package test.pivotal.pal.trackerapi; + +import io.pivotal.pal.tracker.PalTrackerApplication; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = PalTrackerApplication.class, webEnvironment = RANDOM_PORT) +public class WelcomeApiTest { + + @Autowired + private TestRestTemplate restTemplate; + + @Test + public void exampleTest() { + String body = this.restTemplate.getForObject("/", String.class); + assertThat(body).isEqualTo("Hello from test"); + } +} From 9ed2470a980c574f7c1871a83a797b7c33df69d5 Mon Sep 17 00:00:00 2001 From: Subbu Perugu Date: Tue, 17 Oct 2017 14:18:25 -0400 Subject: [PATCH 04/24] commiting changes --- build.gradle | 20 ++ ci/build.yml | 22 +++ ci/pipeline.yml | 31 ++++ ci/variables.example.yml | 9 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54712 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 172 ++++++++++++++++++ gradlew.bat | 84 +++++++++ manifest.yml | 4 + src/main/java/EnvController.java | 41 +++++ .../pal/tracker/WelcomeController.java | 8 +- 11 files changed, 396 insertions(+), 1 deletion(-) create mode 100644 build.gradle create mode 100644 ci/build.yml create mode 100644 ci/pipeline.yml create mode 100644 ci/variables.example.yml create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 manifest.yml create mode 100644 src/main/java/EnvController.java diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..8009ec0fa --- /dev/null +++ b/build.gradle @@ -0,0 +1,20 @@ +plugins { + id "java" + id "org.springframework.boot" version "1.5.4.RELEASE" +} + +repositories { + mavenCentral() +} + +dependencies { + compile("org.springframework.boot:spring-boot-starter-web") + testCompile("org.springframework.boot:spring-boot-starter-test") +} +bootRun.environment([ + "WELCOME_MESSAGE": "hello", +]) + +test.environment([ + "WELCOME_MESSAGE": "Hello from test", +]) \ No newline at end of file diff --git a/ci/build.yml b/ci/build.yml new file mode 100644 index 000000000..e735e47d6 --- /dev/null +++ b/ci/build.yml @@ -0,0 +1,22 @@ +platform: linux + +image_resource: + type: docker-image + source: + repository: openjdk + tag: '8-jdk' + +inputs: + - name: pal-tracker + +outputs: + - name: build-output + +run: + path: bash + args: + - -exc + - | + cd pal-tracker + ./gradlew build + cp build/libs/pal-tracker.jar ../build-output \ No newline at end of file diff --git a/ci/pipeline.yml b/ci/pipeline.yml new file mode 100644 index 000000000..01d389043 --- /dev/null +++ b/ci/pipeline.yml @@ -0,0 +1,31 @@ +--- +resources: +- name: pal-tracker + type: git + source: + uri: {{github-repository}} + branch: master + private_key: {{github-private-key}} + +- name: deploy + type: cf + source: + api: {{cf-api-url}} + username: {{cf-username}} + password: {{cf-password}} + organization: {{cf-org}} + space: sandbox + +jobs: +- name: build-and-deploy + plan: + - get: pal-tracker + trigger: true + - task: build and test + file: pal-tracker/ci/build.yml + - put: deploy + params: + manifest: pal-tracker/manifest.yml + path: build-output/pal-tracker.jar + environment_variables: + WELCOME_MESSAGE: "Hello from Concourse" \ No newline at end of file diff --git a/ci/variables.example.yml b/ci/variables.example.yml new file mode 100644 index 000000000..6359be75e --- /dev/null +++ b/ci/variables.example.yml @@ -0,0 +1,9 @@ +cf-api-url: https://api.sys.longs.pal.pivotal.io +cf-username: sperugu@hcl.com +cf-password: d0d2724a +cf-org: sperugu-pal +github-repository: git@github.com:GITHUB_USERNAME/pal-tracker.git +github-private-key: + -----BEGIN RSA PRIVATE KEY----- + ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDPJH8S0+Kjb03y+6BDfMtSfZrMNuw7QuiW5jHarsz9UvII9WIudCvE5aRJwVJfhoRBLg8IFhaA4tmISsuHepaNEZC4OI1Cw7fsBP89kcq7qvbdOn2KPVhXuGh3U4ZYrTxxNS/t6mhNdh/1CF+bu7N1GqpoMMOvycIudqc1pp3ngSiBRO/68dVeRictlgLyy+gnrjsFQ09mBId+tViEc/jFXEVOk3ctE4flk1ViRUUsIyNljEb/Z0kvMHGo0x6tVZ+6VbdighbWcdeK/fNXw5YU/11dpXn/eiXDEd9WAbfI5MX4JjRISQ/JR4m++ZWg3kTu9kxexdvOoLIlAOe4rNyRDG2UKGYdfMzxHxuMPd0PJ7pxe1WV1dC9bZ86L2d4gbqXieL0tco9jKhkpGCPB3Cs7tyd3H2CNGtLVPEnj1j+GxV4Jl3zgV08PCJuZI/vxiS4XV/BTkflJe4BHv1dLlLYU+wLBFCCWxX+OrQDYUzhAAUzysZ/5G+8QY5Jb5XqhbSOyL08UKBv1iJalt6nG97QzRkyfrjVjo2N3tij7nf8kVIujB5vOcK9V01MK1QSW7qrwPfndq77zrYrcWXec3/d3snB2TykqSxF+FRfyLrzlEdubOGCDWRa966j1Oq6YKmvlKEgzTkHwvncAeLEEyWpynIhoXdyJZAJ8ALoZfbj1Q== sperugu@hcl.com + -----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..ed88a042a287c140a32e1639edfc91b2a233da8c GIT binary patch literal 54712 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

49F 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 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..bcfcea881 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Oct 16 18:14:56 EDT 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-all.zip diff --git a/gradlew b/gradlew new file mode 100644 index 000000000..cccdd3d51 --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +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 + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..f9553162f --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/manifest.yml b/manifest.yml new file mode 100644 index 000000000..204fb97a3 --- /dev/null +++ b/manifest.yml @@ -0,0 +1,4 @@ +applications: +- name: pal-tracker + path: build/libs/pal-tracker.jar + random-route: true diff --git a/src/main/java/EnvController.java b/src/main/java/EnvController.java new file mode 100644 index 000000000..b8c0167d2 --- /dev/null +++ b/src/main/java/EnvController.java @@ -0,0 +1,41 @@ +package io.pivotal.pal.tracker; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +@RestController +public class EnvController { + + private final String port; + private final String memoryLimit; + private final String cfInstanceIndex; + private final String cfInstanceAddress; + + public EnvController( + @Value("${PORT:NOT SET}") String port, + @Value("${MEMORY_LIMIT:NOT SET}") String memoryLimit, + @Value("${CF_INSTANCE_INDEX:NOT SET}") String cfInstanceIndex, + @Value("${CF_INSTANCE_ADDR:NOT SET}") String cfInstanceAddress + ) { + this.port = port; + this.memoryLimit = memoryLimit; + this.cfInstanceIndex = cfInstanceIndex; + this.cfInstanceAddress = cfInstanceAddress; + } + + @GetMapping("/env") + public Map getEnv() { + Map env = new HashMap<>(); + + env.put("PORT", port); + env.put("MEMORY_LIMIT", memoryLimit); + env.put("CF_INSTANCE_INDEX", cfInstanceIndex); + env.put("CF_INSTANCE_ADDR", cfInstanceAddress); + + return env; + } +} \ No newline at end of file diff --git a/src/main/java/io/pivotal/pal/tracker/WelcomeController.java b/src/main/java/io/pivotal/pal/tracker/WelcomeController.java index 2d477ae71..cb35a44ed 100644 --- a/src/main/java/io/pivotal/pal/tracker/WelcomeController.java +++ b/src/main/java/io/pivotal/pal/tracker/WelcomeController.java @@ -1,13 +1,19 @@ package io.pivotal.pal.tracker; +import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class WelcomeController { + public String message; + + public WelcomeController(@Value("${WELCOME_MESSAGE}") String message){ + this.message=message; + } @GetMapping("/") public String sayHello() { - return "hello"; + return message; } } \ No newline at end of file From 239f73317046f7034531919749a3a973eac77dda Mon Sep 17 00:00:00 2001 From: Subbu Perugu Date: Tue, 17 Oct 2017 16:00:07 -0400 Subject: [PATCH 05/24] Fixing keys --- ci/variables.example.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ci/variables.example.yml b/ci/variables.example.yml index 6359be75e..7f13ed632 100644 --- a/ci/variables.example.yml +++ b/ci/variables.example.yml @@ -1,9 +1,8 @@ cf-api-url: https://api.sys.longs.pal.pivotal.io cf-username: sperugu@hcl.com -cf-password: d0d2724a +cf-password: cf-org: sperugu-pal github-repository: git@github.com:GITHUB_USERNAME/pal-tracker.git -github-private-key: - -----BEGIN RSA PRIVATE KEY----- - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDPJH8S0+Kjb03y+6BDfMtSfZrMNuw7QuiW5jHarsz9UvII9WIudCvE5aRJwVJfhoRBLg8IFhaA4tmISsuHepaNEZC4OI1Cw7fsBP89kcq7qvbdOn2KPVhXuGh3U4ZYrTxxNS/t6mhNdh/1CF+bu7N1GqpoMMOvycIudqc1pp3ngSiBRO/68dVeRictlgLyy+gnrjsFQ09mBId+tViEc/jFXEVOk3ctE4flk1ViRUUsIyNljEb/Z0kvMHGo0x6tVZ+6VbdighbWcdeK/fNXw5YU/11dpXn/eiXDEd9WAbfI5MX4JjRISQ/JR4m++ZWg3kTu9kxexdvOoLIlAOe4rNyRDG2UKGYdfMzxHxuMPd0PJ7pxe1WV1dC9bZ86L2d4gbqXieL0tco9jKhkpGCPB3Cs7tyd3H2CNGtLVPEnj1j+GxV4Jl3zgV08PCJuZI/vxiS4XV/BTkflJe4BHv1dLlLYU+wLBFCCWxX+OrQDYUzhAAUzysZ/5G+8QY5Jb5XqhbSOyL08UKBv1iJalt6nG97QzRkyfrjVjo2N3tij7nf8kVIujB5vOcK9V01MK1QSW7qrwPfndq77zrYrcWXec3/d3snB2TykqSxF+FRfyLrzlEdubOGCDWRa966j1Oq6YKmvlKEgzTkHwvncAeLEEyWpynIhoXdyJZAJ8ALoZfbj1Q== sperugu@hcl.com - -----END RSA PRIVATE KEY----- \ No newline at end of file +github-private-key:| + -----BEGIN RSA PRIVATE KEY----- + -----END RSA PRIVATE KEY----- From 5fe2f78aaef54184977c599f06c1bc04da8f671e Mon Sep 17 00:00:00 2001 From: Subbu Perugu Date: Tue, 17 Oct 2017 16:04:18 -0400 Subject: [PATCH 06/24] Key fixed --- ci/variables.example.yml | 53 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/ci/variables.example.yml b/ci/variables.example.yml index 7f13ed632..767538e73 100644 --- a/ci/variables.example.yml +++ b/ci/variables.example.yml @@ -3,6 +3,55 @@ cf-username: sperugu@hcl.com cf-password: cf-org: sperugu-pal github-repository: git@github.com:GITHUB_USERNAME/pal-tracker.git -github-private-key:| +github-private-key: -----BEGIN RSA PRIVATE KEY----- - -----END RSA PRIVATE KEY----- + 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 + -----END RSA PRIVATE KEY----- From 84e9db5e9b236c036e58f1fa069bec3e51709eff Mon Sep 17 00:00:00 2001 From: Subbu Perugu Date: Tue, 17 Oct 2017 16:05:45 -0400 Subject: [PATCH 07/24] Keys --- ci/variables.example.yml | 50 +--------------------------------------- 1 file changed, 1 insertion(+), 49 deletions(-) diff --git a/ci/variables.example.yml b/ci/variables.example.yml index 767538e73..2f7fa2acc 100644 --- a/ci/variables.example.yml +++ b/ci/variables.example.yml @@ -5,53 +5,5 @@ cf-org: sperugu-pal github-repository: git@github.com:GITHUB_USERNAME/pal-tracker.git github-private-key: -----BEGIN RSA PRIVATE KEY----- - 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 + -----END RSA PRIVATE KEY----- From bb19df471200a474dbfac71ef7aa68fb8a192b14 Mon Sep 17 00:00:00 2001 From: Subbu Perugu Date: Tue, 17 Oct 2017 16:16:16 -0400 Subject: [PATCH 08/24] keys --- ci/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/build.yml b/ci/build.yml index e735e47d6..688d53085 100644 --- a/ci/build.yml +++ b/ci/build.yml @@ -18,5 +18,6 @@ run: - -exc - | cd pal-tracker + chmod 777 gradlew ./gradlew build cp build/libs/pal-tracker.jar ../build-output \ No newline at end of file From ea6625a6670c5c9eb069ace5840fe37de471f152 Mon Sep 17 00:00:00 2001 From: Subbu Perugu Date: Tue, 17 Oct 2017 16:53:09 -0400 Subject: [PATCH 09/24] keys --- ci/variables.example.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ci/variables.example.yml b/ci/variables.example.yml index 2f7fa2acc..47f1a8f56 100644 --- a/ci/variables.example.yml +++ b/ci/variables.example.yml @@ -5,5 +5,4 @@ cf-org: sperugu-pal github-repository: git@github.com:GITHUB_USERNAME/pal-tracker.git github-private-key: -----BEGIN RSA PRIVATE KEY----- - - -----END RSA PRIVATE KEY----- + -----END RSA PRIVATE KEY----- \ No newline at end of file From 2a8e302331e534dbf84ae8c5e4fe5f87bd18b089 Mon Sep 17 00:00:00 2001 From: Subbu Perugu Date: Tue, 17 Oct 2017 17:29:28 -0400 Subject: [PATCH 10/24] multiple pipes --- ci/pipeline.yml | 66 ++++++++++++++++++++++++++--- ci/variables.example.yml | 6 ++- manifest-production.yml | 23 ++++++++++ manifest.yml => manifest-review.yml | 3 +- 4 files changed, 89 insertions(+), 9 deletions(-) create mode 100644 manifest-production.yml rename manifest.yml => manifest-review.yml (64%) diff --git a/ci/pipeline.yml b/ci/pipeline.yml index 01d389043..c34c3b2a9 100644 --- a/ci/pipeline.yml +++ b/ci/pipeline.yml @@ -7,25 +7,77 @@ resources: branch: master private_key: {{github-private-key}} -- name: deploy +- name: pal-tracker-artifacts + type: s3 + source: + bucket: {{aws-bucket}} + regexp: releases/pal-tracker-(.*).jar + access_key_id: {{aws-access-key-id}} + secret_access_key: {{aws-secret-access-key}} + +- name: version + type: semver + source: + bucket: {{aws-bucket}} + key: pal-tracker/version + access_key_id: {{aws-access-key-id}} + secret_access_key: {{aws-secret-access-key}} + +- name: review-deployment + type: cf + source: + api: {{cf-api-url}} + username: {{cf-username}} + password: {{cf-password}} + organization: {{cf-org}} + space: review + +- name: production-deployment type: cf source: api: {{cf-api-url}} username: {{cf-username}} password: {{cf-password}} organization: {{cf-org}} - space: sandbox + space: production jobs: -- name: build-and-deploy +- name: build plan: - get: pal-tracker trigger: true + - get: version + params: {bump: patch} - task: build and test file: pal-tracker/ci/build.yml - - put: deploy + - put: pal-tracker-artifacts + params: + file: build-output/pal-tracker-*.jar + - put: version + params: + file: version/number + +- name: deploy-review + plan: + - get: pal-tracker + - get: pal-tracker-artifacts + trigger: true + passed: [build] + - put: review-deployment + params: + manifest: pal-tracker/manifest-review.yml + path: pal-tracker-artifacts/pal-tracker-*.jar + environment_variables: + WELCOME_MESSAGE: "Hello from the review environment" + +- name: deploy-production + plan: + - get: pal-tracker + - get: pal-tracker-artifacts + passed: [deploy-review] + - put: production-deployment params: - manifest: pal-tracker/manifest.yml - path: build-output/pal-tracker.jar + manifest: pal-tracker/manifest-production.yml + path: pal-tracker-artifacts/pal-tracker-*.jar environment_variables: - WELCOME_MESSAGE: "Hello from Concourse" \ No newline at end of file + WELCOME_MESSAGE: "Hello from the production environment" \ No newline at end of file diff --git a/ci/variables.example.yml b/ci/variables.example.yml index 47f1a8f56..21898d4e9 100644 --- a/ci/variables.example.yml +++ b/ci/variables.example.yml @@ -5,4 +5,8 @@ cf-org: sperugu-pal github-repository: git@github.com:GITHUB_USERNAME/pal-tracker.git github-private-key: -----BEGIN RSA PRIVATE KEY----- - -----END RSA PRIVATE KEY----- \ No newline at end of file + -----END RSA PRIVATE KEY----- + + aws-access-key-id: + aws-secret-access-key: + aws-bucket: \ No newline at end of file diff --git a/manifest-production.yml b/manifest-production.yml new file mode 100644 index 000000000..4c72d2f54 --- /dev/null +++ b/manifest-production.yml @@ -0,0 +1,23 @@ +platform: linux + +image_resource: + type: docker-image + source: + repository: openjdk + tag: '8-jdk' + +inputs: + - name: pal-tracker + - name: version + +outputs: + - name: build-output + +run: + path: bash + args: + - -exc + - | + cd pal-tracker + ./gradlew -P version=$(cat ../version/number) build + cp build/libs/pal-tracker-*.jar ../build-output \ No newline at end of file diff --git a/manifest.yml b/manifest-review.yml similarity index 64% rename from manifest.yml rename to manifest-review.yml index 204fb97a3..5e74a609f 100644 --- a/manifest.yml +++ b/manifest-review.yml @@ -1,4 +1,5 @@ +--- applications: - name: pal-tracker path: build/libs/pal-tracker.jar - random-route: true + host: sperugu-pal-tracker-review \ No newline at end of file From 3034b633b1275069677572fa73f4c311e4fc86a9 Mon Sep 17 00:00:00 2001 From: Subbu Perugu Date: Tue, 17 Oct 2017 17:36:20 -0400 Subject: [PATCH 11/24] updated build.yaml --- ci/build.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ci/build.yml b/ci/build.yml index 688d53085..6abcc811a 100644 --- a/ci/build.yml +++ b/ci/build.yml @@ -8,6 +8,7 @@ image_resource: inputs: - name: pal-tracker + - name: version outputs: - name: build-output @@ -19,5 +20,5 @@ run: - | cd pal-tracker chmod 777 gradlew - ./gradlew build - cp build/libs/pal-tracker.jar ../build-output \ No newline at end of file + ./gradlew -P version=$(cat ../version/number) build + cp build/libs/pal-tracker-*.jar ../build-output \ No newline at end of file From 5560f80650ab30b0c7ec0d3a7a7ecbb541dca972 Mon Sep 17 00:00:00 2001 From: Subbu Perugu Date: Tue, 17 Oct 2017 17:42:14 -0400 Subject: [PATCH 12/24] multiple pipes --- manifest-production.yml | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/manifest-production.yml b/manifest-production.yml index 4c72d2f54..caf977672 100644 --- a/manifest-production.yml +++ b/manifest-production.yml @@ -1,23 +1,5 @@ -platform: linux - -image_resource: - type: docker-image - source: - repository: openjdk - tag: '8-jdk' - -inputs: - - name: pal-tracker - - name: version - -outputs: - - name: build-output - -run: - path: bash - args: - - -exc - - | - cd pal-tracker - ./gradlew -P version=$(cat ../version/number) build - cp build/libs/pal-tracker-*.jar ../build-output \ No newline at end of file +--- +applications: +- name: pal-tracker + path: build/libs/pal-tracker.jar + host: sperugu-pal-tracker \ No newline at end of file From 152d5082af00b4acab697f7cddfdf59f1d7386ec Mon Sep 17 00:00:00 2001 From: Tyson Gern Date: Thu, 20 Jul 2017 15:04:20 -0600 Subject: [PATCH 13/24] Add tests for MVC lab --- .../InMemoryTimeEntryRepositoryTest.java | 71 ++++++++++ .../pal/tracker/TimeEntryControllerTest.java | 106 +++++++++++++++ .../pal/trackerapi/TimeEntryApiTest.java | 126 ++++++++++++++++++ 3 files changed, 303 insertions(+) create mode 100644 src/test/java/test/pivotal/pal/tracker/InMemoryTimeEntryRepositoryTest.java create mode 100644 src/test/java/test/pivotal/pal/tracker/TimeEntryControllerTest.java create mode 100644 src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java diff --git a/src/test/java/test/pivotal/pal/tracker/InMemoryTimeEntryRepositoryTest.java b/src/test/java/test/pivotal/pal/tracker/InMemoryTimeEntryRepositoryTest.java new file mode 100644 index 000000000..c88bb8266 --- /dev/null +++ b/src/test/java/test/pivotal/pal/tracker/InMemoryTimeEntryRepositoryTest.java @@ -0,0 +1,71 @@ +package test.pivotal.pal.tracker; + +import io.pivotal.pal.tracker.InMemoryTimeEntryRepository; +import io.pivotal.pal.tracker.TimeEntry; +import org.junit.Test; + +import java.time.LocalDate; +import java.util.List; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; + +public class InMemoryTimeEntryRepositoryTest { + @Test + public void create() throws Exception { + InMemoryTimeEntryRepository repo = new InMemoryTimeEntryRepository(); + TimeEntry createdTimeEntry = repo.create(new TimeEntry(123, 456, LocalDate.parse("2017-01-08"), 8)); + + TimeEntry expected = new TimeEntry(1L, 123, 456, LocalDate.parse("2017-01-08"), 8); + assertThat(createdTimeEntry).isEqualTo(expected); + + TimeEntry readEntry = repo.find(createdTimeEntry.getId()); + assertThat(readEntry).isEqualTo(expected); + } + + @Test + public void find() throws Exception { + InMemoryTimeEntryRepository repo = new InMemoryTimeEntryRepository(); + repo.create(new TimeEntry(123, 456, LocalDate.parse("2017-01-08"), 8)); + + TimeEntry expected = new TimeEntry(1L, 123, 456, LocalDate.parse("2017-01-08"), 8); + TimeEntry readEntry = repo.find(1L); + assertThat(readEntry).isEqualTo(expected); + } + + @Test + public void list() throws Exception { + InMemoryTimeEntryRepository repo = new InMemoryTimeEntryRepository(); + repo.create(new TimeEntry(123, 456, LocalDate.parse("2017-01-08"), 8)); + repo.create(new TimeEntry(789, 654, LocalDate.parse("2017-01-07"), 4)); + + List expected = asList( + new TimeEntry(1L, 123, 456, LocalDate.parse("2017-01-08"), 8), + new TimeEntry(2L, 789, 654, LocalDate.parse("2017-01-07"), 4) + ); + assertThat(repo.list()).isEqualTo(expected); + } + + @Test + public void update() throws Exception { + InMemoryTimeEntryRepository repo = new InMemoryTimeEntryRepository(); + TimeEntry created = repo.create(new TimeEntry(123, 456, LocalDate.parse("2017-01-08"), 8)); + + TimeEntry updatedEntry = repo.update( + created.getId(), + new TimeEntry(321, 654, LocalDate.parse("2017-01-09"), 5)); + + TimeEntry expected = new TimeEntry(created.getId(), 321, 654, LocalDate.parse("2017-01-09"), 5); + assertThat(updatedEntry).isEqualTo(expected); + assertThat(repo.find(created.getId())).isEqualTo(expected); + } + + @Test + public void delete() throws Exception { + InMemoryTimeEntryRepository repo = new InMemoryTimeEntryRepository(); + TimeEntry created = repo.create(new TimeEntry(123, 456, LocalDate.parse("2017-01-08"), 8)); + + repo.delete(created.getId()); + assertThat(repo.list()).isEmpty(); + } +} diff --git a/src/test/java/test/pivotal/pal/tracker/TimeEntryControllerTest.java b/src/test/java/test/pivotal/pal/tracker/TimeEntryControllerTest.java new file mode 100644 index 000000000..f7c0090e3 --- /dev/null +++ b/src/test/java/test/pivotal/pal/tracker/TimeEntryControllerTest.java @@ -0,0 +1,106 @@ +package test.pivotal.pal.tracker; + +import io.pivotal.pal.tracker.TimeEntry; +import io.pivotal.pal.tracker.TimeEntryController; +import io.pivotal.pal.tracker.TimeEntryRepository; +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.time.LocalDate; +import java.util.List; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.*; + +public class TimeEntryControllerTest { + private TimeEntryRepository timeEntryRepository; + private TimeEntryController controller; + + @Before + public void setUp() throws Exception { + timeEntryRepository = mock(TimeEntryRepository.class); + controller = new TimeEntryController(timeEntryRepository); + } + + @Test + public void testCreate() throws Exception { + TimeEntry expected = new TimeEntry(1L, 123, 456, LocalDate.parse("2017-01-08"), 8); + doReturn(expected) + .when(timeEntryRepository) + .create(any(TimeEntry.class)); + + ResponseEntity response = controller.create(new TimeEntry(123, 456, LocalDate.parse("2017-01-08"), 8)); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED); + assertThat(response.getBody()).isEqualTo(expected); + } + + @Test + public void testRead() throws Exception { + TimeEntry expected = new TimeEntry(1L, 123, 456, LocalDate.parse("2017-01-08"), 8); + doReturn(expected) + .when(timeEntryRepository) + .find(1L); + + ResponseEntity response = controller.read(1L); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isEqualTo(expected); + } + + @Test + public void testRead_NotFound() throws Exception { + doReturn(null) + .when(timeEntryRepository) + .find(1L); + + ResponseEntity response = controller.read(1L); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); + } + + @Test + public void testList() throws Exception { + List expected = asList( + new TimeEntry(1, 123, 456, LocalDate.parse("2017-01-08"), 8), + new TimeEntry(2, 789, 321, LocalDate.parse("2017-01-07"), 4) + ); + doReturn(expected).when(timeEntryRepository).list(); + + ResponseEntity> response = controller.list(); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isEqualTo(expected); + } + + @Test + public void testUpdate() throws Exception { + TimeEntry expected = new TimeEntry(1, 987, 654, LocalDate.parse("2017-01-07"), 4); + doReturn(expected) + .when(timeEntryRepository) + .update(eq(1L), any(TimeEntry.class)); + + ResponseEntity response = controller.update(1L, expected); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isEqualTo(expected); + } + + @Test + public void testUpdate_NotFound() throws Exception { + doReturn(null) + .when(timeEntryRepository) + .update(eq(1L), any(TimeEntry.class)); + + ResponseEntity response = controller.update(1L, new TimeEntry()); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); + } + + @Test + public void testDelete() throws Exception { + ResponseEntity response = controller.delete(1L); + verify(timeEntryRepository).delete(1L); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT); + } +} diff --git a/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java b/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java new file mode 100644 index 000000000..b742e5447 --- /dev/null +++ b/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java @@ -0,0 +1,126 @@ +package test.pivotal.pal.trackerapi; + +import com.jayway.jsonpath.DocumentContext; +import io.pivotal.pal.tracker.PalTrackerApplication; +import io.pivotal.pal.tracker.TimeEntry; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; + +import java.time.LocalDate; +import java.util.Collection; + +import static com.jayway.jsonpath.JsonPath.parse; +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = PalTrackerApplication.class, webEnvironment = RANDOM_PORT) +public class TimeEntryApiTest { + + @Autowired + private TestRestTemplate restTemplate; + + private TimeEntry timeEntry = new TimeEntry(123, 456, LocalDate.parse("2017-01-08"), 8); + + @Test + public void testCreate() throws Exception { + ResponseEntity createResponse = restTemplate.postForEntity("/time-entries", timeEntry, String.class); + + + assertThat(createResponse.getStatusCode()).isEqualTo(HttpStatus.CREATED); + + DocumentContext createJson = parse(createResponse.getBody()); + assertThat(createJson.read("$.id", Long.class)).isGreaterThan(0); + assertThat(createJson.read("$.projectId", Long.class)).isEqualTo(123L); + assertThat(createJson.read("$.userId", Long.class)).isEqualTo(456L); + assertThat(createJson.read("$.date", String.class)).isEqualTo("2017-01-08"); + assertThat(createJson.read("$.hours", Long.class)).isEqualTo(8); + } + + @Test + public void testList() throws Exception { + Long id = createTimeEntry(); + + + ResponseEntity listResponse = restTemplate.getForEntity("/time-entries", String.class); + + + assertThat(listResponse.getStatusCode()).isEqualTo(HttpStatus.OK); + + DocumentContext listJson = parse(listResponse.getBody()); + + Collection timeEntries = listJson.read("$[*]", Collection.class); + assertThat(timeEntries.size()).isEqualTo(1); + + Long readId = listJson.read("$[0].id", Long.class); + assertThat(readId).isEqualTo(id); + } + + @Test + public void testRead() throws Exception { + Long id = createTimeEntry(); + + + ResponseEntity readResponse = this.restTemplate.getForEntity("/time-entries/" + id, String.class); + + + assertThat(readResponse.getStatusCode()).isEqualTo(HttpStatus.OK); + DocumentContext readJson = parse(readResponse.getBody()); + assertThat(readJson.read("$.id", Long.class)).isEqualTo(id); + assertThat(readJson.read("$.projectId", Long.class)).isEqualTo(123L); + assertThat(readJson.read("$.userId", Long.class)).isEqualTo(456L); + assertThat(readJson.read("$.date", String.class)).isEqualTo("2017-01-08"); + assertThat(readJson.read("$.hours", Long.class)).isEqualTo(8); + } + + @Test + public void testUpdate() throws Exception { + Long id = createTimeEntry(); + TimeEntry updatedTimeEntry = new TimeEntry(2, 3, LocalDate.parse("2017-01-09"), 9); + + + ResponseEntity updateResponse = restTemplate.exchange("/time-entries/" + id, HttpMethod.PUT, new HttpEntity<>(updatedTimeEntry, null), String.class); + + + assertThat(updateResponse.getStatusCode()).isEqualTo(HttpStatus.OK); + + DocumentContext updateJson = parse(updateResponse.getBody()); + assertThat(updateJson.read("$.id", Long.class)).isEqualTo(id); + assertThat(updateJson.read("$.projectId", Long.class)).isEqualTo(2L); + assertThat(updateJson.read("$.userId", Long.class)).isEqualTo(3L); + assertThat(updateJson.read("$.date", String.class)).isEqualTo("2017-01-09"); + assertThat(updateJson.read("$.hours", Long.class)).isEqualTo(9); + } + + @Test + public void testDelete() throws Exception { + Long id = createTimeEntry(); + + + ResponseEntity deleteResponse = restTemplate.exchange("/time-entries/" + id, HttpMethod.DELETE, null, String.class); + + + assertThat(deleteResponse.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT); + + ResponseEntity deletedReadResponse = this.restTemplate.getForEntity("/time-entries/" + id, String.class); + assertThat(deletedReadResponse.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); + } + + private Long createTimeEntry() { + HttpEntity entity = new HttpEntity<>(timeEntry); + + ResponseEntity response = restTemplate.exchange("/time-entries", HttpMethod.POST, entity, TimeEntry.class); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED); + + return response.getBody().getId(); + } +} From 6e3f11b3a4d52e3415a14faea8f4fb1e2612059f Mon Sep 17 00:00:00 2001 From: Subbu Perugu Date: Tue, 17 Oct 2017 18:39:46 -0400 Subject: [PATCH 14/24] test --- build.gradle | 1 + .../pivotal/pal/tracker/EnvController.class | Bin 0 -> 1631 bytes .../pal/tracker/PalTrackerApplication.class | Bin 0 -> 734 bytes .../pal/tracker/WelcomeController.class | Bin 0 -> 830 bytes .../tracker/InMemoryTimeEntryRepository.class | Bin 0 -> 2168 bytes .../pal/tracker/PalTrackerApplication.class | Bin 0 -> 2276 bytes .../io/pivotal/pal/tracker/TimeEntry.class | Bin 0 -> 2515 bytes .../TimeEntryRepository$TimeEntry.class | Bin 0 -> 2656 bytes .../pal/tracker/TimeEntryRepository.class | Bin 0 -> 781 bytes .../pal/tracker/EnvControllerTest.class | Bin 0 -> 1469 bytes .../pal/tracker/WelcomeControllerTest.class | Bin 0 -> 1029 bytes .../pal/trackerapi/WelcomeApiTest.class | Bin 0 -> 1704 bytes src/main/java/PalTrackerApplication.java | 12 -- .../pivotal/pal/tracker}/EnvController.java | 0 .../tracker/InMemoryTimeEntryRepository.java | 38 ++++++ .../pal/tracker/PalTrackerApplication.java | 32 +++++ .../io/pivotal/pal/tracker/TimeEntry.java | 88 ++++++++++++ .../pal/tracker/TimeEntryController.java | 57 ++++++++ .../pal/tracker/TimeEntryRepository.java | 101 ++++++++++++++ .../pal/tracker/EnvControllerTest.java | 28 ---- .../InMemoryTimeEntryRepositoryTest.java | 71 ---------- .../pal/tracker/TimeEntryControllerTest.java | 106 --------------- .../pal/tracker/WelcomeControllerTest.java | 16 --- .../pal/trackerapi/TimeEntryApiTest.java | 126 ------------------ .../pal/trackerapi/WelcomeApiTest.java | 26 ---- 25 files changed, 317 insertions(+), 385 deletions(-) create mode 100644 out/production/classes/io/pivotal/pal/tracker/EnvController.class create mode 100644 out/production/classes/io/pivotal/pal/tracker/PalTrackerApplication.class create mode 100644 out/production/classes/io/pivotal/pal/tracker/WelcomeController.class create mode 100644 out/test/classes/io/pivotal/pal/tracker/InMemoryTimeEntryRepository.class create mode 100644 out/test/classes/io/pivotal/pal/tracker/PalTrackerApplication.class create mode 100644 out/test/classes/io/pivotal/pal/tracker/TimeEntry.class create mode 100644 out/test/classes/io/pivotal/pal/tracker/TimeEntryRepository$TimeEntry.class create mode 100644 out/test/classes/io/pivotal/pal/tracker/TimeEntryRepository.class create mode 100644 out/test/classes/test/pivotal/pal/tracker/EnvControllerTest.class create mode 100644 out/test/classes/test/pivotal/pal/tracker/WelcomeControllerTest.class create mode 100644 out/test/classes/test/pivotal/pal/trackerapi/WelcomeApiTest.class delete mode 100644 src/main/java/PalTrackerApplication.java rename src/main/java/{ => io/pivotal/pal/tracker}/EnvController.java (100%) create mode 100644 src/main/java/io/pivotal/pal/tracker/InMemoryTimeEntryRepository.java create mode 100644 src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java create mode 100644 src/main/java/io/pivotal/pal/tracker/TimeEntry.java create mode 100644 src/main/java/io/pivotal/pal/tracker/TimeEntryController.java create mode 100644 src/main/java/io/pivotal/pal/tracker/TimeEntryRepository.java delete mode 100644 src/test/java/test/pivotal/pal/tracker/EnvControllerTest.java delete mode 100644 src/test/java/test/pivotal/pal/tracker/InMemoryTimeEntryRepositoryTest.java delete mode 100644 src/test/java/test/pivotal/pal/tracker/TimeEntryControllerTest.java delete mode 100644 src/test/java/test/pivotal/pal/tracker/WelcomeControllerTest.java delete mode 100644 src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java delete mode 100644 src/test/java/test/pivotal/pal/trackerapi/WelcomeApiTest.java diff --git a/build.gradle b/build.gradle index 8009ec0fa..4bd77b163 100644 --- a/build.gradle +++ b/build.gradle @@ -10,6 +10,7 @@ repositories { dependencies { compile("org.springframework.boot:spring-boot-starter-web") testCompile("org.springframework.boot:spring-boot-starter-test") + compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.1") } bootRun.environment([ "WELCOME_MESSAGE": "hello", diff --git a/out/production/classes/io/pivotal/pal/tracker/EnvController.class b/out/production/classes/io/pivotal/pal/tracker/EnvController.class new file mode 100644 index 0000000000000000000000000000000000000000..7eab781d1c865e4064fc1033768c5b213022d36a GIT binary patch literal 1631 zcmbVM-A)@v6#mv;Yy%Dur-4F9OQ@583E8wI4GomU1*f%QAhIpms29=L9-K++U2Att z8lg&kj6Oyy6-8~OK0qI;>Njf>uR|_SFJ{i)oHO6~nfd46KmP`>hOfskfjgtPfKSc# znb|%!+gcKL$B@9C6uub8J*=njWeWFG*hpeCiLWf!9naSmGNl*dNH{IwHl2$0mD}8~ zFy5AJ&wpJ~ZN(##^~0hYXyMjn(QU{dEnFOE@{NWsgTO*!Q@KiSS$Mp3Hst@)EFTc& zw%3rvP*Sek>$YpsuZmiWnoP;7i`Iegm67{OTR&ICa-*a?r=yNMEm}^8OZ%e!QuR2*$H7D+rv<00}E($Sfq$C*K}FcsCA?pw&7XVU38c;)-G7qZT0 z9bYo<>%st72rAy`(zYIb!##(M$N=;;{V5Coj=l=l`1wrmZ?c&@3qdYpY7tVFBc!Yd zX$%(Yb(wUU($AzLq;t79uyQLWh~;jbAf8)2ft{PRPcZUZ=rd!^@M8mJc*lnr#Vqfe zW0kLvo2SP7i(w?nbCk?^gTx9sgC!Pn22DKV3?7^Qz^ct-^ITU0d^BJ`9O8Rq9%rAf3FG@`<^X46~8a z@+|Lmq`eZIlrpu*#3F4At9<;MEW3NvW7v(9;hB0ePNY1e*I5xiO3N=qde#3t%Cc0& z!YQMhw52PQW>~J@L=WOg@Kor8x13cvX%;MmO$c{kM42IMncT+mRC!wKBUkKtRED+7 zTszg3ZPimm`bcYH=qV8PVPtH=dzs&PXGK>&8~eyR#yBo=XJVu8RFYfCTSdz;6^1ob zF^&m_o&N|AuvW!7HW=mxs?R$QGIk7&Kk4)d3@xX-vN;`JTpv2UK2CGQiEWQmN&=mH zas1?0sK3>r9hCOvCbm$bf5ty0FfW>HKoKX;$d5PPz+Q`Tg{&`w8YanB`wa*&MbRY1 s)0FYU`vn+Ksg)Y<7`qt_kBF@@sPn!WuA+n zWLOQ(#8~h|=pi3CtMu?C!~83ym3z&w-kRNRhYXdjIVSRQptO8DN+W6Cizq4OOe~U6 zSXIQ+WW}AToS_pa!!tEDP9!{|<*bO$rR9e*iA^fIMmuYgL|SU$>A^@lmC8`%ij>|7 zD^lsC-O-x(P8m&#+kvq|o@YhdCk5cb*mE99p>uvBVrT3n7k>#n6v;@Ec6D4hboOT- z4|_p(zu!CR_XdNVz1}B=g|kx610)>`e^<1=jj-`Wj2x{wi%ls>-N%${_8k!6#C zGtN*On30X;t}5oZF*B3r#XT{+xc11`6*!Q&yE4)`d_1b7ig||CO)r9>`R{q`M`tp2 z40Ek^h$r-%D)hV0X!M&S|G*x-X4FKX{Pyc34* zfs{;P9p8$e4TVi{@Ez(3TjJ%~DPCR?9@{1M5#I!>+s|}^eNsAwbHZU_Z?(+N%pp&I z)xvN~M^*M`pwr2NABD2}1nwz}^Oe9u% zT0FJ%Zcj}F(7%QEPXL^_JQwTXeKh&MGqDtS9`j-XpU3553X8Aeo_{1^o zj{$B-biAcPUSazhZ}amo4hs|fWu$NTe4U?>4BkMFbG8`AOSCLvh%;cUbOfvP9GR^n z4E>4h@A2x(9OY{P*vR4xu5hN3^_Zd;aQ+V7O<6yXC2OZCY9Lc5jbVDvfERwDiE5emnFlRe>&robera|C{ftEsrh#7S5a zvADTIjBd^`@G+)huFFh9zr^%jX0fis?5Sj5N9;r@maI4l8G0z=qtwscGzm_cgmat6 hi#ClsQ`b#{(;+)4IfIX>G=pon$frZ{2z^Z={}SOu9Z3KH literal 0 HcmV?d00001 diff --git a/out/test/classes/io/pivotal/pal/tracker/PalTrackerApplication.class b/out/test/classes/io/pivotal/pal/tracker/PalTrackerApplication.class new file mode 100644 index 0000000000000000000000000000000000000000..32ed650d9f198a70eb25c1ffb7dfb14ffd7080a8 GIT binary patch literal 2276 zcmb_e{Zku77=D%nj>N-KXi-qBRcOOUxfUpXq+&w|HJGFwlG366z-*Ew-Sl#IvvTP)Q?&IRn?yxQ>j0 z`81N4OJf0xDJnIB7Se#9-CJ1wgkDCxAaJrrEEDDPyMCJXdpp>q2T}e_T*L&!(tkNL z17Di>3STp<3o_$D#fr5!`+%xe#iVSn_ z;IHg9yPB)D!G6K9C#<%wR&U+3cBq;L7lxVG!4D=%xXQ2)ciKHVLjoh@QJ<(TNw<-S zC^tl(jZy1->`A>5Qoessx}zdB$gFinlE}vadlCem*5Z*+q%7JUX=*3b)=E@Tn_X!) zwG@-uh2b74@*MJI4U`9R9%+poDq3_JcDE^J`y+#KIx~xoBUIkzzAvcbMl1I#2bG7V z62tm{zWMju#MYEA=ytN@&%HM8Rr0By8Ggz9zm(_07sKjcf%YzfY83aj-~sh6T~Lp9 zi?zbRR<2g49^|SAwPLwYt>wxOstmUWZ0PiEiAzZoT|5l4`!#n<`r*y~_^n&GujTXF zy3y%1DV?xOW!}L<*qPb$sy$rS9Y7adeA#$C;VOpc)v7;95Mh|0-$`0oG%^~87hZt< zMw202qTi98aSWGfZiWt+!UwcU;X_Q5MF+2ww+vVNj{F9HW@Q zM`Q;+#wX;VbF93{@e0ZP7a0DPY*7rC---`0Tp=Hg@KYKYKI?5x=aPNgi!U+q9L5Po zlYbz!pLkAhp%c7E3v)m53S(5~eKK4)!T4FZ843mg93hC41aOuhUdI*sKQR-5u-K;| e3L$~dDRKy3kaq}dmd3mEO%uE&e2eeyPX7(0c$=L7 literal 0 HcmV?d00001 diff --git a/out/test/classes/io/pivotal/pal/tracker/TimeEntry.class b/out/test/classes/io/pivotal/pal/tracker/TimeEntry.class new file mode 100644 index 0000000000000000000000000000000000000000..eb642119f57db423e0d2ffa6717ed9b8a2281f40 GIT binary patch literal 2515 zcmah~T~k|C6kYcwHwkHg5WWhbC81S9LK~H;RX&8$Qj&%igd+8m3*3QQQxZeMjMM3Z zDO+V&?$RI1jEkANWoWV>03o_r9;Z6XH0W1aZuB?`2 zSP9^+%&RiI=f|1`-QLomWi_-EONIN^W;w&Pud-*A#PDvJWlKYM?!LKirpxvNE1fHB zn)&N=k83b?3YF3x<1>tzuy^h9lm<^?WR;P#g)K(5=j>hUcICl_Ra!AO^7KShf>pC* z%i48%%R4sn4CU-Xx@hkg%4R-Yxu*=$oh8M$|WyQgd$ z+m>QTkmPLaIV*1>E3qMNE;dwX;qtC4grByM*3g?6X?nYc0ME=jf-t>EVtrOI^W@c? za6b$w>5OtAgZK}m%;^?32xJxv!x7ebxAMF{UO9L6hajJAq>cH4(CG{L@b0p zS>6NT#{RE5etZyO`@SB+IL0VdgP8;3TE)(9StW9+-~Q0vnhfDXd?dritifok-iVVF zVl?LTBJIFvOxkgBSdM-o!@6>+CR9mCV-jq^aN}Y}ZJuLsX7lDAtI>H(9_6Guohn9j zZ?rf|XcmjsE?>f>qa}1DbsOP`$}T~sV;`QjeJowABaU>!F}+l}+0)VuBr^ZK&C&+= z#Q+TGQcYUAR9ej}wwhUQHM8WLL(rrlI9}jtjI+ksNIrx16Ac{~IR-h)Q7>BYlCsnx zyo_P4BtgPW@C7{Z(-)Nl$-zVD$@n2W$v)!{-ekA$5Jn?yKYTpe&a`2)paX4)pbMSE z*`=&$RGVyavZ7aP(vk;SiKxfeIW0 zbzb4`A*f!4o+YS0!s+KT;3T=q#=ov z3o%V?CDKSfh3^L%j^x4vJl;(1QCxC^r^rt(^NG~HhO$nM??DnpMdpsxTs-is-c5Gej4#)P09dT^p?+g!D(G}@-97YvA;YV~-(R;EL7msw)l8aem Jh2+gn{|mmrDK|-#`5V zU<4nfZ~&7@Bye7a3o=}k;gSrO>im`vch%ri3FNMJUBxdd*? zYF>tS5?GM=mJGM!cvr!2R}|EgLbC4F?mEkUnd{hE!|}w3uGqe#&^LY8UbjoWd(SCN z*Ou+ddAdgx%+=bO*I;~^F=KAk^(Pb}`Js76PS#c!xqI5JI#<{3Ejiwty;PwmtrN`K zo-6Cn8TD6P=1I=+gbP*QyRTrCt5wIFtk{i)!&j%L-CC*cuGf6KQmXUuJ$w0M$1Al$ zZaDQ?!}V+4{bMa3F=p~pQyt-#1!h0rKIgT0$I*naCQ@WJf>ks>C9xJ?UT>3@GNx|zUNl&DD-YZ)JX#`iA&8A+_H1g zm98CVQQD^Ygdnky!x0M^9J0_a!wWcUVE|bR2W9yl-jCx0g^~Z$zBoR#a2jVUjN=r! zY?mIOJh$dnRvb^Es|D|YyE1NJ5d^k~B{pd|+Y*i(&zJxGHRX}u=L#@kh$3hiq82?jw#MSFQVwfi?k$24tWVfoFzelqa^qg5yUxX zBtc4;sNkuUYrx?Qdl6#WemD_|96F5d;AK?>!Tt%19e#AKTJ>q49yZ$j-Zb4 zxT9Ph)4(P)@LUU6PLnObavGul%V~%LET=EVY>~n+j)xFSHr2i(tQG@FnEIX$zd^}I@V@3XjqZF4C@U0bOU28z_Adp!pj|j7(0QKIs$p6V{l|= z@X3zBuWGf4Xr!7=3Zmcdpx{i$;1Qe(DcBlpKHd>L+A)|PxiI)owpsXFFBHFp8GT%Q zita)a@gkpZU`8Gne?{VobY_^(7hj2sD}yVewTf^RX{}7I%&)_Z(WltM{Zaa&O(Y*- zw^+X;9({ypM9@a`1v^4f0B0$^x8%nN1#*41V8rp^k#{Hljl|3>-BgayW0`hc!41F4OLUFmzs_G0Z9@0%>0a9x1PXCcQx5Xmes`WbWdO# zV($s;h}h}CCjPxWgH80N`vQmA#J-+~bTqMlrxX{qsA(p|Y_MZ;XU`$eIYl4|Rxya= EKm2RvC;$Ke literal 0 HcmV?d00001 diff --git a/out/test/classes/io/pivotal/pal/tracker/TimeEntryRepository.class b/out/test/classes/io/pivotal/pal/tracker/TimeEntryRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..ad1b05352eb55f0db0603bb6d329cdd20feafcb1 GIT binary patch literal 781 zcmbtSyH3ME5S$B%O-vvm;q?I&HU)0d1<@c7AWKLD(I3uXT{zCx`5Y+?A4b6k@KK13 zk(|O!WQx6&W_L%oGyDDX`2}E%V;2W54qY5Ebf4tAxYpJz8G3hG3v;D-o{OBpiH+b^ zFlaxLX_(1HYPkwC3Tt@$CQMijKZ-2PrA^K9w8ADqvoVJDtJE`wTR)of1rHV1Nf@O% z`3Iwtxn&so!5?K|r3xd$CM3aBCYsyA(5m;fhKpuPToSQUWV2GhkJ=*5NE-|4%!Cpv zD@V1T1kViKG%ZXlZlt1?lghxgspl&n7|u371BSucyN8##h)MqO=JV?_au^s|bhtpL qO`^gDC~u=%W)C}5?@_gdUBdU!FYy8POKe!aBXlTx)VW2`M)wzYV)OI> literal 0 HcmV?d00001 diff --git a/out/test/classes/test/pivotal/pal/tracker/EnvControllerTest.class b/out/test/classes/test/pivotal/pal/tracker/EnvControllerTest.class new file mode 100644 index 0000000000000000000000000000000000000000..7edbc421f4d5471d58ffe66c6c9c26a499ee4a53 GIT binary patch literal 1469 zcmbVMTT|0O6#lj?O$bFwxG3HLMWld4?jRSfZBfP+6gntROo%H4OHz_8qd&_R@G|50 z?1Mka@oZBuoqA#HOwaCqd(OAtxlF!(|M(fe6dr5Paa+NVij<0B6(cG}HAHYnnlTkd z3+`fE!GwlMOlg?LjEY$mb1LQ)+*7c?5MQ(%TP!m~hf-S%v5fnYGqmMxhp$x*cez(I zcT0rmdDk*aTc&5ra}A7%ecNXk%?s`eqii3$!YmnO`U}srUUSdLIma2-5uRHr5fypK zC-=A@oI$hBt{4XMw)<}i3XgIb+m=5tk4>W@?2=J1%Y=8=D|}o2sdBpBbul=WOlQy7 z6rSzuEi@v_M5yK7SiCH3*YOoBGW2Xz9AO{wE!(%L25HBkbgS14$-L|B83z@rrBO>$ z+jJ|Q#aC=uzV3!Xj7j|obfA-A;5?~QbLM7dr>Owr6OR}olT!@s;}er(KKIQ-KIRLL zb0Ha*61WU%Y3%9xMp4221Rfww8mkox<*ndh0vTi#HFLh2ZrT(+%U_wk*)%AXlR0h+v2`S zyty!MZSYdnv1#PkrDmFz$n2ZmCO@ih$KnAmjivGXXa5+&&}{p;ql#H7x(xGS6=&J~ zm9q9S`q54w9QliYnuboYU34;NWwbU8pMt%sUU!p?S3wOIX`cuja0xwhVlV7s6O9z}muCQ-CGI zSX^RKT@+H?btq(8kt8KugrrF466t$J3OVVMs`~#j`Y!UiMhp@3l9$Xa aK>7&jCnQG54cw%itib@S47bQeF!%$aGJFF7 literal 0 HcmV?d00001 diff --git a/out/test/classes/test/pivotal/pal/tracker/WelcomeControllerTest.class b/out/test/classes/test/pivotal/pal/tracker/WelcomeControllerTest.class new file mode 100644 index 0000000000000000000000000000000000000000..a49636ed7b17145d6c5abfaff7a477b310365353 GIT binary patch literal 1029 zcmb7D%Wl&^6g`uqb?TV5q%D2$Xm}?nWLU64K}b;)#KHoJM5(%*m{Bspu~U0o=x4EM z5lDOhABDJMM-5T~DwaIgb06oPduIIe*S8-4o`c(1!JdViMclHH!)>$Nv9XVejk~yK z;l70j49hQ7p!6$-e6`kRD73>5l3~qNfqa`x1~Tf2flr>Y8+yX;i%6OM%vsPQ6*D|@ zwTv~Ns*_L)pHJ!4k?=msh#yMd3n#K22099TpWrC7$xu?dD^BA#()U9K+q;M})Lj)` z4U2H>&Sy_fwF-mS!b67L_er4DMD|szh-WJZh*BD6Sa-u{$j1rI%V#v~ZkR-#e67rg zdke`n#^OXcuu)>DUX>Zc^;YFmHhX0v<5&!(!Fc50F{&174(d3t@WjDWG#DEHCm_vY zSaDwjL;h|smY!zVn7b}SVc4p=bAMMyDi}7+K^J3jI#YkMTDzRXuy527aV#S}=3W>{ zE~bjNQisu-#LuF=k)ZaSMU7I$FJla1b3b%@B%-c7PGsQ843|!nFK+wS5{4BOca9U` z_vj|Q_>)DZOtbcH7PHRuQxxf!F;5OO8%ne~wBuwM*`@k9*q1bXjn?JVS;Q)RJ6QtO zuueNVd@9bT#XYh)^6h*_?x>tUN5MTf!_rqQm-A<^4(E8QlxdJ`Lfj@KgL@;Zo#C2D Y85@MZNwEUiEy~E4Y?Ec!p*4ry-!S$OL;wH) literal 0 HcmV?d00001 diff --git a/out/test/classes/test/pivotal/pal/trackerapi/WelcomeApiTest.class b/out/test/classes/test/pivotal/pal/trackerapi/WelcomeApiTest.class new file mode 100644 index 0000000000000000000000000000000000000000..f2402b774f4ccc70b4482b3200fb43b572c7f5f8 GIT binary patch literal 1704 zcmah}ZBraY5Ps%fSU64+4^1Q*f-x$P$oPUW@nX!0q|`b?3a+5ae6Z%00fy}DTxMq> z`B_$3(XvX*&;BUOXZHvkVZD5~>Fb{Er@No-*?<21<1YZqXw^`~`~+%P7{_9O>j7@m z@DY}3kob5UpVaUvJ`1ocPmU+qzwu^qq-!cbw|!L+c*PuC-ozI?6-c zm0@Ib>}7}g`cQCFU}imyz3J&qlbCL#TXAf?^2WxAz^!6rt>Q%PsL)&YOseAm*-E`V zG)`|5cI=LcjlU~Uo?qA$sI1s+mU^yjV*McPb+zj#W)o<%ZK$G6aD{A^Q_EH8RaA_#YrE#pCBzIfJ?=il5prVwaHw!7t z`v`69^;0kNyvuSU9~cn`M1bPJlPSK49)x|v=^nKmgpo=TO}>`e#>xV=Iw75HKP>>3YQ^5HZe z4Os?(&4sfjfh$>E*~&P-FGK6J%xGxkhQYh94Q@MoiVtbEAooJP1ZOe2N%lbf%ARr? z`bnzeP!Di9a9N!8e?tT&OtShUGcS)GMDaNBErRDiLHAyKbtBb!u{E}ZccxKL^(=^WvfDU*AZ&C_epuLm_|AY$v0kez0 zqSPoiD&;>>eU7oeQE6ZQ9YJIKIVQGFP^VC)_eG+x_3_aF literal 0 HcmV?d00001 diff --git a/src/main/java/PalTrackerApplication.java b/src/main/java/PalTrackerApplication.java deleted file mode 100644 index 80f2a72a5..000000000 --- a/src/main/java/PalTrackerApplication.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.pivotal.pal.tracker; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class PalTrackerApplication { - - public static void main(String[] args) { - SpringApplication.run(PalTrackerApplication.class, args); - } -} \ No newline at end of file diff --git a/src/main/java/EnvController.java b/src/main/java/io/pivotal/pal/tracker/EnvController.java similarity index 100% rename from src/main/java/EnvController.java rename to src/main/java/io/pivotal/pal/tracker/EnvController.java diff --git a/src/main/java/io/pivotal/pal/tracker/InMemoryTimeEntryRepository.java b/src/main/java/io/pivotal/pal/tracker/InMemoryTimeEntryRepository.java new file mode 100644 index 000000000..1699bdd24 --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/InMemoryTimeEntryRepository.java @@ -0,0 +1,38 @@ +package io.pivotal.pal.tracker; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class InMemoryTimeEntryRepository implements io.pivotal.pal.tracker.TimeEntryRepository { + private HashMap timeEntries = new HashMap<>(); + + @Override + public TimeEntry create(TimeEntry timeEntry) { + timeEntry.setId(timeEntries.size() + 1); + timeEntries.put(timeEntry.getId(), timeEntry); + return timeEntry; + } + + @Override + public TimeEntry find(Long id) { + return timeEntries.get(id); + } + + @Override + public List list() { + return new ArrayList<>(timeEntries.values()); + } + + @Override + public TimeEntry update(Long id, TimeEntry timeEntry) { + timeEntries.replace(id, timeEntry); + timeEntry.setId(id); + return timeEntry; + } + + @Override + public void delete(Long id) { + timeEntries.remove(id); + } +} \ No newline at end of file diff --git a/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java b/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java new file mode 100644 index 000000000..78cb5b60f --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java @@ -0,0 +1,32 @@ +package io.pivotal.pal.tracker; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; + +@SpringBootApplication +public class PalTrackerApplication { + + public static void main(String[] args) { + SpringApplication.run(PalTrackerApplication.class, args); + } + + @Bean + TimeEntryRepository timeEntryRepository() { + return new InMemoryTimeEntryRepository(); + } + + @Bean + public ObjectMapper jsonObjectMapper() { + return Jackson2ObjectMapperBuilder.json() + .serializationInclusion(JsonInclude.Include.NON_NULL) // Don’t include null values + .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) //ISODate + .modules(new JavaTimeModule()) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/io/pivotal/pal/tracker/TimeEntry.java b/src/main/java/io/pivotal/pal/tracker/TimeEntry.java new file mode 100644 index 000000000..abc194422 --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/TimeEntry.java @@ -0,0 +1,88 @@ +package io.pivotal.pal.tracker; + +import java.time.LocalDate; + +public class TimeEntry { + private long id; + private long projectId; + private long userId; + private LocalDate date; + private int hours; + + public TimeEntry() { + } + + public TimeEntry(long projectId, long userId, LocalDate date, int hours) { + this.projectId = projectId; + this.userId = userId; + this.date = date; + this.hours = hours; + } + + public TimeEntry(long id, long projectId, long userId, LocalDate date, int hours) { + this.id = id; + this.projectId = projectId; + this.userId = userId; + this.date = date; + this.hours = hours; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public long getProjectId() { + return projectId; + } + + public long getUserId() { + return userId; + } + + public LocalDate getDate() { + return date; + } + + public int getHours() { + return hours; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TimeEntry timeEntry = (TimeEntry) o; + + if (id != timeEntry.id) return false; + if (projectId != timeEntry.projectId) return false; + if (userId != timeEntry.userId) return false; + if (hours != timeEntry.hours) return false; + return date != null ? date.equals(timeEntry.date) : timeEntry.date == null; + } + + @Override + public int hashCode() { + int result = (int) (id ^ (id >>> 32)); + result = 31 * result + (int) (projectId ^ (projectId >>> 32)); + result = 31 * result + (int) (userId ^ (userId >>> 32)); + result = 31 * result + (date != null ? date.hashCode() : 0); + result = 31 * result + hours; + return result; + } + + @Override + public String toString() { + return "TimeEntry{" + + "id=" + id + + ", projectId=" + projectId + + ", userId=" + userId + + ", date='" + date + '\'' + + ", hours=" + hours + + '}'; + } +} diff --git a/src/main/java/io/pivotal/pal/tracker/TimeEntryController.java b/src/main/java/io/pivotal/pal/tracker/TimeEntryController.java new file mode 100644 index 000000000..a9b854f6c --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/TimeEntryController.java @@ -0,0 +1,57 @@ +package io.pivotal.pal.tracker; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/time-entries") +public class TimeEntryController { + + private TimeEntryRepository timeEntriesRepo; + + public TimeEntryController(TimeEntryRepository timeEntriesRepo) { + this.timeEntriesRepo = timeEntriesRepo; + } + + @PostMapping + public ResponseEntity create(@RequestBody TimeEntry timeEntry) { + TimeEntry createdTimeEntry = timeEntriesRepo.create(timeEntry); + + return new ResponseEntity<>(createdTimeEntry, HttpStatus.CREATED); + } + + @GetMapping("{id}") + public ResponseEntity read(@PathVariable Long id) { + TimeEntry timeEntry = timeEntriesRepo.find(id); + if (timeEntry != null) { + return new ResponseEntity<>(timeEntry, HttpStatus.OK); + } else { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + } + + @GetMapping + public ResponseEntity> list() { + return new ResponseEntity<>(timeEntriesRepo.list(), HttpStatus.OK); + } + + @PutMapping("{id}") + public ResponseEntity update(@PathVariable Long id, @RequestBody TimeEntry timeEntry) { + TimeEntry updatedTimeEntry = timeEntriesRepo.update(id, timeEntry); + if (updatedTimeEntry != null) { + return new ResponseEntity<>(updatedTimeEntry, HttpStatus.OK); + } else { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + } + + @DeleteMapping("{id}") + public ResponseEntity delete(@PathVariable Long id) { + timeEntriesRepo.delete(id); + + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } +} diff --git a/src/main/java/io/pivotal/pal/tracker/TimeEntryRepository.java b/src/main/java/io/pivotal/pal/tracker/TimeEntryRepository.java new file mode 100644 index 000000000..b8c8017d3 --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/TimeEntryRepository.java @@ -0,0 +1,101 @@ +package io.pivotal.pal.tracker; + +import java.time.LocalDate; +import java.util.List; + +public interface TimeEntryRepository { + TimeEntry create(TimeEntry timeEntry); + + TimeEntry find(Long id); + + List list(); + + TimeEntry update(Long id, TimeEntry timeEntry); + + void delete(Long id); + + class TimeEntry { + private long id; + private long projectId; + private long userId; + private LocalDate date; + private int hours; + + public TimeEntry() { + } + + public TimeEntry(long projectId, long userId, LocalDate date, int hours) { + this.projectId = projectId; + this.userId = userId; + this.date = date; + this.hours = hours; + } + + public TimeEntry(long id, long projectId, long userId, LocalDate date, int hours) { + this.id = id; + this.projectId = projectId; + this.userId = userId; + this.date = date; + this.hours = hours; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public long getProjectId() { + return projectId; + } + + public long getUserId() { + return userId; + } + + public LocalDate getDate() { + return date; + } + + public int getHours() { + return hours; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TimeEntry timeEntry = (TimeEntry) o; + + if (id != timeEntry.id) return false; + if (projectId != timeEntry.projectId) return false; + if (userId != timeEntry.userId) return false; + if (hours != timeEntry.hours) return false; + return date != null ? date.equals(timeEntry.date) : timeEntry.date == null; + } + + @Override + public int hashCode() { + int result = (int) (id ^ (id >>> 32)); + result = 31 * result + (int) (projectId ^ (projectId >>> 32)); + result = 31 * result + (int) (userId ^ (userId >>> 32)); + result = 31 * result + (date != null ? date.hashCode() : 0); + result = 31 * result + hours; + return result; + } + + @Override + public String toString() { + return "TimeEntry{" + + "id=" + id + + ", projectId=" + projectId + + ", userId=" + userId + + ", date='" + date + '\'' + + ", hours=" + hours + + '}'; + } + } +} diff --git a/src/test/java/test/pivotal/pal/tracker/EnvControllerTest.java b/src/test/java/test/pivotal/pal/tracker/EnvControllerTest.java deleted file mode 100644 index fda0f0f34..000000000 --- a/src/test/java/test/pivotal/pal/tracker/EnvControllerTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package test.pivotal.pal.tracker; - -import org.junit.Test; - -import java.util.Map; -import io.pivotal.pal.tracker.EnvController; - -import static org.assertj.core.api.Assertions.assertThat; - -public class EnvControllerTest { - @Test - public void getEnv() throws Exception { - EnvController controller = new EnvController( - "8675", - "12G", - "34", - "123.sesame.street" - ); - - Map env = controller.getEnv(); - - assertThat(env.get("PORT")).isEqualTo("8675"); - assertThat(env.get("MEMORY_LIMIT")).isEqualTo("12G"); - assertThat(env.get("CF_INSTANCE_INDEX")).isEqualTo("34"); - assertThat(env.get("CF_INSTANCE_ADDR")).isEqualTo("123.sesame.street"); - } - -} diff --git a/src/test/java/test/pivotal/pal/tracker/InMemoryTimeEntryRepositoryTest.java b/src/test/java/test/pivotal/pal/tracker/InMemoryTimeEntryRepositoryTest.java deleted file mode 100644 index c88bb8266..000000000 --- a/src/test/java/test/pivotal/pal/tracker/InMemoryTimeEntryRepositoryTest.java +++ /dev/null @@ -1,71 +0,0 @@ -package test.pivotal.pal.tracker; - -import io.pivotal.pal.tracker.InMemoryTimeEntryRepository; -import io.pivotal.pal.tracker.TimeEntry; -import org.junit.Test; - -import java.time.LocalDate; -import java.util.List; - -import static java.util.Arrays.asList; -import static org.assertj.core.api.Assertions.assertThat; - -public class InMemoryTimeEntryRepositoryTest { - @Test - public void create() throws Exception { - InMemoryTimeEntryRepository repo = new InMemoryTimeEntryRepository(); - TimeEntry createdTimeEntry = repo.create(new TimeEntry(123, 456, LocalDate.parse("2017-01-08"), 8)); - - TimeEntry expected = new TimeEntry(1L, 123, 456, LocalDate.parse("2017-01-08"), 8); - assertThat(createdTimeEntry).isEqualTo(expected); - - TimeEntry readEntry = repo.find(createdTimeEntry.getId()); - assertThat(readEntry).isEqualTo(expected); - } - - @Test - public void find() throws Exception { - InMemoryTimeEntryRepository repo = new InMemoryTimeEntryRepository(); - repo.create(new TimeEntry(123, 456, LocalDate.parse("2017-01-08"), 8)); - - TimeEntry expected = new TimeEntry(1L, 123, 456, LocalDate.parse("2017-01-08"), 8); - TimeEntry readEntry = repo.find(1L); - assertThat(readEntry).isEqualTo(expected); - } - - @Test - public void list() throws Exception { - InMemoryTimeEntryRepository repo = new InMemoryTimeEntryRepository(); - repo.create(new TimeEntry(123, 456, LocalDate.parse("2017-01-08"), 8)); - repo.create(new TimeEntry(789, 654, LocalDate.parse("2017-01-07"), 4)); - - List expected = asList( - new TimeEntry(1L, 123, 456, LocalDate.parse("2017-01-08"), 8), - new TimeEntry(2L, 789, 654, LocalDate.parse("2017-01-07"), 4) - ); - assertThat(repo.list()).isEqualTo(expected); - } - - @Test - public void update() throws Exception { - InMemoryTimeEntryRepository repo = new InMemoryTimeEntryRepository(); - TimeEntry created = repo.create(new TimeEntry(123, 456, LocalDate.parse("2017-01-08"), 8)); - - TimeEntry updatedEntry = repo.update( - created.getId(), - new TimeEntry(321, 654, LocalDate.parse("2017-01-09"), 5)); - - TimeEntry expected = new TimeEntry(created.getId(), 321, 654, LocalDate.parse("2017-01-09"), 5); - assertThat(updatedEntry).isEqualTo(expected); - assertThat(repo.find(created.getId())).isEqualTo(expected); - } - - @Test - public void delete() throws Exception { - InMemoryTimeEntryRepository repo = new InMemoryTimeEntryRepository(); - TimeEntry created = repo.create(new TimeEntry(123, 456, LocalDate.parse("2017-01-08"), 8)); - - repo.delete(created.getId()); - assertThat(repo.list()).isEmpty(); - } -} diff --git a/src/test/java/test/pivotal/pal/tracker/TimeEntryControllerTest.java b/src/test/java/test/pivotal/pal/tracker/TimeEntryControllerTest.java deleted file mode 100644 index f7c0090e3..000000000 --- a/src/test/java/test/pivotal/pal/tracker/TimeEntryControllerTest.java +++ /dev/null @@ -1,106 +0,0 @@ -package test.pivotal.pal.tracker; - -import io.pivotal.pal.tracker.TimeEntry; -import io.pivotal.pal.tracker.TimeEntryController; -import io.pivotal.pal.tracker.TimeEntryRepository; -import org.junit.Before; -import org.junit.Test; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; - -import java.time.LocalDate; -import java.util.List; - -import static java.util.Arrays.asList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.*; - -public class TimeEntryControllerTest { - private TimeEntryRepository timeEntryRepository; - private TimeEntryController controller; - - @Before - public void setUp() throws Exception { - timeEntryRepository = mock(TimeEntryRepository.class); - controller = new TimeEntryController(timeEntryRepository); - } - - @Test - public void testCreate() throws Exception { - TimeEntry expected = new TimeEntry(1L, 123, 456, LocalDate.parse("2017-01-08"), 8); - doReturn(expected) - .when(timeEntryRepository) - .create(any(TimeEntry.class)); - - ResponseEntity response = controller.create(new TimeEntry(123, 456, LocalDate.parse("2017-01-08"), 8)); - - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED); - assertThat(response.getBody()).isEqualTo(expected); - } - - @Test - public void testRead() throws Exception { - TimeEntry expected = new TimeEntry(1L, 123, 456, LocalDate.parse("2017-01-08"), 8); - doReturn(expected) - .when(timeEntryRepository) - .find(1L); - - ResponseEntity response = controller.read(1L); - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(response.getBody()).isEqualTo(expected); - } - - @Test - public void testRead_NotFound() throws Exception { - doReturn(null) - .when(timeEntryRepository) - .find(1L); - - ResponseEntity response = controller.read(1L); - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); - } - - @Test - public void testList() throws Exception { - List expected = asList( - new TimeEntry(1, 123, 456, LocalDate.parse("2017-01-08"), 8), - new TimeEntry(2, 789, 321, LocalDate.parse("2017-01-07"), 4) - ); - doReturn(expected).when(timeEntryRepository).list(); - - ResponseEntity> response = controller.list(); - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(response.getBody()).isEqualTo(expected); - } - - @Test - public void testUpdate() throws Exception { - TimeEntry expected = new TimeEntry(1, 987, 654, LocalDate.parse("2017-01-07"), 4); - doReturn(expected) - .when(timeEntryRepository) - .update(eq(1L), any(TimeEntry.class)); - - ResponseEntity response = controller.update(1L, expected); - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(response.getBody()).isEqualTo(expected); - } - - @Test - public void testUpdate_NotFound() throws Exception { - doReturn(null) - .when(timeEntryRepository) - .update(eq(1L), any(TimeEntry.class)); - - ResponseEntity response = controller.update(1L, new TimeEntry()); - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); - } - - @Test - public void testDelete() throws Exception { - ResponseEntity response = controller.delete(1L); - verify(timeEntryRepository).delete(1L); - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT); - } -} diff --git a/src/test/java/test/pivotal/pal/tracker/WelcomeControllerTest.java b/src/test/java/test/pivotal/pal/tracker/WelcomeControllerTest.java deleted file mode 100644 index bfa8271a0..000000000 --- a/src/test/java/test/pivotal/pal/tracker/WelcomeControllerTest.java +++ /dev/null @@ -1,16 +0,0 @@ -package test.pivotal.pal.tracker; - -import io.pivotal.pal.tracker.WelcomeController; -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class WelcomeControllerTest { - - @Test - public void itSaysHello() throws Exception { - WelcomeController controller = new WelcomeController("A welcome message"); - - assertThat(controller.sayHello()).isEqualTo("A welcome message"); - } -} diff --git a/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java b/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java deleted file mode 100644 index b742e5447..000000000 --- a/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java +++ /dev/null @@ -1,126 +0,0 @@ -package test.pivotal.pal.trackerapi; - -import com.jayway.jsonpath.DocumentContext; -import io.pivotal.pal.tracker.PalTrackerApplication; -import io.pivotal.pal.tracker.TimeEntry; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.test.context.junit4.SpringRunner; - -import java.time.LocalDate; -import java.util.Collection; - -import static com.jayway.jsonpath.JsonPath.parse; -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@RunWith(SpringRunner.class) -@SpringBootTest(classes = PalTrackerApplication.class, webEnvironment = RANDOM_PORT) -public class TimeEntryApiTest { - - @Autowired - private TestRestTemplate restTemplate; - - private TimeEntry timeEntry = new TimeEntry(123, 456, LocalDate.parse("2017-01-08"), 8); - - @Test - public void testCreate() throws Exception { - ResponseEntity createResponse = restTemplate.postForEntity("/time-entries", timeEntry, String.class); - - - assertThat(createResponse.getStatusCode()).isEqualTo(HttpStatus.CREATED); - - DocumentContext createJson = parse(createResponse.getBody()); - assertThat(createJson.read("$.id", Long.class)).isGreaterThan(0); - assertThat(createJson.read("$.projectId", Long.class)).isEqualTo(123L); - assertThat(createJson.read("$.userId", Long.class)).isEqualTo(456L); - assertThat(createJson.read("$.date", String.class)).isEqualTo("2017-01-08"); - assertThat(createJson.read("$.hours", Long.class)).isEqualTo(8); - } - - @Test - public void testList() throws Exception { - Long id = createTimeEntry(); - - - ResponseEntity listResponse = restTemplate.getForEntity("/time-entries", String.class); - - - assertThat(listResponse.getStatusCode()).isEqualTo(HttpStatus.OK); - - DocumentContext listJson = parse(listResponse.getBody()); - - Collection timeEntries = listJson.read("$[*]", Collection.class); - assertThat(timeEntries.size()).isEqualTo(1); - - Long readId = listJson.read("$[0].id", Long.class); - assertThat(readId).isEqualTo(id); - } - - @Test - public void testRead() throws Exception { - Long id = createTimeEntry(); - - - ResponseEntity readResponse = this.restTemplate.getForEntity("/time-entries/" + id, String.class); - - - assertThat(readResponse.getStatusCode()).isEqualTo(HttpStatus.OK); - DocumentContext readJson = parse(readResponse.getBody()); - assertThat(readJson.read("$.id", Long.class)).isEqualTo(id); - assertThat(readJson.read("$.projectId", Long.class)).isEqualTo(123L); - assertThat(readJson.read("$.userId", Long.class)).isEqualTo(456L); - assertThat(readJson.read("$.date", String.class)).isEqualTo("2017-01-08"); - assertThat(readJson.read("$.hours", Long.class)).isEqualTo(8); - } - - @Test - public void testUpdate() throws Exception { - Long id = createTimeEntry(); - TimeEntry updatedTimeEntry = new TimeEntry(2, 3, LocalDate.parse("2017-01-09"), 9); - - - ResponseEntity updateResponse = restTemplate.exchange("/time-entries/" + id, HttpMethod.PUT, new HttpEntity<>(updatedTimeEntry, null), String.class); - - - assertThat(updateResponse.getStatusCode()).isEqualTo(HttpStatus.OK); - - DocumentContext updateJson = parse(updateResponse.getBody()); - assertThat(updateJson.read("$.id", Long.class)).isEqualTo(id); - assertThat(updateJson.read("$.projectId", Long.class)).isEqualTo(2L); - assertThat(updateJson.read("$.userId", Long.class)).isEqualTo(3L); - assertThat(updateJson.read("$.date", String.class)).isEqualTo("2017-01-09"); - assertThat(updateJson.read("$.hours", Long.class)).isEqualTo(9); - } - - @Test - public void testDelete() throws Exception { - Long id = createTimeEntry(); - - - ResponseEntity deleteResponse = restTemplate.exchange("/time-entries/" + id, HttpMethod.DELETE, null, String.class); - - - assertThat(deleteResponse.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT); - - ResponseEntity deletedReadResponse = this.restTemplate.getForEntity("/time-entries/" + id, String.class); - assertThat(deletedReadResponse.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); - } - - private Long createTimeEntry() { - HttpEntity entity = new HttpEntity<>(timeEntry); - - ResponseEntity response = restTemplate.exchange("/time-entries", HttpMethod.POST, entity, TimeEntry.class); - - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED); - - return response.getBody().getId(); - } -} diff --git a/src/test/java/test/pivotal/pal/trackerapi/WelcomeApiTest.java b/src/test/java/test/pivotal/pal/trackerapi/WelcomeApiTest.java deleted file mode 100644 index cc7091ed4..000000000 --- a/src/test/java/test/pivotal/pal/trackerapi/WelcomeApiTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package test.pivotal.pal.trackerapi; - -import io.pivotal.pal.tracker.PalTrackerApplication; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.test.context.junit4.SpringRunner; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@RunWith(SpringRunner.class) -@SpringBootTest(classes = PalTrackerApplication.class, webEnvironment = RANDOM_PORT) -public class WelcomeApiTest { - - @Autowired - private TestRestTemplate restTemplate; - - @Test - public void exampleTest() { - String body = this.restTemplate.getForObject("/", String.class); - assertThat(body).isEqualTo("Hello from test"); - } -} From 049bf726cc92b40e0b8cf95c9c5b6fe4fb854f96 Mon Sep 17 00:00:00 2001 From: Tyson Gern Date: Thu, 20 Jul 2017 15:04:20 -0600 Subject: [PATCH 15/24] Add tests for MVC lab --- .../InMemoryTimeEntryRepositoryTest.java | 71 ++++++++++ .../pal/tracker/TimeEntryControllerTest.java | 106 +++++++++++++++ .../pal/trackerapi/TimeEntryApiTest.java | 126 ++++++++++++++++++ 3 files changed, 303 insertions(+) create mode 100644 src/test/java/test/pivotal/pal/tracker/InMemoryTimeEntryRepositoryTest.java create mode 100644 src/test/java/test/pivotal/pal/tracker/TimeEntryControllerTest.java create mode 100644 src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java diff --git a/src/test/java/test/pivotal/pal/tracker/InMemoryTimeEntryRepositoryTest.java b/src/test/java/test/pivotal/pal/tracker/InMemoryTimeEntryRepositoryTest.java new file mode 100644 index 000000000..c88bb8266 --- /dev/null +++ b/src/test/java/test/pivotal/pal/tracker/InMemoryTimeEntryRepositoryTest.java @@ -0,0 +1,71 @@ +package test.pivotal.pal.tracker; + +import io.pivotal.pal.tracker.InMemoryTimeEntryRepository; +import io.pivotal.pal.tracker.TimeEntry; +import org.junit.Test; + +import java.time.LocalDate; +import java.util.List; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; + +public class InMemoryTimeEntryRepositoryTest { + @Test + public void create() throws Exception { + InMemoryTimeEntryRepository repo = new InMemoryTimeEntryRepository(); + TimeEntry createdTimeEntry = repo.create(new TimeEntry(123, 456, LocalDate.parse("2017-01-08"), 8)); + + TimeEntry expected = new TimeEntry(1L, 123, 456, LocalDate.parse("2017-01-08"), 8); + assertThat(createdTimeEntry).isEqualTo(expected); + + TimeEntry readEntry = repo.find(createdTimeEntry.getId()); + assertThat(readEntry).isEqualTo(expected); + } + + @Test + public void find() throws Exception { + InMemoryTimeEntryRepository repo = new InMemoryTimeEntryRepository(); + repo.create(new TimeEntry(123, 456, LocalDate.parse("2017-01-08"), 8)); + + TimeEntry expected = new TimeEntry(1L, 123, 456, LocalDate.parse("2017-01-08"), 8); + TimeEntry readEntry = repo.find(1L); + assertThat(readEntry).isEqualTo(expected); + } + + @Test + public void list() throws Exception { + InMemoryTimeEntryRepository repo = new InMemoryTimeEntryRepository(); + repo.create(new TimeEntry(123, 456, LocalDate.parse("2017-01-08"), 8)); + repo.create(new TimeEntry(789, 654, LocalDate.parse("2017-01-07"), 4)); + + List expected = asList( + new TimeEntry(1L, 123, 456, LocalDate.parse("2017-01-08"), 8), + new TimeEntry(2L, 789, 654, LocalDate.parse("2017-01-07"), 4) + ); + assertThat(repo.list()).isEqualTo(expected); + } + + @Test + public void update() throws Exception { + InMemoryTimeEntryRepository repo = new InMemoryTimeEntryRepository(); + TimeEntry created = repo.create(new TimeEntry(123, 456, LocalDate.parse("2017-01-08"), 8)); + + TimeEntry updatedEntry = repo.update( + created.getId(), + new TimeEntry(321, 654, LocalDate.parse("2017-01-09"), 5)); + + TimeEntry expected = new TimeEntry(created.getId(), 321, 654, LocalDate.parse("2017-01-09"), 5); + assertThat(updatedEntry).isEqualTo(expected); + assertThat(repo.find(created.getId())).isEqualTo(expected); + } + + @Test + public void delete() throws Exception { + InMemoryTimeEntryRepository repo = new InMemoryTimeEntryRepository(); + TimeEntry created = repo.create(new TimeEntry(123, 456, LocalDate.parse("2017-01-08"), 8)); + + repo.delete(created.getId()); + assertThat(repo.list()).isEmpty(); + } +} diff --git a/src/test/java/test/pivotal/pal/tracker/TimeEntryControllerTest.java b/src/test/java/test/pivotal/pal/tracker/TimeEntryControllerTest.java new file mode 100644 index 000000000..f7c0090e3 --- /dev/null +++ b/src/test/java/test/pivotal/pal/tracker/TimeEntryControllerTest.java @@ -0,0 +1,106 @@ +package test.pivotal.pal.tracker; + +import io.pivotal.pal.tracker.TimeEntry; +import io.pivotal.pal.tracker.TimeEntryController; +import io.pivotal.pal.tracker.TimeEntryRepository; +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.time.LocalDate; +import java.util.List; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.*; + +public class TimeEntryControllerTest { + private TimeEntryRepository timeEntryRepository; + private TimeEntryController controller; + + @Before + public void setUp() throws Exception { + timeEntryRepository = mock(TimeEntryRepository.class); + controller = new TimeEntryController(timeEntryRepository); + } + + @Test + public void testCreate() throws Exception { + TimeEntry expected = new TimeEntry(1L, 123, 456, LocalDate.parse("2017-01-08"), 8); + doReturn(expected) + .when(timeEntryRepository) + .create(any(TimeEntry.class)); + + ResponseEntity response = controller.create(new TimeEntry(123, 456, LocalDate.parse("2017-01-08"), 8)); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED); + assertThat(response.getBody()).isEqualTo(expected); + } + + @Test + public void testRead() throws Exception { + TimeEntry expected = new TimeEntry(1L, 123, 456, LocalDate.parse("2017-01-08"), 8); + doReturn(expected) + .when(timeEntryRepository) + .find(1L); + + ResponseEntity response = controller.read(1L); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isEqualTo(expected); + } + + @Test + public void testRead_NotFound() throws Exception { + doReturn(null) + .when(timeEntryRepository) + .find(1L); + + ResponseEntity response = controller.read(1L); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); + } + + @Test + public void testList() throws Exception { + List expected = asList( + new TimeEntry(1, 123, 456, LocalDate.parse("2017-01-08"), 8), + new TimeEntry(2, 789, 321, LocalDate.parse("2017-01-07"), 4) + ); + doReturn(expected).when(timeEntryRepository).list(); + + ResponseEntity> response = controller.list(); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isEqualTo(expected); + } + + @Test + public void testUpdate() throws Exception { + TimeEntry expected = new TimeEntry(1, 987, 654, LocalDate.parse("2017-01-07"), 4); + doReturn(expected) + .when(timeEntryRepository) + .update(eq(1L), any(TimeEntry.class)); + + ResponseEntity response = controller.update(1L, expected); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isEqualTo(expected); + } + + @Test + public void testUpdate_NotFound() throws Exception { + doReturn(null) + .when(timeEntryRepository) + .update(eq(1L), any(TimeEntry.class)); + + ResponseEntity response = controller.update(1L, new TimeEntry()); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); + } + + @Test + public void testDelete() throws Exception { + ResponseEntity response = controller.delete(1L); + verify(timeEntryRepository).delete(1L); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT); + } +} diff --git a/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java b/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java new file mode 100644 index 000000000..b742e5447 --- /dev/null +++ b/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java @@ -0,0 +1,126 @@ +package test.pivotal.pal.trackerapi; + +import com.jayway.jsonpath.DocumentContext; +import io.pivotal.pal.tracker.PalTrackerApplication; +import io.pivotal.pal.tracker.TimeEntry; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; + +import java.time.LocalDate; +import java.util.Collection; + +import static com.jayway.jsonpath.JsonPath.parse; +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = PalTrackerApplication.class, webEnvironment = RANDOM_PORT) +public class TimeEntryApiTest { + + @Autowired + private TestRestTemplate restTemplate; + + private TimeEntry timeEntry = new TimeEntry(123, 456, LocalDate.parse("2017-01-08"), 8); + + @Test + public void testCreate() throws Exception { + ResponseEntity createResponse = restTemplate.postForEntity("/time-entries", timeEntry, String.class); + + + assertThat(createResponse.getStatusCode()).isEqualTo(HttpStatus.CREATED); + + DocumentContext createJson = parse(createResponse.getBody()); + assertThat(createJson.read("$.id", Long.class)).isGreaterThan(0); + assertThat(createJson.read("$.projectId", Long.class)).isEqualTo(123L); + assertThat(createJson.read("$.userId", Long.class)).isEqualTo(456L); + assertThat(createJson.read("$.date", String.class)).isEqualTo("2017-01-08"); + assertThat(createJson.read("$.hours", Long.class)).isEqualTo(8); + } + + @Test + public void testList() throws Exception { + Long id = createTimeEntry(); + + + ResponseEntity listResponse = restTemplate.getForEntity("/time-entries", String.class); + + + assertThat(listResponse.getStatusCode()).isEqualTo(HttpStatus.OK); + + DocumentContext listJson = parse(listResponse.getBody()); + + Collection timeEntries = listJson.read("$[*]", Collection.class); + assertThat(timeEntries.size()).isEqualTo(1); + + Long readId = listJson.read("$[0].id", Long.class); + assertThat(readId).isEqualTo(id); + } + + @Test + public void testRead() throws Exception { + Long id = createTimeEntry(); + + + ResponseEntity readResponse = this.restTemplate.getForEntity("/time-entries/" + id, String.class); + + + assertThat(readResponse.getStatusCode()).isEqualTo(HttpStatus.OK); + DocumentContext readJson = parse(readResponse.getBody()); + assertThat(readJson.read("$.id", Long.class)).isEqualTo(id); + assertThat(readJson.read("$.projectId", Long.class)).isEqualTo(123L); + assertThat(readJson.read("$.userId", Long.class)).isEqualTo(456L); + assertThat(readJson.read("$.date", String.class)).isEqualTo("2017-01-08"); + assertThat(readJson.read("$.hours", Long.class)).isEqualTo(8); + } + + @Test + public void testUpdate() throws Exception { + Long id = createTimeEntry(); + TimeEntry updatedTimeEntry = new TimeEntry(2, 3, LocalDate.parse("2017-01-09"), 9); + + + ResponseEntity updateResponse = restTemplate.exchange("/time-entries/" + id, HttpMethod.PUT, new HttpEntity<>(updatedTimeEntry, null), String.class); + + + assertThat(updateResponse.getStatusCode()).isEqualTo(HttpStatus.OK); + + DocumentContext updateJson = parse(updateResponse.getBody()); + assertThat(updateJson.read("$.id", Long.class)).isEqualTo(id); + assertThat(updateJson.read("$.projectId", Long.class)).isEqualTo(2L); + assertThat(updateJson.read("$.userId", Long.class)).isEqualTo(3L); + assertThat(updateJson.read("$.date", String.class)).isEqualTo("2017-01-09"); + assertThat(updateJson.read("$.hours", Long.class)).isEqualTo(9); + } + + @Test + public void testDelete() throws Exception { + Long id = createTimeEntry(); + + + ResponseEntity deleteResponse = restTemplate.exchange("/time-entries/" + id, HttpMethod.DELETE, null, String.class); + + + assertThat(deleteResponse.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT); + + ResponseEntity deletedReadResponse = this.restTemplate.getForEntity("/time-entries/" + id, String.class); + assertThat(deletedReadResponse.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); + } + + private Long createTimeEntry() { + HttpEntity entity = new HttpEntity<>(timeEntry); + + ResponseEntity response = restTemplate.exchange("/time-entries", HttpMethod.POST, entity, TimeEntry.class); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED); + + return response.getBody().getId(); + } +} From 34ed0b0d5dbcb47f87e03a584f4c28531fa0bd72 Mon Sep 17 00:00:00 2001 From: Subbu Perugu Date: Tue, 17 Oct 2017 18:49:49 -0400 Subject: [PATCH 16/24] test --- .../tracker/InMemoryTimeEntryRepository.class | Bin 0 -> 1945 bytes .../pal/tracker/PalTrackerApplication.class | Bin 734 -> 2276 bytes .../io/pivotal/pal/tracker/TimeEntry.class | Bin .../pal/tracker/TimeEntryController.class | Bin 0 -> 3445 bytes .../pal/tracker/TimeEntryRepository.class | Bin 0 -> 560 bytes .../tracker/InMemoryTimeEntryRepository.class | Bin 2168 -> 0 bytes .../pal/tracker/PalTrackerApplication.class | Bin 2276 -> 0 bytes .../TimeEntryRepository$TimeEntry.class | Bin 2656 -> 0 bytes .../pal/tracker/TimeEntryRepository.class | Bin 781 -> 0 bytes .../pal/tracker/EnvControllerTest.class | Bin 1469 -> 0 bytes .../InMemoryTimeEntryRepositoryTest.class | Bin 0 -> 3211 bytes .../pal/tracker/TimeEntryControllerTest.class | Bin 0 -> 5246 bytes .../pal/tracker/WelcomeControllerTest.class | Bin 1029 -> 0 bytes .../pal/trackerapi/TimeEntryApiTest.class | Bin 0 -> 6651 bytes .../pal/trackerapi/WelcomeApiTest.class | Bin 1704 -> 0 bytes .../pal/tracker/TimeEntryRepository.java | 90 ------------------ 16 files changed, 90 deletions(-) create mode 100644 out/production/classes/io/pivotal/pal/tracker/InMemoryTimeEntryRepository.class rename out/{test => production}/classes/io/pivotal/pal/tracker/TimeEntry.class (100%) create mode 100644 out/production/classes/io/pivotal/pal/tracker/TimeEntryController.class create mode 100644 out/production/classes/io/pivotal/pal/tracker/TimeEntryRepository.class delete mode 100644 out/test/classes/io/pivotal/pal/tracker/InMemoryTimeEntryRepository.class delete mode 100644 out/test/classes/io/pivotal/pal/tracker/PalTrackerApplication.class delete mode 100644 out/test/classes/io/pivotal/pal/tracker/TimeEntryRepository$TimeEntry.class delete mode 100644 out/test/classes/io/pivotal/pal/tracker/TimeEntryRepository.class delete mode 100644 out/test/classes/test/pivotal/pal/tracker/EnvControllerTest.class create mode 100644 out/test/classes/test/pivotal/pal/tracker/InMemoryTimeEntryRepositoryTest.class create mode 100644 out/test/classes/test/pivotal/pal/tracker/TimeEntryControllerTest.class delete mode 100644 out/test/classes/test/pivotal/pal/tracker/WelcomeControllerTest.class create mode 100644 out/test/classes/test/pivotal/pal/trackerapi/TimeEntryApiTest.class delete mode 100644 out/test/classes/test/pivotal/pal/trackerapi/WelcomeApiTest.class diff --git a/out/production/classes/io/pivotal/pal/tracker/InMemoryTimeEntryRepository.class b/out/production/classes/io/pivotal/pal/tracker/InMemoryTimeEntryRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..2962951325efc5672b6ceab301eb3f4d0a8f8db7 GIT binary patch literal 1945 zcma)+Z&MRj6vm$oA#1`lprB$~75_E_G-|2UhEf$pi9uSSK^}EC_X6XBV zpLV8&PMzuax*w|3=j^UYBqF@X&An&u{hf23bMF23zrX(hFomZ%4C9)O>p7%wEsLZ0 zN@i0z4B%@SZshO{Zi?-e4By%)+nAQ=cR47`$n1OBb6aM2Y}~amYvZ1UfzWK|`(79r z-Lx=VedBC8rB-O%(nF{DdckRwEo7I>y61$gKwEgwyJI>wxsJD9s`}n~xoZ4U!)*GY zAW zj$3mABkRPN4qqF_JGGaqd2c~)_`z1&M`8oL0TiXSO0YB2ie(lRRJMcR};B1M+3Qw>k&X*N_5?Botg%u%Q--Gyx zR9N1~M|7(eSKsJ5>ss7{({9#Im$Mg|<`?S4+cj^Yzp2A{_Ds4^5k>pg<0U^W`;S^^o-jCBb z!xejr-eT4ZvSjbrMDY)#x6z+|`Rg_^vY6P0{TB}LFS|1F8~3Cj>^x5hF^|LKF!#SL%2MZT)k)uR?n|s8QO4zw~80b(XLok1dHm0dIP2m#0jA#XnlOeQQi3Wpj zp`OK|AKpQJH*mBo{lNqni&`Xb0+UH_&xwN-KXi-qBRcOOUxfUpXq+&w|HJGFwlG366z-*Ew-Sl#IvvTP)Q?&IRn?yxQ>j0 z`81N4OJf0xDJnIB7Se#9-CJ1wgkDCxAaJrrEEDDPyMCJXdpp>q2T}e_T*L&!(tkNL z17Di>3STp<3o_$D#fr5!`+%xe#iVSn_ z;IHg9yPB)D!G6K9C#<%wR&U+3cBq;L7lxVG!4D=%xXQ2)ciKHVLjoh@QJ<(TNw<-S zC^tl(jZy1->`A>5Qoessx}zdB$gFinlE}vadlCem*5Z*+q%7JUX=*3b)=E@Tn_X!) zwG@-uh2b74@*MJI4U`9R9%+poDq3_JcDE^J`y+#KIx~xoBUIkzzAvcbMl1I#2bG7V z62tm{zWMju#MYEA=ytN@&%HM8Rr0By8Ggz9zm(_07sKjcf%YzfY83aj-~sh6T~Lp9 zi?zbRR<2g49^|SAwPLwYt>wxOstmUWZ0PiEiAzZoT|5l4`!#n<`r*y~_^n&GujTXF zy3y%1DV?xOW!}L<*qPb$sy$rS9Y7adeA#$C;VOpc)v7;95Mh|0-$`0oG%^~87hZt< zMw202qTi98aSWGfZiWt+!UwcU;X_Q5MF+2ww+vVNj{F9HW@Q zM`Q;+#wX;VbF93{@e0ZP7a0DPY*7rC---`0Tp=Hg@KYKYKI?5x=aPNgi!U+q9L5Po zlYbz!pLkAhp%c7E3v)m53S(5~eKK4)!T4FZ843mg93hC41aOuhUdI*sKQR-5u-K;| e3L$~dDRKy3kaq}dmd3mEO%uE&e2eeyPX7(0c$=L7 delta 270 zcmYL?F-ikr6okL`ubY3fMonVYXj}q<)f(7FEWLn*cGjzi5XDMt1#b}kgGfM7EIfc` z5GxPh2_&Z4Z>CQaL*1-?e#bebqKvw zDK>FjJY)05PK33M4%))b?*S>xQf=uKZR+qrQwB-j6)!}m|M^r7N_b_|vHMq&u*#YY NN4LQqeR2J49Trdd9{>OV diff --git a/out/test/classes/io/pivotal/pal/tracker/TimeEntry.class b/out/production/classes/io/pivotal/pal/tracker/TimeEntry.class similarity index 100% rename from out/test/classes/io/pivotal/pal/tracker/TimeEntry.class rename to out/production/classes/io/pivotal/pal/tracker/TimeEntry.class diff --git a/out/production/classes/io/pivotal/pal/tracker/TimeEntryController.class b/out/production/classes/io/pivotal/pal/tracker/TimeEntryController.class new file mode 100644 index 0000000000000000000000000000000000000000..eba45958f5a782b19307f4ca8e9093bb15de2462 GIT binary patch literal 3445 zcmcIm?^7F96g_W~k`k65LMd&jic}$l(5(su3!;=Ctu`bWQYzMp8?uFMLpI%P=rE4X z_|gAH865_l@uMI7n;ggUcDK8+w3DTyAM$qJ%e&{Ed+vSr?Vo@D`a6J0l#=MdNA2jw z#~oO}%>)+Pv4rI$;#iR@x8(G;96ph2pUSm6a>&YIRsNAnAfLdRK(}M=8Ov42HjR40 zs96G;tZ8XAbKi3GidN&#vGwi8hOHI-odEmmt46M|S2FCPUaD}VH*0O{l?~lC<=kJ4JG*8*3cW>(;aHW5VM}`3wxK)3Ty0Hn zb}VbzJ6gSFo7J6%w!UXPwd}{*uH)3Sf>Ezo)w-0*bY$G^&xAV=7;YIk4SGB#Pa zteZPk-D%k5_vF9iH`kibJSkl)G#JjFv0>It3cXORGU<-VI0dF&sKryGq?OESS<{2P z+L~2&R`ptqq7ifW- z>CW!6gCzy6m~}^BJarlY_2|3jJeeDgS<$j|6-gCYnr)3***&)pPNbl9ZJ#EX9?wzp zvHbr3bQwtjf#`Ly)@Z7Wwz5&-X%m=^ToFsIMRsG_wHdaj8i7y1*D(tFP<=GRM ziR^*B(+SO)+|!u4$3JA(smX>lVR$!|iar!nyoTNc)>T|WQD8J;4^QB(iVfV87@w=y z#1|^=%juWi+5;8W@Rf@9v87@LU#rk@Paxgub_^qBmkHZczVyi0=207JWiJp*$=k7| zqwpFQSc!Cdv~tbVTzRQ_w|Nb+Lplv!4vEFW@zYe}VE7X9~`9 z>_UuVH@^sb&`#?C&dz(gaRC=Oqn(Q?h;|8s+$k|P`IMFe>7SvDA0wt<^*6*fGlw{H zgti~~m**hqg7u?=&=P#eGYh~6a2c-?x&tcS;G2RsUE3jsSV(Uqpf`e1q+EJ~ zE>Q(zv^xv&7X}kZ`((#mvR&Ml;cwzTL>*T{(wa;98s4RIDc(Gt3qH?DW|H_ZIs^{k zvgNv`oJ=AZsHCNO`+ZuTLhl6%Ww=AICr$dI{J^ISZ`2hfa||_x+=Q>ngzx9&s*KZJ zhV?MPh&9)J+Jg&J8TD163tttL3QkeWSg6nCP@n68KGP)HtWT%H1y3I@wKG9#R|zJ| zw%<=Jw|bnr{7g0%*c7XD+U4q1Z-2nG-{0Zv~IR7x% GKK~Cc3a-%r literal 0 HcmV?d00001 diff --git a/out/production/classes/io/pivotal/pal/tracker/TimeEntryRepository.class b/out/production/classes/io/pivotal/pal/tracker/TimeEntryRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..7f2d9dfdfd81f3a85f7ae54dbc34aea50b2ce830 GIT binary patch literal 560 zcma)3y-ve05dN;D2`zu6U3i3q0WTFx5nDS&LZYadL>_0<@703Hf)4H6wP zq=R+R_j7mt?)&HS3&0eU2-gwDgfMeTdPP{oskWlhTkE9}6+iD}_O6_G(I0AFdbgV; z-4a6Zrpuf#jnlQNozu~A`F+dc9~je!;+i-aGzCWH`-gbyW8hRxa~T} z!9f@uvVMA9tIUT4LXU3^^aH{P0)T5DVN)ZVGCyFphaux0X!M&S|G*x-X4FKX{Pyc34* zfs{;P9p8$e4TVi{@Ez(3TjJ%~DPCR?9@{1M5#I!>+s|}^eNsAwbHZU_Z?(+N%pp&I z)xvN~M^*M`pwr2NABD2}1nwz}^Oe9u% zT0FJ%Zcj}F(7%QEPXL^_JQwTXeKh&MGqDtS9`j-XpU3553X8Aeo_{1^o zj{$B-biAcPUSazhZ}amo4hs|fWu$NTe4U?>4BkMFbG8`AOSCLvh%;cUbOfvP9GR^n z4E>4h@A2x(9OY{P*vR4xu5hN3^_Zd;aQ+V7O<6yXC2OZCY9Lc5jbVDvfERwDiE5emnFlRe>&robera|C{ftEsrh#7S5a zvADTIjBd^`@G+)huFFh9zr^%jX0fis?5Sj5N9;r@maI4l8G0z=qtwscGzm_cgmat6 hi#ClsQ`b#{(;+)4IfIX>G=pon$frZ{2z^Z={}SOu9Z3KH diff --git a/out/test/classes/io/pivotal/pal/tracker/PalTrackerApplication.class b/out/test/classes/io/pivotal/pal/tracker/PalTrackerApplication.class deleted file mode 100644 index 32ed650d9f198a70eb25c1ffb7dfb14ffd7080a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2276 zcmb_e{Zku77=D%nj>N-KXi-qBRcOOUxfUpXq+&w|HJGFwlG366z-*Ew-Sl#IvvTP)Q?&IRn?yxQ>j0 z`81N4OJf0xDJnIB7Se#9-CJ1wgkDCxAaJrrEEDDPyMCJXdpp>q2T}e_T*L&!(tkNL z17Di>3STp<3o_$D#fr5!`+%xe#iVSn_ z;IHg9yPB)D!G6K9C#<%wR&U+3cBq;L7lxVG!4D=%xXQ2)ciKHVLjoh@QJ<(TNw<-S zC^tl(jZy1->`A>5Qoessx}zdB$gFinlE}vadlCem*5Z*+q%7JUX=*3b)=E@Tn_X!) zwG@-uh2b74@*MJI4U`9R9%+poDq3_JcDE^J`y+#KIx~xoBUIkzzAvcbMl1I#2bG7V z62tm{zWMju#MYEA=ytN@&%HM8Rr0By8Ggz9zm(_07sKjcf%YzfY83aj-~sh6T~Lp9 zi?zbRR<2g49^|SAwPLwYt>wxOstmUWZ0PiEiAzZoT|5l4`!#n<`r*y~_^n&GujTXF zy3y%1DV?xOW!}L<*qPb$sy$rS9Y7adeA#$C;VOpc)v7;95Mh|0-$`0oG%^~87hZt< zMw202qTi98aSWGfZiWt+!UwcU;X_Q5MF+2ww+vVNj{F9HW@Q zM`Q;+#wX;VbF93{@e0ZP7a0DPY*7rC---`0Tp=Hg@KYKYKI?5x=aPNgi!U+q9L5Po zlYbz!pLkAhp%c7E3v)m53S(5~eKK4)!T4FZ843mg93hC41aOuhUdI*sKQR-5u-K;| e3L$~dDRKy3kaq}dmd3mEO%uE&e2eeyPX7(0c$=L7 diff --git a/out/test/classes/io/pivotal/pal/tracker/TimeEntryRepository$TimeEntry.class b/out/test/classes/io/pivotal/pal/tracker/TimeEntryRepository$TimeEntry.class deleted file mode 100644 index 32fd9fed79600fb71a65fafc1eaa1b4cac6175c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2656 zcmbVO+in|G6kTWR@vW0Ib(7v48oMpTvC|r8+5)*yn%*4JOPVyK<(5e@G*evLj3<$* zst<@4J^&tg;tBdvB#?+JfrQ|tNPGz&fhcRAnb=8WDukjrXP-T1@3Z#W>rDK|-#`5V zU<4nfZ~&7@Bye7a3o=}k;gSrO>im`vch%ri3FNMJUBxdd*? zYF>tS5?GM=mJGM!cvr!2R}|EgLbC4F?mEkUnd{hE!|}w3uGqe#&^LY8UbjoWd(SCN z*Ou+ddAdgx%+=bO*I;~^F=KAk^(Pb}`Js76PS#c!xqI5JI#<{3Ejiwty;PwmtrN`K zo-6Cn8TD6P=1I=+gbP*QyRTrCt5wIFtk{i)!&j%L-CC*cuGf6KQmXUuJ$w0M$1Al$ zZaDQ?!}V+4{bMa3F=p~pQyt-#1!h0rKIgT0$I*naCQ@WJf>ks>C9xJ?UT>3@GNx|zUNl&DD-YZ)JX#`iA&8A+_H1g zm98CVQQD^Ygdnky!x0M^9J0_a!wWcUVE|bR2W9yl-jCx0g^~Z$zBoR#a2jVUjN=r! zY?mIOJh$dnRvb^Es|D|YyE1NJ5d^k~B{pd|+Y*i(&zJxGHRX}u=L#@kh$3hiq82?jw#MSFQVwfi?k$24tWVfoFzelqa^qg5yUxX zBtc4;sNkuUYrx?Qdl6#WemD_|96F5d;AK?>!Tt%19e#AKTJ>q49yZ$j-Zb4 zxT9Ph)4(P)@LUU6PLnObavGul%V~%LET=EVY>~n+j)xFSHr2i(tQG@FnEIX$zd^}I@V@3XjqZF4C@U0bOU28z_Adp!pj|j7(0QKIs$p6V{l|= z@X3zBuWGf4Xr!7=3Zmcdpx{i$;1Qe(DcBlpKHd>L+A)|PxiI)owpsXFFBHFp8GT%Q zita)a@gkpZU`8Gne?{VobY_^(7hj2sD}yVewTf^RX{}7I%&)_Z(WltM{Zaa&O(Y*- zw^+X;9({ypM9@a`1v^4f0B0$^x8%nN1#*41V8rp^k#{Hljl|3>-BgayW0`hc!41F4OLUFmzs_G0Z9@0%>0a9x1PXCcQx5Xmes`WbWdO# zV($s;h}h}CCjPxWgH80N`vQmA#J-+~bTqMlrxX{qsA(p|Y_MZ;XU`$eIYl4|Rxya= EKm2RvC;$Ke diff --git a/out/test/classes/io/pivotal/pal/tracker/TimeEntryRepository.class b/out/test/classes/io/pivotal/pal/tracker/TimeEntryRepository.class deleted file mode 100644 index ad1b05352eb55f0db0603bb6d329cdd20feafcb1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 781 zcmbtSyH3ME5S$B%O-vvm;q?I&HU)0d1<@c7AWKLD(I3uXT{zCx`5Y+?A4b6k@KK13 zk(|O!WQx6&W_L%oGyDDX`2}E%V;2W54qY5Ebf4tAxYpJz8G3hG3v;D-o{OBpiH+b^ zFlaxLX_(1HYPkwC3Tt@$CQMijKZ-2PrA^K9w8ADqvoVJDtJE`wTR)of1rHV1Nf@O% z`3Iwtxn&so!5?K|r3xd$CM3aBCYsyA(5m;fhKpuPToSQUWV2GhkJ=*5NE-|4%!Cpv zD@V1T1kViKG%ZXlZlt1?lghxgspl&n7|u371BSucyN8##h)MqO=JV?_au^s|bhtpL qO`^gDC~u=%W)C}5?@_gdUBdU!FYy8POKe!aBXlTx)VW2`M)wzYV)OI> diff --git a/out/test/classes/test/pivotal/pal/tracker/EnvControllerTest.class b/out/test/classes/test/pivotal/pal/tracker/EnvControllerTest.class deleted file mode 100644 index 7edbc421f4d5471d58ffe66c6c9c26a499ee4a53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1469 zcmbVMTT|0O6#lj?O$bFwxG3HLMWld4?jRSfZBfP+6gntROo%H4OHz_8qd&_R@G|50 z?1Mka@oZBuoqA#HOwaCqd(OAtxlF!(|M(fe6dr5Paa+NVij<0B6(cG}HAHYnnlTkd z3+`fE!GwlMOlg?LjEY$mb1LQ)+*7c?5MQ(%TP!m~hf-S%v5fnYGqmMxhp$x*cez(I zcT0rmdDk*aTc&5ra}A7%ecNXk%?s`eqii3$!YmnO`U}srUUSdLIma2-5uRHr5fypK zC-=A@oI$hBt{4XMw)<}i3XgIb+m=5tk4>W@?2=J1%Y=8=D|}o2sdBpBbul=WOlQy7 z6rSzuEi@v_M5yK7SiCH3*YOoBGW2Xz9AO{wE!(%L25HBkbgS14$-L|B83z@rrBO>$ z+jJ|Q#aC=uzV3!Xj7j|obfA-A;5?~QbLM7dr>Owr6OR}olT!@s;}er(KKIQ-KIRLL zb0Ha*61WU%Y3%9xMp4221Rfww8mkox<*ndh0vTi#HFLh2ZrT(+%U_wk*)%AXlR0h+v2`S zyty!MZSYdnv1#PkrDmFz$n2ZmCO@ih$KnAmjivGXXa5+&&}{p;ql#H7x(xGS6=&J~ zm9q9S`q54w9QliYnuboYU34;NWwbU8pMt%sUU!p?S3wOIX`cuja0xwhVlV7s6O9z}muCQ-CGI zSX^RKT@+H?btq(8kt8KugrrF466t$J3OVVMs`~#j`Y!UiMhp@3l9$Xa aK>7&jCnQG54cw%itib@S47bQeF!%$aGJFF7 diff --git a/out/test/classes/test/pivotal/pal/tracker/InMemoryTimeEntryRepositoryTest.class b/out/test/classes/test/pivotal/pal/tracker/InMemoryTimeEntryRepositoryTest.class new file mode 100644 index 0000000000000000000000000000000000000000..8bebdbf5b5d0aa67cddc463aea369972f359cded GIT binary patch literal 3211 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 Y85@MZNwEUiEy~E4Y?Ec!p*4ry-!S$OL;wH) diff --git a/out/test/classes/test/pivotal/pal/trackerapi/TimeEntryApiTest.class b/out/test/classes/test/pivotal/pal/trackerapi/TimeEntryApiTest.class new file mode 100644 index 0000000000000000000000000000000000000000..0c3e8de7c31ebec4e0bc347205a1a50a59e888e7 GIT binary patch literal 6651 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 c@;XT4Wq7%cS2I;#McEkFUx6>&PMXaE2J literal 0 HcmV?d00001 diff --git a/out/test/classes/test/pivotal/pal/trackerapi/WelcomeApiTest.class b/out/test/classes/test/pivotal/pal/trackerapi/WelcomeApiTest.class deleted file mode 100644 index f2402b774f4ccc70b4482b3200fb43b572c7f5f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1704 zcmah}ZBraY5Ps%fSU64+4^1Q*f-x$P$oPUW@nX!0q|`b?3a+5ae6Z%00fy}DTxMq> z`B_$3(XvX*&;BUOXZHvkVZD5~>Fb{Er@No-*?<21<1YZqXw^`~`~+%P7{_9O>j7@m z@DY}3kob5UpVaUvJ`1ocPmU+qzwu^qq-!cbw|!L+c*PuC-ozI?6-c zm0@Ib>}7}g`cQCFU}imyz3J&qlbCL#TXAf?^2WxAz^!6rt>Q%PsL)&YOseAm*-E`V zG)`|5cI=LcjlU~Uo?qA$sI1s+mU^yjV*McPb+zj#W)o<%ZK$G6aD{A^Q_EH8RaA_#YrE#pCBzIfJ?=il5prVwaHw!7t z`v`69^;0kNyvuSU9~cn`M1bPJlPSK49)x|v=^nKmgpo=TO}>`e#>xV=Iw75HKP>>3YQ^5HZe z4Os?(&4sfjfh$>E*~&P-FGK6J%xGxkhQYh94Q@MoiVtbEAooJP1ZOe2N%lbf%ARr? z`bnzeP!Di9a9N!8e?tT&OtShUGcS)GMDaNBErRDiLHAyKbtBb!u{E}ZccxKL^(=^WvfDU*AZ&C_epuLm_|AY$v0kez0 zqSPoiD&;>>eU7oeQE6ZQ9YJIKIVQGFP^VC)_eG+x_3_aF diff --git a/src/main/java/io/pivotal/pal/tracker/TimeEntryRepository.java b/src/main/java/io/pivotal/pal/tracker/TimeEntryRepository.java index b8c8017d3..f106aa201 100644 --- a/src/main/java/io/pivotal/pal/tracker/TimeEntryRepository.java +++ b/src/main/java/io/pivotal/pal/tracker/TimeEntryRepository.java @@ -1,101 +1,11 @@ package io.pivotal.pal.tracker; -import java.time.LocalDate; import java.util.List; public interface TimeEntryRepository { TimeEntry create(TimeEntry timeEntry); - TimeEntry find(Long id); - List list(); - TimeEntry update(Long id, TimeEntry timeEntry); - void delete(Long id); - - class TimeEntry { - private long id; - private long projectId; - private long userId; - private LocalDate date; - private int hours; - - public TimeEntry() { - } - - public TimeEntry(long projectId, long userId, LocalDate date, int hours) { - this.projectId = projectId; - this.userId = userId; - this.date = date; - this.hours = hours; - } - - public TimeEntry(long id, long projectId, long userId, LocalDate date, int hours) { - this.id = id; - this.projectId = projectId; - this.userId = userId; - this.date = date; - this.hours = hours; - } - - public long getId() { - return id; - } - - public void setId(long id) { - this.id = id; - } - - public long getProjectId() { - return projectId; - } - - public long getUserId() { - return userId; - } - - public LocalDate getDate() { - return date; - } - - public int getHours() { - return hours; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - TimeEntry timeEntry = (TimeEntry) o; - - if (id != timeEntry.id) return false; - if (projectId != timeEntry.projectId) return false; - if (userId != timeEntry.userId) return false; - if (hours != timeEntry.hours) return false; - return date != null ? date.equals(timeEntry.date) : timeEntry.date == null; - } - - @Override - public int hashCode() { - int result = (int) (id ^ (id >>> 32)); - result = 31 * result + (int) (projectId ^ (projectId >>> 32)); - result = 31 * result + (int) (userId ^ (userId >>> 32)); - result = 31 * result + (date != null ? date.hashCode() : 0); - result = 31 * result + hours; - return result; - } - - @Override - public String toString() { - return "TimeEntry{" + - "id=" + id + - ", projectId=" + projectId + - ", userId=" + userId + - ", date='" + date + '\'' + - ", hours=" + hours + - '}'; - } - } } From 233e86768ef324bf7707753de8a7ae27aab0aeae Mon Sep 17 00:00:00 2001 From: Tyson Gern Date: Thu, 20 Jul 2017 15:04:20 -0600 Subject: [PATCH 17/24] Databases --- databases/tracker/create_databases.sql | 10 ++++++++++ databases/tracker/migrations/V1__initial_schema.sql | 11 +++++++++++ 2 files changed, 21 insertions(+) create mode 100644 databases/tracker/create_databases.sql create mode 100644 databases/tracker/migrations/V1__initial_schema.sql diff --git a/databases/tracker/create_databases.sql b/databases/tracker/create_databases.sql new file mode 100644 index 000000000..6f1e86abf --- /dev/null +++ b/databases/tracker/create_databases.sql @@ -0,0 +1,10 @@ +DROP DATABASE IF EXISTS tracker_dev; +DROP DATABASE IF EXISTS tracker_test; + +CREATE DATABASE tracker_dev; +CREATE DATABASE tracker_test; + +CREATE USER IF NOT EXISTS 'tracker'@'localhost' + IDENTIFIED BY ''; +GRANT ALL PRIVILEGES ON tracker_dev.* TO 'tracker' @'localhost'; +GRANT ALL PRIVILEGES ON tracker_test.* TO 'tracker' @'localhost'; \ No newline at end of file diff --git a/databases/tracker/migrations/V1__initial_schema.sql b/databases/tracker/migrations/V1__initial_schema.sql new file mode 100644 index 000000000..daca8c4e3 --- /dev/null +++ b/databases/tracker/migrations/V1__initial_schema.sql @@ -0,0 +1,11 @@ +CREATE TABLE time_entries ( + id BIGINT(20) NOT NULL AUTO_INCREMENT, + project_id BIGINT(20), + user_id BIGINT(20), + date DATE, + hours INT, + + PRIMARY KEY (id) +) + ENGINE = innodb + DEFAULT CHARSET = utf8; \ No newline at end of file From 93b7295bda28c84d944ab89b0698cebb1f4827a1 Mon Sep 17 00:00:00 2001 From: Tyson Gern Date: Wed, 26 Jul 2017 11:45:04 -0600 Subject: [PATCH 18/24] Add tests for JDBC lab --- .../tracker/JdbcTimeEntryRepositoryTest.java | 159 ++++++++++++++++++ .../pal/trackerapi/TimeEntryApiTest.java | 12 ++ 2 files changed, 171 insertions(+) create mode 100644 src/test/java/test/pivotal/pal/tracker/JdbcTimeEntryRepositoryTest.java diff --git a/src/test/java/test/pivotal/pal/tracker/JdbcTimeEntryRepositoryTest.java b/src/test/java/test/pivotal/pal/tracker/JdbcTimeEntryRepositoryTest.java new file mode 100644 index 000000000..e1eac20fc --- /dev/null +++ b/src/test/java/test/pivotal/pal/tracker/JdbcTimeEntryRepositoryTest.java @@ -0,0 +1,159 @@ +package test.pivotal.pal.tracker; + + +import com.mysql.cj.jdbc.MysqlDataSource; +import io.pivotal.pal.tracker.JdbcTimeEntryRepository; +import io.pivotal.pal.tracker.TimeEntry; +import io.pivotal.pal.tracker.TimeEntryRepository; +import org.junit.Before; +import org.junit.Test; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.sql.Date; +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; + +import static org.assertj.core.api.Assertions.assertThat; + +public class JdbcTimeEntryRepositoryTest { + private TimeEntryRepository subject; + private JdbcTemplate jdbcTemplate; + + @Before + public void setUp() throws Exception { + MysqlDataSource dataSource = new MysqlDataSource(); + dataSource.setUrl(System.getenv("SPRING_DATASOURCE_URL")); + + subject = new JdbcTimeEntryRepository(dataSource); + + jdbcTemplate = new JdbcTemplate(dataSource); + jdbcTemplate.execute("DELETE FROM time_entries"); + + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + } + + @Test + public void createInsertsATimeEntryRecord() throws Exception { + TimeEntry newTimeEntry = new TimeEntry(123, 321, LocalDate.parse("2017-01-09"), 8); + TimeEntry entry = subject.create(newTimeEntry); + + Map foundEntry = jdbcTemplate.queryForMap("Select * from time_entries where id = ?", entry.getId()); + + assertThat(foundEntry.get("id")).isEqualTo(entry.getId()); + assertThat(foundEntry.get("project_id")).isEqualTo(123L); + assertThat(foundEntry.get("user_id")).isEqualTo(321L); + assertThat(((Date)foundEntry.get("date")).toLocalDate()).isEqualTo(LocalDate.parse("2017-01-09")); + assertThat(foundEntry.get("hours")).isEqualTo(8); + } + + @Test + public void createReturnsTheCreatedTimeEntry() throws Exception { + TimeEntry newTimeEntry = new TimeEntry(123, 321, LocalDate.parse("2017-01-09"), 8); + TimeEntry entry = subject.create(newTimeEntry); + + assertThat(entry.getId()).isNotNull(); + assertThat(entry.getProjectId()).isEqualTo(123); + assertThat(entry.getUserId()).isEqualTo(321); + assertThat(entry.getDate()).isEqualTo(LocalDate.parse("2017-01-09")); + assertThat(entry.getHours()).isEqualTo(8); + } + + @Test + public void findFindsATimeEntry() throws Exception { + jdbcTemplate.execute( + "INSERT INTO time_entries (id, project_id, user_id, date, hours) " + + "VALUES (999, 123, 321, '2017-01-09', 8)" + ); + + TimeEntry timeEntry = subject.find(999L); + + assertThat(timeEntry.getId()).isEqualTo(999L); + assertThat(timeEntry.getProjectId()).isEqualTo(123L); + assertThat(timeEntry.getUserId()).isEqualTo(321L); + assertThat(timeEntry.getDate()).isEqualTo(LocalDate.parse("2017-01-09")); + assertThat(timeEntry.getHours()).isEqualTo(8); + } + + @Test + public void findReturnsNullWhenNotFound() throws Exception { + TimeEntry timeEntry = subject.find(999L); + + assertThat(timeEntry).isNull(); + } + + @Test + public void listFindsAllTimeEntries() throws Exception { + jdbcTemplate.execute( + "INSERT INTO time_entries (id, project_id, user_id, date, hours) " + + "VALUES (999, 123, 321, '2017-01-09', 8), (888, 456, 678, '2017-01-08', 9)" + ); + + List timeEntries = subject.list(); + assertThat(timeEntries.size()).isEqualTo(2); + + TimeEntry timeEntry = timeEntries.get(0); + assertThat(timeEntry.getId()).isEqualTo(888L); + assertThat(timeEntry.getProjectId()).isEqualTo(456L); + assertThat(timeEntry.getUserId()).isEqualTo(678L); + assertThat(timeEntry.getDate()).isEqualTo(LocalDate.parse("2017-01-08")); + assertThat(timeEntry.getHours()).isEqualTo(9); + + timeEntry = timeEntries.get(1); + assertThat(timeEntry.getId()).isEqualTo(999L); + assertThat(timeEntry.getProjectId()).isEqualTo(123L); + assertThat(timeEntry.getUserId()).isEqualTo(321L); + assertThat(timeEntry.getDate()).isEqualTo(LocalDate.parse("2017-01-09")); + assertThat(timeEntry.getHours()).isEqualTo(8); + } + + @Test + public void updateReturnsTheUpdatedRecord() throws Exception { + jdbcTemplate.execute( + "INSERT INTO time_entries (id, project_id, user_id, date, hours) " + + "VALUES (1000, 123, 321, '2017-01-09', 8)"); + + TimeEntry timeEntryUpdates = new TimeEntry(456, 987, LocalDate.parse("2017-01-10"), 10); + + TimeEntry updatedTimeEntry = subject.update(1000L, timeEntryUpdates); + + assertThat(updatedTimeEntry.getId()).isEqualTo(1000L); + assertThat(updatedTimeEntry.getProjectId()).isEqualTo(456L); + assertThat(updatedTimeEntry.getUserId()).isEqualTo(987L); + assertThat(updatedTimeEntry.getDate()).isEqualTo(LocalDate.parse("2017-01-10")); + assertThat(updatedTimeEntry.getHours()).isEqualTo(10); + } + + @Test + public void updateUpdatesTheRecord() throws Exception { + jdbcTemplate.execute( + "INSERT INTO time_entries (id, project_id, user_id, date, hours) " + + "VALUES (1000, 123, 321, '2017-01-09', 8)"); + + TimeEntry updatedTimeEntry = new TimeEntry(456, 322, LocalDate.parse("2017-01-10"), 10); + + TimeEntry timeEntry = subject.update(1000L, updatedTimeEntry); + + Map foundEntry = jdbcTemplate.queryForMap("Select * from time_entries where id = ?", timeEntry.getId()); + + assertThat(foundEntry.get("id")).isEqualTo(timeEntry.getId()); + assertThat(foundEntry.get("project_id")).isEqualTo(456L); + assertThat(foundEntry.get("user_id")).isEqualTo(322L); + assertThat(((Date)foundEntry.get("date")).toLocalDate()).isEqualTo(LocalDate.parse("2017-01-10")); + assertThat(foundEntry.get("hours")).isEqualTo(10); + } + + @Test + public void deleteRemovesTheRecord() throws Exception { + jdbcTemplate.execute( + "INSERT INTO time_entries (id, project_id, user_id, date, hours) " + + "VALUES (999, 123, 321, '2017-01-09', 8)" + ); + + subject.delete(999L); + + Map foundEntry = jdbcTemplate.queryForMap("Select count(*) count from time_entries where id = ?", 999); + assertThat(foundEntry.get("count")).isEqualTo(0L); + } +} diff --git a/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java b/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java index b742e5447..2b7464304 100644 --- a/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java +++ b/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java @@ -1,8 +1,10 @@ package test.pivotal.pal.trackerapi; import com.jayway.jsonpath.DocumentContext; +import com.mysql.cj.jdbc.MysqlDataSource; import io.pivotal.pal.tracker.PalTrackerApplication; import io.pivotal.pal.tracker.TimeEntry; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -12,6 +14,7 @@ import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.junit4.SpringRunner; import java.time.LocalDate; @@ -30,6 +33,15 @@ public class TimeEntryApiTest { private TimeEntry timeEntry = new TimeEntry(123, 456, LocalDate.parse("2017-01-08"), 8); + @Before + public void setUp() throws Exception { + MysqlDataSource dataSource = new MysqlDataSource(); + dataSource.setUrl(System.getenv("SPRING_DATASOURCE_URL")); + + JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); + jdbcTemplate.execute("TRUNCATE time_entries"); + } + @Test public void testCreate() throws Exception { ResponseEntity createResponse = restTemplate.postForEntity("/time-entries", timeEntry, String.class); From 6d9e602023de2f15a322184308841bc38cca0e6e Mon Sep 17 00:00:00 2001 From: Tyson Gern Date: Wed, 26 Jul 2017 11:45:04 -0600 Subject: [PATCH 19/24] Database --- build.gradle | 27 +++++- ci/build.yml | 21 ++++- manifest-production.yml | 4 +- manifest-review.yml | 4 +- .../pal/tracker/JdbcTimeEntryRepository.java | 88 +++++++++++++++++++ .../pal/tracker/PalTrackerApplication.java | 5 +- .../pal/trackerapi/TimeEntryApiTest.java | 2 +- 7 files changed, 144 insertions(+), 7 deletions(-) create mode 100644 src/main/java/io/pivotal/pal/tracker/JdbcTimeEntryRepository.java diff --git a/build.gradle b/build.gradle index 4bd77b163..4563f9acc 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,9 @@ +import org.flywaydb.gradle.task.FlywayMigrateTask + plugins { id "java" id "org.springframework.boot" version "1.5.4.RELEASE" + id "org.flywaydb.flyway" version "4.2.0" } repositories { @@ -9,13 +12,33 @@ repositories { dependencies { compile("org.springframework.boot:spring-boot-starter-web") - testCompile("org.springframework.boot:spring-boot-starter-test") compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.1") + compile("org.springframework.boot:spring-boot-starter-jdbc") + + compile("mysql:mysql-connector-java:6.0.6") + + testCompile("org.springframework.boot:spring-boot-starter-test") } + +def developmentDbUrl = "jdbc:mysql://localhost:3306/tracker_dev?user=tracker&useSSL=false&useTimezone=true&serverTimezone=UTC&useLegacyDatetimeCode=false" bootRun.environment([ "WELCOME_MESSAGE": "hello", + "SPRING_DATASOURCE_URL": developmentDbUrl, ]) +def testDbUrl = "jdbc:mysql://localhost:3306/tracker_test?user=tracker&useSSL=false&useTimezone=true&serverTimezone=UTC&useLegacyDatetimeCode=false" test.environment([ "WELCOME_MESSAGE": "Hello from test", -]) \ No newline at end of file + "SPRING_DATASOURCE_URL": testDbUrl, +]) + +flyway { + url = developmentDbUrl + user = "tracker" + password = "" + locations = ["filesystem:databases/tracker/migrations"] +} + +task testMigrate(type: FlywayMigrateTask) { + url = testDbUrl +} \ No newline at end of file diff --git a/ci/build.yml b/ci/build.yml index 6abcc811a..a01190ebb 100644 --- a/ci/build.yml +++ b/ci/build.yml @@ -18,7 +18,26 @@ run: args: - -exc - | + + function stop_mysql { + service mysql stop + } + trap stop_mysql EXIT + + export DEBIAN_FRONTEND="noninteractive" + + apt-get update + apt-get install -y software-properties-common + apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0xcbcb082a1bb943db + apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0xF1656F24C74CD1D8 + add-apt-repository 'deb http://ftp.osuosl.org/pub/mariadb/repo/10.2/ubuntu trusty main' + + apt-get -y install mariadb-server + service mysql start + cd pal-tracker + + mysql -uroot < databases/tracker/create_databases.sql chmod 777 gradlew - ./gradlew -P version=$(cat ../version/number) build + ./gradlew -P version=$(cat ../version/number) testMigrate build cp build/libs/pal-tracker-*.jar ../build-output \ No newline at end of file diff --git a/manifest-production.yml b/manifest-production.yml index caf977672..eae8d855e 100644 --- a/manifest-production.yml +++ b/manifest-production.yml @@ -2,4 +2,6 @@ applications: - name: pal-tracker path: build/libs/pal-tracker.jar - host: sperugu-pal-tracker \ No newline at end of file + host: sperugu-pal-tracker + services: + - tracker-database \ No newline at end of file diff --git a/manifest-review.yml b/manifest-review.yml index 5e74a609f..83e217e91 100644 --- a/manifest-review.yml +++ b/manifest-review.yml @@ -2,4 +2,6 @@ applications: - name: pal-tracker path: build/libs/pal-tracker.jar - host: sperugu-pal-tracker-review \ No newline at end of file + host: sperugu-pal-tracker-review + services: + - tracker-database \ No newline at end of file diff --git a/src/main/java/io/pivotal/pal/tracker/JdbcTimeEntryRepository.java b/src/main/java/io/pivotal/pal/tracker/JdbcTimeEntryRepository.java new file mode 100644 index 000000000..67b4370d0 --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/JdbcTimeEntryRepository.java @@ -0,0 +1,88 @@ +package io.pivotal.pal.tracker; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.ResultSetExtractor; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; + +import javax.sql.DataSource; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.util.List; + +import static java.sql.Statement.RETURN_GENERATED_KEYS; + +public class JdbcTimeEntryRepository implements TimeEntryRepository { + + private final JdbcTemplate jdbcTemplate; + + public JdbcTimeEntryRepository(DataSource dataSource) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + } + + @Override + public TimeEntry create(TimeEntry timeEntry) { + KeyHolder generatedKeyHolder = new GeneratedKeyHolder(); + + jdbcTemplate.update(connection -> { + PreparedStatement statement = connection.prepareStatement( + "INSERT INTO time_entries (project_id, user_id, date, hours) " + + "VALUES (?, ?, ?, ?)", + RETURN_GENERATED_KEYS + ); + + statement.setLong(1, timeEntry.getProjectId()); + statement.setLong(2, timeEntry.getUserId()); + statement.setDate(3, Date.valueOf(timeEntry.getDate())); + statement.setInt(4, timeEntry.getHours()); + + return statement; + }, generatedKeyHolder); + + return find(generatedKeyHolder.getKey().longValue()); + } + + @Override + public TimeEntry find(Long id) { + return jdbcTemplate.query( + "SELECT id, project_id, user_id, date, hours FROM time_entries WHERE id = ?", + new Object[]{id}, + extractor); + } + + @Override + public List list() { + return jdbcTemplate.query("SELECT id, project_id, user_id, date, hours FROM time_entries", mapper); + } + + @Override + public TimeEntry update(Long id, TimeEntry timeEntry) { + jdbcTemplate.update("UPDATE time_entries " + + "SET project_id = ?, user_id = ?, date = ?, hours = ? " + + "WHERE id = ?", + timeEntry.getProjectId(), + timeEntry.getUserId(), + Date.valueOf(timeEntry.getDate()), + timeEntry.getHours(), + id); + + return find(id); + } + + @Override + public void delete(Long id) { + jdbcTemplate.update("DELETE FROM time_entries WHERE id = ?", id); + } + + private final RowMapper mapper = (rs, rowNum) -> new TimeEntry( + rs.getLong("id"), + rs.getLong("project_id"), + rs.getLong("user_id"), + rs.getDate("date").toLocalDate(), + rs.getInt("hours") + ); + + private final ResultSetExtractor extractor = + (rs) -> rs.next() ? mapper.mapRow(rs, 1) : null; +} \ No newline at end of file diff --git a/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java b/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java index 78cb5b60f..add8fdf2a 100644 --- a/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java +++ b/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.mysql.cj.jdbc.MysqlDataSource; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; @@ -18,7 +19,9 @@ public static void main(String[] args) { @Bean TimeEntryRepository timeEntryRepository() { - return new InMemoryTimeEntryRepository(); + MysqlDataSource dataSource = new MysqlDataSource(); + dataSource.setUrl(System.getenv("SPRING_DATASOURCE_URL")); + return new JdbcTimeEntryRepository(dataSource); } @Bean diff --git a/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java b/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java index 2b7464304..3377d33b5 100644 --- a/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java +++ b/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java @@ -135,4 +135,4 @@ private Long createTimeEntry() { return response.getBody().getId(); } -} +} \ No newline at end of file From 0d59d2fdd3668046354e8e3ce4873949625e9c57 Mon Sep 17 00:00:00 2001 From: Subbu Perugu Date: Wed, 18 Oct 2017 17:21:43 -0400 Subject: [PATCH 20/24] Pal tracker datasource passed in contructor --- .../java/io/pivotal/pal/tracker/PalTrackerApplication.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java b/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java index add8fdf2a..8d30b4ff4 100644 --- a/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java +++ b/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java @@ -10,6 +10,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; +import javax.sql.DataSource; + @SpringBootApplication public class PalTrackerApplication { @@ -18,9 +20,7 @@ public static void main(String[] args) { } @Bean - TimeEntryRepository timeEntryRepository() { - MysqlDataSource dataSource = new MysqlDataSource(); - dataSource.setUrl(System.getenv("SPRING_DATASOURCE_URL")); + TimeEntryRepository timeEntryRepository(DataSource dataSource) { return new JdbcTimeEntryRepository(dataSource); } From 8092e5847d99d578fd7aa9363679b8803bf5d218 Mon Sep 17 00:00:00 2001 From: Tyson Gern Date: Wed, 26 Jul 2017 12:28:32 -0600 Subject: [PATCH 21/24] Add tests for Actuator lab --- .../pivotal/pal/trackerapi/HealthApiTest.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/test/java/test/pivotal/pal/trackerapi/HealthApiTest.java diff --git a/src/test/java/test/pivotal/pal/trackerapi/HealthApiTest.java b/src/test/java/test/pivotal/pal/trackerapi/HealthApiTest.java new file mode 100644 index 000000000..b3eef23cc --- /dev/null +++ b/src/test/java/test/pivotal/pal/trackerapi/HealthApiTest.java @@ -0,0 +1,38 @@ +package test.pivotal.pal.trackerapi; + +import com.jayway.jsonpath.DocumentContext; +import io.pivotal.pal.tracker.PalTrackerApplication; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; + +import static com.jayway.jsonpath.JsonPath.parse; +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = PalTrackerApplication.class, webEnvironment = RANDOM_PORT) +public class HealthApiTest { + + @Autowired + private TestRestTemplate restTemplate; + + @Test + public void healthTest() { + ResponseEntity response = this.restTemplate.getForEntity("/health", String.class); + + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + + DocumentContext healthJson = parse(response.getBody()); + + assertThat(healthJson.read("$.status", String.class)).isEqualTo("UP"); + assertThat(healthJson.read("$.db.status", String.class)).isEqualTo("UP"); + assertThat(healthJson.read("$.diskSpace.status", String.class)).isEqualTo("UP"); + } +} From ecb6a9d4ce4cb54a36e5968a1cb8027e1e25af39 Mon Sep 17 00:00:00 2001 From: Subbu Perugu Date: Wed, 18 Oct 2017 18:32:48 -0400 Subject: [PATCH 22/24] Health Indicator --- build.gradle | 2 ++ .../pal/tracker/JdbcTimeEntryRepository.class | Bin 0 -> 5866 bytes .../pal/tracker/PalTrackerApplication.class | Bin 2276 -> 2376 bytes .../pal/tracker/TimeEntryController.class | Bin 3445 -> 4289 bytes .../tracker/TimeEntryHealthIndicator.class | Bin 0 -> 1394 bytes .../tracker/JdbcTimeEntryRepositoryTest.class | Bin 0 -> 6439 bytes .../pal/tracker/TimeEntryControllerTest.class | Bin 5246 -> 5704 bytes .../pal/trackerapi/HealthApiTest.class | Bin 0 -> 2761 bytes .../pal/trackerapi/TimeEntryApiTest.class | Bin 6651 -> 7269 bytes .../pal/tracker/TimeEntryController.java | 21 +++++++++++-- .../pal/tracker/TimeEntryHealthIndicator.java | 29 ++++++++++++++++++ .../pal/tracker/TimeEntryControllerTest.java | 6 +++- 12 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 out/production/classes/io/pivotal/pal/tracker/JdbcTimeEntryRepository.class create mode 100644 out/production/classes/io/pivotal/pal/tracker/TimeEntryHealthIndicator.class create mode 100644 out/test/classes/test/pivotal/pal/tracker/JdbcTimeEntryRepositoryTest.class create mode 100644 out/test/classes/test/pivotal/pal/trackerapi/HealthApiTest.class create mode 100644 src/main/java/io/pivotal/pal/tracker/TimeEntryHealthIndicator.java diff --git a/build.gradle b/build.gradle index 4563f9acc..488644013 100644 --- a/build.gradle +++ b/build.gradle @@ -18,6 +18,7 @@ dependencies { compile("mysql:mysql-connector-java:6.0.6") testCompile("org.springframework.boot:spring-boot-starter-test") + compile("org.springframework.boot:spring-boot-starter-actuator") } def developmentDbUrl = "jdbc:mysql://localhost:3306/tracker_dev?user=tracker&useSSL=false&useTimezone=true&serverTimezone=UTC&useLegacyDatetimeCode=false" @@ -30,6 +31,7 @@ def testDbUrl = "jdbc:mysql://localhost:3306/tracker_test?user=tracker&useSSL=fa test.environment([ "WELCOME_MESSAGE": "Hello from test", "SPRING_DATASOURCE_URL": testDbUrl, + "MANAGEMENT_SECURITY_ENABLED": false, ]) flyway { diff --git a/out/production/classes/io/pivotal/pal/tracker/JdbcTimeEntryRepository.class b/out/production/classes/io/pivotal/pal/tracker/JdbcTimeEntryRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..16cf4e36d2e5831e77e6616b851c5c5bb49c7f62 GIT binary patch literal 5866 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 literal 0 HcmV?d00001 diff --git a/out/production/classes/io/pivotal/pal/tracker/PalTrackerApplication.class b/out/production/classes/io/pivotal/pal/tracker/PalTrackerApplication.class index 32ed650d9f198a70eb25c1ffb7dfb14ffd7080a8..205a43b3ebded1f2e5612deb8687c54a17907a4c 100644 GIT binary patch delta 521 zcmZ{gPft@p6vcnjR~X-ONWhi?28vK?p+%lZ{e#H=qF7NX5)+n>(ijk^mTF}07a+4@ z=SMJMVWNp{3E#mTTN9VA2=}2OE@Uz1-gED{=g!Pg%fY46;qjMmK%PYpmyD**qo1rM z=MiPV<2r*bJ~zT_NORL=n0!cXX>M!ocr=j-`P~pkc1wrtg}(Xq;9cmGj|p$2pJae_?+*d(mf#yv!%|KSqB?RI}aF6Advmar;Aem&S02 z$hW#*i7w|p)9^`1gt%ZMO0b<)!Nebt#5Q3fCrP*TuSg#1dL$}(N!h6j#pN!kC5egR nswgtj&C+%1V@^GDe|`R)c0?O)PM>*R6JC(x$&AkwGm{-B*OXma delta 422 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 klhlG*r<0fDg~`lx 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 POA?=>8ZM%44=(;Mao~m* literal 0 HcmV?d00001 diff --git a/out/test/classes/test/pivotal/pal/tracker/TimeEntryControllerTest.class b/out/test/classes/test/pivotal/pal/tracker/TimeEntryControllerTest.class index edd418e0053d4edd675b188688747299f80ec8f6..ab70b3257b8d574ff91ab3bd62d6c7d95b16a409 100644 GIT binary patch literal 5704 zcmbVQ30vIO6+Idjfh^+~FloHRjuS9oV__TDICv?>fK3KyVGJcsN;Ja*1jA^o(O}cG zO}g)K_q|Q`eM!<7hLFT=)3j;2?@PM>f&PPjy;ss`1Pm~a{V^lG<=%7dJ@>u`FaGbT zX92X}KMKxZT*icgNf_m5$CQj|8F3ji3S^8cC`CessX#$OK?#z)=#mVpgbeV%2fYXB zg)*cR*hnj=z$_cQya`t@r{MK?a1%m!15dn>jl4<0LwH!hoAH)%ycKU#@OHd|$KJ_f z?^5t?eti$#%iG?^JKoRh9^sFxS;ryop=etcL3+Dfl=( zk=^i;_Yiu7v3-(Pe~MRsngJ5!5`2c?f0idc$37pG@p&0vkWghCGomwLTXO>B!j`a+5Pj*HsIVe>G)^OxVKb)3hjh!}cWoM{JyHK9$V39)uYjGLBV z_UydF61q(=%fywWh}mLH!rl#oNSn(nkxG(l+S(8{$#^Ph8HtH;OP>*!O>0`4wC$ue z;4Pv=8TK4)yTw`S0`d5iF^mDBljoY{v6@R}t(fRCSe!fkx>zo=gA7JRH_l0DEWD&-d`ZQZ@f8&vI9tHZoKp#%`9u^oO{)wPX2>Lpz`76*JU9dL0+J zj=~}Ms)UZ0iLF~tPY4egoBEoHPIO7wvr$MXO^2Hg9cyYn)YRN6jh; zz9ZwiD!zxuWIV3o36A&o@gz@QJP#jkKZ2;-QHU#s{Heyiel_&tC8K|;lH(X!1y z%J>s|`Ll|@;IArP!r%Dg?<)SmrvHh@_~~CN{>}EAS+F4@v8R9iz}#I;Q2)m~q?pgdu^A{us7 z+Qw(YqxBnhrJUs23ir2M`ZuvLs7nW-8$doiG^Yihb-GDOQn$FlYs+FasZ+6CN{d8H zWJ9)mBSmdzeNRu18_pGT=Q$u{W9ERc(^i6xz@Tj}>h6J(&0NY(N27GKXoLJ9EH)p^ z?89q;a9VzirmPzedkp3-7;yU_Hc zp|v{$d_SQ+;?71Ubt>2HU}8GV8Il>_jVOBg?L-D)fD_Z zQaye1#$5=$i-vWn1s7-AyVD}WW@3UOIjhIhqJNy4Ykf};AWu`=ipLGUO0@)0I#UG| zMln?y?QYAnd(Awrx?zU;@1cD3^+!g#`Y-fR&fW9Exy!X%mOe*nEY(x)MX$F0;_4`S zyn?sC;<$4!tjs1QIJbYjGFb)hh(|7GVGY$YKtE~u3oN%p!pUIJ3R7KfJ7q2;E>Zd@ zsXY{)w0E5x3L16txN(0gDAV`9EW?G1pmfZ;?Uv38%NVD6s#@id4OmK|KrS=<__iY4k18cS+*{q{p3QJ@i@X4Bm=+=vj3a zAdFsmlh8+-i=531|I2ckG|X-r*d9y(lBsccBKgM4=A#*hhZ{X&|`!@j4v96q+1Tdt8VfsTy2B zgnpRT4h&(KvbCD%YaOB^KGEkqqQiv|RWi795qEhc`E4GxIZ}1`NELxq;F3B-qz)6Q zBMzlzyb>oUM?GjIz{dlmhJ8})9;p*Pse8#9>&P9sxDL(xe41A&R3X}TR9ca_yoAD( z5I0Nvm=I^iC9$2v0-U7$oWfR|#&)WVI;xEpstcBPjqAh%UxzJD9wcNTyCW9~{92j0 z--rBPYvQ+F`x1(x|G`EB+y)K-d*If=bRJEwmczNs~98L zMy0ijSkw`lyD>shxR*vQLdSgp?op5XHj0j0jNQI&*GhTR=XaI5605X3k4o33!ogU; z2_lq7w;`waT-`oEy46XyQR*vWRC0oP_;^6K-5&T>U$<*Pj`@%u_6qADH$Ar>oD6eo z%-|F?Mz_39hu~M#tCm+(B~b=-G!@g>jW|hcCV=Rmhe)ON!2-%vk~^|qC5@5Y5XAoh DuTTi4 literal 5246 zcmbVQ`Ck<08UN05%(9L~0coQo8dDKaSX8VMNK!-~Asm*oHc=dw0S15(2u z@7Vjk+w{JYDl9QcZPT=Adfz|vkLZ8UPruL1FuTA4Yu%5t@4WB(9N*{pKF|9uul@J= zmjE>5UlFw7q6$5N3B=0Kib)kl1SOb~muVFTg2WY67Q9L?~})q zD~3b}WKL|Z7zFm{uACui57Y8rO z=o8}pCuQMNV)N4~KBMBZ3Mw3P-sns?_QIf%v{I&H*$WDe^qN*QXU^*c|88uTTEwv{SjDoxDI$=*oXVdgJdfJ$>Y(vnC@ebS29l9Adt|pC`V@xVI zuw@V_9JY;AlBOwI*K1LEDruXE=_y;EH?COrTy(~9lF>nb6BA`R3l!Vw?)4n;@r1>V zK|`nIs@1VtNUp@IuTT?hTl@Y1-PpBabG+Yjx~z19#$7*p&6Fcv&gz%-XxcI3QJJAc z^O~sw4UHG>_-c~{=@%$&7DtkkZWb#9m-mu`%-wRvh#QjV$dHw`V@8)Lqc+S)*d>OBx=;n-uKd${;};YdU(eq3LKt({y*r(fKp-|>u${-NQYqQ6Pfc{}Za)OBsf z^~7{^U}BaP%~qM63R(tdrD*4^*c@ADw9ory8G6ct ztUWB|!jsmZ;iT;Zn{BRcAA#4pR-%w{(i0PGV$mTP7`CX-jndk6hU?0VLDe;RRcDa| z@v6*K*QYzN8N=o|P^2e#c$LQdUfi{HR2h%0s)XL}WpFl*O1n3ndp87^^-qD?G1%EY+I6x>bx`A0Z!EfJbEtPFs_tT_uB`hpG?WzH-lnz(n1q&~i7gv`V+#^je8& zM)I;APa6YM>=?B@xem%Q<5oOn%Awty>!rI?P+=6a(kR`UM|#6Nue-~H1|DHP`Ui%` zy9P%3nRBoAy0?aQ+twGz#!@}yT`3OMKDs{2KCaw*MseJ|ft0T#C3o%cW@WO@z2iQ( ztcDF(FAfGt%U@vGEeh_>4O(HW%Zf8|A>$I$$E5Z!JZb;pGMcN@&EwYnt)NVw|GyL$ zPJ+@&|6Wyk*|5zi)>Fkgi*gV}`Ul242Kt9P`-gE4f4H^r2V00AUX^b!WlkG-N{OHa;$k9(1mWkDR_v&MXqo^iruy9`tnc)MYj;T zf#Uj`C^=Vu9mPv1y#Y0=a|fk>J=C|CpXwal)xPeBxh@)e@CZlZL$7PGFR=IuBCeLX zn+TtaRHCdB+se0Ruw!Rz>2w7jfqp-}Yp%_wXXB$Fcwp@CPHXA61O*-8g_++{<6j2XPjMaDi{LsB=N> z_gwTrRbd4E7$BIlgnbroW{B4V9dv;X1wdc+fsPdhG?Kwx%edPIDQ}C|mj$&yAE+WX z(;lb>f@&nFD4{g*bByFU&O9_@6en^(jRm0EeNb%ys9{17u6Q#eo4_0mU|wUWLX;BgB^6)ocA;Un+<0(N%}ePrKjf^!&3%@2Wgg?p z1U%x&m+KTrFLTnstW|e(v1WL&>k7ew#87apL&&L{rf?W#IB&Qm5+69^l{&@bZlGme zv~}Mv=BZs|S+A@Jfq)1gSn#CQTGGDQ3e{jUJE~scwVI%;E~E{M1>}@vcqQkjH(}cq zL-(qXbY7+cPML>>W0^AWDsn{2chRUP9!m?8EhVWy2j6DrR5vQ=9^OuDu5N9-$ZT!r zG+e{DhO2mAfy(@9qa>Y9T^DJqP!>9w{7l0R@+!X7@EyKav8&++JXi6fh8Orr1=mo3 zuAvB_Vo$?9N>s#C3pMJ+ItA@UP$_1p)nu4R#L!U2fds86=zEq?Q6=N27K(*7M3hcO z?I3G&U9^cYFXsW-IMZP&s3nr;PnzKDpNgu2`{$#aGp{Jm`@&tb9KT}~@R_r%(?Rj- zA->x|@e>mNKSNkgT982;6(-bPG_5nI_z#0ZxCd=^(o~6o~8nj{Zi~1 z#$<^*S@E(iOkK1?%Fa*a84(4GK{fKWxr-0!FKK}OnyAv`e5L&ZIt|i3MtdLaGh zPA5jYLi@n%Z%}^ru5ZvPnSoJE(5m?ixQSbIQV4#WK&0?L2%(?$SF^vOFWMgs_5Xo^ zV+^L}j-kFr_!kTvBa)}n@LveMhPE*uz2tFSeuFC^c*K$DJI9FT=cDiTycz9rBkk>n zw08n3t>YM>w+XT{hL4cMB%WgmN4V|zam%;0>Bk64#4}0$#OQ}`ig^0)F=0q={kv&; yYXxq`(-fn%53?S1g5KtcV_wG{EYN}NLecRFJ{9-^cj=cEh|v9K$l~*dWB&m%UP3_t literal 0 HcmV?d00001 diff --git a/out/test/classes/test/pivotal/pal/trackerapi/TimeEntryApiTest.class b/out/test/classes/test/pivotal/pal/trackerapi/TimeEntryApiTest.class index 0c3e8de7c31ebec4e0bc347205a1a50a59e888e7..07dc1c0e6bc4a34df5c251213d77def770420121 100644 GIT binary patch literal 7269 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 Tyd7_)Opx<$#xL-eZPot+`t9!2 delta 2688 zcma)-d0bRg6vuyW7T%l3bDMAhH&P-UW)Klm5Ct?`638}jTv7uP%me|I)Oy)sWo64t zElVx4GTRhvKykseGAqqiD_g9z(pKBktlynCGl-0Re1F_`?>%=r-}AfY9_oL*kG}il zrmX-{B!`NYSRmszJDcL08>mRT6x^=j4lGn*!JQ5a!(F1dTfrh3_sF{My-loSfHRz{5OhXlPKzg z%UOaItAfp04s5|z8QWA0K!ez0d(i9GTR2yKCZXu~z8~BU`Sr=HX(aK~8S_riMM(t050>X~;pYhT#}t ztW^5<*{9)cyd&dX4e#N786Rlaj}K*hq~T*6knxFzPw|S}tD=(c|R8TphP)uZ(5|=o$qNsdCp@fLI#KOsyMPklT8Q)2e z;}VNYXO>rJn1ON)-{S`jKjMI~($TdIe!|aU?&B`p_yT!H3p$Xj2WR59j2w71e+ib!eOGcD8f2=co=F5p6>Q z+Seh*;X}uKw^Q%b%n}8w51nc262@dVOzu};~Cb2+J7m`UBmr1d#kaymt)cll8n!F2T7hUNU{|mndfo#aP~Zf zWY08Pv~43?66%m(^TD0(aeA5(NV7-V$(!LwY{noNx*dJd7X67MjUJ~X3j-PH3=|*} z*Rmz_FvvV>fPoz)m_$|*=Xr4zX;?TjlQX;`77sIH^7T6qrJm0r7dxEIyC( zOycvLjrd%=Z9q1u45$1?>PTVg%iz@F(X=%IeH!Pf0nR$4*-&^I;HV#TJuKcR^o>G4XKEdk){a^@a)my_(62^X zJqCE)QZ3Shkr>0^$`?ZI%f1fHG+cmkIicn@d}`RM0PA3lol1kqbC2TrwEc zBpK+GZX}%_O$7N&fn)i&PGG{DNa0SxT$W2I@fwPG4`qFX!WJ<}F@c3*aySr^80^C} zjHQLEtj091XypnEd6B$~VlXSsbEJ5TM{w! zzd>f^wD^!+hoS2@-+Ta^exkFOwX1Z*Vh%Mh&+n0$?9&4tX-J}{l{`W$I$@^Yqo5zA zZC1!On;uzF#Vstj&VSdsur`PGxdmMOdNyw0(@?;G-&llOcw=woSQQQl^YJ=v%8dRG D2^sW; diff --git a/src/main/java/io/pivotal/pal/tracker/TimeEntryController.java b/src/main/java/io/pivotal/pal/tracker/TimeEntryController.java index a9b854f6c..427ed4aaf 100644 --- a/src/main/java/io/pivotal/pal/tracker/TimeEntryController.java +++ b/src/main/java/io/pivotal/pal/tracker/TimeEntryController.java @@ -1,5 +1,7 @@ package io.pivotal.pal.tracker; +import org.springframework.boot.actuate.metrics.CounterService; +import org.springframework.boot.actuate.metrics.GaugeService; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -10,15 +12,25 @@ @RequestMapping("/time-entries") public class TimeEntryController { + private final CounterService counter; + private final GaugeService gauge; private TimeEntryRepository timeEntriesRepo; - public TimeEntryController(TimeEntryRepository timeEntriesRepo) { + public TimeEntryController( + TimeEntryRepository timeEntriesRepo, + CounterService counter, + GaugeService gauge + ) { this.timeEntriesRepo = timeEntriesRepo; + this.counter = counter; + this.gauge = gauge; } @PostMapping public ResponseEntity create(@RequestBody TimeEntry timeEntry) { TimeEntry createdTimeEntry = timeEntriesRepo.create(timeEntry); + counter.increment("TimeEntry.created"); + gauge.submit("timeEntries.count", timeEntriesRepo.list().size()); return new ResponseEntity<>(createdTimeEntry, HttpStatus.CREATED); } @@ -27,6 +39,7 @@ public ResponseEntity create(@RequestBody TimeEntry timeEntry) { public ResponseEntity read(@PathVariable Long id) { TimeEntry timeEntry = timeEntriesRepo.find(id); if (timeEntry != null) { + counter.increment("TimeEntry.read"); return new ResponseEntity<>(timeEntry, HttpStatus.OK); } else { return new ResponseEntity<>(HttpStatus.NOT_FOUND); @@ -35,6 +48,7 @@ public ResponseEntity read(@PathVariable Long id) { @GetMapping public ResponseEntity> list() { + counter.increment("TimeEntry.listed"); return new ResponseEntity<>(timeEntriesRepo.list(), HttpStatus.OK); } @@ -42,6 +56,7 @@ public ResponseEntity> list() { public ResponseEntity update(@PathVariable Long id, @RequestBody TimeEntry timeEntry) { TimeEntry updatedTimeEntry = timeEntriesRepo.update(id, timeEntry); if (updatedTimeEntry != null) { + counter.increment("TimeEntry.updated"); return new ResponseEntity<>(updatedTimeEntry, HttpStatus.OK); } else { return new ResponseEntity<>(HttpStatus.NOT_FOUND); @@ -51,7 +66,9 @@ public ResponseEntity update(@PathVariable Long id, @RequestBody Time @DeleteMapping("{id}") public ResponseEntity delete(@PathVariable Long id) { timeEntriesRepo.delete(id); + counter.increment("TimeEntry.deleted"); + gauge.submit("timeEntries.count", timeEntriesRepo.list().size()); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } -} +} \ No newline at end of file diff --git a/src/main/java/io/pivotal/pal/tracker/TimeEntryHealthIndicator.java b/src/main/java/io/pivotal/pal/tracker/TimeEntryHealthIndicator.java new file mode 100644 index 000000000..49b1f175d --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/TimeEntryHealthIndicator.java @@ -0,0 +1,29 @@ +package io.pivotal.pal.tracker; + +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.stereotype.Component; + +@Component +public class TimeEntryHealthIndicator implements HealthIndicator { + + private static final int MAX_TIME_ENTRIES = 5; + private final TimeEntryRepository timeEntryRepo; + + public TimeEntryHealthIndicator(TimeEntryRepository timeEntryRepo) { + this.timeEntryRepo = timeEntryRepo; + } + + @Override + public Health health() { + Health.Builder builder = new Health.Builder(); + + if(timeEntryRepo.list().size() < MAX_TIME_ENTRIES) { + builder.up(); + } else { + builder.down(); + } + + return builder.build(); + } +} \ No newline at end of file diff --git a/src/test/java/test/pivotal/pal/tracker/TimeEntryControllerTest.java b/src/test/java/test/pivotal/pal/tracker/TimeEntryControllerTest.java index f7c0090e3..860cbb141 100644 --- a/src/test/java/test/pivotal/pal/tracker/TimeEntryControllerTest.java +++ b/src/test/java/test/pivotal/pal/tracker/TimeEntryControllerTest.java @@ -5,6 +5,9 @@ import io.pivotal.pal.tracker.TimeEntryRepository; import org.junit.Before; import org.junit.Test; +import org.springframework.boot.actuate.metrics.repository.InMemoryMetricRepository; +import org.springframework.boot.actuate.metrics.writer.DefaultCounterService; +import org.springframework.boot.actuate.metrics.writer.DefaultGaugeService; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -24,7 +27,8 @@ public class TimeEntryControllerTest { @Before public void setUp() throws Exception { timeEntryRepository = mock(TimeEntryRepository.class); - controller = new TimeEntryController(timeEntryRepository); + controller = new TimeEntryController(timeEntryRepository, new DefaultCounterService(new InMemoryMetricRepository()) + , new DefaultGaugeService(new InMemoryMetricRepository())); } @Test From d3d16a6d32487d3e6af9b354caddfcb5a23f6a2e Mon Sep 17 00:00:00 2001 From: Tyson Gern Date: Wed, 26 Jul 2017 12:50:47 -0600 Subject: [PATCH 23/24] Add tests for Security lab --- .../pal/trackerapi/SecurityApiTest.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/test/java/test/pivotal/pal/trackerapi/SecurityApiTest.java diff --git a/src/test/java/test/pivotal/pal/trackerapi/SecurityApiTest.java b/src/test/java/test/pivotal/pal/trackerapi/SecurityApiTest.java new file mode 100644 index 000000000..72099994b --- /dev/null +++ b/src/test/java/test/pivotal/pal/trackerapi/SecurityApiTest.java @@ -0,0 +1,52 @@ +package test.pivotal.pal.trackerapi; + +import io.pivotal.pal.tracker.PalTrackerApplication; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.embedded.LocalServerPort; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = PalTrackerApplication.class, webEnvironment = RANDOM_PORT) +public class SecurityApiTest { + + @LocalServerPort + private String port; + private TestRestTemplate authorizedRestTemplate; + + @Autowired + private TestRestTemplate unAuthorizedRestTemplate; + + @Before + public void setUp() throws Exception { + RestTemplateBuilder builder = new RestTemplateBuilder() + .rootUri("http://localhost:" + port) + .basicAuthorization("user", "password"); + + authorizedRestTemplate = new TestRestTemplate(builder); + } + + @Test + public void unauthorizedTest() { + ResponseEntity response = this.unAuthorizedRestTemplate.getForEntity("/", String.class); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); + } + + @Test + public void authorizedTest() { + ResponseEntity response = this.authorizedRestTemplate.getForEntity("/", String.class); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + } +} From f6161c2dff4da9299bf25c3ece2a358e34c94825 Mon Sep 17 00:00:00 2001 From: Subbu Perugu Date: Thu, 19 Oct 2017 11:54:35 -0400 Subject: [PATCH 24/24] Secuity Test --- build.gradle | 7 +++- manifest-production.yml | 4 +- manifest-review.yml | 4 +- .../pal/tracker/SecurityConfiguration.java | 33 ++++++++++++++++ .../pivotal/pal/trackerapi/HealthApiTest.java | 15 ++++++++ .../pal/trackerapi/TimeEntryApiTest.java | 9 +++++ .../pal/trackerapi/WelcomeApiTest.java | 38 +++++++++++++++++++ 7 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 src/main/java/io/pivotal/pal/tracker/SecurityConfiguration.java create mode 100644 src/test/java/test/pivotal/pal/trackerapi/WelcomeApiTest.java diff --git a/build.gradle b/build.gradle index 488644013..406ff8d52 100644 --- a/build.gradle +++ b/build.gradle @@ -12,19 +12,22 @@ repositories { dependencies { compile("org.springframework.boot:spring-boot-starter-web") - compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.1") compile("org.springframework.boot:spring-boot-starter-jdbc") + compile("org.springframework.boot:spring-boot-starter-actuator") + compile("org.springframework.boot:spring-boot-starter-security") compile("mysql:mysql-connector-java:6.0.6") + compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.1") testCompile("org.springframework.boot:spring-boot-starter-test") - compile("org.springframework.boot:spring-boot-starter-actuator") + } def developmentDbUrl = "jdbc:mysql://localhost:3306/tracker_dev?user=tracker&useSSL=false&useTimezone=true&serverTimezone=UTC&useLegacyDatetimeCode=false" bootRun.environment([ "WELCOME_MESSAGE": "hello", "SPRING_DATASOURCE_URL": developmentDbUrl, + ]) def testDbUrl = "jdbc:mysql://localhost:3306/tracker_test?user=tracker&useSSL=false&useTimezone=true&serverTimezone=UTC&useLegacyDatetimeCode=false" diff --git a/manifest-production.yml b/manifest-production.yml index eae8d855e..52b5ff80f 100644 --- a/manifest-production.yml +++ b/manifest-production.yml @@ -4,4 +4,6 @@ applications: path: build/libs/pal-tracker.jar host: sperugu-pal-tracker services: - - tracker-database \ No newline at end of file + - tracker-database + env: + SECURITY_FORCE_HTTPS: false \ No newline at end of file diff --git a/manifest-review.yml b/manifest-review.yml index 83e217e91..4dc9fc722 100644 --- a/manifest-review.yml +++ b/manifest-review.yml @@ -4,4 +4,6 @@ applications: path: build/libs/pal-tracker.jar host: sperugu-pal-tracker-review services: - - tracker-database \ No newline at end of file + - tracker-database + env: + SECURITY_FORCE_HTTPS: false \ No newline at end of file diff --git a/src/main/java/io/pivotal/pal/tracker/SecurityConfiguration.java b/src/main/java/io/pivotal/pal/tracker/SecurityConfiguration.java new file mode 100644 index 000000000..ac12e03b2 --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/SecurityConfiguration.java @@ -0,0 +1,33 @@ + +package io.pivotal.pal.tracker; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@EnableWebSecurity +public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + String forceHttps = System.getenv("SECURITY_FORCE_HTTPS"); + if (forceHttps != null && forceHttps.equals("true")) { + http.requiresChannel().anyRequest().requiresSecure(); + } + + http + .authorizeRequests().antMatchers("/**").hasRole("USER") + .and() + .httpBasic() + .and() + .csrf().disable(); + } + + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + auth + .inMemoryAuthentication() + .withUser("user").password("password").roles("USER"); + } +} \ No newline at end of file diff --git a/src/test/java/test/pivotal/pal/trackerapi/HealthApiTest.java b/src/test/java/test/pivotal/pal/trackerapi/HealthApiTest.java index b3eef23cc..d91cb56fd 100644 --- a/src/test/java/test/pivotal/pal/trackerapi/HealthApiTest.java +++ b/src/test/java/test/pivotal/pal/trackerapi/HealthApiTest.java @@ -2,11 +2,14 @@ import com.jayway.jsonpath.DocumentContext; import io.pivotal.pal.tracker.PalTrackerApplication; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.embedded.LocalServerPort; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.test.context.junit4.SpringRunner; @@ -22,6 +25,18 @@ public class HealthApiTest { @Autowired private TestRestTemplate restTemplate; + @LocalServerPort + private String port; + + @Before + public void setUp() throws Exception { + RestTemplateBuilder builder = new RestTemplateBuilder() + .rootUri("http://localhost:" + port) + .basicAuthorization("user", "password"); + + restTemplate = new TestRestTemplate(builder); + } + @Test public void healthTest() { ResponseEntity response = this.restTemplate.getForEntity("/health", String.class); diff --git a/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java b/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java index 3377d33b5..d93f88598 100644 --- a/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java +++ b/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java @@ -8,8 +8,10 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.embedded.LocalServerPort; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.http.HttpEntity; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -32,12 +34,19 @@ public class TimeEntryApiTest { private TestRestTemplate restTemplate; private TimeEntry timeEntry = new TimeEntry(123, 456, LocalDate.parse("2017-01-08"), 8); + @LocalServerPort + private String port; + @Before public void setUp() throws Exception { MysqlDataSource dataSource = new MysqlDataSource(); dataSource.setUrl(System.getenv("SPRING_DATASOURCE_URL")); + RestTemplateBuilder builder = new RestTemplateBuilder() + .rootUri("http://localhost:" + port) + .basicAuthorization("user", "password"); + restTemplate = new TestRestTemplate(builder); JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); jdbcTemplate.execute("TRUNCATE time_entries"); } diff --git a/src/test/java/test/pivotal/pal/trackerapi/WelcomeApiTest.java b/src/test/java/test/pivotal/pal/trackerapi/WelcomeApiTest.java new file mode 100644 index 000000000..782c5d64d --- /dev/null +++ b/src/test/java/test/pivotal/pal/trackerapi/WelcomeApiTest.java @@ -0,0 +1,38 @@ +package test.pivotal.pal.trackerapi; + +import io.pivotal.pal.tracker.PalTrackerApplication; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.context.embedded.LocalServerPort; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = PalTrackerApplication.class, webEnvironment = RANDOM_PORT) +public class WelcomeApiTest { + + @LocalServerPort + private String port; + private TestRestTemplate restTemplate; + + @Before + public void setUp() throws Exception { + RestTemplateBuilder builder = new RestTemplateBuilder() + .rootUri("http://localhost:" + port) + .basicAuthorization("user", "password"); + + restTemplate = new TestRestTemplate(builder); + } + + @Test + public void exampleTest() { + String body = this.restTemplate.getForObject("/", String.class); + assertThat(body).isEqualTo("Hello from test"); + } +} \ No newline at end of file