From f1c11281dc67e5c61a5d0c66cfdb6d6aca9c1eba Mon Sep 17 00:00:00 2001 From: Tyson Gern Date: Fri, 21 Jul 2017 09:26:37 -0600 Subject: [PATCH 01/10] 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 5b27a31ac0d45b5698be18b7fc9637383bc3213c Mon Sep 17 00:00:00 2001 From: Michael Meelis Date: Wed, 7 Mar 2018 09:46:30 +0100 Subject: [PATCH 02/10] simple spring boot app --- build.gradle | 12 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 172 ++++++++++++++++++ gradlew.bat | 84 +++++++++ settings.gradle | 1 + .../pal/tracker/PalTrackerApplication.java | 12 ++ .../pal/tracker/WelcomeController.java | 13 ++ 8 files changed, 300 insertions(+) create mode 100644 build.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle create mode 100644 src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java create mode 100644 src/main/java/io/pivotal/pal/tracker/WelcomeController.java diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..edb330bac --- /dev/null +++ b/build.gradle @@ -0,0 +1,12 @@ +plugins { + id "java" + id "org.springframework.boot" version "1.5.4.RELEASE" +} + +repositories { + mavenCentral() +} + +dependencies { + compile("org.springframework.boot:spring-boot-starter-web") +} \ 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..f6b961fd5a86aa5fbfe90f707c3138408be7c718 GIT binary patch literal 54329 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

