From 3d74570213d9f0792a487fcdc49c44c5285e1de8 Mon Sep 17 00:00:00 2001 From: Mike Gehard Date: Wed, 3 Jan 2018 16:17:48 -0700 Subject: [PATCH 01/12] Add deployment pipeline --- .travis.yml | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..4cbb91300 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,39 @@ +dist: trusty +sudo: false +notifications: + email: false +env: + - RELEASE_TAG="release-$TRAVIS_BUILD_NUMBER" +if: tag IS blank + + +jobs: + include: + - stage: build and publish + language: java + jdk: oraclejdk8 + install: skip + script: ./gradlew clean build + before_deploy: + - git config --local user.name "Travis CI" + - git config --local user.email "travis@example.com" + - git tag -f $RELEASE_TAG + deploy: + provider: releases + api_key: $GITHUB_OAUTH_TOKEN + file: "build/libs/pal-tracker.jar" + skip_cleanup: true + - stage: deploy to review + language: bash + script: + - echo "Downloading $RELEASE_TAG" + - wget -P build/libs https://github.com/$GITHUB_USERNAME/pal-tracker/releases/download/$RELEASE_TAG/pal-tracker.jar + before_deploy: + - echo "Deploying $RELEASE_TAG to review" + deploy: + provider: cloudfoundry + api: $CF_API_URL + username: $CF_USERNAME + password: $CF_PASSWORD + organization: $CF_ORG + space: review From cacb8b8b5b7a5d8a3409e42dfe530b68c57177d5 Mon Sep 17 00:00:00 2001 From: Chandrashekhar Date: Tue, 31 Jul 2018 10:48:04 -0600 Subject: [PATCH 02/12] Hooked up Travis CI, added travis.yml --- manifest.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/manifest.yml b/manifest.yml index 7f06f91ee..2b77a53a7 100644 --- a/manifest.yml +++ b/manifest.yml @@ -3,5 +3,7 @@ applications: - name: pal-tracker path: build/libs/pal-tracker.jar random-route: true + routes: + - route : cj-ps.evans.pal.pivotal.io env: - WELCOME_MESSAGE: Hello from Cloud Foundry \ No newline at end of file + WELCOME_MESSAGE: Hello from the review environment \ No newline at end of file From 75f485ef57c1096a8ffcd3b84d53fc37b0710937 Mon Sep 17 00:00:00 2001 From: Chandrashekhar Date: Tue, 31 Jul 2018 11:24:19 -0600 Subject: [PATCH 03/12] Fixed domain URL in manifest.yml --- manifest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.yml b/manifest.yml index 2b77a53a7..4fc7b3698 100644 --- a/manifest.yml +++ b/manifest.yml @@ -4,6 +4,6 @@ applications: path: build/libs/pal-tracker.jar random-route: true routes: - - route : cj-ps.evans.pal.pivotal.io + - route : cj-pal-tracker-review.apps.evans.pal.pivotal.io env: WELCOME_MESSAGE: Hello from the review environment \ No newline at end of file From 61ba3b5e591256846b32c4f5b4b85ee6f5e8a1fe Mon Sep 17 00:00:00 2001 From: Tyson Gern Date: Thu, 20 Jul 2017 15:04:20 -0600 Subject: [PATCH 04/12] 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 4e5a403c8b88b716428a66e5096994c37b036903 Mon Sep 17 00:00:00 2001 From: Chandrashekhar Date: Tue, 31 Jul 2018 17:11:45 -0600 Subject: [PATCH 05/12] Added TimeEntryController backed by a map --- .travis.yml | 77 +++++++++++++ build.gradle | 1 + .../pivotal/pal/tracker/EnvController.class | Bin 0 -> 1505 bytes .../tracker/InMemoryTimeEntryRepository.class | Bin 0 -> 1954 bytes .../pal/tracker/PalTrackerApplication.class | Bin 734 -> 2267 bytes .../io/pivotal/pal/tracker/TimeEntry.class | Bin 0 -> 2947 bytes .../pal/tracker/TimeEntryController.class | Bin 0 -> 3270 bytes .../pal/tracker/TimeEntryRepository.class | Bin 0 -> 515 bytes .../pal/tracker/WelcomeController.class | Bin 0 -> 840 bytes .../pal/tracker/EnvControllerTest.class | Bin 0 -> 1469 bytes .../InMemoryTimeEntryRepositoryTest.class | Bin 0 -> 3089 bytes .../pal/tracker/TimeEntryControllerTest.class | Bin 0 -> 5216 bytes .../pal/tracker/WelcomeControllerTest.class | Bin 0 -> 1034 bytes .../pal/trackerapi/TimeEntryApiTest.class | Bin 0 -> 6651 bytes .../pal/trackerapi/WelcomeApiTest.class | Bin 0 -> 1704 bytes .../tracker/InMemoryTimeEntryRepository.java | 48 ++++++++ .../pal/tracker/PalTrackerApplication.java | 23 +++- .../io/pivotal/pal/tracker/TimeEntry.java | 104 ++++++++++++++++++ .../pal/tracker/TimeEntryController.java | 53 +++++++++ .../pal/tracker/TimeEntryRepository.java | 18 +++ .../pal/tracker/WelcomeController.java | 2 +- .../pal/tracker/EnvControllerTest.java | 10 +- .../pal/tracker/TimeEntryControllerTest.java | 24 ++-- .../pal/tracker/WelcomeControllerTest.java | 2 +- 24 files changed, 342 insertions(+), 20 deletions(-) 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/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/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/.travis.yml b/.travis.yml index 4cbb91300..998ffd83f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,3 +37,80 @@ jobs: password: $CF_PASSWORD organization: $CF_ORG space: review + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build.gradle b/build.gradle index 64c81fc43..853ba0046 100644 --- a/build.gradle +++ b/build.gradle @@ -10,6 +10,7 @@ repositories { dependencies { compile("org.springframework.boot:spring-boot-starter-web") testCompile("org.springframework.boot:spring-boot-starter-test") + compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.1") } bootRun.environment([ 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..a30238f8bd4dce286adcb7486f864d576d8201d6 GIT binary patch literal 1505 zcmbVMZBG+H5Pr5j+5*KYD2gDWR;3jVME*7JaI>wYWfHK?Fj$ri#dX^e3`>Wt54x(wRl_W6 zN2W2(ZW{*1jAClT(Ffw+XSS9jOi9)+s6;6d$EkZ`~c8yy*+>xH% z0}cI3x(qk+(l%SNV{2}iEn3>)wKu{sv#OJ^m3C~)5)P3zTDGITSbj!=mt+`jil*(n zw`5b2OQN=yQ?BN!CUUAS-Z7l%BOCR)BV0Q8YN@TXY>FM}N@DetJG?2ha5j`8E?U}( zoEG!8(=gqZm&`rS;jQhwF{^?r*WBYZO^2HN?*?;+TWvvH2RfcWOp^Ogi>1n1p;(zI zXDc5V&L{V`v)jee?#p~`I~TM}CHFI1FLQ-*WuuVElCYV59@sc}*u1g1SqfMq4WTJv zbko^XH$j1xS@{<`aidHe&d~`{^Hx;Ns#Nu0RvrmWz1#Bok;-UNZ!-*)?Y2`BThi~3 zL$zM;3e2#2Z0e7TUlOhkv`QSOu|k>XPtIfRz9I&3#4$n)yplw*8pnOCF~o!QplwHr zz;HTFW1V^}s^eXI+Ko7zd zdQXPwoniF2&@JhIinI_Cw1@`I(0`PWj|gLc{@L^aSo#M-y9Wq=g^~UOV|3uwH$=jC z<}1;;fD)SzD1#G5SNrTAF-G5MMCm&QgN{z%ET(V{DU4$o=dp`Rj*859YaH^}W0t!WpR`{GPrywVr1l6VbC3gX?@B+WZwz`N5Y u5<&_;}gq?4x1@Z^*KM`u(- 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 VAb{KM_^AuFkLzi!XX@g!{{ap4bxZ&N literal 0 HcmV?d00001 diff --git a/out/production/classes/io/pivotal/pal/tracker/TimeEntryRepository.class b/out/production/classes/io/pivotal/pal/tracker/TimeEntryRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..06c9fd17ed79be8d1a968c575f9908bd6a8a3f68 GIT binary patch literal 515 zcma)3yG{c!5S$Gc9|^Az8h!xjLV+7Z6O@i1MJFKbQ8iJ5`bqHBF+^1%=eh>pwIXL4kLbqW5x(4ks15|b6b^I literal 0 HcmV?d00001 diff --git a/out/production/classes/io/pivotal/pal/tracker/WelcomeController.class b/out/production/classes/io/pivotal/pal/tracker/WelcomeController.class new file mode 100644 index 0000000000000000000000000000000000000000..37e31996d35a4f885e11b1af3f587fd0138217fe GIT binary patch literal 840 zcma)4%We}f6g{5i(T4O%8z^s37fF!trVAtxQWd3TF`=kI>4F9FOx@J&j2+odstWN> zAc2rr@Bw@j;yQ_-C|z`6--pjR_xjque*gFhU>lDrDB$h_=5Vi!`(->}DCg2S(U%Ns z@u?UIo(bLOU2m1{zhEf6R9g903>)>?<3^96&@x9vUXGQPZ-#jy?K_cVV>y$GtS77r z@ibZRC(1E2V`cb2jf@u=AJFnvq-WCdLz$%}mo1~cHCZMtwJ`3`g$|z_47FFe>?x;6 z^R2KVmtNW(tx3r%qe*)^Hnz{*AY9-$0M3m)Zp+uhk~e`1(-;^MW;GQ+T5Z_EyYVe|jiuF=uCOn9R7(dB0Lr0RMKstYExK)(#u!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..0a534e2fcaad22afa29d2422c6064e0d86117714 GIT binary patch literal 3089 zcmbVO+fv(B6kW0faum@J;*ydpJ;jU*>wI@8Yd z1NsyFg1$IRGi_fx{gS@*4?0~(l4U2wHPkcMNBiu3_FflB{PFkCzXC|(m5M94tKgoB znE1Wl!m#*!6E39R6PQymj|VDbd?eBq#44lWV`NnHTD8k;0-uVlb@BC?*m|nqa|K^WXq~of%eyThp6XkbAm1zIO$jHm zmTfMTcQ#CC#n>n?(vdCZjKZqnSmHVi%HEdcN|?-grt9e?>qXHs3VMluo@3;mn~uI< zFPb|=XK%&YG3RX0*?Vl3imv5}5NoD5K+Z7@j~!%(TN2V)t9aC-cuae6Og?HP;ZmcM zq8!#G%vU8d=b3p4$2dsdA5wxk_bO+WJgaEC3ci%k^|)+%#IS0)7V*#8Hlh04Y@IDS zoBDQ{JnA7R`I%+&M7dzOo=Dg>UKo1WvkH2a5w`7oo6fybmD2P(xzo)-+!idBOL?C~ zCFDVZ7KEyZTJvVX6g;cT#j=w#=Pe=Yl_mmR7n5taie3q$M;AsycY~rx0}eDgJTf*k zJTf#quHY*TU&HUXp8f6VLc%k8?05<-A?MrEWWebMAsXyGIFYdX4xP;V z*>dN0%SK^^PnC(IcaD0tY?|Hz?`A$BGZL<*GKXR=aE5=+&pM8=M=n|oSKR*MjHh*a zM@0PVDbbM%<*$Eq+z6r6Vht!1y&@0?D^*L8aO>z4)ygT&gH)!uqYg!#Op0k@h5GH3 zygl^bGT)=((+ZM@UVdNWmCj#@zb*Z*A^pTf45=^)XhR>@T9AN#4Dd?g6Qvh%zxX$( z5bwt)ZxCC5jd*t84dnGCT9QyYj_pHjPrSzQ3baKLSKrxaTN>QQdqWkRSQ{9upuK{V zlX90_jn#&^0EJE3k-$mfIK^jH5_iyv3{GPOANWJ2gF(U}6MX9r;W~%y;lB_%h6!s7 z7cjzAoOQ=B%9V^8JXdB`8aKHT%zVgSiMdsXKN6zM*n?&e*A9fZIG_^N(hxxr;)%5y zs55K_oFl079P%PtcHJQpRfM!BKV{85MJ?m(_gzyM zp@bbtBs)4Op%N&#*76G_j2{Scd0h^4Kb=H(Ox#!4%PdX$_WE@2ct(+ybP&u2x^3tF-$T`*ffYtG15rc>4=g) z=>lP1rx_)SzB>bi=LIbnNlRet?j;cx=1ro0t8G#il6T4V=8gh26ZBI;aMlDJ*hBAK zrVU2<=AGdB7E!HYim&o%?D9%55xCrmnAnb7{)icYDQ@Dn@A6feOBkiG%V#1kzNZ?( z9_hnzQ9)0rY6X{SXgYm9XNl%6(cI_6bHp}Zr(rspd@!2)R5*Df#qMyf7-s(g>VL$T 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 new file mode 100644 index 0000000000000000000000000000000000000000..c3b89cd5b91edb855a0961e1c023510e65c0882d GIT binary patch literal 5216 zcmb7IX?qjr6+I&_BM$>H#*hL|a7X~*1+fKiO-PNg6C7D?ZGq#^))~uVdu&-Fk4Au# zmUIbC=#uXHzVAy?uuW)Ex~FN{rs>E2gZ_vWbT7$R4R57NZ3ghx)LWNbuh4Sy|;6`~Zj7besNNEUTS}tkXZErvZ zGa4LZHy{L8jO1kEqJ~SjtYH>U)!=EoPs96hMaG_yu@7kYAg;)c56O-X%aUj1ay9Qb z6x>v7%_Hat<0BfL!}GH3nr!>1ijRr4k8Ah@UdV6wPjC}@L12ASR)0!Xe_B8ra`zb- z`>eS8oQlt@_=18ht~G6TrCn#X-^|!q%e9?Z1uZelj%KWjwrixK8UA-2BROR{(ZurA z?FzJ{O$&A^Wjf{T?JTV3-s+Q9+Hy}RsEF)8ub{Hi9yJx*6|>T2JU2aJItgPW#gV$0 zoitMC4abt-!C<93VPzE@j=5&mT^n%GX@YC%xGHP9=Q0G}bvbEfT+2>pReVvw_WoR2 zNI7q1Eqdxmr^$jh?SXK{qmwxzj-EEhY{!)7#d)V=8ZKdOF7TPK^JBZ$Ws_DmnU^zW z(ltl>&1^2^DhNAfHbYP}x<6*qZ8qar>G3hgm^Lrj&Qx^5bu&?}_7*XPmOD#3wcc7E zVi0Lq0QQ^4D2?X5DA=}`(Ai9$)ZTKvom@YzQ$(~m?|wLLyWMs!EjVN>W@Q;?ZEVuG zXhd_am5Ry;cN|?ZRHmch@_{dQS(beX#Y^?M%&3>@EfRzOqa-qK$r&?ciW;;5JLe?L zZc7y0P$-_oT09_Nbi4x{3YyBVI~8Bj@nw8P$4NX?#?GQs@%2?5r|__X#`PRBh{FdD z9dA8&sP*6p6<^cwb$mlCd{Zvp((!G4N5yw_d=KAO@dF({6i7e9kLB_c9Y2-fpXvBH zexc%*I(~&0RlKC**SN0YhK@PR>sY`|nR!{oEgiqXD>`1qZ*|JSRs3GZ zAMi&Vf5M;T@)s7*;(Us&*HrvftiP_~Z+Jt;-|-K*{8Pu9V){+|OUGMc`k$yuA>G5da$Rw+leY7}K=qDVNx}CO@Q|znJUjLgtPDT{$iTE=2>~yA#Y_@%W$%t>6 zqKih>t+Qh9ZkHzNgON@Y^@{wAr`M{C;Yc^3y&~Ao3BwsMXL4pbY3AK8-be&HBRxI- zo+VRf#ajbs*O(5E+Wn@RbJFb7rJjTM{N9%5DC_1%M%d+|19D?JblyAfqOF2-44v%b_^ui+*qOovBEg^}jDSDg&nc=!68ldbh?hv<9DGE{zhufAwH))=Ag zD|3&6jpL>}z#}Y|_0QIQku}bfM|r6DiwS<0_@FLL$DNEOdD=ydj1}$h2IRbCUHY?$ z34=B8NNEl_?P=;w&T101vYnBvJXW~HmA+*S zr$j^M3=>4Edq__%c+_|#(zAZEDJz%p@+_5~=IYVlSw}GQG|=LG!<~KcL{~h4`}k7T z&X=hWKYU2>?)cQf?;0rbz{Bfl{)+Nj;dfQjJd_u`Wu3fMdxP~j!+YIdfG%|N7kdpi zS9qJ_sPyIgcAevyMmg+ExSmcpj`$fsGysSF2v3I4f36)WA@sFX!F$c%UA8>mpRN zI*;0c1-^eS&EicoDSi^?&|H#3e|&igZ-WKUN-hqRP~hwKEVi)HT3KZeQn78+Vjn+) zRNpvtn4>12WyXq`yc$r_=4En-1PqciQLCRxAi76 zajJTP&1jM(Iz^4AN>tquz&>11^=e?>TLAl1P;e3HmFyhHV~aY+i3$`dt!;6o)p_kM j!{E@XPY?$PQ@Z|pimBm1$ literal 0 HcmV?d00001 diff --git a/out/test/classes/test/pivotal/pal/tracker/WelcomeControllerTest.class b/out/test/classes/test/pivotal/pal/tracker/WelcomeControllerTest.class new file mode 100644 index 0000000000000000000000000000000000000000..5996089799bbbef5ccd0743f8d27221b2d488b06 GIT binary patch literal 1034 zcmb7DZEq4m5PlXYM~}0uw6!n3p;iT|tRIYt!I%(&G3H_uAvAtm&J8Rccl2(l_GkH} zHJbPX{87f)J5o%Fre1P0yYuqQGqbxte|`G_;3>F`W$aqGUce0-8Qe7DmW|se+qi?f z7VcTtW5~ZyfzmG-vXyF&A=e5&NQPBc1@i55+?P>T^nJ3F+|Uz#PejV>XU3c!s+i%4 zt7WYDL>-4(_Rf&oA1kEEv=*5{`4p)hP#+_}A@BNYr9=Aa9)IGIgd2$13_O|Q(y{V|wf~;N zu&mDnL9z*Pi;xWN fwKR8zYdj^a6aEJIa-=sYB4x5gnqiw{20On2ue=ih 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/InMemoryTimeEntryRepository.java b/src/main/java/io/pivotal/pal/tracker/InMemoryTimeEntryRepository.java new file mode 100644 index 000000000..582df807a --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/InMemoryTimeEntryRepository.java @@ -0,0 +1,48 @@ +package io.pivotal.pal.tracker; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class InMemoryTimeEntryRepository implements TimeEntryRepository { + + private Map timeEntryMap = new HashMap(); + private int id = 1; + + + @Override + public TimeEntry create(TimeEntry timeEntry) { + timeEntry.setId(id++); + timeEntryMap.put(timeEntry.getId(), timeEntry); + return timeEntry; + } + + @Override + public TimeEntry find(long id) { + return timeEntryMap.get(id); + } + + @Override + public List list() { + return new ArrayList(timeEntryMap.values()); + } + + @Override + public TimeEntry update(long id, TimeEntry timeEntry) { + if (timeEntryMap.get(id) != null) { + timeEntry.setId(id); + timeEntryMap.put(id, timeEntry); + return timeEntry; + } else { + return null; + } + } + + @Override + public void delete(long id) { + if (timeEntryMap.get(id) != null) { + timeEntryMap.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 11526fedd..3e030bab0 100644 --- a/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java +++ b/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java @@ -1,13 +1,34 @@ package io.pivotal.pal.tracker; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.util.ISO8601DateFormat; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; @SpringBootApplication public class PalTrackerApplication { - public static void main(String[] args) { + public static void main(String[] args) + { SpringApplication.run(PalTrackerApplication.class, args); } + @Bean + public TimeEntryRepository getRepository(){ + return new InMemoryTimeEntryRepository(); + } + + @Bean + public ObjectMapper getDateMapper(){ + return Jackson2ObjectMapperBuilder.json() + .serializationInclusion(JsonInclude.Include.NON_NULL) // Don’t include null values + .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) //ISODate + .modules(new JavaTimeModule()) + .build(); + } } \ No newline at end of file diff --git a/src/main/java/io/pivotal/pal/tracker/TimeEntry.java b/src/main/java/io/pivotal/pal/tracker/TimeEntry.java new file mode 100644 index 000000000..60187fd3e --- /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; +import java.util.Objects; + +public class TimeEntry { + + private long id; + private long projectId; + private long userId; + private LocalDate date; + private int hours; + + + + public TimeEntry(long id, long projectId, long userId, LocalDate date, int hours) { + this.id =id; + this.projectId =projectId; + this.userId =userId; + this.date =date; + this.hours =hours; + + } + + public TimeEntry() { + } + + public TimeEntry(long projectId, long userId, LocalDate date, int hours) { + this.projectId = projectId; + this.userId = userId; + this.date = date; + this.hours = hours; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public long getProjectId() { + return projectId; + } + + public void setProjectId(long projectId) { + this.projectId = projectId; + } + + public long getUserId() { + return userId; + } + + public void setUserId(long userId) { + this.userId = userId; + } + + public LocalDate getDate() { + return date; + } + + public void setDate(LocalDate 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; + return + id == timeEntry.id && + projectId == timeEntry.projectId && + userId == timeEntry.userId && + hours == timeEntry.hours && + Objects.equals(date, timeEntry.date); + } + + @Override + public int hashCode() { + + return Objects.hash(id, projectId, userId, date, hours); + } + + @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..487ec546b --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/TimeEntryController.java @@ -0,0 +1,53 @@ +package io.pivotal.pal.tracker; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/time-entries") +public class TimeEntryController { + + TimeEntryRepository timeEntryRepository; + + public TimeEntryController(TimeEntryRepository timeEntryRepository) { + this.timeEntryRepository = timeEntryRepository; + } + + @PostMapping + public ResponseEntity create(@RequestBody TimeEntry timeEntry) { + return new ResponseEntity(timeEntryRepository.create(timeEntry), HttpStatus.CREATED); + } + + @GetMapping("{id}") + public ResponseEntity read(@PathVariable long id) { + TimeEntry timeEntry = timeEntryRepository.find(id); + if (timeEntry == null) { + return new ResponseEntity(HttpStatus.NOT_FOUND); + } + + return new ResponseEntity(timeEntry, HttpStatus.OK); + } + + @GetMapping + public ResponseEntity> list() { + return new ResponseEntity(timeEntryRepository.list(), HttpStatus.OK); + } + + @PutMapping("{id}") + public ResponseEntity update(@PathVariable long id, @RequestBody TimeEntry timeEntry) { + TimeEntry update = timeEntryRepository.update(id, timeEntry); + if (update == null) { + return new ResponseEntity(HttpStatus.NOT_FOUND); + } + return new ResponseEntity(update, HttpStatus.OK); + } + + @DeleteMapping ("{id}") + public ResponseEntity delete(@PathVariable long id) { + timeEntryRepository.delete(id); + return new ResponseEntity( HttpStatus.NO_CONTENT); + } +} diff --git a/src/main/java/io/pivotal/pal/tracker/TimeEntryRepository.java b/src/main/java/io/pivotal/pal/tracker/TimeEntryRepository.java new file mode 100644 index 000000000..f6c5edf2c --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/TimeEntryRepository.java @@ -0,0 +1,18 @@ +package io.pivotal.pal.tracker; + +import org.springframework.http.ResponseEntity; + +import java.util.List; + +public interface TimeEntryRepository { + + public TimeEntry create(TimeEntry timeEntry); + + public TimeEntry find(long id); + + public List list(); + + public TimeEntry update(long id, TimeEntry timeEntry); + + public 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 32833afd9..b112e182c 100644 --- a/src/main/java/io/pivotal/pal/tracker/WelcomeController.java +++ b/src/main/java/io/pivotal/pal/tracker/WelcomeController.java @@ -8,7 +8,7 @@ public class WelcomeController { - public WelcomeController( @Value("${WELCOME_MESSAGE}") String messsge){ + public WelcomeController(@Value("${WELCOME_MESSAGE}") String messsge){ this.message=messsge; } diff --git a/src/test/java/test/pivotal/pal/tracker/EnvControllerTest.java b/src/test/java/test/pivotal/pal/tracker/EnvControllerTest.java index fda0f0f34..b04a6cd6f 100644 --- a/src/test/java/test/pivotal/pal/tracker/EnvControllerTest.java +++ b/src/test/java/test/pivotal/pal/tracker/EnvControllerTest.java @@ -1,9 +1,9 @@ package test.pivotal.pal.tracker; +import io.pivotal.pal.tracker.EnvController; import org.junit.Test; import java.util.Map; -import io.pivotal.pal.tracker.EnvController; import static org.assertj.core.api.Assertions.assertThat; @@ -11,10 +11,10 @@ public class EnvControllerTest { @Test public void getEnv() throws Exception { EnvController controller = new EnvController( - "8675", - "12G", - "34", - "123.sesame.street" + "8675", + "12G", + "34", + "123.sesame.street" ); Map env = controller.getEnv(); diff --git a/src/test/java/test/pivotal/pal/tracker/TimeEntryControllerTest.java b/src/test/java/test/pivotal/pal/tracker/TimeEntryControllerTest.java index d80f2b999..2150e5f66 100644 --- a/src/test/java/test/pivotal/pal/tracker/TimeEntryControllerTest.java +++ b/src/test/java/test/pivotal/pal/tracker/TimeEntryControllerTest.java @@ -32,8 +32,8 @@ 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)); + .when(timeEntryRepository) + .create(any(TimeEntry.class)); ResponseEntity response = controller.create(timeEntryToCreate); @@ -48,8 +48,8 @@ public void testCreate() throws Exception { public void testRead() throws Exception { TimeEntry expected = new TimeEntry(1L, 123L, 456L, LocalDate.parse("2017-01-08"), 8); doReturn(expected) - .when(timeEntryRepository) - .find(1L); + .when(timeEntryRepository) + .find(1L); ResponseEntity response = controller.read(1L); @@ -61,8 +61,8 @@ public void testRead() throws Exception { @Test public void testRead_NotFound() throws Exception { doReturn(null) - .when(timeEntryRepository) - .find(1L); + .when(timeEntryRepository) + .find(1L); ResponseEntity response = controller.read(1L); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); @@ -71,8 +71,8 @@ public void testRead_NotFound() throws Exception { @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) + 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(); @@ -87,8 +87,8 @@ public void testList() throws Exception { 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)); + .when(timeEntryRepository) + .update(eq(1L), any(TimeEntry.class)); ResponseEntity response = controller.update(1L, expected); @@ -100,8 +100,8 @@ public void testUpdate() throws Exception { @Test public void testUpdate_NotFound() throws Exception { doReturn(null) - .when(timeEntryRepository) - .update(eq(1L), any(TimeEntry.class)); + .when(timeEntryRepository) + .update(eq(1L), any(TimeEntry.class)); ResponseEntity response = controller.update(1L, new TimeEntry()); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); diff --git a/src/test/java/test/pivotal/pal/tracker/WelcomeControllerTest.java b/src/test/java/test/pivotal/pal/tracker/WelcomeControllerTest.java index bfa8271a0..f57910236 100644 --- a/src/test/java/test/pivotal/pal/tracker/WelcomeControllerTest.java +++ b/src/test/java/test/pivotal/pal/tracker/WelcomeControllerTest.java @@ -3,7 +3,7 @@ import io.pivotal.pal.tracker.WelcomeController; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Java6Assertions.assertThat; public class WelcomeControllerTest { From e5640d6a9faa74974bf0e537bb4d53ee9055a8ab Mon Sep 17 00:00:00 2001 From: Mike Gehard Date: Tue, 9 Jan 2018 09:47:21 -0700 Subject: [PATCH 06/12] Add task for migrating databases --- .travis.yml | 19 +++++++++++++++++-- scripts/migrate-databases.sh | 22 ++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100755 scripts/migrate-databases.sh diff --git a/.travis.yml b/.travis.yml index 998ffd83f..1e8b71cd9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,13 +6,18 @@ env: - RELEASE_TAG="release-$TRAVIS_BUILD_NUMBER" if: tag IS blank - jobs: include: - stage: build and publish language: java jdk: oraclejdk8 + addons: + mariadb: '10.2' install: skip + before_script: + - mysql -uroot < databases/tracker/create_databases.sql + - curl https://repo1.maven.org/maven2/org/flywaydb/flyway-commandline/5.1.1/flyway-commandline-5.1.1-linux-x64.tar.gz | tar xvz + - flyway-*/flyway -url="jdbc:mysql://localhost:3306/tracker_test" -locations=filesystem:databases/tracker -user=tracker -password= clean migrate script: ./gradlew clean build before_deploy: - git config --local user.name "Travis CI" @@ -24,12 +29,22 @@ jobs: file: "build/libs/pal-tracker.jar" skip_cleanup: true - stage: deploy to review - language: bash + language: java + before_install: + - wget -q -O - https://packages.cloudfoundry.org/debian/cli.cloudfoundry.org.key | sudo apt-key add - + - echo "deb https://packages.cloudfoundry.org/debian stable main" | sudo tee /etc/apt/sources.list.d/cloudfoundry-cli.list + - sudo apt-get update + - sudo apt-get install cf-cli + - sudo apt-get -y install jq + - curl https://repo1.maven.org/maven2/org/flywaydb/flyway-commandline/5.1.1/flyway-commandline-5.1.1-linux-x64.tar.gz | tar xvz script: - echo "Downloading $RELEASE_TAG" - wget -P build/libs https://github.com/$GITHUB_USERNAME/pal-tracker/releases/download/$RELEASE_TAG/pal-tracker.jar before_deploy: - echo "Deploying $RELEASE_TAG to review" + after_success: + - cf login -a $CF_API_URL -u $CF_USERNAME -p $CF_PASSWORD -o $CF_ORG -s review + - scripts/migrate-databases.sh pal-tracker . deploy: provider: cloudfoundry api: $CF_API_URL diff --git a/scripts/migrate-databases.sh b/scripts/migrate-databases.sh new file mode 100755 index 000000000..dc9d77f1c --- /dev/null +++ b/scripts/migrate-databases.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -e + + +app_guid=`cf app $1 --guid` +credentials=`cf curl /v2/apps/$app_guid/env | jq '.system_env_json.VCAP_SERVICES' | jq '.["p-mysql"][0].credentials'` + +ip_address=`echo $credentials | jq -r '.hostname'` +db_name=`echo $credentials | jq -r '.name'` +db_username=`echo $credentials | jq -r '.username'` +db_password=`echo $credentials | jq -r '.password'` + +echo "Opening ssh tunnel to $ip_address" +cf ssh -N -L 63306:$ip_address:3306 pal-tracker & +cf_ssh_pid=$! + +echo "Waiting for tunnel" +sleep 5 + +flyway-*/flyway -url="jdbc:mysql://127.0.0.1:63306/$db_name" -locations=filesystem:$2/databases/tracker -user=$db_username -password=$db_password migrate + +kill -STOP $cf_ssh_pid From 29073a1c7c3e66b468d61a7aba7321fa2533e69a Mon Sep 17 00:00:00 2001 From: Chandrashekhar Date: Tue, 31 Jul 2018 18:09:53 -0600 Subject: [PATCH 07/12] Added DB migration scripts and bound DB Service to pal-tracer app --- databases/tracker/create_databases.sql | 10 ++++++++++ databases/tracker/migrations/V1__initial_schema.sql | 11 +++++++++++ 2 files changed, 21 insertions(+) create mode 100644 databases/tracker/create_databases.sql create mode 100644 databases/tracker/migrations/V1__initial_schema.sql diff --git a/databases/tracker/create_databases.sql b/databases/tracker/create_databases.sql new file mode 100644 index 000000000..a0674bdda --- /dev/null +++ b/databases/tracker/create_databases.sql @@ -0,0 +1,10 @@ +DROP DATABASE IF EXISTS tracker_dev; +DROP DATABASE IF EXISTS tracker_test; + +CREATE DATABASE tracker_dev; +CREATE DATABASE tracker_test; + +CREATE USER IF NOT EXISTS 'tracker'@'localhost' + IDENTIFIED BY ''; +GRANT ALL PRIVILEGES ON tracker_dev.* TO 'tracker' @'localhost'; +GRANT ALL PRIVILEGES ON tracker_test.* TO 'tracker' @'localhost'; diff --git a/databases/tracker/migrations/V1__initial_schema.sql b/databases/tracker/migrations/V1__initial_schema.sql new file mode 100644 index 000000000..24a4432b1 --- /dev/null +++ b/databases/tracker/migrations/V1__initial_schema.sql @@ -0,0 +1,11 @@ +CREATE TABLE time_entries ( + id BIGINT(20) NOT NULL AUTO_INCREMENT, + project_id BIGINT(20), + user_id BIGINT(20), + date DATE, + hours INT, + + PRIMARY KEY (id) +) + ENGINE = innodb + DEFAULT CHARSET = utf8 \ No newline at end of file From 98ff33de8ed41d600e92a351e6b23ec2b59dc2d7 Mon Sep 17 00:00:00 2001 From: Tyson Gern Date: Wed, 26 Jul 2017 11:45:04 -0600 Subject: [PATCH 08/12] 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 7e435cfc2db218bacf67e4ac62764b41056cad69 Mon Sep 17 00:00:00 2001 From: Chandrshekhar Joshi Date: Wed, 1 Aug 2018 10:59:13 -0600 Subject: [PATCH 09/12] Added JDBCRepo to project --- build.gradle | 24 ++++- .../pal/tracker/JdbcTimeEntryRepository.class | Bin 0 -> 5824 bytes .../pal/tracker/PalTrackerApplication.class | Bin 2267 -> 2677 bytes .../tracker/JdbcTimeEntryRepositoryTest.class | Bin 0 -> 6379 bytes .../pal/trackerapi/TimeEntryApiTest.class | Bin 6651 -> 7430 bytes .../pal/tracker/JdbcTimeEntryRepository.java | 92 ++++++++++++++++++ .../pal/tracker/PalTrackerApplication.java | 8 +- .../pal/trackerapi/TimeEntryApiTest.java | 16 +++ 8 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 out/production/classes/io/pivotal/pal/tracker/JdbcTimeEntryRepository.class create mode 100644 out/test/classes/test/pivotal/pal/tracker/JdbcTimeEntryRepositoryTest.class create mode 100644 src/main/java/io/pivotal/pal/tracker/JdbcTimeEntryRepository.java diff --git a/build.gradle b/build.gradle index 853ba0046..aed97dd08 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 { @@ -11,12 +14,31 @@ dependencies { compile("org.springframework.boot:spring-boot-starter-web") testCompile("org.springframework.boot:spring-boot-starter-test") compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.1") + compile("org.springframework.boot:spring-boot-starter-jdbc") + compile("mysql:mysql-connector-java:6.0.6") } +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": "jdbc:mysql://localhost:3306/tracker_dev?user=tracker&useSSL=false&useTimezone=true&serverTimezone=UTC&useLegacyDatetimeCode=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": "jdbc:mysql://localhost:3306/tracker_test?user=tracker&useSSL=false&useTimezone=true&serverTimezone=UTC&useLegacyDatetimeCode=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/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..8cf85b5f149046f5362d9e31f314af4859f01009 GIT binary patch literal 5824 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 UpT!9n)+mpYIAx7)#~rx+znS-KUjP6A 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 844c8e4985a8684ae70aa46388aa21bf585b0702..1824b4be6cfdc2abe0f6b6d85654dc987e1f12cc 100644 GIT binary patch delta 962 zcmah{OHUI~6#i~oI_)qZg|<)>c|ThjP*emJNEQ zaY?|02n)uzyDZ>}2mx0`OyZgyQ<&!2bpbc*xQQ7KW(D{K%!z2?J(CDX~ z``Ti(veD+oO+{BqQEjJ~R^!DSL-XEG=a|T=|B4K~!L*i_^Sh;|IXRt`vzw{3yu#7^ zG0B)?p@E*rPC?J))kLP0N#)eAQdIJ)t`_};f~G5aMk|yUCJe>unoFrlp(H<6(z;gM zm6cNG=LnI{Zweb1xe$kOWK#=Jk~kA~urz`{m65`1C2VsFe&xD3B(ys{wS z78V)0|0`6&Z7ea2W;A&_QzltCd7DfxD(NR`QC_CI#pntP1--Z%QMa{HhAWid#Ik@p z5`tI}5Rwov3rI+~hx-zec)-waFl(n_G0)UL#F~V4 z_!vfvKg?rnnJe|ylB(DC?xJ7f?(hr+vr1WcCi7*V?00%N!4NRMTLZ?3CCVPXdujP$ za!B+>KyQJOJQsPlp$YE6R;pPE0S>g0muh((JZL9m$61`KabF9b3g?P2k$2Hsa-ZVc z(1G(*XXr#1%=8UdqiCk+K7=KCfVvf*w+ibagrx5Pwzu@f1b$*;jpc!jCN|JKu7aOI zokuEo){UMD9d9FgFZ!spp9oy|0L4Kvi4hng8!lmlf)_qwaUq$b3wqzfz7MgAddo*h zN%KA$qE$2!IFmFVqKQO0sL)(R%Za*nY6j`CqC18b^w8{Hn%74&x=t;?dnEO=Dl;xn Ws|lmWT4lm_$wbmOg;j)SJ%0h9zPX_Q delta 668 zcmZvaOHUI~7>1vjX?vJqkV`=X5kaY7iwsmiyn6_XY%4cS!CoPPKGn?5$;Pic<7r!D%M`g^0D z4u8g&s9v&};j%_DS9D6b^c>K}&G&g=v&f)E+#lDw{I{VuTFU>bQvPN*tu1}}8UCeLO?edL1vCU-_NY{j zL``BizK!-#5}i)jBlTu0U6R`k0mtc<%H#w+(g}vAMI8(`b?EUOjAw`QmYg6OC2j{E zRT0w5NjcEyz9S5Y|1cPw XsZ*bosRr4Q#5q}(P>yn)*}0y-^uS#y 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..05edba1aef48be8ace95cea90d4ebdf91d2109be GIT binary patch literal 6379 zcmcgw30Pd!75?80$qd7b5(0@D6JHXQfeabQ3L%;xfe0N)gds6nO((+x9u6~;nKwyr zZLKwG-CJ#IwR_cWwi+d1>e5AP?Y=MC)wbHbcCT%1|9jtiGjBqG=-03C@#fz1&OOV2 z|8wpgo__h^M**x>`~jSd%Y3*z01qzne-z|FWNfC}7NhPU9Ye%$86?PC760N##w z2t#-H@y;^biMs-L7v3Gfd+=UA-si{r{rG?n9}J)xcZ*l|1aPl-^`QVhjE@M(M+5j6 z?i1ENE=Hdaqx;3<0r5B}R2~fAA&mKP$PUW;yoh65c$x6yVKF&ehEL)V@p#lx+HVh( zr|f~}Za*IL<8epf89zSdj9(B&KJBbHOUypw$7lU`!iOgn__Dd(!+P9Q5Q-S7aN5|L zGGmEwntx^{7QaZ(grmlY-j*~oqdj^$l{L&%X0%y>I^4fI9@R(EiI}O2)v3&2IGfHG z$-#k4Y((Fe%3Kr{I^lRKqlY_P+FU(*gON1MGZlD)HQN=Gw5IxLA9+cxog(GMe3!2*-!5$h*XBb6$x{18w``dfGHn$*d2bQ*dHWE@{%yb|Y&rz?Ni^ z@X6OQc0w|oW1``+^Z}wk1B>Droq%;DvwFtNwz#k*O#Os4sqf1R6)c@`Q$#+x*c}Ep zkjf?d=@FM z-6^wOWT#+3!pNG|`b5I9Yv?3InPWrF%3WDSxP$?(K0>r%W^_fKi*ji)MK2`PC+7^& z+Ou-U@2K^Hp_E%o+8cweeTdiUT z&Q-8vMj9)q>fP4U(YO##qJkAuw1X+)5bnRE zq8CvGwa4BIRP=!j;DpU>k+x`?*50$VOA~PJ5}f8fs`#=9#@iQd_2DZjzKXA@_&UBp zK&FV#p@b-imGuql>gpTn>YIG{riyRj+ak>G`0!m7-^2G6RQD45xT)1@1DVuFk!Nk+ zke<;sqhC8i+sGZ2cFSMh87My&s>55HrD53x(JCjg5^VZS|V9A#LqCjtWsVGO8v<^&eucD5~NS$ULWb-Rx*oF!a9) zwV}SgemZVVhi%r|XK}N*5%~1| zdORlzqe4a~&oNQj=cSSqmNA;bEO;~ru3qnGvX=)K)MkAkmP_!;v%p?gWL&zpU@_@0 zEKf~QlPtq@ER*GD*zK=%D3JZ7+&eX~ZH!s3! z9kB}9gEJprH8ZO)ErQv>p2WLYr-I`OjEJO?gWQU}u|!VaIzX}nJKZ}iUNg5c=AIdC zPi1(`;H}J!jZKg9UG%W;54IiG^~7p`j)2iHsw3D|yw=r=8O15$%!y^iwQAV9a>mj| zxJ3%Yb&XqZy^RjVXm_JK%)H&B+t{5IS7FoAvMkbJ%`q$Uc4K7Q_T*xTXi7oTEBBoD zS!SkObX*Xeif&unK3Y>FRlN!s{4Pj4Mm{xf^kgMDYFmRG>Sd zDobd$gtB`MqheMBlkq%=xhOL$sASd|x^E`^VydJMsF)JXSZsHF|Mwk((X z$3g4Mb_iI!5Ib=?O8Klt6@mn#7L5p@8+A-{1@?3MZbd!rMuUW>)=JTar;6_eFi03Q z9KaAq9FJ<~nAX*63jbX(O4#VT9BRXTK z7qbRAtFmV+-AM55s&T9?_m5$Xguo*qSW36R8bYv^sWf6an)tgOThL4!-MJ?BMapu}C z&Wg6nv6!c#RgGt%oLQaA?YaQ-*a?@g{e)OK>)CqFW=(D<83)*QvfP}@NxJJLV^6+5t#>)Tn`*1~I;it&yj7p3M7^D;TZP(E zi7gc!qT2Jl D;EizM 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..b6217cc5b76ee74584406511c308e0e591f7a4e1 100644 GIT binary patch literal 7430 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 I*Jnll2llr1wEzGB 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..af771a445 --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/JdbcTimeEntryRepository.java @@ -0,0 +1,92 @@ +package io.pivotal.pal.tracker; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.ResultSetExtractor; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; + +import javax.sql.DataSource; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.util.List; + +import static java.sql.Statement.RETURN_GENERATED_KEYS; + +public class JdbcTimeEntryRepository implements TimeEntryRepository{ + + private final JdbcTemplate jdbcTemplate; + + + private final RowMapper mapper = (rs, rowNum) -> new TimeEntry( + rs.getLong("id"), + rs.getLong("project_id"), + rs.getLong("user_id"), + rs.getDate("date").toLocalDate(), + rs.getInt("hours") + ); + + private final ResultSetExtractor extractor = + (rs) -> rs.next() ? mapper.mapRow(rs, 1) : null; + + public JdbcTimeEntryRepository(DataSource dataSource){ + this.jdbcTemplate=new JdbcTemplate(dataSource); + + } + + @Override + public TimeEntry create(TimeEntry timeEntry) { + + KeyHolder generatedKeyHolder = new GeneratedKeyHolder(); + + jdbcTemplate.update(connection -> { + PreparedStatement statement = connection.prepareStatement( + "INSERT INTO time_entries (project_id, user_id, date, hours) " + + "VALUES (?, ?, ?, ?)", + RETURN_GENERATED_KEYS + ); + + statement.setLong(1, timeEntry.getProjectId()); + statement.setLong(2, timeEntry.getUserId()); + statement.setDate(3, Date.valueOf(timeEntry.getDate())); + statement.setInt(4, timeEntry.getHours()); + + return statement; + }, generatedKeyHolder); + + return find(generatedKeyHolder.getKey().longValue()); + } + + @Override + public TimeEntry find(long id) { + return jdbcTemplate.query( + "SELECT id, project_id, user_id, date, hours FROM time_entries WHERE id = ?", + new Object[]{id}, + extractor); + } + + @Override + public List list() { + return jdbcTemplate.query("SELECT id, project_id, user_id, date, hours FROM time_entries", mapper); + } + + @Override + public TimeEntry update(long id, TimeEntry timeEntry) { + jdbcTemplate.update("UPDATE time_entries " + + "SET project_id = ?, user_id = ?, date = ?, hours = ? " + + "WHERE id = ?", + timeEntry.getProjectId(), + timeEntry.getUserId(), + Date.valueOf(timeEntry.getDate()), + timeEntry.getHours(), + id); + + return find(id); + } + + @Override + public void delete(long id) { + jdbcTemplate.update("DELETE FROM time_entries WHERE id = ?", id); + + } +} diff --git a/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java b/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java index 3e030bab0..b400b46c2 100644 --- a/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java +++ b/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java @@ -6,6 +6,8 @@ import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.util.ISO8601DateFormat; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.mysql.cj.jdbc.MysqlDataSource; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; @@ -19,8 +21,10 @@ public static void main(String[] args) SpringApplication.run(PalTrackerApplication.class, args); } @Bean - public TimeEntryRepository getRepository(){ - return new InMemoryTimeEntryRepository(); + public TimeEntryRepository getRepository(@Value("${SPRING_DATASOURCE_URL}") final String datasSourceUrl) { + MysqlDataSource dataSource = new MysqlDataSource(); + dataSource.setUrl(datasSourceUrl); + return new JdbcTimeEntryRepository(dataSource); } @Bean diff --git a/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java b/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java index 91e271b45..1484a8d4e 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,10 +14,12 @@ 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; import java.util.Collection; +import java.util.TimeZone; import static com.jayway.jsonpath.JsonPath.parse; import static org.assertj.core.api.Assertions.assertThat; @@ -25,6 +29,18 @@ @SpringBootTest(classes = PalTrackerApplication.class, webEnvironment = RANDOM_PORT) public class TimeEntryApiTest { + @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"); + + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + } + + @Autowired private TestRestTemplate restTemplate; From 576426a81e3961657d0e26f4da6f1754199dd9e7 Mon Sep 17 00:00:00 2001 From: Chandrshekhar Joshi Date: Wed, 1 Aug 2018 11:14:50 -0600 Subject: [PATCH 10/12] Fixed Spring App to get DataSource as constructor injection --- .../io/pivotal/pal/tracker/PalTrackerApplication.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java b/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java index b400b46c2..a9629f96b 100644 --- a/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java +++ b/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java @@ -13,6 +13,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; +import javax.sql.DataSource; + @SpringBootApplication public class PalTrackerApplication { @@ -20,10 +22,10 @@ public static void main(String[] args) { SpringApplication.run(PalTrackerApplication.class, args); } + + @Bean - public TimeEntryRepository getRepository(@Value("${SPRING_DATASOURCE_URL}") final String datasSourceUrl) { - MysqlDataSource dataSource = new MysqlDataSource(); - dataSource.setUrl(datasSourceUrl); + public TimeEntryRepository getRepository(DataSource dataSource) { return new JdbcTimeEntryRepository(dataSource); } From 2d91dfd27e92fbbfa271c8610aa8512284b259ec Mon Sep 17 00:00:00 2001 From: Tyson Gern Date: Wed, 26 Jul 2017 12:28:32 -0600 Subject: [PATCH 11/12] 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 7d07d3416e4bddffee0aca7524cb2f668992feff Mon Sep 17 00:00:00 2001 From: Chandrshekhar Joshi Date: Wed, 1 Aug 2018 12:12:08 -0600 Subject: [PATCH 12/12] Added SpringBoot Actuator Hooks --- build.gradle | 13 ++++++--- .../pal/tracker/TimeEntryController.java | 25 ++++++++++++++-- .../pal/tracker/TimeEntryHealthIndicator.java | 29 +++++++++++++++++++ .../pal/tracker/TimeEntryControllerTest.java | 10 ++++++- 4 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 src/main/java/io/pivotal/pal/tracker/TimeEntryHealthIndicator.java diff --git a/build.gradle b/build.gradle index aed97dd08..c4bd82acf 100644 --- a/build.gradle +++ b/build.gradle @@ -16,20 +16,21 @@ dependencies { compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.1") compile("org.springframework.boot:spring-boot-starter-jdbc") compile("mysql:mysql-connector-java:6.0.6") + compile("org.springframework.boot:spring-boot-starter-actuator") } def developmentDbUrl = "jdbc:mysql://localhost:3306/tracker_dev?user=tracker&useSSL=false&useTimezone=true&serverTimezone=UTC&useLegacyDatetimeCode=false" - bootRun.environment([ "WELCOME_MESSAGE": "hello", - "SPRING_DATASOURCE_URL": "jdbc:mysql://localhost:3306/tracker_dev?user=tracker&useSSL=false&useTimezone=true&serverTimezone=UTC&useLegacyDatetimeCode=false" + "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", - "SPRING_DATASOURCE_URL": "jdbc:mysql://localhost:3306/tracker_test?user=tracker&useSSL=false&useTimezone=true&serverTimezone=UTC&useLegacyDatetimeCode=false" + "SPRING_DATASOURCE_URL": testDbUrl, + "MANAGEMENT_SECURITY_ENABLED": false ]) flyway { @@ -41,4 +42,8 @@ flyway { task testMigrate(type: FlywayMigrateTask) { url = testDbUrl +} + +springBoot { + buildInfo() } \ 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 487ec546b..5eded2b77 100644 --- a/src/main/java/io/pivotal/pal/tracker/TimeEntryController.java +++ b/src/main/java/io/pivotal/pal/tracker/TimeEntryController.java @@ -1,5 +1,7 @@ package io.pivotal.pal.tracker; +import org.springframework.boot.actuate.metrics.CounterService; +import org.springframework.boot.actuate.metrics.GaugeService; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -10,15 +12,25 @@ @RequestMapping("/time-entries") public class TimeEntryController { - TimeEntryRepository timeEntryRepository; + private final CounterService counter; + private final GaugeService gauge; + private final TimeEntryRepository timeEntryRepository; - public TimeEntryController(TimeEntryRepository timeEntryRepository) { + public TimeEntryController(TimeEntryRepository timeEntryRepository, + CounterService counter, + GaugeService gauge) { this.timeEntryRepository = timeEntryRepository; + this.counter = counter; + this.gauge = gauge; } @PostMapping public ResponseEntity create(@RequestBody TimeEntry timeEntry) { - return new ResponseEntity(timeEntryRepository.create(timeEntry), HttpStatus.CREATED); + TimeEntry createdTimeEntry = timeEntryRepository.create(timeEntry); + counter.increment("TimeEntry.created"); + gauge.submit("timeEntries.count", timeEntryRepository.list().size()); + + return new ResponseEntity<>(createdTimeEntry, HttpStatus.CREATED); } @GetMapping("{id}") @@ -26,6 +38,8 @@ public ResponseEntity read(@PathVariable long id) { TimeEntry timeEntry = timeEntryRepository.find(id); if (timeEntry == null) { return new ResponseEntity(HttpStatus.NOT_FOUND); + } else { + counter.increment("TimeEntry.read"); } return new ResponseEntity(timeEntry, HttpStatus.OK); @@ -33,6 +47,7 @@ public ResponseEntity read(@PathVariable long id) { @GetMapping public ResponseEntity> list() { + counter.increment("TimeEntry.listed"); return new ResponseEntity(timeEntryRepository.list(), HttpStatus.OK); } @@ -41,6 +56,8 @@ public ResponseEntity update(@PathVariable long id, @RequestBody Time TimeEntry update = timeEntryRepository.update(id, timeEntry); if (update == null) { return new ResponseEntity(HttpStatus.NOT_FOUND); + } else { + counter.increment("TimeEntry.updated"); } return new ResponseEntity(update, HttpStatus.OK); } @@ -48,6 +65,8 @@ public ResponseEntity update(@PathVariable long id, @RequestBody Time @DeleteMapping ("{id}") public ResponseEntity delete(@PathVariable long id) { timeEntryRepository.delete(id); + 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..d5f917800 --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/TimeEntryHealthIndicator.java @@ -0,0 +1,29 @@ +package io.pivotal.pal.tracker; + +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.stereotype.Component; + +@Component +public class TimeEntryHealthIndicator implements HealthIndicator { + + private static final int MAX_TIME_ENTRIES = 5; + private final TimeEntryRepository timeEntryRepo; + + public TimeEntryHealthIndicator(TimeEntryRepository timeEntryRepo) { + this.timeEntryRepo = timeEntryRepo; + } + + @Override + public Health health() { + Health.Builder builder = new Health.Builder(); + + if(timeEntryRepo.list().size() < MAX_TIME_ENTRIES) { + builder.up(); + } else { + builder.down(); + } + + return builder.build(); + } +} diff --git a/src/test/java/test/pivotal/pal/tracker/TimeEntryControllerTest.java b/src/test/java/test/pivotal/pal/tracker/TimeEntryControllerTest.java index 2150e5f66..236b62c42 100644 --- a/src/test/java/test/pivotal/pal/tracker/TimeEntryControllerTest.java +++ b/src/test/java/test/pivotal/pal/tracker/TimeEntryControllerTest.java @@ -5,6 +5,9 @@ import io.pivotal.pal.tracker.TimeEntryRepository; import org.junit.Before; import org.junit.Test; +import org.mockito.Mock; +import org.springframework.boot.actuate.metrics.CounterService; +import org.springframework.boot.actuate.metrics.GaugeService; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -21,10 +24,15 @@ public class TimeEntryControllerTest { private TimeEntryRepository timeEntryRepository; private TimeEntryController controller; + private GaugeService gauge; + private CounterService counter; + @Before public void setUp() throws Exception { timeEntryRepository = mock(TimeEntryRepository.class); - controller = new TimeEntryController(timeEntryRepository); + gauge = mock(GaugeService.class); + counter = mock(CounterService.class); + controller = new TimeEntryController(timeEntryRepository, counter, gauge); } @Test