All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hexagonkt.http.test.examples.BooksTest.kt Maven / Gradle / Ivy

package com.hexagonkt.http.test.examples

import com.hexagonkt.core.require
import com.hexagonkt.http.client.HttpClientPort
import com.hexagonkt.http.model.*
import com.hexagonkt.http.model.HttpMethod.*
import com.hexagonkt.http.model.HttpMethod.Companion.ALL
import com.hexagonkt.http.model.CREATED_201
import com.hexagonkt.http.server.HttpServerPort
import com.hexagonkt.http.server.HttpServerSettings
import com.hexagonkt.http.handlers.PathHandler
import com.hexagonkt.http.handlers.HttpHandler
import com.hexagonkt.http.handlers.path
import com.hexagonkt.http.test.BaseTest
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals

@Suppress("FunctionName") // This class's functions are intended to be used only in tests
abstract class BooksTest(
    final override val clientAdapter: () -> HttpClientPort,
    final override val serverAdapter: () -> HttpServerPort,
    final override val serverSettings: HttpServerSettings = HttpServerSettings(),
) : BaseTest() {

    // books
    data class Book(val author: String, val title: String)

    private val books: MutableMap = linkedMapOf(
        100 to Book("Miguel de Cervantes", "Don Quixote"),
        101 to Book("William Shakespeare", "Hamlet"),
        102 to Book("Homer", "The Odyssey")
    )

    private val path: PathHandler = path {

        post("/books") {
            val author = queryParameters["author"]?.string() ?: return@post badRequest("Missing author")
            val title = queryParameters["title"]?.string() ?: return@post badRequest("Missing title")
            val id = (books.keys.maxOrNull() ?: 0) + 1
            books += id to Book(author, title)
            created(id.toString())
        }

        get("/books/{id}") {
            val bookId = pathParameters.require("id").toInt()
            val book = books[bookId]
            if (book != null)
                ok("Title: ${book.title}, Author: ${book.author}")
            else
                notFound("Book not found")
        }

        put("/books/{id}") {
            val bookId = pathParameters.require("id").toInt()
            val book = books[bookId]
            if (book != null) {
                books += bookId to book.copy(
                    author = queryParameters["author"]?.string() ?: book.author,
                    title = queryParameters["title"]?.string() ?: book.title
                )

                ok("Book with id '$bookId' updated")
            }
            else {
                notFound("Book not found")
            }
        }

        delete("/books/{id}") {
            val bookId = pathParameters.require("id").toInt()
            val book = books[bookId]
            books -= bookId
            if (book != null)
                ok("Book with id '$bookId' deleted")
            else
                notFound("Book not found")
        }

        // Matches path's requests with *any* HTTP method as a fallback (return 404 instead 405)
        after(ALL - DELETE - PUT - GET, "/books/{id}", status = NOT_FOUND_404) {
            send(METHOD_NOT_ALLOWED_405)
        }

        get("/books") {
            ok(books.keys.joinToString(" ", transform = Int::toString))
        }
    }
    // books

    private val pathAlternative: PathHandler = path("/books") {

        post {
            val author = queryParameters["author"]?.string() ?: return@post badRequest("Missing author")
            val title = queryParameters["title"]?.string() ?: return@post badRequest("Missing title")
            val id = (books.keys.maxOrNull() ?: 0) + 1
            books += id to Book(author, title)
            created(id.toString())
        }

        get("/{id}") {
            val bookId = pathParameters.require("id").toInt()
            val book = books[bookId]
            if (book != null)
                ok("Title: ${book.title}, Author: ${book.author}")
            else
                notFound("Book not found")
        }

        put("/{id}") {
            val bookId = pathParameters.require("id").toInt()
            val book = books[bookId]
            if (book != null) {
                books += bookId to book.copy(
                    author = queryParameters["author"]?.string() ?: book.author,
                    title = queryParameters["title"]?.string() ?: book.title
                )

                ok("Book with id '$bookId' updated")
            }
            else {
                notFound("Book not found")
            }
        }

        delete("/{id}") {
            val bookId = pathParameters.require("id").toInt()
            val book = books[bookId]
            books -= bookId
            if (book != null)
                ok("Book with id '$bookId' deleted")
            else
                notFound("Book not found")
        }

        // Matches path's requests with *any* HTTP method as a fallback (return 404 instead 405)
        after(ALL - DELETE - PUT - GET, "/{id}", status = NOT_FOUND_404) {
            send(METHOD_NOT_ALLOWED_405)
        }

        get {
            ok(books.keys.joinToString(" ", transform = Int::toString))
        }
    }

    private val pathAlternative2: PathHandler = path("/books") {

        post {
            val author = queryParameters["author"]?.string() ?: return@post badRequest("Missing author")
            val title = queryParameters["title"]?.string() ?: return@post badRequest("Missing title")
            val id = (books.keys.maxOrNull() ?: 0) + 1
            books += id to Book(author, title)
            created(id.toString())
        }

        path("/{id}") {
            get {
                val bookId = pathParameters.require("id").toInt()
                val book = books[bookId]
                if (book != null)
                    ok("Title: ${book.title}, Author: ${book.author}")
                else
                    notFound("Book not found")
            }

            put {
                val bookId = pathParameters.require("id").toInt()
                val book = books[bookId]
                if (book != null) {
                    books += bookId to book.copy(
                        author = queryParameters["author"]?.string() ?: book.author,
                        title = queryParameters["title"]?.string() ?: book.title
                    )

                    ok("Book with id '$bookId' updated")
                }
                else {
                    notFound("Book not found")
                }
            }

            delete {
                val bookId = pathParameters.require("id").toInt()
                val book = books[bookId]
                books -= bookId
                if (book != null)
                    ok("Book with id '$bookId' deleted")
                else
                    notFound("Book not found")
            }

            // Matches path's requests with *any* HTTP method as a fallback (return 404 instead 405)
            after(ALL - DELETE - PUT - GET, status = NOT_FOUND_404) {
                send(METHOD_NOT_ALLOWED_405)
            }
        }

        get {
            ok(books.keys.joinToString(" ", transform = Int::toString))
        }
    }

    override val handler: HttpHandler = path {
        path("/a", path)
        path("/b", pathAlternative)
        path("/c", pathAlternative2)
    }

    @Test fun `Create book returns 201 and new book ID`() {
        listOf("/a", "/b", "/c").forEach {
            val result = client.post("$it/books?author=Vladimir%20Nabokov&title=Lolita")
            assert(Integer.valueOf(result.body as String) > 0)
            assertEquals(CREATED_201, result.status)
        }
    }

    @Test fun `Create book returns 400 if a parameter is missing`() {
        listOf("/a", "/b", "/c").forEach { p ->
            client.post("$p/books?title=Lolita").let {
                assertEquals("Missing author", it.body)
                assertEquals(BAD_REQUEST_400, it.status)
            }

            client.post("$p/books?author=Vladimir%20Nabokov").let {
                assertEquals("Missing title", it.body)
                assertEquals(BAD_REQUEST_400, it.status)
            }
        }
    }

    @Test fun `List books contains all books IDs`() {
        listOf("/a", "/b", "/c").forEach {
            val result = client.get("$it/books")
            assertResponseContains(result, "100", "101")
        }
    }

    @Test fun `Get book returns all book's fields`() {
        listOf("/a", "/b", "/c").forEach {
            val result = client.get("$it/books/101")
            assertResponseContains(result, "William Shakespeare", "Hamlet")
        }
    }

    @Test fun `Update book overrides existing book data`() {
        listOf("/a", "/b", "/c").forEach {
            val resultPut = client.put("$it/books/100?title=Don%20Quixote")
            assertResponseContains(resultPut, "100", "updated")

            val resultGet = client.get("$it/books/100")
            assertResponseContains(resultGet, "Miguel de Cervantes", "Don Quixote")
        }
    }

    @Test fun `Update not found book returns a 404`() {
        listOf("/a", "/b", "/c").forEach {
            val result = client.put("$it/books/9999?title=Don%20Quixote")
            assertResponseContains(result, NOT_FOUND_404, "not found")
        }
    }

    @Test fun `Delete book returns the deleted record ID`() {
        listOf("/a", "/b", "/c").forEach {
            val createResult =
                client.post("$it/books?author=Ken%20Follett&title=The%20Pillars%20of%20the%20Earth")
            val id = Integer.valueOf(createResult.body as String)
            assert(id > 0)
            assertEquals(CREATED_201, createResult.status)
            val result = client.delete("$it/books/$id")
            assertResponseContains(result, id.toString(), "deleted")
        }
    }

    @Test fun `Delete not found book returns a 404`() {
        listOf("/a", "/b", "/c").forEach {
            val result = client.delete("$it/books/9999")
            assertResponseContains(result, NOT_FOUND_404, "not found")
        }
    }

    @Test fun `Book not found returns a 404`() {
        listOf("/a", "/b", "/c").forEach {
            val result = client.get("$it/books/9999")
            assertResponseContains(result, NOT_FOUND_404, "not found")
        }
    }

    @Test fun `Invalid method returns 405`() {
        listOf("/a", "/b", "/c").forEach {
            val result = client.options("$it/books/9999")
            assertEquals(METHOD_NOT_ALLOWED_405, result.status)
        }
    }

    @Test fun `Not handled method returns 404`() {
        listOf("/a", "/b", "/c").forEach {
            val result = client.options("$it/books")
            assertEquals(NOT_FOUND_404, result.status)
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy