Репозиторий

interface Repo<E> {
    fun create(element: E): Boolean
    fun read(): List<Item<E>>
    fun read(id: String): Item<E>?
    fun read(ids: List<String>): List<Item<E>>
    fun update(id: String, value: E): Boolean
    fun delete(id: String): Boolean
}

Репозиторий на коллекциях

class ListRepo<E> : Repo<E> {
    private val list = ConcurrentHashMap<String, E>()

    override fun create(element: E): Boolean {
        list[UUID.randomUUID().toString()] = element
        return true
    }

Репозиторий на коллекциях

    override fun read(): List<Item<E>> =
        list.map {
            Item(it.value, it.key)
        }
    override fun read(id: String): Item<E>? =
        list[id]?.let {
            Item(it, id)
        }
    override fun read(ids: List<String>): List<Item<E>> =
        ids.mapNotNull { id ->
            list[id]?.let {
                Item(it, id)
            }
        }

Репозиторий на коллекциях

    override fun update(id: String, value: E): Boolean =
        if (list.containsKey(id)) {
            list[id] = value
            true
        } else false

    override fun delete(id: String): Boolean =
        list.remove(id) != null

Репозиторий на студентов

val studentsRepo = ListRepo<Student>()

fun createTestData() {
    listOf(
        Student("Sheldon", "Cooper"),
        Student("Leonard", "Hofstadter"),
        Student("Howard", "Wolowitz"),
        Student("Penny", "Hofstadter"),
    ).map {
        studentsRepo.create(it)
    }
}

Тестовая конфигурация

fun Application.config(isTest: Boolean) {
    install(ContentNegotiation) {
        json()
    }
    if (isTest) {
        createTestData()
        install(createApplicationPlugin("DelayEmulator") {
            onCall {
                delay(1000L)
            }
        })
    }
}

REST API

fun Route.studentRoutes() {
    route(Config.studentsPath) {
        get {
            val students = studentsRepo.read()
            if (students.isEmpty()) {
                call.respondText(
                    "No students found", 
                    status = HttpStatusCode.NotFound
                )
            } else {
                call.respond(students)
            }
        }

REST API. Get by Id

        get("{id}") {
            val id =
                call.parameters["id"] 
                    ?: return@get call.respondText(
                        "Missing or malformed id",
                        status = HttpStatusCode.BadRequest)
            val studentItem =
                studentsRepo.read(id) 
                    ?: return@get call.respondText(
                        "No student with id $id",
                        status = HttpStatusCode.NotFound)
            call.respond(studentItem)
        }

REST API. Post

        post {
            val student = call.receive<Student>()
            studentsRepo.create(student)
            call.respondText(
                "Student stored correctly",
                status = HttpStatusCode.Created
            )
        }

REST API. Delete

        delete("{id}") {
            val id = call.parameters["id"]
                ?: return@delete call.respond(
                    HttpStatusCode.BadRequest)
            if (studentsRepo.delete(id)) {
                call.respondText(
                    "Student removed correctly",
                    status = HttpStatusCode.Accepted)
            } else {
                call.respondText(
                    "Not Found",
                    status = HttpStatusCode.NotFound)
            }
        }

REST API. Put

        put("{id}") {
            val id = call.parameters["id"] 
                ?: return@put call.respondText(
                    "Missing or malformed id",
                    status = HttpStatusCode.BadRequest)
            studentsRepo.read(id) 
                ?: return@put call.respondText(
                    "No student with id $id",
                    status = HttpStatusCode.NotFound)
            val newStudent = call.receive<Student>()
            studentsRepo.update(id, newStudent)
            call.respondText(
                "Student updates correctly",
                status = HttpStatusCode.Created)}

Тест сервера

class ApplicationTest : StringSpec({
    "Students routes" {
        testApplication {
            application {
                main()
            }
            // this: TestApplicationBuilder

Тест сервера

val students = withClue("read") {
    val response = client.get("/students/")
    response.status shouldBe HttpStatusCode.OK
    Json.decodeFromString<List<Item<Student>>>(
        response.bodyAsText()
    ).apply { size shouldBe 4 }
}

Тест сервера

val newStudents = withClue("create") {
    val response = client.post("/students/") {
        contentType(ContentType.Application.Json)
        setBody(Student("Raj", "Koothrappali").json)
    }
    response.status shouldBe HttpStatusCode.Created
    Json.decodeFromString<List<Item<Student>>>(
        client.get("/students/").bodyAsText()
    ).apply { size shouldBe 5 }
}