From 08f9760af43cb332d329d43bff6b9381eeecafea Mon Sep 17 00:00:00 2001 From: Sebastien Deleuze Date: Sat, 19 Mar 2016 12:18:44 +0100 Subject: [PATCH] Refactoring + tests --- .../kotlin/io/spring/messenger/Application.kt | 25 ++++++++++- .../kotlin/io/spring/messenger/Database.kt | 42 +++++++++++++++++++ .../spring/messenger/config/DatabaseConfig.kt | 42 ------------------- .../messenger/repository/MessageRepository.kt | 10 +++++ .../messenger/repository/SpatialExtension.kt | 39 ----------------- .../io/spring/messenger/repository/Tables.kt | 17 -------- .../messenger/repository/UserRepository.kt | 16 +++++-- .../io/spring/messenger/web/UserController.kt | 6 +-- .../messenger/MessageControllerTests.kt | 40 ++++++++++++++++++ .../spring/messenger/UserControllerTests.kt | 40 ++++++++++++++++++ 10 files changed, 170 insertions(+), 107 deletions(-) create mode 100644 src/main/kotlin/io/spring/messenger/Database.kt delete mode 100644 src/main/kotlin/io/spring/messenger/config/DatabaseConfig.kt delete mode 100644 src/main/kotlin/io/spring/messenger/repository/SpatialExtension.kt delete mode 100644 src/main/kotlin/io/spring/messenger/repository/Tables.kt create mode 100644 src/test/kotlin/io/spring/messenger/MessageControllerTests.kt create mode 100644 src/test/kotlin/io/spring/messenger/UserControllerTests.kt diff --git a/src/main/kotlin/io/spring/messenger/Application.kt b/src/main/kotlin/io/spring/messenger/Application.kt index 769da15..550a9f5 100644 --- a/src/main/kotlin/io/spring/messenger/Application.kt +++ b/src/main/kotlin/io/spring/messenger/Application.kt @@ -1,19 +1,40 @@ package io.spring.messenger import com.fasterxml.jackson.module.kotlin.KotlinModule +import io.spring.messenger.domain.Message +import io.spring.messenger.domain.User +import io.spring.messenger.repository.MessageRepository +import io.spring.messenger.repository.UserRepository +import org.jetbrains.exposed.sql.Database import org.postgis.geojson.PostGISModule +import org.springframework.boot.CommandLineRunner import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.context.annotation.Bean import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder +import javax.sql.DataSource @SpringBootApplication open class Application { - @Bean - open fun objectMapperBuilder(): Jackson2ObjectMapperBuilder + @Bean open fun objectMapperBuilder(): Jackson2ObjectMapperBuilder = Jackson2ObjectMapperBuilder().modulesToInstall(PostGISModule(), KotlinModule()) + @Bean open fun db(dataSource: DataSource) = Database.connect(dataSource) + + @Bean open fun init(db: Database, ur: UserRepository, mr: MessageRepository) = CommandLineRunner { + ur.createTable() + mr.createTable() + mr.deleteAll() + ur.deleteAll() + + ur.create(User("swhite", "Skyler", "White")) + ur.create(User("jpinkman", "Jesse", "Pinkman")) + ur.create(User("wwhite", "Walter", "White")) + ur.create(User("sgoodman", "Saul", "Goodman")) + mr.create(Message("This is a test!", "swhite")) + } + } fun main(args: Array) { diff --git a/src/main/kotlin/io/spring/messenger/Database.kt b/src/main/kotlin/io/spring/messenger/Database.kt new file mode 100644 index 0000000..42fe08f --- /dev/null +++ b/src/main/kotlin/io/spring/messenger/Database.kt @@ -0,0 +1,42 @@ +package io.spring.messenger + +import org.jetbrains.exposed.sql.* +import org.postgis.PGbox2d +import org.postgis.PGgeometry +import org.postgis.Point + +object Messages : Table() { + val id = integer("id").autoIncrement().primaryKey() + val content = text("content") + val author = reference("author", Users.userName) + val location = point("location").nullable() +} + +object Users : Table() { + val userName = text("user_name").primaryKey() + val firstName = text("first_name") + val lastName = text("last_name") + val location = point("location").nullable() +} + + +fun Table.point(name: String, srid: Int = 4326): Column = registerColumn(name, PointColumnType()) + +infix fun ExpressionWithColumnType<*>.within(box: PGbox2d) : Op = WithinOp(this, box) + +private class PointColumnType(val srid: Int = 4326): ColumnType() { + override fun sqlType() = "GEOMETRY(Point, $srid)" + override fun valueFromDB(value: Any) = if (value is PGgeometry) value.geometry else value + override fun notNullValueToDB(value: Any): Any { + if (value is Point) { + if (value.srid == Point.UNKNOWN_SRID) value.srid = srid + return PGgeometry(value) + } + return value + } +} + +private class WithinOp(val expr1: Expression<*>, val box: PGbox2d) : Op() { + override fun toSQL(queryBuilder: QueryBuilder) = + "${expr1.toSQL(queryBuilder)} && ST_MakeEnvelope(${box.llb.x}, ${box.llb.y}, ${box.urt.x}, ${box.urt.y}, 4326)" +} diff --git a/src/main/kotlin/io/spring/messenger/config/DatabaseConfig.kt b/src/main/kotlin/io/spring/messenger/config/DatabaseConfig.kt deleted file mode 100644 index c2600aa..0000000 --- a/src/main/kotlin/io/spring/messenger/config/DatabaseConfig.kt +++ /dev/null @@ -1,42 +0,0 @@ -package io.spring.messenger.config - -import io.spring.messenger.domain.Message -import io.spring.messenger.domain.User -import io.spring.messenger.repository.* -import org.jetbrains.exposed.sql.Database -import org.jetbrains.exposed.sql.exists -import org.springframework.boot.CommandLineRunner -import javax.sql.DataSource - -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration - -@Configuration -open class DatabaseConfig { - - @Bean - open fun db(dataSource: DataSource) = Database.connect(dataSource) - - @Bean - open fun init(db: Database, userRepository: UserRepository, messageRepository: MessageRepository) = CommandLineRunner { - val swhite = User("swhite", "Skyler", "White") - val jpinkman = User("jpinkman", "Jesse", "Pinkman") - val walter = User("wwhite", "Walter", "White") - val sgoodman = User("sgoodman", "Saul", "Goodman") - - db.transaction { - if(!Users.exists()) { - create(Users) - userRepository.create(swhite) - userRepository.create(jpinkman) - userRepository.create(walter) - userRepository.create(sgoodman) - } - if(!Messages.exists()) { - create(Messages) - messageRepository.create(Message("This is a test!", swhite.userName)) - } - } - } - -} \ No newline at end of file diff --git a/src/main/kotlin/io/spring/messenger/repository/MessageRepository.kt b/src/main/kotlin/io/spring/messenger/repository/MessageRepository.kt index bf65697..35e0545 100644 --- a/src/main/kotlin/io/spring/messenger/repository/MessageRepository.kt +++ b/src/main/kotlin/io/spring/messenger/repository/MessageRepository.kt @@ -1,6 +1,8 @@ package io.spring.messenger.repository +import io.spring.messenger.Messages import io.spring.messenger.domain.Message +import io.spring.messenger.within import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.statements.UpdateBuilder import org.postgis.PGbox2d @@ -10,6 +12,10 @@ import org.springframework.stereotype.Repository @Repository open class MessageRepository @Autowired constructor(val db: Database) { + open fun createTable() = db.transaction { + create(Messages) + } + open fun create(m: Message) = db.transaction { m.id = Messages.insert(map(m)).get(Messages.id) m @@ -23,6 +29,10 @@ open class MessageRepository @Autowired constructor(val db: Database) { unmap(Messages.select { Messages.location within box }) } + open fun deleteAll() = db.transaction { + Messages.deleteAll() + } + private fun map(m: Message): Messages.(UpdateBuilder<*>) -> Unit = { if (m.id != null) it[id] = m.id it[content] = m.content diff --git a/src/main/kotlin/io/spring/messenger/repository/SpatialExtension.kt b/src/main/kotlin/io/spring/messenger/repository/SpatialExtension.kt deleted file mode 100644 index 84219b4..0000000 --- a/src/main/kotlin/io/spring/messenger/repository/SpatialExtension.kt +++ /dev/null @@ -1,39 +0,0 @@ -package io.spring.messenger.repository - -import org.jetbrains.exposed.sql.* -import org.postgis.PGbox2d -import org.postgis.PGgeometry -import org.postgis.Point - -fun Table.point(name: String, srid: Int = 4326): Column = registerColumn(name, PointColumnType()) - -class PointColumnType(val srid: Int = 4326): ColumnType() { - - override fun sqlType(): String = "GEOMETRY(Point, $srid)" - - override fun valueFromDB(value: Any): Any { - if (value is PGgeometry) { - return value.geometry - } - return value - } - - override fun notNullValueToDB(value: Any): Any { - if (value is Point) { - if (value.srid == Point.UNKNOWN_SRID) value.srid = srid - return PGgeometry(value) - } - return value - } -} - -infix fun ExpressionWithColumnType<*>.within(box: PGbox2d) : Op { - return WithinOp(this, box) -} - -class WithinOp(val expr1: Expression<*>, val box: PGbox2d) : Op() { - - override fun toSQL(queryBuilder: QueryBuilder) = """${expr1.toSQL(queryBuilder)} - && ST_MakeEnvelope(${box.llb.x}, ${box.llb.y}, ${box.urt.x}, ${box.urt.y}, 4326)""" -} - diff --git a/src/main/kotlin/io/spring/messenger/repository/Tables.kt b/src/main/kotlin/io/spring/messenger/repository/Tables.kt deleted file mode 100644 index 819cf02..0000000 --- a/src/main/kotlin/io/spring/messenger/repository/Tables.kt +++ /dev/null @@ -1,17 +0,0 @@ -package io.spring.messenger.repository - -import org.jetbrains.exposed.sql.Table - -object Messages : Table() { - val id = integer("id").autoIncrement().primaryKey() - val content = text("content") - val author = reference("author", Users.userName) - val location = point("location").nullable() -} - -object Users : Table() { - val userName = text("user_name").primaryKey() - val firstName = text("first_name") - val lastName = text("last_name") - val location = point("location").nullable() -} \ No newline at end of file diff --git a/src/main/kotlin/io/spring/messenger/repository/UserRepository.kt b/src/main/kotlin/io/spring/messenger/repository/UserRepository.kt index 66d67da..0b14eb6 100644 --- a/src/main/kotlin/io/spring/messenger/repository/UserRepository.kt +++ b/src/main/kotlin/io/spring/messenger/repository/UserRepository.kt @@ -1,6 +1,8 @@ package io.spring.messenger.repository +import io.spring.messenger.Users import io.spring.messenger.domain.User +import io.spring.messenger.within import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.statements.UpdateBuilder import org.postgis.PGbox2d @@ -11,15 +13,19 @@ import org.springframework.stereotype.Repository @Repository open class UserRepository @Autowired constructor(val db: Database) { - open fun updateLocation(userName:String, location: Point) = db.transaction { - location.srid = 4326 - Users.update({Users.userName eq userName}) { it[Users.location] = location} + open fun createTable() = db.transaction { + create(Users) } open fun create(user: User) = db.transaction { Users.insert( map(user) ) } + open fun updateLocation(userName:String, location: Point) = db.transaction { + location.srid = 4326 + Users.update({Users.userName eq userName}) { it[Users.location] = location} + } + open fun findAll() = db.transaction { unmap(Users.selectAll()) } @@ -28,6 +34,10 @@ open class UserRepository @Autowired constructor(val db: Database) { unmap(Users.select { Users.location within box }) } + open fun deleteAll() = db.transaction { + Users.deleteAll() + } + private fun map(u: User): Users.(UpdateBuilder<*>) -> Unit = { it[userName] = u.userName it[firstName] = u.firstName diff --git a/src/main/kotlin/io/spring/messenger/web/UserController.kt b/src/main/kotlin/io/spring/messenger/web/UserController.kt index 7de79c3..8bf6855 100644 --- a/src/main/kotlin/io/spring/messenger/web/UserController.kt +++ b/src/main/kotlin/io/spring/messenger/web/UserController.kt @@ -11,11 +11,9 @@ import org.springframework.web.bind.annotation.* @RestController @RequestMapping("/user") class UserController @Autowired constructor(val repository: UserRepository) { - @GetMapping - fun findAll() = repository.findAll() + @GetMapping fun findAll() = repository.findAll() - @PostMapping - fun create(@RequestBody u: User) = repository.create(u) + @PostMapping fun create(@RequestBody u: User) = repository.create(u) @PutMapping("/{userName}/location/{x},{y}") @ResponseStatus(NO_CONTENT) fun updateLocation(@PathVariable userName:String, @PathVariable x: Double, @PathVariable y: Double) diff --git a/src/test/kotlin/io/spring/messenger/MessageControllerTests.kt b/src/test/kotlin/io/spring/messenger/MessageControllerTests.kt new file mode 100644 index 0000000..9f61cd4 --- /dev/null +++ b/src/test/kotlin/io/spring/messenger/MessageControllerTests.kt @@ -0,0 +1,40 @@ +package io.spring.messenger + +import io.spring.messenger.repository.MessageRepository +import io.spring.messenger.repository.UserRepository +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.springframework.http.MediaType.APPLICATION_JSON +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.SpringApplicationConfiguration +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner +import org.springframework.test.context.web.WebAppConfiguration +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.setup.MockMvcBuilders +import org.springframework.web.context.WebApplicationContext + +@RunWith(SpringJUnit4ClassRunner::class) +@SpringApplicationConfiguration(classes = arrayOf(Application::class)) +@WebAppConfiguration +class MessageControllerTests { + + @Autowired lateinit var context: WebApplicationContext + @Autowired lateinit var userRepository: UserRepository + @Autowired lateinit var messageRepository: MessageRepository + lateinit var mockMvc: MockMvc + + @Before fun setUp() { + messageRepository.deleteAll() + userRepository.deleteAll() + mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build() + } + + @Test fun findUsers() { + mockMvc.perform(get("/message").accept(APPLICATION_JSON)).andExpect(status().isOk) + } + +} diff --git a/src/test/kotlin/io/spring/messenger/UserControllerTests.kt b/src/test/kotlin/io/spring/messenger/UserControllerTests.kt new file mode 100644 index 0000000..57ee963 --- /dev/null +++ b/src/test/kotlin/io/spring/messenger/UserControllerTests.kt @@ -0,0 +1,40 @@ +package io.spring.messenger + +import io.spring.messenger.repository.MessageRepository +import io.spring.messenger.repository.UserRepository +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.springframework.http.MediaType.APPLICATION_JSON +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.SpringApplicationConfiguration +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner +import org.springframework.test.context.web.WebAppConfiguration +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.setup.MockMvcBuilders +import org.springframework.web.context.WebApplicationContext + +@RunWith(SpringJUnit4ClassRunner::class) +@SpringApplicationConfiguration(classes = arrayOf(Application::class)) +@WebAppConfiguration +class UserControllerTests { + + @Autowired lateinit var context: WebApplicationContext + @Autowired lateinit var userRepository: UserRepository + @Autowired lateinit var messageRepository: MessageRepository + lateinit var mockMvc: MockMvc + + @Before fun setUp() { + messageRepository.deleteAll() + userRepository.deleteAll() + mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build() + } + + @Test fun findUsers() { + mockMvc.perform(get("/user").accept(APPLICATION_JSON)).andExpect(status().isOk) + } + +}