<5G2M1WZP5 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 F{|7XC?P&l2 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..88e5849e6 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Mar 05 13:13:47 CET 2018 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip diff --git a/gradlew b/gradlew new file mode 100755 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..e95643d6a --- /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/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/io/pivotal/pal/tracker/PalTrackerApplication.java b/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java new file mode 100644 index 000000000..80f2a72a5 --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/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 6ede8b556f5e271b432fa3bd3661aa33f765bbe7 Mon Sep 17 00:00:00 2001 From: Tyson Gern Date: Thu, 20 Jul 2017 13:56:50 -0600 Subject: [PATCH 03/10] 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 518dbf6045b4342d749ec1ea0664001809e7258a Mon Sep 17 00:00:00 2001 From: Tyson Gern Date: Thu, 20 Jul 2017 15:04:20 -0600 Subject: [PATCH 04/10] Add tests for MVC lab --- .../InMemoryTimeEntryRepositoryTest.java | 71 ++++++++++ .../pal/tracker/TimeEntryControllerTest.java | 116 ++++++++++++++++ .../pal/trackerapi/TimeEntryApiTest.java | 126 ++++++++++++++++++ 3 files changed, 313 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..d0ae6cbe6 --- /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(123L, 456L, LocalDate.parse("2017-01-08"), 8)); + + TimeEntry expected = new TimeEntry(1L, 123L, 456L, 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(123L, 456L, LocalDate.parse("2017-01-08"), 8)); + + TimeEntry expected = new TimeEntry(1L, 123L, 456L, 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(123L, 456L, LocalDate.parse("2017-01-08"), 8)); + repo.create(new TimeEntry(789L, 654L, LocalDate.parse("2017-01-07"), 4)); + + List expected = asList( + new TimeEntry(1L, 123L, 456L, LocalDate.parse("2017-01-08"), 8), + new TimeEntry(2L, 789L, 654L, 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(123L, 456L, LocalDate.parse("2017-01-08"), 8)); + + TimeEntry updatedEntry = repo.update( + created.getId(), + new TimeEntry(321L, 654L, LocalDate.parse("2017-01-09"), 5)); + + TimeEntry expected = new TimeEntry(created.getId(), 321L, 654L, 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(123L, 456L, 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..d80f2b999 --- /dev/null +++ b/src/test/java/test/pivotal/pal/tracker/TimeEntryControllerTest.java @@ -0,0 +1,116 @@ +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 timeEntryToCreate = new TimeEntry(123L, 456L, LocalDate.parse("2017-01-08"), 8); + TimeEntry expectedResult = new TimeEntry(1L, 123L, 456L, LocalDate.parse("2017-01-08"), 8); + doReturn(expectedResult) + .when(timeEntryRepository) + .create(any(TimeEntry.class)); + + + ResponseEntity response = controller.create(timeEntryToCreate); + + + verify(timeEntryRepository).create(timeEntryToCreate); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED); + assertThat(response.getBody()).isEqualTo(expectedResult); + } + + @Test + public void testRead() throws Exception { + TimeEntry expected = new TimeEntry(1L, 123L, 456L, LocalDate.parse("2017-01-08"), 8); + doReturn(expected) + .when(timeEntryRepository) + .find(1L); + + ResponseEntity response = controller.read(1L); + + verify(timeEntryRepository).find(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(1L, 123L, 456L, LocalDate.parse("2017-01-08"), 8), + new TimeEntry(2L, 789L, 321L, LocalDate.parse("2017-01-07"), 4) + ); + doReturn(expected).when(timeEntryRepository).list(); + + ResponseEntity> response = controller.list(); + + verify(timeEntryRepository).list(); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isEqualTo(expected); + } + + @Test + public void testUpdate() throws Exception { + TimeEntry expected = new TimeEntry(1L, 987L, 654L, LocalDate.parse("2017-01-07"), 4); + doReturn(expected) + .when(timeEntryRepository) + .update(eq(1L), any(TimeEntry.class)); + + ResponseEntity response = controller.update(1L, expected); + + verify(timeEntryRepository).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..91e271b45 --- /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(123L, 456L, 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(2L, 3L, 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 ebc0140d457219b540912ca74ab5e37c0cc9c711 Mon Sep 17 00:00:00 2001 From: Michael Meelis Date: Thu, 8 Mar 2018 15:47:53 +0100 Subject: [PATCH 05/10] spring MVC --- build.gradle | 11 +- ci/build.yml | 22 ++++ ci/pipeline.yml | 31 ++++++ ci/variables.example.yml | 9 ++ manifest-production.yml | 5 + manifest-review.yml | 5 + .../pivotal/pal/tracker/EnvController.class | Bin 0 -> 1628 bytes .../tracker/InMemoryTimeEntryRepository.class | Bin 0 -> 2378 bytes .../pal/tracker/PalTrackerApplication.class | Bin 0 -> 990 bytes .../io/pivotal/pal/tracker/TimeEntry.class | Bin 0 -> 2868 bytes .../pal/tracker/TimeEntryController.class | Bin 0 -> 3422 bytes .../pal/tracker/TimeEntryRepository.class | Bin 0 -> 560 bytes .../pal/tracker/WelcomeController.class | Bin 0 -> 838 bytes .../pal/tracker/EnvControllerTest.class | Bin 0 -> 1469 bytes .../InMemoryTimeEntryRepositoryTest.class | Bin 0 -> 3211 bytes .../pal/tracker/TimeEntryControllerTest.class | Bin 0 -> 5344 bytes .../pal/tracker/WelcomeControllerTest.class | Bin 0 -> 1029 bytes .../pal/trackerapi/TimeEntryApiTest.class | Bin 0 -> 6651 bytes .../pal/trackerapi/WelcomeApiTest.class | Bin 0 -> 1704 bytes .../io/pivotal/pal/tracker/EnvController.java | 42 +++++++ .../tracker/InMemoryTimeEntryRepository.java | 58 ++++++++++ .../pal/tracker/PalTrackerApplication.java | 6 + .../io/pivotal/pal/tracker/TimeEntry.java | 104 ++++++++++++++++++ .../pal/tracker/TimeEntryController.java | 57 ++++++++++ .../pal/tracker/TimeEntryRepository.java | 11 ++ .../pal/tracker/WelcomeController.java | 9 +- 26 files changed, 368 insertions(+), 2 deletions(-) create mode 100644 ci/build.yml create mode 100644 ci/pipeline.yml create mode 100644 ci/variables.example.yml create mode 100644 manifest-production.yml create mode 100644 manifest-review.yml create mode 100644 out/production/classes/io/pivotal/pal/tracker/EnvController.class create mode 100644 out/production/classes/io/pivotal/pal/tracker/InMemoryTimeEntryRepository.class create mode 100644 out/production/classes/io/pivotal/pal/tracker/PalTrackerApplication.class create mode 100644 out/production/classes/io/pivotal/pal/tracker/TimeEntry.class create mode 100644 out/production/classes/io/pivotal/pal/tracker/TimeEntryController.class create mode 100644 out/production/classes/io/pivotal/pal/tracker/TimeEntryRepository.class create mode 100644 out/production/classes/io/pivotal/pal/tracker/WelcomeController.class create 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 create mode 100644 out/test/classes/test/pivotal/pal/tracker/WelcomeControllerTest.class create mode 100644 out/test/classes/test/pivotal/pal/trackerapi/TimeEntryApiTest.class create mode 100644 out/test/classes/test/pivotal/pal/trackerapi/WelcomeApiTest.class create mode 100644 src/main/java/io/pivotal/pal/tracker/EnvController.java create mode 100644 src/main/java/io/pivotal/pal/tracker/InMemoryTimeEntryRepository.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 diff --git a/build.gradle b/build.gradle index edb330bac..64c81fc43 100644 --- a/build.gradle +++ b/build.gradle @@ -9,4 +9,13 @@ repositories { dependencies { compile("org.springframework.boot:spring-boot-starter-web") -} \ No newline at end of file + 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..649a717a6 --- /dev/null +++ b/ci/variables.example.yml @@ -0,0 +1,9 @@ +cf-api-url: CF_API_URL +cf-username: CF_USERNAME +cf-password: CF_PASSWORD +cf-org: CF_ORG +github-repository: git@github.com:GITHUB_USERNAME/pal-tracker.git +github-private-key: | + -----BEGIN RSA PRIVATE KEY----- + REPLACE WITH YOUR PRIVATE KEY HERE + -----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/manifest-production.yml b/manifest-production.yml new file mode 100644 index 000000000..340b53421 --- /dev/null +++ b/manifest-production.yml @@ -0,0 +1,5 @@ +--- +applications: +- name: pal-tracker + path: build/libs/pal-tracker.jar + host: ps-pal-tracker \ No newline at end of file diff --git a/manifest-review.yml b/manifest-review.yml new file mode 100644 index 000000000..cd3361d30 --- /dev/null +++ b/manifest-review.yml @@ -0,0 +1,5 @@ +--- +applications: +- name: pal-tracker + path: build/libs/pal-tracker.jar + host: ps-pal-tracker-review \ No newline at end of file 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..4a15cd58586fc7b17520da5e17d53743e94f8646 GIT binary patch literal 1628 zcmbVMZ%@wyEs6NYwBw`K)Q+n{DI$Z311N85Xwdv9@u zEZN7{$Ji2yQImaueJHcvIW22Dd_nf&oag^@p15fmH1~zCG~Hv`;Zqe@SS%{fX{#eI6iuhi6Z)d|T>4JlJ=*fz z(D#~6=`+h~rH&h_mfTl?qL+ul7cCh|Kj*r%4VCB8+iKDC8&1&Hl{wT3FFgObQXfv9q z)RM_F{ORE`zHFbC^7gRm#(7$}$%jVEs^N;T<1_U;{|)5aUS@|IbB=zzkX5Iu-1=ZE zevl!vZ+9o!c1#Jz%3jB>$p^{|(a=EL)O%oI?SuXPkpGel!hv~8;$v(kF^Q=pF6t|# zua7XrmY5YY{rFJ?PZk{SDq<$kDA)L#;hLv4DK_q+Q1kvou3GD2weS(ocjm?bK;jaeF@QqI}idnul z$0}bYHcyWJ6pbg+bELGsj>IyUjwNC+9Zl3=Iv$($z=}<^d7di)KJBBQ_0g++bb;tK zd`>4if!_(W;Mx%JNoEjq$Jtf9T*r!C2#7K|$gC3$x?KH^+ zB>2K5r1{cyI9S96GA7CC?rXgy`Nvb|IwM-7ZIZCd`GQ!6oCubXC7d9=%yZ>uSOIR3 Ozha~}jWkAy1N;NH*NRvG literal 0 HcmV?d00001 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..832e5154dcf47d4cf5dfe10ae41898a15f003d8b GIT binary patch literal 2378 zcmbVO?@!xS6g_rA+!#an)hun-E$d1f64q>t(Xues!b(dP%2pUHTPMu~m%24^4{`nffG=9`EiWvbIsK_@}_bk(=XG>=hcosp)r*>nTKFchl8c&>M zwQ6(i(9!l_tsT4Nv$BPWm|Zo-Seu!i;N0m{K|i&d+kyCvo@7r4IFp=#HJ^>lJt3Pg zwQV_;uzR$JQB*@;bK73sTCznK=ikziH^inhZ~+$;4EJn61Lu)7P{r2zs$Bt7OKL;zMb%qheTDuU<58s8U@;(>9noYr=kW` z*-cTC@@#3gnZT`heZs6Nv96Sn_;MLy8ZG;0mXGv2EE7yd!L?)1j$NAylH)Ozd!wfp zUB{s>w%cI4UyxwIksEs>v}>N#bj&*G(+<^c2&vrU9#6JC3m@<^FbV}@{HdZxczuVT z3l%w><2~Dn<0VEGkl-CKR6KxEdUF2_O60CXhzLCNrgahXp>xCZax zeLe}iyu`CPTV-tg2wo|@kG|q>NS5}I`U4s-=N?5QS@dzyAW1U(8)D+axJnU%EE@t! zGD1_{719aX5BW~Wc+9Jy)dSj+6p%n9V2s%3ktSM%sdq7TY0_0VH2}S!zCx80o7N2Et`d{de zP*a=*dJ!oyx@ZQ9DDl05Nqj^Uk@u`DcIFj~%`o+W0}LJoydD6CHe@7VXu}P{mGLo0 z0r!G%qBn9ua+CYW{EdN0R(6PHOukeEZdig5nhz#oFqJVPWHA+R~%LJAKxQ7XjB8QkD@#vVDILjMX@ zMIiA3d=%n((vU$+7etEfdwd_~p6mJf>)Q_ikMSTt6>BxD2e^gXHQWhM@%CMY>Qkka z+h!=Y+CzrQo_Q}BmU>FdS7|(wb|6L(A$DzQ+SVYT(HcO*^(kAxoczOzb?yBP~%$CP)a;F?gU zV);xvd%7oY7c|R+@ zOBZ?R8mOVp@bG{1uHjw-_p!mSbwSt*{ZhuHVu4T$&H030kB(&M7&=AjWIaq-=NQww zvMG%^Y~?0B&C@NIZo5#Pnecn(>i8F!t3j}zOBWj`(L49QGMHD2)7?Tq$?&KcMj zI+PI5TFt;ZF44I$b)fw+S&O)WB|?1g6Y}=K_2p9ME6NA+_<(Le(KU*i=cuEJtAx`h rgB9}dIksjwzN7N?3=5wL%X4V{6+h*0jeI=A>zVIrw%))Dy$SpVm|q4Q literal 0 HcmV?d00001 diff --git a/out/production/classes/io/pivotal/pal/tracker/TimeEntry.class b/out/production/classes/io/pivotal/pal/tracker/TimeEntry.class new file mode 100644 index 0000000000000000000000000000000000000000..0605cce1f03fbda8f1ca8796296f244ff0333fa1 GIT binary patch literal 2868 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 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/InMemoryTimeEntryRepositoryTest.class b/out/test/classes/test/pivotal/pal/tracker/InMemoryTimeEntryRepositoryTest.class new file mode 100644 index 0000000000000000000000000000000000000000..8bebdbf5b5d0aa67cddc463aea369972f359cded GIT binary patch literal 3211 zcmbVO+g95~6y34~GKxqD5OPl-R|6p;gw#ODg(T2IZIS}Tq3KQ70z{1L$TESZO`rPO z&**z!469k~L!Y|(G3_68b&n*=PKslw*RnM_b7s!j7mfVqKfnA2U>L7eT*gBMk5oj& zb+UA?RuNe?Lvjp;dgS!8-utjSS%H? z9*auAg9ObFRS-32&73KCR_F62Cu2@oLe@(S1iCId*Kh?_B@7*07zy2V7KIwHp`pPW ziGjfz1A`+9mNjIM6%|bl16UEGRRwDrEUYVdt|5mF4L0%$3L0L((J%sXz7qgP{;!QGv8Qm(uvhiR%j-0hgpO(`G*cxi ztfYGuc;uRgd{Hv(jCs4)pLlyAg+Cpinb~t{T0G8E@y7P) zZJZ+E49mQy&Ux_!7{#LLxa)c*@0hw#u=EKpAf6%e?$0i)8KmZ3Eli&;0^EC#<)S!C zTtCa-k+6IBoy_OjDo(yA8My_XE~5vp9M)`FHQi}G)jTO@Bpj~Fo6Os*5|m9NS27>3 zP~-SaAeX13m}bkvwWYn-zL@u&<((Z9j$>?-%Vwh}ju8o$Pm8tdJ)RmHbYDz%{AXkFh$wW&M#0@i z4=(YQEk4bV#EHk>IPY{`C0?8Qb|C%CK?J=)63~i1jx|34{kXb#rM+brYFlgvhsw}WBCftO(K>r=7l#MRIFjzaRz}+{j+W6r zCZCim;c7qUps-FGVmL}D9Y~;)M_U(CIF2+#0?@#aP1IB zQLa0Ln;gmfGXwrgxP=cm667R!<#8FJ{D~-K#%3C!Oz(*@)vx-vPz3&?$354wE z#Ig3{6jJeZOgH^XA)|ZZoL`cC^-px7c3tcIE>5NT%Q(G@GlYNEgCF&5b_FeL=L+Fp zCFnTcXnp*9*N+ui&F0u4+}9LQ*s$AgJ*47gK!rn4Ll`3!x0xl(>PN;IsVDB8kdnUw z3vV@|BvtXK5AvK~rkl+84)2*2Vd3IV)K0aI$wKv$@;fdMV^u5ku@g|r7`yJF886Zd zw`jw0j_(uS67gkt_mVGm!G~}6t&opGX!kVk2G$tCJ+1<9I@3?-bd)B6zpFz6Vdrw#%~Zy|V5p z8J;d!5BU!jI|?wm!gyZ<@5cvZ+ZEaNK@}eol^>4aBlu`x!+-pT5a+V^n5_P|tp0=` zAD5?3itJNj?$athqvEp)wmasm(VKSc`9ULNWlhJj=M}WZOe>l(&s&b3ie~uVvGwFR z!;U6au0F0HlC-G7N~H|DoW0$})!bWs!c3daeF`d?4vZ+M?6xKh1-Hb^v=PtEjvIDD zA5T$I8?%ynYDBk9dH02t&a|0T&=GTttg}AglFWE|5e=e9^&t98!vb*7&?l(0U`4^sr4F6X6r9>$uC)^z^SY0SwiV2e#VzNQl}if_k;Tj` zd2Wr(=;!rl&M{L_k#NW1Wuh_-jg=34smZeR3l%ri!{F6l8+hGauz z$jaGCYd|ksg@J$up((rA3N5ywFd`}>KA3u=chZ=q) z;vZ}H37%2$tcIWBITcqmEMQSZUc)s!ui-j=CNnRncu~dAHQc~2H2f03((r5iM#FEh zsNr{53}QK|;w25g$IBZ2fIrIcCl=AtxQfm{tN4pp`KyM%;S~*k$3JBFr-oNW^{e=o zhSx-OyM*&*dI(e3HJ8%UQ_+F(8CEa*X-QP-LZ_mow5y zqhNmNMk3hV)Ys?jSvGZAtkrXNo$2s|HE1|FJI#I_wCwZqdR)OrStmC>&TbbSk_W@4 z@%o?-m(SCg7p4uW-oL8q63?PH>w@a~btgG(*gQ%q^fb@E>ZIS!Pi^dTdTXN^BKrQa z6qVoRtIr#@IZ3FEW$sb1dCG8xc$DR`-Wl82w9dKmAP*OBF~Q#k*T4xPB5R5^$HOqXHGnqMKrhq7@F|)mMIX#uI6m$mJElJ*$9`czL`KS!RNo#^(tE2N*$;$h0 zUbzRMXyU-S)Pjo(?b7W#F)KYqPoCFPIb&dwt)r17ofyCIUf#EoV>~&rDzR+dc`aILGTF-iyiG59HSGv_F61-tl zrrf(nZ6ON5xt8_nVgOQ(B z=U-Vyf=b=MZQQ%c3T?cpuN+O~N4 zyb8HbyxZ`*1`6*eCpmWWEy{0&-&M_vP@dsK2t6FDU11%1`K);ha0(CbO~HfQT;Xnx zqtczLZ>|mHQE>yItEgEi6=BGZOyWZD*n)9Nu4-fH4 zFvMJo{Y8uaMT9GW$FC!NrVcfA*i^eYk1boni~M;c!pJh|*8bK!wh5kVs15NF6{-#A zvHb?Z*H9P2C}-4`1?-5+l00^DL5nxjwt&~=@%m?R>jHL-@@;n>^{orIZM4LxnRtLb z*g=Q4U@z`M1Ks@woJJ!C(TpUeb7;jSw7GnnJ@h``9WW8c0C~1x2yfz3h`UiN{)&5s&0?`!(F*la4H4Pk)JYX_6E@-ggRbz6o!-$m3n% z=Er`f5pW1Q(Sh5^x{-fPXvf{?#4+@9Jd1lU#T1nM+2djKIqxL5VV83!lTVn2Fv3-m zWu9b|lC`)$T;zU*T0`8|p=`)9M(`e^y~0SM=dswu90RZXVHUvbf(hF>xMe)t3zY$0j9RP zh^<3;G_MHfHMA&R{w|<35J^wIGKx3Qmd8Q*A{uakm%yj7okiEivO7X5oebdsKcl2O z#Q^1)oKGSV26MNNrnlGry!3vUWqk&>k>7qdp&wytj8XC^ zy`uB4N#g;JIK`44^V7SB1>vRlTFLRYBI_&sJCr1I&nn5h20`)9B94;gD(UU9@X~vd z={?2tp2j}RFuv!Q$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/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 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/io/pivotal/pal/tracker/EnvController.java b/src/main/java/io/pivotal/pal/tracker/EnvController.java new file mode 100644 index 000000000..cc7df5a5a --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/EnvController.java @@ -0,0 +1,42 @@ +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 String port; + private String memoryLimit; + private String cfInstanceIndex; + private String cfInstanceAddr; + + 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 cfInstanceAddr + ) { + this.port = port; + this.memoryLimit = memoryLimit; + this.cfInstanceIndex = cfInstanceIndex; + this.cfInstanceAddr = cfInstanceAddr; + } + + @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", cfInstanceAddr); + + return env; + } +} 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..a77b8c2ea --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/InMemoryTimeEntryRepository.java @@ -0,0 +1,58 @@ +package io.pivotal.pal.tracker; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class InMemoryTimeEntryRepository implements TimeEntryRepository { + private HashMap timeEntries; + + public InMemoryTimeEntryRepository() { + this.timeEntries = new HashMap<>(); + } + + public InMemoryTimeEntryRepository(TimeEntry timeEntry) { + this.timeEntries = new HashMap<>(); + timeEntry.setId(0); + this.timeEntries.put(timeEntry.getId(), timeEntry); + } + + public InMemoryTimeEntryRepository(HashMap timeEntries){ + this.timeEntries = timeEntries; + } + + @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) { + if (timeEntries.replace(id, timeEntry) != null) { + timeEntry.setId(id); + + return timeEntry; + } + + return null; + } + + @Override + public void delete(Long id) { + timeEntries.remove(id); + + } +} diff --git a/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java b/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java index 80f2a72a5..bcd5b96e9 100644 --- a/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java +++ b/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java @@ -2,6 +2,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; @SpringBootApplication public class PalTrackerApplication { @@ -9,4 +10,9 @@ public class PalTrackerApplication { public static void main(String[] args) { SpringApplication.run(PalTrackerApplication.class, args); } + + @Bean + public TimeEntryRepository timeEntryRepository() { + return new InMemoryTimeEntryRepository(); + } } \ 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..eb12b7194 --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/TimeEntry.java @@ -0,0 +1,104 @@ +package io.pivotal.pal.tracker; + +import java.time.LocalDate; + +public class TimeEntry { + private long id; + private long projectId; + private long userId; + private String 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.toString(); + 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.toString(); + this.hours = hours; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public long getProjectId() { + return projectId; + } + + public void setProjectId(long projectId) { + this.projectId = projectId; + } + + public long getUserId() { + return userId; + } + + public void setUserId(long userId) { + this.userId = userId; + } + + public String getDate() { + return date; + } + + public void setDate(String date) { + this.date = date; + } + + public int getHours() { + return hours; + } + + public void setHours(int hours) { + this.hours = 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..5114e2b36 --- /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 +public class TimeEntryController { + private TimeEntryRepository timeEntryRepository; + + public TimeEntryController(TimeEntryRepository timeEntryRepository) { + this.timeEntryRepository = timeEntryRepository; + } + + @PostMapping("/time-entries") + public ResponseEntity create(@RequestBody TimeEntry timeEntry) { + TimeEntry created = timeEntryRepository.create(timeEntry); + + return new ResponseEntity<>(created, HttpStatus.CREATED); + } + + @GetMapping("/time-entries/{timeEntryId}") + public ResponseEntity read(@PathVariable("timeEntryId") long timeEntryId) { + TimeEntry timeEntry = timeEntryRepository.find(timeEntryId); + if (timeEntry != null) { + return new ResponseEntity<>(timeEntry, HttpStatus.OK); + } + + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + + @GetMapping("/time-entries") + public ResponseEntity> list() { + return new ResponseEntity<>(timeEntryRepository.list(), HttpStatus.OK); + } + + @PutMapping("/time-entries/{timeEntryId}") + public ResponseEntity update(@PathVariable("timeEntryId") long timeEntryId, @RequestBody TimeEntry timeEntry) { + TimeEntry updated = timeEntryRepository.update(timeEntryId, timeEntry); + + if (updated != null) { + return new ResponseEntity<>(updated, HttpStatus.OK); + } + + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + + } + + @DeleteMapping("/time-entries/{timeEntryId}") + public ResponseEntity delete(@PathVariable("timeEntryId") long timeEntryId) { + timeEntryRepository.delete(timeEntryId); + + 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..9eca16cd1 --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/TimeEntryRepository.java @@ -0,0 +1,11 @@ +package io.pivotal.pal.tracker; + +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); +} diff --git a/src/main/java/io/pivotal/pal/tracker/WelcomeController.java b/src/main/java/io/pivotal/pal/tracker/WelcomeController.java index 2d477ae71..40fbfa9ed 100644 --- a/src/main/java/io/pivotal/pal/tracker/WelcomeController.java +++ b/src/main/java/io/pivotal/pal/tracker/WelcomeController.java @@ -1,13 +1,20 @@ 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 { + private String message; + + public WelcomeController(@Value("${WELCOME_MESSAGE:NOT_SET}") String message) { + this.message = message; + } + @GetMapping("/") public String sayHello() { - return "hello"; + return message; } } \ No newline at end of file From 0d3c66547ca9bc48d53511711ad2eac68801f423 Mon Sep 17 00:00:00 2001 From: Tyson Gern Date: Wed, 26 Jul 2017 11:45:04 -0600 Subject: [PATCH 06/10] Add tests for JDBC lab --- .../tracker/JdbcTimeEntryRepositoryTest.java | 159 ++++++++++++++++++ 1 file changed, 159 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); + } +} From c2a85e4f62a2394c0d0e4a65439e946b4b3c1d44 Mon Sep 17 00:00:00 2001 From: Tyson Gern Date: Wed, 26 Jul 2017 12:28:32 -0600 Subject: [PATCH 07/10] 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 b80c3537d1fc44856b1ab369bf8c6136528ff71b Mon Sep 17 00:00:00 2001 From: Michael Meelis Date: Wed, 14 Mar 2018 10:05:52 +0100 Subject: [PATCH 08/10] actuator --- build.gradle | 25 +++++- ci/build.yml | 24 +++++- databases/tracker/create_database.sql | 9 ++ .../migrations/V1___initial_schema.sql | 11 +++ manifest-production.yml | 4 +- .../pal/tracker/JdbcTimeEntryRepository.class | Bin 0 -> 5002 bytes .../pal/tracker/PalTrackerApplication.class | Bin 990 -> 1302 bytes .../pal/tracker/TimeEntryController.class | Bin 3422 -> 4388 bytes .../pivotal/pal/tracker/TimeEntryMapper.class | Bin 0 -> 1417 bytes .../tracker/JdbcTimeEntryRepositoryTest.class | Bin 0 -> 6423 bytes .../pal/tracker/TimeEntryControllerTest.class | Bin 5344 -> 5788 bytes .../pal/trackerapi/HealthApiTest.class | Bin 0 -> 2761 bytes .../pal/trackerapi/TimeEntryApiTest.class | Bin 6651 -> 7269 bytes .../pal/tracker/JdbcTimeEntryRepository.java | 78 ++++++++++++++++++ .../pal/tracker/PalTrackerApplication.java | 5 +- .../pal/tracker/TimeEntryController.java | 20 ++++- .../pal/tracker/TimeEntryHealthIndicator.java | 24 ++++++ .../pivotal/pal/tracker/TimeEntryMapper.java | 21 +++++ .../tracker/JdbcTimeEntryRepositoryTest.java | 14 ++-- .../pal/tracker/TimeEntryControllerTest.java | 8 +- .../pal/trackerapi/TimeEntryApiTest.java | 12 +++ 21 files changed, 241 insertions(+), 14 deletions(-) create mode 100644 databases/tracker/create_database.sql create mode 100644 databases/tracker/migrations/V1___initial_schema.sql create mode 100644 out/production/classes/io/pivotal/pal/tracker/JdbcTimeEntryRepository.class create mode 100644 out/production/classes/io/pivotal/pal/tracker/TimeEntryMapper.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/JdbcTimeEntryRepository.java create mode 100644 src/main/java/io/pivotal/pal/tracker/TimeEntryHealthIndicator.java create mode 100644 src/main/java/io/pivotal/pal/tracker/TimeEntryMapper.java diff --git a/build.gradle b/build.gradle index 64c81fc43..04d33f58b 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") + compile("org.springframework.boot:spring-boot-starter-jdbc") + compile("mysql:mysql-connector-java:6.0.6") + compile("org.springframework.boot:spring-boot-starter-actuator") 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, + "MANAGEMENT_SECURITY_ENABLED": false, ]) +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, + "MANAGEMENT_SECURITY_ENABLED": false, +]) + +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 e735e47d6..b01d4da2d 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 @@ -17,6 +18,25 @@ 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 - ./gradlew build - cp build/libs/pal-tracker.jar ../build-output \ No newline at end of file + + mysql -uroot < databases/tracker/create_databases.sql + ./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/databases/tracker/create_database.sql b/databases/tracker/create_database.sql new file mode 100644 index 000000000..07418b83f --- /dev/null +++ b/databases/tracker/create_database.sql @@ -0,0 +1,9 @@ +DROP DATABASE IF EXISTS tracker_dev; +DROP DATABASE IF EXISTS tracker_test; + +CREATE USER 'tracker'@'localhost' + IDENTIFIED BY ''; +GRANT ALL PRIVILEGES ON *.* TO 'tracker' @'localhost'; + +CREATE DATABASE tracker_dev; +CREATE DATABASE tracker_test; \ 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..eaaa0c152 --- /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 VARCHAR(255), + hours INT, + + PRIMARY KEY (id) +) + ENGINE = innodb + DEFAULT CHARSET = utf8; \ No newline at end of file diff --git a/manifest-production.yml b/manifest-production.yml index 340b53421..5636129d5 100644 --- a/manifest-production.yml +++ b/manifest-production.yml @@ -2,4 +2,6 @@ applications: - name: pal-tracker path: build/libs/pal-tracker.jar - host: ps-pal-tracker \ No newline at end of file + host: ps-pal-tracker-production + services: + - tracker-database \ No newline at end of file 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..fe7edfbda30e9a530111fec7eea0fcffc1f8db60 GIT binary patch literal 5002 zcmbtY33n6M75<(rdu(|CV+V*0p$ainmR$rPO-TeKU<(k)*jUEIkQR@nu^23C#LS4L zTe_!B_r2+wuIb*S4F*G#rhA*_H}&*;Gb7CqS=c!}2aoQ&ch~RUci$b}`0wr406c;> z6`YG=BhL5mRqMn%F{dCOMH>q8MOQE%MTguLFy!*3E-auF#Ujd4n5aa_b-4qU0$Xl7 z3aTBrqTp&2n{Zw(UF*R0D0=aJ`S^f*d{90<6vc<}jAZyo2R_hNll+s@Hqva7wB;G#Y#zYbb;ZlX%&-p#WKppc}rW=ubS3E@={?gpUj(< zo;*Qn%I8iAbn4d}OUpZ^CGhYnqLaE^Ejc;eN!Mhgf}Bw-Yfjao?di9z;6T1d^7g@4kS1>H(c8H;+l>{!=RhiIhzfKfJ_LjryA>?Q4rb}eaNE+t1bN6VR2E3c;p zrv)M-W`Ry@$Qot+RCRGqx2CkY5?6Y%W?m~zYnCD3{l$oL(Xa*fG;_ekspiC_UNLQh zAYG?3(L&v>z~+_xq04zocN20n-poW0(EXV@fP|_CX3fh2|`GLIx zXV#E=4*^-vBWAg*=N-c=6J1&6Zk111dPTGJLe61ui+WjN6wTLJ1vUk|Aq}h-yCxy9 zE#O&EU;0{JuSmcAd>{eHO8$$Ar5V+B||WcLNu195?M3@N`brd8Y{ z$sVcTi;T@X(nk$>bhkA*hPx#^ETB3{b#zoD@g)}B5z};Rc9Y7O?p!nr_J($Rxf@^M z&sV!Ksp4z+x{4tT3nbRwFs1T6Dh81d*w<97c8-oU9KUXSgAROC#kcUhiZs5h;5#b5 zi|;A;zKS2<1qDA;@gw|L#UVT)kj5-|JQ5cR@Eb|hpSiDl?^?CkOMWSZi!gE6Y>cHwf*@c5imrs5~~sX%PCCM@v8|LKXq zuGNgN5GuMQUHX~8Q)ebd4^O4MTnVH8xnh!<4+Tt0q* zU#j>Oey!j)67}E8l>AO$=O|I9?bYBsn>m$BPfo=$r>4g5b~HX4WQ*CE_46f3KVh?O z#%2d&(}%NX(z#fiq4@s>6}+V4W&B>jA5^@88!DD?Q^hSTE4Z!V4*sa(RlFv!y9uOC z4h>7ai9r1(3NAYKmEdrl&ej`*z|cyCU(ihVK0ch!>$V-@ZjBF4NAV}TE)Z|hM&4SL zDLFpiA+^EHoXrBcptbi(x?-l+}vtQ3#H z_IvZ;LAK_N{OpdS1wC>Q;}?35oa4CmF48U7%`bBy=QKX|a^)N%Tm||QH_?)~jaJ|W zB8fX_n;E)^_8U-McI6)CvxmIkhjoP1I&4NCwqPGuHhUWQUjy&v8+GAPyodTZ8sjnF zPMMmi8}U2nn7N70vBWD-hHfJY3@@SUY+`sBs^C?=?iSWBVZ%%0ZGp79-j&-*zaOBz zA=hHe(^a<^$Ky!RQU_u5jaLjWex7Ile`f(DOQa17qzJ z0l^8v>Z=>+!;?77eSsqwp+X5?ieN~IjjrnZ?qCyx+q{G=Z&mrYt5WrteU)BDM!A;8 zQ9h-@XQ8;U9ds4m-{Ja7g!%|eZYDyi-=*3}ZOzTJ^Jm*FJdm4FhH^{Tel{_57kFOE zjxb%Lbl?cnax4Vk!Fp^5{n$Y8B$N`sV>s?RbJlKmdpPIBuq9%{v~LUP+O z21o`d_tQmSJL~BoR_FlhU?*#37b`Iy3V6R892r@eCy`}%QJ#n~oT5MDT$Rb3poi>S zzK5sjpGKuKlvM_slVgj_y>>H(|nf)>%HW8A1+|lrHZ(S;{O0KHH3%& 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 715ca4f96dd225ae8e5bf732cde5be665d4573c0..7510c44e11ef7fc86278ebb21bae7c33daea70e9 100644 GIT binary patch delta 644 zcmah_$xa(V5Pdzs%(xjAo5kR;*sNZFCJ7`5P6!YJ84``L5hpYZBg>W-7!L@a_y#mm z&iQ~Gk_$#4nDxVjNSL z)-c1-iKvcQjycTBBCa995kOKy>SpTiGC#FED^tC2ZHHkxZCA^pT(6y%gk2QH(-T|l z$!hZnV%uq36&c4_@Vq3mo`JhyH-XVqH4tGaaq23GtHdc`o%brV;F7quY~-F_S@GIImDpQ z7t7NDMoxMt_EIG%Gs<1b2G|d}DCnc8UN^e|^iyH95C|efRR9ARq>f~NAh=}fAtiZ- zj>EP9r-@{0w5hG3V+h04X9#125K<#YjwCknfX<@^y3(nTdIwv(h5mrRQK|v{oyIL+ oy#xjs(aB1Xj7aI>7X8tloQxtuE8fsdr99STk2mAQQ+E>h4>!AW{r~^~ delta 340 zcmYjLyG{a86r6h(xOXp45d}n8prj!ZqhDY_VPi}TAFu@lk01>d-#|9uD=4&Df{7ns zPs@*x80W4KIK_F)nVHPe>{RpTck>P49!-M;$_h6I72GOR4Py4LEgtuQFG5E_2cEoh zy%g$b2y_EL_6MWM?6p7epN7-P?8P6C-uqKR!B(#3Xh71}mIDELu)Dn4oPsmn z3L!qZNEr4+1ZpI?r~V0?X6y1}gP1+2lq);eTKoCho(?KmzXhESbTGFJnp^B(DNv7#QnhBFh5fEP} z_?BOwe?hxsVRS7&^1-VyJYgLoJ31@S(v`0+spRHQl~Bi)G%G_hehfUNku zA|AR}yBfeXv62g5RgC!n46#`7N47)9MO%$pJhW4fUf^fkj$ zb4r0f%T&|XG*d}Fy?azbTUIS)H3`RKhM8501yj#wmrZq5yJ48ul$2puikh}cs--Eb znq}(gqB1Qc7BzECPixfRPaCDYr4i5x_d3QMnVOdNF+HzarzQLx-i!nP+oF5H26qRR zB>2XS3>n)R)AL%qw3^b)q?*d{ODJZf)!dS5>f*if(PypbMK@s?HyBDIm(xs=x_^P3 zGGI%3QKznXRfL2jpULMLSW7qZ)H%}J2~Euxm1Rba`Jkv(0p&tT&FRa!t-oze&6Nnb zs}j6QASB3kK2%@Z)27DQOPFgNmeGNxcvdW{pe$&`0x^qp)vcR!u%nv1bhc&0?KHEI zWGK3*XY;C6G70PY^Mp0l8pmlO>1&KlLr9J`rQn8^Qc`+8Qy-0aqiD^kg#ynTb#74v zA5o1dplb}Rdp!%<^^#V!&Kem(tB)j+qQUU&W`Yr{24jZ8(k$^TTC5txXJ@e=gfzbi zoi3RicFoqbR!3HU16j)LI`nrW9CNL7UbR*>POIQ9rxz^=hr^qpPIbR~EOu30QN@iM{(na0gmE35JwHiDmV)d*N+spzhoK+Jam2m);j2E#-#$N1`@e=m? zQIerxO~SUS#UZD_X8gDz<3q54Z`(*`7J?zWJjfb@kQU&^?L`!c@5*R20FAfj@}_(tse*4g}>gznn9I2{^yOym0o2P!Ef zBZeOu7Mv(i;z!(-5NUNSsgXys_DRX9`K*#iUDeV&mit=COYnw8dv0l{{J>5-Y)z zIM2w%6Ui%6iHmVM84kOKy7ZLE))rlX>Um1BV5T#kxH6uICnw`c7NLIrVFA4Gh|1EQ4>$@|hXugBzhi3|-5$iFkFneKVVmuPKpI^~NFWvSb@V*Kb^+RJgLYUy zM6+Q!FhE`-d`9bl4_AQ?(}`2W&_zk%f)}T$CjjXm4EXUz#f>+sZk(yQan^Q27H*7- zk40*d*pDWuLj+=uo``7fwUO_rBCm2^@qk+#t~)t`5Q1b-a4b9G6{rrMQ%!)3aNjh} zRVe(4qe!}u4GN=W^m=j9*XMhTejCfqDwaN9OBzSe%>eCSi1yLJC@HBW??{!#kqV8p zTcNR=Ycur5S0gl9BXqXv$9bZ6^1>lB^~h5B4% z_&Ro1a-}}OC$R-nJfG79JYzf9YX{?0)xlF02c0m~@?q`?_>K9%+$B^G61Kadg3JT| E0l(eBPyhe` delta 1218 zcmaiz-A)rh6vzKFyY15L02K0$!={)<-bLc+PGu)tGp(b7sz*Ip;V3Gy5X;IgqZVHq_k5C#L7Z1;K%;>fg;rb;h}&O1@euf*R(uM7`GS_1MHeug0o9v8w zR1ASvBIdf$BWJ~fjWHD8OCNcRQdaiF3o^^O%(wYl4) zZ^3r7Q*J*LI%p;-g3op$Mz)T2y6Eekg;+)pc973tlsHWUdXXTIA`?BPiB2}bL>+Ho zlu#ovWo@FKxvW+An2YDDFxTN{M9mSQ!8sVAVy$%5HiGD=LQPaq6G$RORXL8KHS|%A z8WhD3v>UL>oFZgwtHDnZTOOG%hMLkNOwkoouqB~Wmgnf$Jn+*hF6 zfm3cg!xhOHb%Of;QM!i$2e6L{aG_^pJ!GxlGFc_mY8cYP`X&NgJ*@v%ss#OsDe7k* zl}jVU^^=ZXX zGk$;{<+x5-D}ylb(Co`Od#{zXclzu1_n!bpuo6QX1~lA@A%p=PgAEvhse=1DhE?!D z1rK$MDEmmqW0icOf~Pu0m3u z?xro*tjz*5t!Xc771k~3sJNO8`&&*)Adz-Fv*_%3zEv=b{Q1(#?%2{?b9U`n*O#A` ztYXoYV}#qaimTqfz-S`9ZS7fR=|jO>wM*rKpRxV1RPx+bY!Z?LZ9lD^0MgqhaRHGM zi28pC2=rW}b8Plw)-L*v=aw`~3M9{B8E`LHZr)tk+_tkmSw%BW-nIO)q*@yla>p;E zJ()L4Md`Tt_tM(6_dU5|Zs#_$X4aFoNl_{yji1kRf@rafSC(0O&QZf@KOyvxqGzB3 zmjt@cn+^4JavEM3n8Hf~)0p8JmwY!R+xAcaL@7p13Odc(;Xc!jiq zB~_%AOSTMh%a}3Hi5mjF=b=|#S|EN>QGvb-DkjiTTcymKbZvxO+FHK0V%(6t?WbIy zRn_4JQ_A>iGGj?@O{ByNIf{^2RG&nY_baD-ozqH=&AQr!TCm3qH9P}_ZFSyQP@*cJ zlmgNSl=j^G88sB%V9o1__KIMX;ah5+%HbA2>o#x`_)k$s%hB_BnUP5=Xe0fH5Z~y8 zaD~tMAgSXjeWP*#*KnPYP?O^=Rs4m`!nE^05PElry5+%v*6!}N;nuYUwE8R1Razv0 zF5C>L&ZC literal 0 HcmV?d00001 diff --git a/out/test/classes/test/pivotal/pal/tracker/JdbcTimeEntryRepositoryTest.class b/out/test/classes/test/pivotal/pal/tracker/JdbcTimeEntryRepositoryTest.class new file mode 100644 index 0000000000000000000000000000000000000000..e85ec9b0b21d7032e923c832e640f60d8deb5520 GIT binary patch literal 6423 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 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 68a7b15d3f033e979122f150b138e95193c11b22..aa7172fe02314cf7dfa7d6381867bec51ef21fdb 100644 GIT binary patch literal 5788 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 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/JdbcTimeEntryRepository.java b/src/main/java/io/pivotal/pal/tracker/JdbcTimeEntryRepository.java new file mode 100644 index 000000000..665ccba64 --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/JdbcTimeEntryRepository.java @@ -0,0 +1,78 @@ +package io.pivotal.pal.tracker; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.ResultSetExtractor; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; + +import javax.sql.DataSource; +import java.sql.PreparedStatement; +import java.util.List; + +import static java.sql.Statement.RETURN_GENERATED_KEYS; + +public class JdbcTimeEntryRepository implements TimeEntryRepository { + + private final JdbcTemplate template; + + private final ResultSetExtractor extractor = + (rs) -> rs.next() ? new TimeEntryMapper().mapRow(rs, 1) : null; + + public JdbcTimeEntryRepository(DataSource dataSource) { + this.template = new JdbcTemplate(dataSource); + } + + @Override + public TimeEntry create(TimeEntry timeEntry) { + KeyHolder generatedKeyHolder = new GeneratedKeyHolder(); + + template.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.setString(3, timeEntry.getDate()); + statement.setInt(4, timeEntry.getHours()); + + return statement; + }, generatedKeyHolder); + + return find(generatedKeyHolder.getKey().longValue()); + } + + @Override + public TimeEntry find(Long id) { + return template.query( + "SELECT id, project_id, user_id, `date`, hours FROM time_entries WHERE id = ?", + new Object[]{id}, + extractor + + ); + } + + @Override + public List list() { + return template.query( + "SELECT id, project_id, user_id, `date`, hours FROM time_entries", + new TimeEntryMapper() + ); + } + + @Override + public TimeEntry update(Long id, TimeEntry timeEntry) { + template.update( + "UPDATE time_entries SET project_id = ?, user_id = ?, `date` = ? , hours = ? WHERE id = ?", + timeEntry.getProjectId(), timeEntry.getUserId(), timeEntry.getDate(), timeEntry.getHours(), id + ); + + return find(id); + } + + @Override + public void delete(Long id) { + template.update("DELETE FROM time_entries WHERE id = ?", id); + } +} diff --git a/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java b/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java index bcd5b96e9..2f0016493 100644 --- a/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java +++ b/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java @@ -1,5 +1,6 @@ package io.pivotal.pal.tracker; +import com.mysql.cj.jdbc.MysqlDataSource; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; @@ -13,6 +14,8 @@ public static void main(String[] args) { @Bean public TimeEntryRepository timeEntryRepository() { - return new InMemoryTimeEntryRepository(); + MysqlDataSource dataSource = new MysqlDataSource(); + dataSource.setUrl(System.getenv("SPRING_DATASOURCE_URL")); + return new JdbcTimeEntryRepository(dataSource); } } \ No newline at end of file diff --git a/src/main/java/io/pivotal/pal/tracker/TimeEntryController.java b/src/main/java/io/pivotal/pal/tracker/TimeEntryController.java index 5114e2b36..2f6d5069f 100644 --- a/src/main/java/io/pivotal/pal/tracker/TimeEntryController.java +++ b/src/main/java/io/pivotal/pal/tracker/TimeEntryController.java @@ -1,5 +1,8 @@ package io.pivotal.pal.tracker; +import org.springframework.beans.factory.annotation.Qualifier; +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.*; @@ -9,14 +12,24 @@ @RestController public class TimeEntryController { private TimeEntryRepository timeEntryRepository; + private GaugeService gauge; + private CounterService counter; - public TimeEntryController(TimeEntryRepository timeEntryRepository) { + public TimeEntryController( + TimeEntryRepository timeEntryRepository, + @Qualifier("counterService") CounterService counter, + @Qualifier("gaugeService") GaugeService gauge + ) { this.timeEntryRepository = timeEntryRepository; + this.gauge = gauge; + this.counter = counter; } @PostMapping("/time-entries") public ResponseEntity create(@RequestBody TimeEntry timeEntry) { TimeEntry created = timeEntryRepository.create(timeEntry); + counter.increment("TimeEntry.created"); + gauge.submit("timeEntries.count", timeEntryRepository.list().size()); return new ResponseEntity<>(created, HttpStatus.CREATED); } @@ -25,6 +38,7 @@ public ResponseEntity create(@RequestBody TimeEntry timeEntry) { public ResponseEntity read(@PathVariable("timeEntryId") long timeEntryId) { TimeEntry timeEntry = timeEntryRepository.find(timeEntryId); if (timeEntry != null) { + counter.increment("TimeEntry.read"); return new ResponseEntity<>(timeEntry, HttpStatus.OK); } @@ -33,6 +47,7 @@ public ResponseEntity read(@PathVariable("timeEntryId") long timeEntr @GetMapping("/time-entries") public ResponseEntity> list() { + counter.increment("TimeEntry.listed"); return new ResponseEntity<>(timeEntryRepository.list(), HttpStatus.OK); } @@ -41,6 +56,7 @@ public ResponseEntity update(@PathVariable("timeEntryId") long timeEn TimeEntry updated = timeEntryRepository.update(timeEntryId, timeEntry); if (updated != null) { + counter.increment("TimeEntry.updated"); return new ResponseEntity<>(updated, HttpStatus.OK); } @@ -51,6 +67,8 @@ public ResponseEntity update(@PathVariable("timeEntryId") long timeEn @DeleteMapping("/time-entries/{timeEntryId}") public ResponseEntity delete(@PathVariable("timeEntryId") long timeEntryId) { timeEntryRepository.delete(timeEntryId); + counter.increment("TimeEntry.deleted"); + gauge.submit("timeEntries.count", timeEntryRepository.list().size()); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } 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..073ddfac3 --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/TimeEntryHealthIndicator.java @@ -0,0 +1,24 @@ +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 TimeEntryRepository timeEntryRepository; + + public TimeEntryHealthIndicator(TimeEntryRepository timeEntryRepository) { + this.timeEntryRepository = timeEntryRepository; + } + + @Override + public Health health() { + if (timeEntryRepository.list().size() < 5) { + return Health.up().build(); + } + + return Health.down().build(); + } +} diff --git a/src/main/java/io/pivotal/pal/tracker/TimeEntryMapper.java b/src/main/java/io/pivotal/pal/tracker/TimeEntryMapper.java new file mode 100644 index 000000000..f5884fd84 --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/TimeEntryMapper.java @@ -0,0 +1,21 @@ +package io.pivotal.pal.tracker; + +import org.springframework.jdbc.core.RowMapper; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class TimeEntryMapper implements RowMapper { + @Override + public TimeEntry mapRow(ResultSet rs, int rowNum) throws SQLException { + TimeEntry timeEntry = new TimeEntry(); + + timeEntry.setId(rs.getInt("id")); + timeEntry.setDate(rs.getString("date")); + timeEntry.setHours(rs.getInt("hours")); + timeEntry.setProjectId(rs.getLong("project_id")); + timeEntry.setUserId(rs.getLong("user_id")); + + return timeEntry; + } +} diff --git a/src/test/java/test/pivotal/pal/tracker/JdbcTimeEntryRepositoryTest.java b/src/test/java/test/pivotal/pal/tracker/JdbcTimeEntryRepositoryTest.java index e1eac20fc..10e22dc97 100644 --- a/src/test/java/test/pivotal/pal/tracker/JdbcTimeEntryRepositoryTest.java +++ b/src/test/java/test/pivotal/pal/tracker/JdbcTimeEntryRepositoryTest.java @@ -44,7 +44,7 @@ public void createInsertsATimeEntryRecord() throws Exception { 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("date"))).isEqualTo(LocalDate.parse("2017-01-09").toString()); assertThat(foundEntry.get("hours")).isEqualTo(8); } @@ -56,7 +56,7 @@ public void createReturnsTheCreatedTimeEntry() throws Exception { 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.getDate()).isEqualTo(LocalDate.parse("2017-01-09").toString()); assertThat(entry.getHours()).isEqualTo(8); } @@ -72,7 +72,7 @@ public void findFindsATimeEntry() throws Exception { 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.getDate()).isEqualTo(LocalDate.parse("2017-01-09").toString()); assertThat(timeEntry.getHours()).isEqualTo(8); } @@ -97,14 +97,14 @@ public void listFindsAllTimeEntries() throws Exception { 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.getDate()).isEqualTo(LocalDate.parse("2017-01-08").toString()); 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.getDate()).isEqualTo(LocalDate.parse("2017-01-09").toString()); assertThat(timeEntry.getHours()).isEqualTo(8); } @@ -121,7 +121,7 @@ public void updateReturnsTheUpdatedRecord() throws Exception { 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.getDate()).isEqualTo(LocalDate.parse("2017-01-10").toString()); assertThat(updatedTimeEntry.getHours()).isEqualTo(10); } @@ -140,7 +140,7 @@ public void updateUpdatesTheRecord() throws Exception { 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("date"))).isEqualTo(LocalDate.parse("2017-01-10").toString()); assertThat(foundEntry.get("hours")).isEqualTo(10); } diff --git a/src/test/java/test/pivotal/pal/tracker/TimeEntryControllerTest.java b/src/test/java/test/pivotal/pal/tracker/TimeEntryControllerTest.java index d80f2b999..5d3625d8f 100644 --- a/src/test/java/test/pivotal/pal/tracker/TimeEntryControllerTest.java +++ b/src/test/java/test/pivotal/pal/tracker/TimeEntryControllerTest.java @@ -5,6 +5,8 @@ import io.pivotal.pal.tracker.TimeEntryRepository; import org.junit.Before; import org.junit.Test; +import org.springframework.boot.actuate.metrics.CounterService; +import org.springframework.boot.actuate.metrics.GaugeService; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -20,11 +22,15 @@ public class TimeEntryControllerTest { private TimeEntryRepository timeEntryRepository; private TimeEntryController controller; + private CounterService counter; + private GaugeService gauge; @Before public void setUp() throws Exception { timeEntryRepository = mock(TimeEntryRepository.class); - controller = new TimeEntryController(timeEntryRepository); + counter = mock(CounterService.class); + gauge = mock(GaugeService.class); + controller = new TimeEntryController(timeEntryRepository, counter, gauge); } @Test diff --git a/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java b/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java index 91e271b45..085589c79 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(123L, 456L, 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 8160aaaef8e0f6420960f6ee7aef8499a03185e2 Mon Sep 17 00:00:00 2001 From: Tyson Gern Date: Wed, 26 Jul 2017 12:50:47 -0600 Subject: [PATCH 09/10] 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 2e7d8702c76e7d53b416c12079b5f70ec44d8eba Mon Sep 17 00:00:00 2001 From: Michael Meelis Date: Wed, 14 Mar 2018 11:37:40 +0100 Subject: [PATCH 10/10] basic-auth-ssl --- build.gradle | 3 ++ manifest-production.yml | 4 ++- .../pal/tracker/SecurityConfiguration.class | Bin 0 -> 4858 bytes .../tracker/TimeEntryHealthIndicator.class | Bin 0 -> 1227 bytes .../pal/trackerapi/HealthApiTest.class | Bin 2761 -> 3756 bytes .../pal/trackerapi/SecurityApiTest.class | Bin 0 -> 3404 bytes .../pal/trackerapi/TimeEntryApiTest.class | Bin 7269 -> 8021 bytes .../pal/trackerapi/WelcomeApiTest.class | Bin 1704 -> 2678 bytes .../pal/tracker/SecurityConfiguration.java | 32 ++++++++++++++++++ .../pivotal/pal/trackerapi/HealthApiTest.java | 16 +++++++-- .../pal/trackerapi/TimeEntryApiTest.java | 11 +++++- .../pal/trackerapi/WelcomeApiTest.java | 15 +++++++- 12 files changed, 76 insertions(+), 5 deletions(-) create mode 100644 out/production/classes/io/pivotal/pal/tracker/SecurityConfiguration.class create mode 100644 out/production/classes/io/pivotal/pal/tracker/TimeEntryHealthIndicator.class create mode 100644 out/test/classes/test/pivotal/pal/trackerapi/SecurityApiTest.class create mode 100644 src/main/java/io/pivotal/pal/tracker/SecurityConfiguration.java diff --git a/build.gradle b/build.gradle index 04d33f58b..1394b2948 100644 --- a/build.gradle +++ b/build.gradle @@ -15,6 +15,7 @@ dependencies { compile("org.springframework.boot:spring-boot-starter-jdbc") compile("mysql:mysql-connector-java:6.0.6") compile("org.springframework.boot:spring-boot-starter-actuator") + compile("org.springframework.boot:spring-boot-starter-security") testCompile("org.springframework.boot:spring-boot-starter-test") } @@ -23,6 +24,7 @@ bootRun.environment([ "WELCOME_MESSAGE": "hello", "SPRING_DATASOURCE_URL": developmentDbUrl, "MANAGEMENT_SECURITY_ENABLED": false, + "SECURITY_FORCE_HTTPS": true, ]) def testDbUrl = "jdbc:mysql://localhost:3306/tracker_test?user=tracker&useSSL=false&useTimezone=true&serverTimezone=UTC&useLegacyDatetimeCode=false" @@ -30,6 +32,7 @@ test.environment([ "WELCOME_MESSAGE": "Hello from test", "SPRING_DATASOURCE_URL": testDbUrl, "MANAGEMENT_SECURITY_ENABLED": false, + "SECURITY_FORCE_HTTPS": true, ]) flyway { diff --git a/manifest-production.yml b/manifest-production.yml index 5636129d5..7c54c6b29 100644 --- a/manifest-production.yml +++ b/manifest-production.yml @@ -4,4 +4,6 @@ applications: path: build/libs/pal-tracker.jar host: ps-pal-tracker-production services: - - tracker-database \ No newline at end of file + - tracker-database + env: + SECURITY_FORCE_HTTPS: true \ No newline at end of file diff --git a/out/production/classes/io/pivotal/pal/tracker/SecurityConfiguration.class b/out/production/classes/io/pivotal/pal/tracker/SecurityConfiguration.class new file mode 100644 index 0000000000000000000000000000000000000000..e22fac3cb6bdef0b6cbac2f746455db4bbcf22e0 GIT binary patch literal 4858 zcmd5=TXz#x6#h<=cET{UG(Zt32wI^nmGOcK7J)VdT5KpJy>PE5$!Rh)oe49OmI8{R z;svj7x|aS3U!u#UE}newH@RHylguP(sYnt)vsPx#IXP$VZ-4tTd-BI$KmP_`KfYG6 z0|!HRUBw^{h46-owK%LIj3X+JVn{(mMF_{`$_WJ{A&jaxi8mF*LKs)kj?;2}MnPO| zNvN2>q#RADn8sNZ=WsrR3o0(+QV5q-%-}6~;R=!pQVeZ}4AXEAGX#43rWk?|D=ip0 z#tc)OD$FN^J;9SXS_zL?DW03+wjt-GVLSSW>*jqmgCz^Hw8C)m(Me@cXDvG=q$;vDJa&aIa6QM(jGk~MYO-iu z!X=YqJYQml97Tts*HR)c5jzUf#A(Io6FDo%a}3$KP3HwSD@@l&m6@opFtltv#!a3P z_Hdb_GAr^VgQ%C4h7>Yvi5CdvyqGc^gTiH~;ymK-z^WLMf}4&$%TumpFV%XROtTgZ zTcn9)HDMLV5yuS~kz0MYFN>~y(m4zht19`Ipzjunk3?riCMHfN6bKEo$WRJR#3!Q~vM@AUK~BNE1{0P7ipseLLfuCtqdk(P zJPl$w0Cc{Vr zpemVDTR$|sjdv8>l5*eG@E&d}cwfT@xT)Yn4R`PndAHyQTfxT~KEYjvP@Z_Cf|XYA zxrWQQtKkcL$uLk)MP#_tfC+wFl%usKPODT7rFq^JHp7OhZ!1-4xyP3rSIpDnAR}C1 zE->uwy}!)$-M>U9DDZfW@_JM4iSgtWk#Yz7&Qsm9mxm_9b}vX}cnZgjaW|C}c3fl( z$F-LjuJ`selfDm5ln3gj8ne)ZE1J+u#f_Uw9wSs^h=QumJT@85J}z(!?Y^5z#f9o? zC-?GGc&Nz7JE{phYEop$2OK47)pd88cU@J*18PKd(`q=#jklX*s;Z!26bx&*=|?w_ zx2N|)jZeMSVO2M$o&b0TCCi<-B_E~V)TV9)*b@NJQ>FPI{21US^-d=n3#;1V?sr4f zeaa4Tm^%iwzf+9~_G_=-ftI9I>n_D#yh-*X^c7MWhU-V`w+dDBgDJD@se^;Aw$UvW&h+{igz|Gtdc8+B6qDAiM&vuL+YdF+)>2(B0BFOT*QVV zHkJmPis+h_I07XcyRnAG?S!WT8?c_VhH;QaN3apY*n|;uVH}%FICqw@csL0adeKMN z+vsnce(WX810*X1_9u2Jcpm#`7Np-L1ux)5IxTIwNH(?5y_+C!4nI}Imhjdhwt3(? z2z+-5Uzw*cNdUHygl^KaoklxKSh~tGDpsy2X literal 0 HcmV?d00001 diff --git a/out/production/classes/io/pivotal/pal/tracker/TimeEntryHealthIndicator.class b/out/production/classes/io/pivotal/pal/tracker/TimeEntryHealthIndicator.class new file mode 100644 index 0000000000000000000000000000000000000000..253a137a4f76fab0b92a9a9c330fab3e15a3994e GIT binary patch literal 1227 zcmb7D+fEcg5IsG+Fzhf~Tm)SeML}H_qy-U-7!xHDNmfmO@ZwYNZVL^w-IJak2!4)V zpotgo!4L4Gj5W)OiY8D5~X&*3?(0jHXg5|J9N>X{B)o zvE<9985=FNTME&K$q>B=eN;kBVcN%?5HsLD?uMAfypQ`K7BC!Q5%V;Svxay|F2i6Z zm`6fy@mIASS$7PJo%QOZuQl%!5Yyh=VALI2+#dOC@E zOWh}$Rfd_)WHNYZ%-}ywRn(A{Azam3+T}jh%b(x< z2;c}l)6jt=#~8=s8WfCkOlSxpsp6!xr<$=JlhQja(^D$WsF;@S6C5)dTJWSq%&K^b zV~*pj44u>PG%O7%q??g}&~P4E8Ov#~F|Q*23`c$&7Eq8NM@3P?v&gD&Rg^e9hG5Zg z$r#CbYtb_ERv~Lndahl_R=?<~y1S!kr{IZY&lC$Ok;#aRnRL=tep0xL!kwV4iK;d3 zbLO^(O2V5eGVoN{&S!+nFzElL>^i(8Qf4}Di-KpK5+(1nSSaQ#PmDBB30c~4v*vuc zV0-3>IPbXP>LUAtu|+=?p(HCirt&Vs!nTpfK_V%Ygl z!Rw3|+H#&(jGJa&D$O}1FRo)5&*``wcj#Eb^EzI@1!`TnBxR~at5jNY+>DMFQSqap zAx)NeNk~q}z(hBV-gO*r%C#BB{^fAL%ECh_LYP~OXrq>qvPyRP zSlP=tuDxP;wo_m@=R2VYZ*WM+!(#vMu%Vc2&rNO*_2%ka=+}U`pPnh`)hTk?U^qal zCbF@5lqO0xKx?{}^d0ceHis*Bu48~OO!9TgfL`)J+(nk*5MdPzkbeO|@_@lLC^LZ{ z5LiWU4WXG;G#R`GwfAc@lc_bDE*fJ4lGYq4hxOLR9BiUC& z_!@S8g0(D&x zaPkkJm0BGkf;|)+B~f!EW{Daq8{FL>yDraZMGi+vJ0tKo1`#Lq34+NvG;%}a-I(Nk u^-}lKSHT1IXoS3BLL5tDglZ{xa270$hw!k#qj(G_2&+;4ILdfrxbrXjH&&tm delta 696 zcmZ{i$x;(h6o$Xs>Ew2rR%0M)48%xqAOwR03QmBCBM4D3Dx!oC1c#&wLxHP2gVj&K z-i=~Ku+(y^kKoF+4`F$_(IuPzKf`~z|8wts+x9(H{PE}0SKt&?n;>Vx#OSl=*PPXy zvpLTNn~MzC3^Ekv62mr^8S!dYY_4+6;=1NW6E_+4G-DRGY{nU}n6SueZo4gN{LSZ9 z)#v7oSo>_bSb3G3FP3W6rPsM)rBbaGYm3!NZfLnyU0GZz&$%;ddA-m0t<+sNZLV7T zNjU6bue%xS>D5d++@qkm@9=lbHxcS?UUYcI zb5Hz2VN6bFUb<7Eyjf8UyK83LoiQ^3MZX^ps?_?L^)pb9$(eu*cn*a3Nfr{03md{> zPOw$7oUjrOWp7sayNA>j1ZbA~+pq?HMJ3F&??)|>|n#n)^{`oh6Yxp^ZL%6Tufrf`EB=D(* zM=5mReiAt~KJLPCOsKa>^?53Z&ytu{uQM9*DeS|nl9*GE`6NEqu%O|IT3<}z3w)Wv zS9scmuVJQ8fTiAw8aP9{=L*S?&aIfMrcpMXWn)GP+gToFI656T(ys71+qVm4o^>2o zn$mV1pJ6!XiepXGNFN~aqr4T6vZzUjF#wcHM1t+1Nz^O+?!C*)V?t2t& zBG(sdz9gk*kj+qNoRUSZlcwHgN344_XWV*QB_li2@wS2(?vHR+9o4-Zb85y7gvIaK zDkKNDdG?aZa)x6KdPLwjTo{z<^R_IJk&acf9MInDv0%e6)-pUqa?O|ttAI3+B2o~) zF)WRy-O;vDieQzCW|d9f=aep&a<*&4B#SX+mh(~GWIeBJTVXkp&2Gwpac9*Qu2Z4n z&`_ea`KC1n{h>lQRx>glOYluN&o*l%+k2+76L%*cKb@MK&g(db4;gN>Y!18UD#Osb z?i>qz=~k$d2xZ%5IJ~`+RqKFZze?93!zimdExEoN(y@YPI^M?#9c5H>IB==L17B5K z(xYflTPy1D@SG~JCA2WC4a`(L8!J&EBY3B9TE`ijW?%*-^LCs~OOu5aZb=c@w$laEx2@VC6jmq0(-yWBFo-`R&d2#UMwDSMft#UwN8m>X?|F)8%gqm{!Xse{^GPM9 zI;xN>bSn|uP^FL!7ouBCD*Bo)nRMFSZ=^luRyr=EhZj&NC*j5L$sGjD;9 zygoaToy|X(oPPLZ{4T?}w`6rOo>dDp3|+QA{yd;_gF4$_BgLkuwWPO2Dlz$p;aVKC zHys(&_>R-{7HFgUD)m}WXlT|+;|DY*RAUTB>WvNG&eB>t&FMs;=XshLG%^}H`ZvLT z3AbeEsT6?>^wCp~1kjHGnlW6U&28c4SEMXKYlE+mSZMnTZJTI+jgEy)bf&cpB>R6u z7Y!RoZD3DYPw(A8cdq|6_ALx-qUU$)uN&;8dpHRDDf-J0<_kEBn}kyV=#7k20bIad zT*M^;V$e6m=JFQzk7$MA3IR-n04r61CkaV9eIR|1Kpxtl+tk1Y4*!88zvAcyj?F6= zZDbbcrV(Vchq#^~5Own>tLA&?2f$VOdBN~8Nh;q&E7$3*7J?h}g%SKX82e*kycY-K zGO4~oFs}X&7;P9NFbNEW;7-unFhLu!a0|C-^2s8Yg*(V{jN=~el5C2;k75~PcaHuC DoEqeP 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 07dc1c0e6bc4a34df5c251213d77def770420121..862bd0d086b284bf5385ce95e09e4e6cd9b3e626 100644 GIT binary patch literal 8021 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 a4Asl}zB&#Y*Wp_F7_?u5U*Xd$YTg2xNJMJ@ diff --git a/out/test/classes/test/pivotal/pal/trackerapi/WelcomeApiTest.class b/out/test/classes/test/pivotal/pal/trackerapi/WelcomeApiTest.class index f2402b774f4ccc70b4482b3200fb43b572c7f5f8..f8784b3b62e775febbf641d47cccc797764993c3 100644 GIT binary patch literal 2678 zcmbVO`&S!96#j@_v?{N%KCmqhTLJ~!8XiIbDJpI@0~y%tZf7U7_8k8x z&r$Hm(c{nlQ6BGX0!c`Mr=Fbb?99%+-*@l*=I;Le&+mT%n8Z;Wy_hxdg@K20G~tnf zxj0%e8$(i$^X=%zg1&pKA1}tR6vLPL_KAU~adhFC-tkooDFe#}R`k+p9BcSGhV?i$ zkT$T%&=UAkF(gul_OWfbws&A{C@H*y8HWD#f~Q2DZ;4Q3T%PniU)f6dUdS+$^5uaQ z272qhwDbI>FORH@?<>pkJ;h%si{~>uo8?(6@iy~U^v*fcYB|N@TAPzEZ6MG$_=CNf< zq35N=7L^ke&Uh*U3dAvm=vo2&m9(8BF6}^A+uU{hJWmEf!xLm{$kk@RU}Ory&2q^w z)i@s&>?@hi7fs0<5u;ol7AOj9p6~mT(_#mIWmA5*Mx>xJe)dO(>lHgoRN@(xS?43zgHM9NTVzOz+hVAj-;lK_iBf)<#rx@<5KrL4CtW z302lbvw2cTiMippb{KNX{zOXnR-N2iYqpy%mLVAgu5cpt5oRalbkRE&()aSD3Ip4; zwq3PGB73SCE}Vlb)(!B#c;2b@stdE8TzR~@ytlTxo;ERvA%^=6&%)KUXPACd%Y{Oy z{JeP0rN;h-;l|~IThbLncTTBb+Ok|N`xQV zHRu#)P3++ZQglOVQCTY@)Ag)Wwq|CV7{F}?W|3+yQ>;n~%EqYg2%TMQ4m38?oH{67-=mpDBcri(^c3vpXxsaA)kZ)M?$FgNZomilkY)@Yk#SRG z{1aN}4wyPa(@ygrXg)>D8CrKv(Uvey5F7mk?KGSqeu9pKnYea>&eZ4`x^~7+as5|x zmjV0es|v(5)M%a{Wt%pPEB@DmSE7BXk5GTuka6Gpb#cd|CGi?!!@qqD_{9nG4 BO*#Mo 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..a21ae096b --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/SecurityConfiguration.java @@ -0,0 +1,32 @@ +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"); + } + +} diff --git a/src/test/java/test/pivotal/pal/trackerapi/HealthApiTest.java b/src/test/java/test/pivotal/pal/trackerapi/HealthApiTest.java index b3eef23cc..890e6e4e8 100644 --- a/src/test/java/test/pivotal/pal/trackerapi/HealthApiTest.java +++ b/src/test/java/test/pivotal/pal/trackerapi/HealthApiTest.java @@ -2,11 +2,13 @@ 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; @@ -19,9 +21,19 @@ @SpringBootTest(classes = PalTrackerApplication.class, webEnvironment = RANDOM_PORT) public class HealthApiTest { - @Autowired + @LocalServerPort + private String port; private TestRestTemplate restTemplate; + @Before + public void setUp(){ + 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 085589c79..3f69e7f6c 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; @@ -28,7 +30,8 @@ @SpringBootTest(classes = PalTrackerApplication.class, webEnvironment = RANDOM_PORT) public class TimeEntryApiTest { - @Autowired + @LocalServerPort + private String port; private TestRestTemplate restTemplate; private TimeEntry timeEntry = new TimeEntry(123L, 456L, LocalDate.parse("2017-01-08"), 8); @@ -40,6 +43,12 @@ public void setUp() throws Exception { JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); jdbcTemplate.execute("TRUNCATE time_entries"); + + RestTemplateBuilder builder = new RestTemplateBuilder() + .rootUri("http://localhost:" + port) + .basicAuthorization("user", "password"); + + restTemplate = new TestRestTemplate(builder); } @Test diff --git a/src/test/java/test/pivotal/pal/trackerapi/WelcomeApiTest.java b/src/test/java/test/pivotal/pal/trackerapi/WelcomeApiTest.java index cc7091ed4..60b3e1454 100644 --- a/src/test/java/test/pivotal/pal/trackerapi/WelcomeApiTest.java +++ b/src/test/java/test/pivotal/pal/trackerapi/WelcomeApiTest.java @@ -1,11 +1,14 @@ 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.test.context.junit4.SpringRunner; import static org.assertj.core.api.Assertions.assertThat; @@ -15,9 +18,19 @@ @SpringBootTest(classes = PalTrackerApplication.class, webEnvironment = RANDOM_PORT) public class WelcomeApiTest { - @Autowired + @LocalServerPort + private String port; private TestRestTemplate restTemplate; + @Before + public void setUp(){ + 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);