From a9ac4b9599815e92a936715c84f489824ed59705 Mon Sep 17 00:00:00 2001 From: Tyson Gern Date: Wed, 26 Jul 2017 11:45:04 -0600 Subject: [PATCH 01/18] 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 46d64088a2c9024d6bafe67b5d2df7e1c6434f76 Mon Sep 17 00:00:00 2001 From: pal_user Date: Thu, 22 Feb 2018 11:47:05 -0800 Subject: [PATCH 02/18] add jdbc support --- build.gradle | 22 ++++- .../pal/tracker/JdbcTimeEntryRepository.java | 88 +++++++++++++++++++ .../tracker/JdbcTimeEntryRepositoryTest.java | 2 +- .../pal/trackerapi/TimeEntryApiTest.java | 15 ++++ 4 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 src/main/java/io/pivotal/pal/tracker/JdbcTimeEntryRepository.java diff --git a/build.gradle b/build.gradle index e6dae2a9d..7867f134c 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.10.RELEASE' + id "org.flywaydb.flyway" version "4.2.0" } repositories { @@ -10,13 +13,30 @@ repositories { dependencies { compile("org.springframework.boot:spring-boot-starter-web") compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.1") + compile("org.springframework.boot:spring-boot-starter-jdbc") + compile("mysql:mysql-connector-java:6.0.6") testCompile("org.springframework.boot:spring-boot-starter-test") } +def developmentDbUrl = "jdbc:mysql://localhost:3306/tracker_dev?user=tracker&useSSL=false&useTimezone=true&serverTimezone=UTC&useLegacyDatetimeCode=false" bootRun.environment([ "WELCOME_MESSAGE": "hello", + "SPRING_DATASOURCE_URL": developmentDbUrl, ]) +def testDbUrl = "jdbc:mysql://localhost:3306/tracker_test?user=tracker&useSSL=false&useTimezone=true&serverTimezone=UTC&useLegacyDatetimeCode=false" test.environment([ "WELCOME_MESSAGE": "Hello from test", -]) \ No newline at end of file + "SPRING_DATASOURCE_URL": testDbUrl, +]) + +flyway { + url = developmentDbUrl + user = "tracker" + password = "" + locations = ["filesystem:databases/tracker/migrations"] +} + +task testMigrate(type: FlywayMigrateTask) { + url = testDbUrl +} \ No newline at end of file diff --git a/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..4e93ae208 --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/JdbcTimeEntryRepository.java @@ -0,0 +1,88 @@ +package io.pivotal.pal.tracker; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.PreparedStatementCreator; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.util.CollectionUtils; + +import javax.sql.DataSource; +import java.sql.*; +import java.util.List; + +public class JdbcTimeEntryRepository implements TimeEntryRepository { + + private JdbcTemplate jdbcTemplate; + + public JdbcTimeEntryRepository(DataSource ds) { + this.jdbcTemplate = new JdbcTemplate(ds); + } + + private RowMapper rowMapper = new RowMapper() { + + @Override + public TimeEntry mapRow(ResultSet rs, int rowNum) throws SQLException { + TimeEntry timeEntry = new TimeEntry(rs.getLong("id"), rs.getLong("project_id"), rs.getLong("user_id"), rs.getDate("date").toLocalDate(), rs.getInt("hours")); + return timeEntry; + } + }; + + @Override + public TimeEntry create(TimeEntry timeEntry) { + final KeyHolder holder = new GeneratedKeyHolder(); + final PreparedStatementCreator psc = new PreparedStatementCreator() { + @Override + public PreparedStatement createPreparedStatement(final Connection connection) throws SQLException { + final PreparedStatement ps = connection.prepareStatement("insert into time_entries (project_id, user_id,date,hours) values (?,?,?,?)", + Statement.RETURN_GENERATED_KEYS); + ps.setLong(1, timeEntry.getProjectId()); + ps.setLong(2, timeEntry.getUserId()); + ps.setDate(3, Date.valueOf(timeEntry.getDate())); + ps.setInt(4, timeEntry.getHours()); + + return ps; + } + }; + jdbcTemplate.update(psc, holder); + timeEntry.setId(holder.getKey().longValue()); + return timeEntry; + } + + @Override + public TimeEntry find(long id) { + List timeEntryList = jdbcTemplate.query("select * from time_entries where id =?", new Object[]{id}, rowMapper); + if (!CollectionUtils.isEmpty(timeEntryList)) { + return timeEntryList.get(0); + } + return null; + } + + @Override + public List list() { + List timeEntryList = jdbcTemplate.query("select * from time_entries", rowMapper); + return timeEntryList; + } + + @Override + public TimeEntry update(long id, TimeEntry timeEntry) { + TimeEntry timeEntryFound = this.find(id); + if (timeEntryFound != null) { + timeEntry.setId(id); + 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 timeEntry; + } + return null; + } + + @Override + public TimeEntry delete(long id) { + TimeEntry timeEntry = this.find(id); + jdbcTemplate.update("delete from time_entries where id =?", id); + if (timeEntry != null) { + return timeEntry; + } + return null; + } +} diff --git a/src/test/java/test/pivotal/pal/tracker/JdbcTimeEntryRepositoryTest.java b/src/test/java/test/pivotal/pal/tracker/JdbcTimeEntryRepositoryTest.java index e1eac20fc..904894e64 100644 --- a/src/test/java/test/pivotal/pal/tracker/JdbcTimeEntryRepositoryTest.java +++ b/src/test/java/test/pivotal/pal/tracker/JdbcTimeEntryRepositoryTest.java @@ -29,7 +29,7 @@ public void setUp() throws Exception { subject = new JdbcTimeEntryRepository(dataSource); jdbcTemplate = new JdbcTemplate(dataSource); - jdbcTemplate.execute("DELETE FROM time_entries"); + jdbcTemplate.execute("TRUNCATE time_entries"); TimeZone.setDefault(TimeZone.getTimeZone("UTC")); } diff --git a/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java b/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java index 91e271b45..a3368d935 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; @@ -30,6 +34,17 @@ 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"); + + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + } + @Test public void testCreate() throws Exception { ResponseEntity createResponse = restTemplate.postForEntity("/time-entries", timeEntry, String.class); From 4d8a8a36853d3435d90c34fcb76d3f7de2e9914a Mon Sep 17 00:00:00 2001 From: pal_user Date: Thu, 22 Feb 2018 11:50:36 -0800 Subject: [PATCH 03/18] add jdbc support --- ci/build.yml | 15 +++++++++++++-- manifest-review.yml | 2 ++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/ci/build.yml b/ci/build.yml index 4ba28db53..2455d052b 100644 --- a/ci/build.yml +++ b/ci/build.yml @@ -18,7 +18,18 @@ run: args: - -exc - | + + export DEBIAN_FRONTEND="noninteractive" + + apt-get update + + apt-get -y install mysql-server + service mysql start + cd pal-tracker + mysql -uroot < databases/tracker/create_databases.sql chmod +x gradlew - ./gradlew -P version=$(cat ../version/number) build - cp build/libs/pal-tracker-*.jar ../build-output + ./gradlew -P version=$(cat ../version/number) testMigrate clean build || (service mysql stop && exit 1) + service mysql stop + + cp build/libs/pal-tracker-*.jar ../build-output \ No newline at end of file diff --git a/manifest-review.yml b/manifest-review.yml index dba79b5b0..c05d72bf9 100644 --- a/manifest-review.yml +++ b/manifest-review.yml @@ -3,3 +3,5 @@ applications: - name: pal-tracker path: build/libs/pal-tracker.jar host: runiu2-pal-tracker-review2 + services: + - tracker-database \ No newline at end of file From 5488b62a8b807ead3e328849d7e75060ec9b656d Mon Sep 17 00:00:00 2001 From: pal_user Date: Thu, 22 Feb 2018 11:57:56 -0800 Subject: [PATCH 04/18] add jdbc support --- databases/tracker/migrations/V1__initial_schema.sql | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 databases/tracker/migrations/V1__initial_schema.sql diff --git a/databases/tracker/migrations/V1__initial_schema.sql b/databases/tracker/migrations/V1__initial_schema.sql new file mode 100644 index 000000000..daca8c4e3 --- /dev/null +++ b/databases/tracker/migrations/V1__initial_schema.sql @@ -0,0 +1,11 @@ +CREATE TABLE time_entries ( + id BIGINT(20) NOT NULL AUTO_INCREMENT, + project_id BIGINT(20), + user_id BIGINT(20), + date DATE, + hours INT, + + PRIMARY KEY (id) +) + ENGINE = innodb + DEFAULT CHARSET = utf8; \ No newline at end of file From 481ffc4a4c0d020188f8450e65cd9c27087a7aaa Mon Sep 17 00:00:00 2001 From: pal_user Date: Thu, 22 Feb 2018 11:59:52 -0800 Subject: [PATCH 05/18] add jdbc support --- .../java/io/pivotal/pal/tracker/PalTrackerApplication.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java b/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java index a91989c33..6e30703f9 100644 --- a/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java +++ b/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java @@ -9,12 +9,14 @@ import org.springframework.context.annotation.Bean; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; +import javax.sql.DataSource; + @SpringBootApplication public class PalTrackerApplication { @Bean - public TimeEntryRepository repo(){ - return new InMemoryTimeEntryRepository(); + public TimeEntryRepository repo(DataSource ds){ + return new JdbcTimeEntryRepository(ds); } @Bean From 286d118c1986754a5e98fbf6b7ec51d50ccd8b56 Mon Sep 17 00:00:00 2001 From: pal_user Date: Thu, 22 Feb 2018 12:01:57 -0800 Subject: [PATCH 06/18] add jdbc support --- databases/tracker/V1__initial_schema.sql | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 databases/tracker/V1__initial_schema.sql diff --git a/databases/tracker/V1__initial_schema.sql b/databases/tracker/V1__initial_schema.sql deleted file mode 100644 index daca8c4e3..000000000 --- a/databases/tracker/V1__initial_schema.sql +++ /dev/null @@ -1,11 +0,0 @@ -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 4253717634e0fd5efa2028bfdf4a540d1da678b6 Mon Sep 17 00:00:00 2001 From: pal_user Date: Thu, 22 Feb 2018 15:17:02 -0800 Subject: [PATCH 07/18] add jpa suppport --- build.gradle | 1 + .../pal/tracker/LocalDateConverter.java | 20 +++ .../pal/tracker/PalTrackerApplication.java | 2 + .../io/pivotal/pal/tracker/TimeEntry.java | 27 ++++ .../pal/tracker/TimeEntryJPAController.java | 64 ++++++++ .../pal/tracker/TimeEntryJPARepository.java | 8 + .../pal/trackerapi/TimeEntryJPAApiTest.java | 141 ++++++++++++++++++ 7 files changed, 263 insertions(+) create mode 100644 src/main/java/io/pivotal/pal/tracker/LocalDateConverter.java create mode 100644 src/main/java/io/pivotal/pal/tracker/TimeEntryJPAController.java create mode 100644 src/main/java/io/pivotal/pal/tracker/TimeEntryJPARepository.java create mode 100644 src/test/java/test/pivotal/pal/trackerapi/TimeEntryJPAApiTest.java diff --git a/build.gradle b/build.gradle index 7867f134c..80a7e5ccf 100644 --- a/build.gradle +++ b/build.gradle @@ -14,6 +14,7 @@ dependencies { compile("org.springframework.boot:spring-boot-starter-web") compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.1") compile("org.springframework.boot:spring-boot-starter-jdbc") + compile("org.springframework.boot:spring-boot-starter-data-jpa") compile("mysql:mysql-connector-java:6.0.6") testCompile("org.springframework.boot:spring-boot-starter-test") } diff --git a/src/main/java/io/pivotal/pal/tracker/LocalDateConverter.java b/src/main/java/io/pivotal/pal/tracker/LocalDateConverter.java new file mode 100644 index 000000000..9293f14ad --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/LocalDateConverter.java @@ -0,0 +1,20 @@ +package io.pivotal.pal.tracker; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; +import java.lang.annotation.Annotation; +import java.sql.Date; +import java.time.LocalDate; + +@Converter(autoApply=true) +public class LocalDateConverter implements AttributeConverter { + @Override + public Date convertToDatabaseColumn(LocalDate localDate) { + return Date.valueOf(localDate); + } + + @Override + public LocalDate convertToEntityAttribute(Date date) { + return date.toLocalDate(); + } +} diff --git a/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java b/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java index 6e30703f9..2b45bcec8 100644 --- a/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java +++ b/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java @@ -7,11 +7,13 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import javax.sql.DataSource; @SpringBootApplication +@EnableJpaRepositories public class PalTrackerApplication { @Bean diff --git a/src/main/java/io/pivotal/pal/tracker/TimeEntry.java b/src/main/java/io/pivotal/pal/tracker/TimeEntry.java index 71e80d9e8..947986a77 100644 --- a/src/main/java/io/pivotal/pal/tracker/TimeEntry.java +++ b/src/main/java/io/pivotal/pal/tracker/TimeEntry.java @@ -1,13 +1,25 @@ package io.pivotal.pal.tracker; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import java.io.Serializable; import java.time.LocalDate; import java.util.Objects; +@Entity(name="time_entries") public class TimeEntry { + @Id + @GeneratedValue private long id; + @Column(name="project_id") private long projectId; + @Column(name="user_id") private long userId; + @Column(name="date") private LocalDate date; + @Column(name="hours") private int hours; public TimeEntry(){ @@ -67,4 +79,19 @@ public int hashCode() { return Objects.hash(id, projectId, userId, date, hours); } + public void setProjectId(long projectId) { + this.projectId = projectId; + } + + public void setUserId(long userId) { + this.userId = userId; + } + + public void setDate(LocalDate date) { + this.date = date; + } + + public void setHours(int hours) { + this.hours = hours; + } } diff --git a/src/main/java/io/pivotal/pal/tracker/TimeEntryJPAController.java b/src/main/java/io/pivotal/pal/tracker/TimeEntryJPAController.java new file mode 100644 index 000000000..879df7d6d --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/TimeEntryJPAController.java @@ -0,0 +1,64 @@ +package io.pivotal.pal.tracker; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.ArrayList; +import java.util.List; + +@RestController +@RequestMapping("/time-entries-jpa") +public class TimeEntryJPAController { + private TimeEntryJPARepository timeEntryRepository; + + public TimeEntryJPAController(TimeEntryJPARepository timeEntryRepository) { + this.timeEntryRepository = timeEntryRepository; + } + + @PostMapping + public ResponseEntity create(@RequestBody TimeEntry timeEntry) { + timeEntryRepository.save(timeEntry); + return ResponseEntity.status(201).body(timeEntry); + } + + @GetMapping("{id}") + public ResponseEntity read(@PathVariable long id) { + TimeEntry timeEntry = this.timeEntryRepository.findOne(id); + if (timeEntry != null) { + return ResponseEntity.ok(timeEntry); + } + return ResponseEntity.status(404).body(null); + } + + @GetMapping + public ResponseEntity> list() { + Iterable all = this.timeEntryRepository.findAll(); + List list = new ArrayList<>(); + all.forEach(a -> list.add(a)); + return ResponseEntity.ok(list); + } + + @PutMapping("{id}") + public ResponseEntity update(@PathVariable long id, @RequestBody TimeEntry timeEntry) { + TimeEntry update = this.timeEntryRepository.findOne(id); + if (update != null) { + update.setDate(timeEntry.getDate()); + update.setHours(timeEntry.getHours()); + update.setProjectId(timeEntry.getProjectId()); + update.setUserId(timeEntry.getUserId()); + timeEntryRepository.save(update); + return ResponseEntity.ok(update); + } + return ResponseEntity.status(404).body(null); + } + + @RequestMapping(method = RequestMethod.DELETE, path = "{id}") + public ResponseEntity delete(@PathVariable long id) { + TimeEntry update = this.timeEntryRepository.findOne(id); + if (update != null) { + this.timeEntryRepository.delete(update); + } + return ResponseEntity.status(204).body(null); + } + +} diff --git a/src/main/java/io/pivotal/pal/tracker/TimeEntryJPARepository.java b/src/main/java/io/pivotal/pal/tracker/TimeEntryJPARepository.java new file mode 100644 index 000000000..7411a5288 --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/TimeEntryJPARepository.java @@ -0,0 +1,8 @@ +package io.pivotal.pal.tracker; + +import org.springframework.data.repository.CrudRepository; + +import java.util.List; + +public interface TimeEntryJPARepository extends CrudRepository{ +} diff --git a/src/test/java/test/pivotal/pal/trackerapi/TimeEntryJPAApiTest.java b/src/test/java/test/pivotal/pal/trackerapi/TimeEntryJPAApiTest.java new file mode 100644 index 000000000..189f1b78d --- /dev/null +++ b/src/test/java/test/pivotal/pal/trackerapi/TimeEntryJPAApiTest.java @@ -0,0 +1,141 @@ +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; +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.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; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = PalTrackerApplication.class, webEnvironment = RANDOM_PORT) +public class TimeEntryJPAApiTest { + + @Autowired + private TestRestTemplate restTemplate; + + 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"); + + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + } + + @Test + public void testCreate() throws Exception { + ResponseEntity createResponse = restTemplate.postForEntity("/time-entries-jpa", 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-jpa", 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-jpa/" + 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-jpa/" + 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-jpa/" + id, HttpMethod.DELETE, null, String.class); + + + assertThat(deleteResponse.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT); + + ResponseEntity deletedReadResponse = this.restTemplate.getForEntity("/time-entries-jpa/" + id, String.class); + assertThat(deletedReadResponse.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); + } + + private Long createTimeEntry() { + HttpEntity entity = new HttpEntity<>(timeEntry); + + ResponseEntity response = restTemplate.exchange("/time-entries-jpa", HttpMethod.POST, entity, TimeEntry.class); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED); + + return response.getBody().getId(); + } +} From aa91c218404a9260524e4fbb16ed28900e31acd5 Mon Sep 17 00:00:00 2001 From: Tyson Gern Date: Wed, 26 Jul 2017 12:28:32 -0600 Subject: [PATCH 08/18] 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 f325c4caf02d2461676a9143e44ffa3e3c80b9b1 Mon Sep 17 00:00:00 2001 From: pal_user Date: Thu, 22 Feb 2018 15:54:34 -0800 Subject: [PATCH 09/18] add actuator --- build.gradle | 6 ++++ .../pal/tracker/TimeEntryController.java | 32 ++++++++++++++----- .../pal/tracker/TimeEntryHealthIndicator.java | 27 ++++++++++++++++ .../pal/tracker/TimeEntryControllerTest.java | 5 ++- 4 files changed, 61 insertions(+), 9 deletions(-) create mode 100644 src/main/java/io/pivotal/pal/tracker/TimeEntryHealthIndicator.java diff --git a/build.gradle b/build.gradle index 80a7e5ccf..ad6d428ac 100644 --- a/build.gradle +++ b/build.gradle @@ -16,6 +16,7 @@ dependencies { compile("org.springframework.boot:spring-boot-starter-jdbc") compile("org.springframework.boot:spring-boot-starter-data-jpa") compile("mysql:mysql-connector-java:6.0.6") + compile("org.springframework.boot:spring-boot-starter-actuator") testCompile("org.springframework.boot:spring-boot-starter-test") } @@ -23,12 +24,14 @@ def developmentDbUrl = "jdbc:mysql://localhost:3306/tracker_dev?user=tracker&use 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", "SPRING_DATASOURCE_URL": testDbUrl, + "MANAGEMENT_SECURITY_ENABLED": false, ]) flyway { @@ -40,4 +43,7 @@ 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 2019b3d52..eb4589f31 100644 --- a/src/main/java/io/pivotal/pal/tracker/TimeEntryController.java +++ b/src/main/java/io/pivotal/pal/tracker/TimeEntryController.java @@ -1,50 +1,66 @@ package io.pivotal.pal.tracker; +import org.springframework.boot.actuate.metrics.CounterService; +import org.springframework.boot.actuate.metrics.GaugeService; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController +@RequestMapping("/time-entries") public class TimeEntryController { private TimeEntryRepository timeEntryRepository; + private final CounterService counter; + private final GaugeService gauge; - public TimeEntryController(TimeEntryRepository timeEntryRepository) { + public TimeEntryController(TimeEntryRepository timeEntryRepository, CounterService counter, GaugeService gauge + ) { this.timeEntryRepository = timeEntryRepository; + this.counter = counter; + this.gauge = gauge; } - @PostMapping("/time-entries") + @PostMapping("") public ResponseEntity create(@RequestBody TimeEntry timeEntry) { - return ResponseEntity.status(201).body(this.timeEntryRepository.create(timeEntry)); + TimeEntry entry = this.timeEntryRepository.create(timeEntry); + counter.increment("TimeEntry.created"); + gauge.submit("timeEntries.count", timeEntryRepository.list().size()); + return ResponseEntity.status(201).body(entry); } - @GetMapping("/time-entries/{id}") + @GetMapping("{id}") public ResponseEntity read(@PathVariable long id) { TimeEntry timeEntry = this.timeEntryRepository.find(id); if (timeEntry != null) { + counter.increment("TimeEntry.read"); return ResponseEntity.ok(timeEntry); } return ResponseEntity.status(404).body(null); } - @GetMapping("/time-entries") + @GetMapping("") public ResponseEntity> list() { + counter.increment("TimeEntry.listed"); return ResponseEntity.ok(this.timeEntryRepository.list()); } - @PutMapping("/time-entries/{id}") + @PutMapping("{id}") public ResponseEntity update(@PathVariable long id, @RequestBody TimeEntry timeEntry) { TimeEntry update = this.timeEntryRepository.update(id, timeEntry); if (update != null) { + counter.increment("TimeEntry.updated"); return ResponseEntity.ok(update); } return ResponseEntity.status(404).body(null); } - @RequestMapping(method = RequestMethod.DELETE, path = "/time-entries/{id}") + @RequestMapping(method = RequestMethod.DELETE, path = "{id}") public ResponseEntity delete(@PathVariable long id) { this.timeEntryRepository.delete(id); - return ResponseEntity.status(204).body(null); + counter.increment("TimeEntry.deleted"); + gauge.submit("timeEntries.count", timeEntryRepository.list().size()); + return ResponseEntity.status(204).body(null); } } 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..7d942d1a4 --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/TimeEntryHealthIndicator.java @@ -0,0 +1,27 @@ +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 2013c28ec..5eac64f02 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; @@ -16,6 +18,7 @@ import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; public class TimeEntryControllerTest { private TimeEntryRepository timeEntryRepository; @@ -24,7 +27,7 @@ public class TimeEntryControllerTest { @Before public void setUp() throws Exception { timeEntryRepository = mock(TimeEntryRepository.class); - controller = new TimeEntryController(timeEntryRepository); + controller = new TimeEntryController(timeEntryRepository, mock(CounterService.class), mock(GaugeService.class)); } @Test From 9a21d7fe8b15a9799809d676884fc7767ec84e14 Mon Sep 17 00:00:00 2001 From: pal_user Date: Fri, 23 Feb 2018 09:33:45 -0800 Subject: [PATCH 10/18] add builder.withDetail(example, Collections.singletonMap(author, runiu)) --- .../pal/tracker/ExampleInfoContributor.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/main/java/io/pivotal/pal/tracker/ExampleInfoContributor.java diff --git a/src/main/java/io/pivotal/pal/tracker/ExampleInfoContributor.java b/src/main/java/io/pivotal/pal/tracker/ExampleInfoContributor.java new file mode 100644 index 000000000..d5f9dae71 --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/ExampleInfoContributor.java @@ -0,0 +1,16 @@ +package io.pivotal.pal.tracker; + +import org.springframework.boot.actuate.info.Info; +import org.springframework.boot.actuate.info.InfoContributor; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +import java.util.Collections; + +@Component +public class ExampleInfoContributor implements InfoContributor { + @Override + public void contribute(Info.Builder builder) { + builder.withDetail("example", Collections.singletonMap("author", "runiu")); + } +} From b1a2c7596eb1a60b5fc66607cd0c5047b2899767 Mon Sep 17 00:00:00 2001 From: Tyson Gern Date: Wed, 26 Jul 2017 12:50:47 -0600 Subject: [PATCH 11/18] 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 5442be497e2e6cdb82c23e4883deb87ee88b3ce5 Mon Sep 17 00:00:00 2001 From: pal_user Date: Fri, 23 Feb 2018 10:07:11 -0800 Subject: [PATCH 12/18] add security --- build.gradle | 1 + manifest-production.yml | 2 ++ manifest-review.yml | 6 ++-- .../pal/tracker/SecurityConfiguration.java | 31 +++++++++++++++++++ .../pivotal/pal/trackerapi/HealthApiTest.java | 15 ++++++++- .../pal/trackerapi/TimeEntryApiTest.java | 11 ++++++- .../pal/trackerapi/TimeEntryJPAApiTest.java | 11 ++++++- .../pal/trackerapi/WelcomeApiTest.java | 14 ++++++++- 8 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 src/main/java/io/pivotal/pal/tracker/SecurityConfiguration.java diff --git a/build.gradle b/build.gradle index ad6d428ac..2203430c6 100644 --- a/build.gradle +++ b/build.gradle @@ -17,6 +17,7 @@ dependencies { compile("org.springframework.boot:spring-boot-starter-data-jpa") 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") } diff --git a/manifest-production.yml b/manifest-production.yml index d22a10f33..90b83dd93 100644 --- a/manifest-production.yml +++ b/manifest-production.yml @@ -3,3 +3,5 @@ applications: - name: pal-tracker path: build/libs/pal-tracker.jar host: runiu2-pal-tracker + env: + - SECURITY_FORCE_HTTPS: true \ No newline at end of file diff --git a/manifest-review.yml b/manifest-review.yml index c05d72bf9..89055435a 100644 --- a/manifest-review.yml +++ b/manifest-review.yml @@ -2,6 +2,8 @@ applications: - name: pal-tracker path: build/libs/pal-tracker.jar - host: runiu2-pal-tracker-review2 + host: runiu2-pal-tracker-review 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/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..01cf23b7d --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/SecurityConfiguration.java @@ -0,0 +1,31 @@ +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.builders.WebSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@EnableWebSecurity +public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + 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..a57db04ea 100644 --- a/src/test/java/test/pivotal/pal/trackerapi/HealthApiTest.java +++ b/src/test/java/test/pivotal/pal/trackerapi/HealthApiTest.java @@ -2,11 +2,14 @@ import com.jayway.jsonpath.DocumentContext; import io.pivotal.pal.tracker.PalTrackerApplication; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.embedded.LocalServerPort; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.test.context.junit4.SpringRunner; @@ -18,10 +21,20 @@ @RunWith(SpringRunner.class) @SpringBootTest(classes = PalTrackerApplication.class, webEnvironment = RANDOM_PORT) public class HealthApiTest { + @LocalServerPort + private String port; - @Autowired private TestRestTemplate restTemplate; + @Before + public void setUp() throws Exception { + RestTemplateBuilder builder = new RestTemplateBuilder() + .rootUri("http://localhost:" + port) + .basicAuthorization("user", "password"); + + restTemplate = new TestRestTemplate(builder); + } + @Test public void 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 a3368d935..42ba4dcfd 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,14 +30,21 @@ @RunWith(SpringRunner.class) @SpringBootTest(classes = PalTrackerApplication.class, webEnvironment = RANDOM_PORT) public class TimeEntryApiTest { + @LocalServerPort + private String port; - @Autowired private TestRestTemplate restTemplate; private TimeEntry timeEntry = new TimeEntry(123L, 456L, LocalDate.parse("2017-01-08"), 8); @Before public void setUp() throws Exception { + RestTemplateBuilder builder = new RestTemplateBuilder() + .rootUri("http://localhost:" + port) + .basicAuthorization("user", "password"); + + restTemplate = new TestRestTemplate(builder); + MysqlDataSource dataSource = new MysqlDataSource(); dataSource.setUrl(System.getenv("SPRING_DATASOURCE_URL")); diff --git a/src/test/java/test/pivotal/pal/trackerapi/TimeEntryJPAApiTest.java b/src/test/java/test/pivotal/pal/trackerapi/TimeEntryJPAApiTest.java index 189f1b78d..f55577fc6 100644 --- a/src/test/java/test/pivotal/pal/trackerapi/TimeEntryJPAApiTest.java +++ b/src/test/java/test/pivotal/pal/trackerapi/TimeEntryJPAApiTest.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,14 +30,21 @@ @RunWith(SpringRunner.class) @SpringBootTest(classes = PalTrackerApplication.class, webEnvironment = RANDOM_PORT) public class TimeEntryJPAApiTest { + @LocalServerPort + private String port; - @Autowired private TestRestTemplate restTemplate; private TimeEntry timeEntry = new TimeEntry(123L, 456L, LocalDate.parse("2017-01-08"), 8); @Before public void setUp() throws Exception { + RestTemplateBuilder builder = new RestTemplateBuilder() + .rootUri("http://localhost:" + port) + .basicAuthorization("user", "password"); + + restTemplate = new TestRestTemplate(builder); + MysqlDataSource dataSource = new MysqlDataSource(); dataSource.setUrl(System.getenv("SPRING_DATASOURCE_URL")); diff --git a/src/test/java/test/pivotal/pal/trackerapi/WelcomeApiTest.java b/src/test/java/test/pivotal/pal/trackerapi/WelcomeApiTest.java index cc7091ed4..19b6984c7 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,18 @@ @SpringBootTest(classes = PalTrackerApplication.class, webEnvironment = RANDOM_PORT) public class WelcomeApiTest { - @Autowired private TestRestTemplate restTemplate; + @LocalServerPort + private String port; + @Before + public void setUp() throws Exception { + RestTemplateBuilder builder = new RestTemplateBuilder() + .rootUri("http://localhost:" + port) + .basicAuthorization("user", "password"); + + restTemplate = new TestRestTemplate(builder); + } @Test public void exampleTest() { String body = this.restTemplate.getForObject("/", String.class); From e2e6b8ad1326d4b224e992b2465b704c33a358a6 Mon Sep 17 00:00:00 2001 From: pal_user Date: Fri, 23 Feb 2018 10:10:10 -0800 Subject: [PATCH 13/18] add security --- manifest-production.yml | 2 +- manifest-review.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest-production.yml b/manifest-production.yml index 90b83dd93..9d53b5d64 100644 --- a/manifest-production.yml +++ b/manifest-production.yml @@ -4,4 +4,4 @@ applications: path: build/libs/pal-tracker.jar host: runiu2-pal-tracker env: - - SECURITY_FORCE_HTTPS: true \ No newline at end of file + SECURITY_FORCE_HTTPS: true \ No newline at end of file diff --git a/manifest-review.yml b/manifest-review.yml index 89055435a..bbf1d9f20 100644 --- a/manifest-review.yml +++ b/manifest-review.yml @@ -6,4 +6,4 @@ applications: services: - tracker-database env: - - SECURITY_FORCE_HTTPS: true \ No newline at end of file + SECURITY_FORCE_HTTPS: true \ No newline at end of file From 7ed40971d8613b08e09af2748f6fde9a7910f80a Mon Sep 17 00:00:00 2001 From: pal_user Date: Fri, 23 Feb 2018 11:08:52 -0800 Subject: [PATCH 14/18] add security --- .../java/io/pivotal/pal/tracker/SecurityConfiguration.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/io/pivotal/pal/tracker/SecurityConfiguration.java b/src/main/java/io/pivotal/pal/tracker/SecurityConfiguration.java index 01cf23b7d..bff234bbc 100644 --- a/src/main/java/io/pivotal/pal/tracker/SecurityConfiguration.java +++ b/src/main/java/io/pivotal/pal/tracker/SecurityConfiguration.java @@ -9,6 +9,12 @@ @EnableWebSecurity public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + @Override + public void configure(WebSecurity web) throws Exception { + web.ignoring().antMatchers("/time-entries-jpa**"); + super.configure(web); + } + protected void configure(HttpSecurity http) throws Exception { String forceHttps = System.getenv("SECURITY_FORCE_HTTPS"); if (forceHttps != null && forceHttps.equals("true")) { @@ -20,6 +26,7 @@ protected void configure(HttpSecurity http) throws Exception { .httpBasic() .and() .csrf().disable(); + } @Autowired From 4f7ea1ca03b16107a7e34b20abab65bfd57346e4 Mon Sep 17 00:00:00 2001 From: pal_user Date: Fri, 23 Feb 2018 11:16:21 -0800 Subject: [PATCH 15/18] add security --- .../pivotal/pal/tracker/SecurityConfiguration.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/pivotal/pal/tracker/SecurityConfiguration.java b/src/main/java/io/pivotal/pal/tracker/SecurityConfiguration.java index bff234bbc..3b1673eb7 100644 --- a/src/main/java/io/pivotal/pal/tracker/SecurityConfiguration.java +++ b/src/main/java/io/pivotal/pal/tracker/SecurityConfiguration.java @@ -20,12 +20,12 @@ protected void configure(HttpSecurity http) throws Exception { if (forceHttps != null && forceHttps.equals("true")) { http.requiresChannel().anyRequest().requiresSecure(); } - http - .authorizeRequests().antMatchers("/**").hasRole("USER") - .and() - .httpBasic() - .and() - .csrf().disable(); +// http +// .authorizeRequests().antMatchers("/**").hasRole("USER") +// .and() +// .httpBasic() +// .and() +// .csrf().disable(); } From c2578ec0b8f046ab59d3803e440b603d0cd6321a Mon Sep 17 00:00:00 2001 From: pal_user Date: Fri, 23 Feb 2018 11:17:53 -0800 Subject: [PATCH 16/18] add security --- .../pal/tracker/SecurityConfiguration.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/pivotal/pal/tracker/SecurityConfiguration.java b/src/main/java/io/pivotal/pal/tracker/SecurityConfiguration.java index 3b1673eb7..a00726924 100644 --- a/src/main/java/io/pivotal/pal/tracker/SecurityConfiguration.java +++ b/src/main/java/io/pivotal/pal/tracker/SecurityConfiguration.java @@ -20,12 +20,15 @@ protected void configure(HttpSecurity http) throws Exception { if (forceHttps != null && forceHttps.equals("true")) { http.requiresChannel().anyRequest().requiresSecure(); } -// http -// .authorizeRequests().antMatchers("/**").hasRole("USER") -// .and() -// .httpBasic() -// .and() -// .csrf().disable(); + String authRequired = System.getenv("AUTH_REQUIRED"); + if(Boolean.valueOf(authRequired)) { + http + .authorizeRequests().antMatchers("/**").hasRole("USER") + .and() + .httpBasic() + .and() + .csrf().disable(); + } } From 7f8399e8b48b7339ad2fa0dcad02900432381f1e Mon Sep 17 00:00:00 2001 From: pal_user Date: Fri, 23 Feb 2018 11:22:44 -0800 Subject: [PATCH 17/18] add security --- build.gradle | 2 ++ src/main/java/io/pivotal/pal/tracker/SecurityConfiguration.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/build.gradle b/build.gradle index 2203430c6..ad300d36e 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,7 @@ bootRun.environment([ "WELCOME_MESSAGE": "hello", "SPRING_DATASOURCE_URL": developmentDbUrl, "MANAGEMENT_SECURITY_ENABLED": false, + "AUTH_REQUIRED": true, ]) def testDbUrl = "jdbc:mysql://localhost:3306/tracker_test?user=tracker&useSSL=false&useTimezone=true&serverTimezone=UTC&useLegacyDatetimeCode=false" @@ -33,6 +34,7 @@ test.environment([ "WELCOME_MESSAGE": "Hello from test", "SPRING_DATASOURCE_URL": testDbUrl, "MANAGEMENT_SECURITY_ENABLED": false, + "AUTH_REQUIRED": true, ]) flyway { diff --git a/src/main/java/io/pivotal/pal/tracker/SecurityConfiguration.java b/src/main/java/io/pivotal/pal/tracker/SecurityConfiguration.java index a00726924..ccdc3d66c 100644 --- a/src/main/java/io/pivotal/pal/tracker/SecurityConfiguration.java +++ b/src/main/java/io/pivotal/pal/tracker/SecurityConfiguration.java @@ -28,6 +28,8 @@ protected void configure(HttpSecurity http) throws Exception { .httpBasic() .and() .csrf().disable(); + }else{ + http.authorizeRequests().antMatchers("/**").anonymous().and().csrf().disable(); } } From fabca71221eb5a1e607325ff311b2063143b1c71 Mon Sep 17 00:00:00 2001 From: pal_user Date: Fri, 23 Feb 2018 11:31:15 -0800 Subject: [PATCH 18/18] add security --- manifest-production.yml | 3 ++- manifest-review.yml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/manifest-production.yml b/manifest-production.yml index 9d53b5d64..54f6f7660 100644 --- a/manifest-production.yml +++ b/manifest-production.yml @@ -4,4 +4,5 @@ applications: path: build/libs/pal-tracker.jar host: runiu2-pal-tracker env: - SECURITY_FORCE_HTTPS: true \ No newline at end of file + SECURITY_FORCE_HTTPS: true + AUTH_REQUIRED: true \ No newline at end of file diff --git a/manifest-review.yml b/manifest-review.yml index bbf1d9f20..c0dbb03c1 100644 --- a/manifest-review.yml +++ b/manifest-review.yml @@ -6,4 +6,5 @@ applications: services: - tracker-database env: - SECURITY_FORCE_HTTPS: true \ No newline at end of file + SECURITY_FORCE_HTTPS: true + AUTH_REQUIRED: true \ No newline at end of file