diff --git a/compose/docker-compose.yml b/compose/docker-compose.yml index 41091bb..bdfaf90 100644 --- a/compose/docker-compose.yml +++ b/compose/docker-compose.yml @@ -7,6 +7,17 @@ services: - "5432:5432" volumes: - db-data:/var/lib/postgresql/data + environment: + POSTGRES_DB: oppivelvollisuus_it + POSTGRES_USER: oppivelvollisuus + POSTGRES_PASSWORD: postgres + + oppivelvollisuus-db-it: + image: postgres:15.4-alpine3.18 + ports: + - "6432:5432" + volumes: + - db-data:/var/lib/postgresql/data environment: POSTGRES_DB: oppivelvollisuus POSTGRES_USER: oppivelvollisuus @@ -15,3 +26,5 @@ services: volumes: db-data: driver: local + db-it-data: + driver: local diff --git a/service/build.gradle.kts b/service/build.gradle.kts index a82ccb8..0949776 100644 --- a/service/build.gradle.kts +++ b/service/build.gradle.kts @@ -19,6 +19,7 @@ repositories { } dependencies { + api(kotlin("stdlib")) implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.springframework.boot:spring-boot-starter-data-jdbc") @@ -37,6 +38,9 @@ dependencies { implementation("com.fasterxml.jackson.module:jackson-module-kotlin") + testImplementation(kotlin("test")) + testImplementation(kotlin("test-junit5")) + testImplementation("org.junit.jupiter:junit-jupiter") testImplementation("org.springframework.boot:spring-boot-starter-test") } @@ -49,6 +53,8 @@ tasks.withType { tasks.withType { useJUnitPlatform() + testClassesDirs = sourceSets["test"].output.classesDirs + classpath = sourceSets["test"].runtimeClasspath } tasks.getByName("jar") { diff --git a/service/src/main/kotlin/fi/espoo/oppivelvollisuus/ServiceApplication.kt b/service/src/main/kotlin/fi/espoo/oppivelvollisuus/ServiceApplication.kt index 51622f4..cd7d10d 100644 --- a/service/src/main/kotlin/fi/espoo/oppivelvollisuus/ServiceApplication.kt +++ b/service/src/main/kotlin/fi/espoo/oppivelvollisuus/ServiceApplication.kt @@ -25,7 +25,7 @@ fun main(args: Array) { } @RestController -class HelloWorldController { +class MainController { @Autowired lateinit var jdbi: Jdbi @@ -33,14 +33,17 @@ class HelloWorldController { val firstName: String, val lastName: String ) + @PostMapping("/students") fun createStudent(@RequestBody body: StudentInput): UUID { val id = UUID.randomUUID() jdbi.inTransactionUnchecked { tx -> - tx.createUpdate(""" + tx.createUpdate( + """ INSERT INTO students (id, first_name, last_name) VALUES (:id, :firstName, :lastName) - """) + """ + ) .bind("id", id) .bindKotlin(body) .execute() @@ -53,6 +56,7 @@ class HelloWorldController { val firstName: String, val lastName: String ) + @GetMapping("/students") fun getStudents(): List { return jdbi.inTransactionUnchecked { tx -> @@ -61,14 +65,17 @@ class HelloWorldController { .list() } } + @GetMapping("/students/{id}") fun getStudent(@PathVariable id: UUID): StudentBasics { return jdbi.inTransactionUnchecked { tx -> - tx.createQuery(""" + tx.createQuery( + """ SELECT id, first_name, last_name FROM students WHERE id = :id - """.trimIndent()) + """.trimIndent() + ) .bind("id", id) .mapTo() .findOne() @@ -78,17 +85,19 @@ class HelloWorldController { } @PutMapping("/students/{id}") - fun updateStudent(@PathVariable id: UUID, @RequestBody body: StudentInput): Unit { + fun updateStudent(@PathVariable id: UUID, @RequestBody body: StudentInput) { jdbi.inTransactionUnchecked { tx -> - tx.createUpdate(""" + tx.createUpdate( + """ UPDATE students SET first_name = :firstName, last_name = :lastName WHERE id = :id - """) + """ + ) .bind("id", id) .bindKotlin(body) .execute() - .also { if(it != 1) error("not found") } + .also { if (it != 1) error("not found") } } } @@ -96,14 +105,17 @@ class HelloWorldController { val openedAt: LocalDate, val info: String ) + @PostMapping("/students/{studentId}/cases") fun createStudentCase(@PathVariable studentId: UUID, @RequestBody body: StudentCaseInput): UUID { val id = UUID.randomUUID() jdbi.inTransactionUnchecked { tx -> - tx.createUpdate(""" + tx.createUpdate( + """ INSERT INTO student_cases (id, student_id, opened_at, info) VALUES (:id, :studentId, :openedAt, :info) - """) + """ + ) .bind("id", id) .bind("studentId", studentId) .bindKotlin(body) @@ -118,14 +130,17 @@ class HelloWorldController { val openedAt: LocalDate, val info: String ) + @GetMapping("/students/{studentId}/cases") fun getStudentCasesByStudent(@PathVariable studentId: UUID): List { return jdbi.inTransactionUnchecked { tx -> - tx.createQuery(""" + tx.createQuery( + """ SELECT id, student_id, opened_at, info FROM student_cases WHERE student_id = :studentId - """.trimIndent()) + """.trimIndent() + ) .bind("studentId", studentId) .mapTo() .list() @@ -139,18 +154,20 @@ class HelloWorldController { @RequestBody body: StudentCaseInput ) { jdbi.inTransactionUnchecked { tx -> - tx.createUpdate(""" + tx.createUpdate( + """ UPDATE student_cases SET opened_at = :openedAt, info = :info WHERE id = :id AND student_id = :studentId - """) + """ + ) .bind("id", id) .bind("studentId", studentId) .bindKotlin(body) .execute() - .also { if(it != 1) error("not found") } + .also { if (it != 1) error("not found") } } } @@ -161,17 +178,19 @@ class HelloWorldController { val lastName: String, val openedAt: LocalDate ) + @GetMapping("/students-cases") fun getStudentCases(): List { return jdbi.inTransactionUnchecked { tx -> - tx.createQuery(""" + tx.createQuery( + """ SELECT c.id, student_id, first_name, last_name, opened_at, info FROM student_cases c JOIN students s on s.id = c.student_id - """.trimIndent()) + """.trimIndent() + ) .mapTo() .list() } } - } diff --git a/service/src/test/kotlin/fi/espoo/oppivelvollisuus/FullApplicationTest.kt b/service/src/test/kotlin/fi/espoo/oppivelvollisuus/FullApplicationTest.kt new file mode 100644 index 0000000..3ce5593 --- /dev/null +++ b/service/src/test/kotlin/fi/espoo/oppivelvollisuus/FullApplicationTest.kt @@ -0,0 +1,50 @@ +package fi.espoo.oppivelvollisuus + +import org.jdbi.v3.core.Jdbi +import org.jdbi.v3.core.kotlin.withHandleUnchecked +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.TestInstance +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT +) +abstract class FullApplicationTest { + @Autowired + protected lateinit var jdbi: Jdbi + + @BeforeAll + fun beforeAll() { + jdbi.withHandleUnchecked { tx -> + tx.execute( + """ +CREATE OR REPLACE FUNCTION reset_database() RETURNS void AS ${'$'}${'$'} +BEGIN + EXECUTE ( + SELECT 'TRUNCATE TABLE ' || string_agg(quote_ident(table_name), ', ') || ' CASCADE' + FROM information_schema.tables + WHERE table_schema = 'public' + AND table_type = 'BASE TABLE' + AND table_name <> 'flyway_schema_history' + ); + EXECUTE ( + SELECT 'SELECT ' || coalesce(string_agg(format('setval(%L, %L, false)', sequence_name, start_value), ', '), '') + FROM information_schema.sequences + WHERE sequence_schema = 'public' + ); +END ${'$'}${'$'} LANGUAGE plpgsql; + """.trimIndent() + ) + } + } + + @BeforeEach + fun beforeEach() { + jdbi.withHandleUnchecked { tx -> + tx.execute("SELECT reset_database()") + } + } +} diff --git a/service/src/test/kotlin/fi/espoo/oppivelvollisuus/ServiceApplicationTests.kt b/service/src/test/kotlin/fi/espoo/oppivelvollisuus/ServiceApplicationTests.kt index b0530ce..7be1a3a 100644 --- a/service/src/test/kotlin/fi/espoo/oppivelvollisuus/ServiceApplicationTests.kt +++ b/service/src/test/kotlin/fi/espoo/oppivelvollisuus/ServiceApplicationTests.kt @@ -1,12 +1,15 @@ package fi.espoo.oppivelvollisuus import org.junit.jupiter.api.Test -import org.springframework.boot.test.context.SpringBootTest +import org.springframework.beans.factory.annotation.Autowired +import kotlin.test.assertEquals -@SpringBootTest -class ServiceApplicationTests { +class ServiceApplicationTests : FullApplicationTest() { + @Autowired + lateinit var controller: MainController @Test - fun contextLoads() { + fun `get empty list of students`() { + assertEquals(emptyList(), controller.getStudents()) } } diff --git a/service/src/test/resources/application.yaml b/service/src/test/resources/application.yaml new file mode 100644 index 0000000..3a5cacd --- /dev/null +++ b/service/src/test/resources/application.yaml @@ -0,0 +1,5 @@ +spring: + datasource: + url: "jdbc:postgresql://localhost:6432/oppivelvollisuus_it" + username: "oppivelvollisuus" + password: "postgres"