Skip to content
This repository has been archived by the owner on Jul 13, 2021. It is now read-only.

Test coverage for backend #28

Open
wants to merge 33 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
6bc3044
Adding GET login test
oleksiyp Nov 1, 2017
c993d0b
Covered Login with tests
oleksiyp Nov 2, 2017
cdac7fb
Refactoring LoginKtTest
oleksiyp Nov 2, 2017
1e8db37
Refactoring LoginKtTest
oleksiyp Nov 2, 2017
d870b53
Covered IndexKt with tests
oleksiyp Nov 2, 2017
b8b3149
Upgrade to MockK 1.2
oleksiyp Nov 2, 2017
19b90d3
Using newer mockDsl in LoginKtTest
oleksiyp Nov 3, 2017
daa5166
Adding PostThoughtKtTest
oleksiyp Nov 3, 2017
73034a1
Adding forbidden case
oleksiyp Nov 3, 2017
c91da67
Added PostThoughtKtTest
oleksiyp Nov 3, 2017
cfbbbca
Ignoring test till next release
oleksiyp Nov 3, 2017
841757a
Revert: gradle-wrapper.properties
oleksiyp Nov 3, 2017
d98ed9d
Adding DeleteKtTest
oleksiyp Nov 4, 2017
f6a7cd3
Adding ViewThoughtKtTest
oleksiyp Nov 4, 2017
313c961
Adding UserPageKt
oleksiyp Nov 4, 2017
00a0500
Adding ApplicationPageTest
oleksiyp Nov 4, 2017
e00e79e
Adding RegisterKtTest
oleksiyp Nov 4, 2017
bd4b582
Using non-Nullable, getting rid of assertk
oleksiyp Nov 5, 2017
c901dbb
Logging config for tests
oleksiyp Nov 5, 2017
90ac181
Logging config for tests
oleksiyp Nov 5, 2017
ad5a990
Release version of MockK
oleksiyp Nov 5, 2017
38e88d1
Adding ThinkerDatabaseTest
oleksiyp Nov 6, 2017
b09f9de
Adding tests to ThinkerDatabaseTest
oleksiyp Nov 6, 2017
a69a90d
Adding tests to ThinkerDatabaseTest
oleksiyp Nov 6, 2017
ccd8402
Revert: gradle-wrapper.properties
oleksiyp Nov 6, 2017
ff25f46
Upgrade to version 1.4
oleksiyp Nov 9, 2017
8e09252
Upgrade to version 1.5
oleksiyp Nov 16, 2017
a25bd5b
Upgrade to version 1.5.1 Auto-hinting
oleksiyp Nov 17, 2017
fac616a
Upgrade to version 1.5.1
oleksiyp Nov 17, 2017
5688aa5
Upgrade to Version 1.5.2
oleksiyp Nov 17, 2017
d3a1ca9
Upgrade to Version 1.5.3
oleksiyp Nov 21, 2017
933f264
Merge branch 'master' of github.com:Kotlin/kotlin-fullstack-sample
oleksiyp Feb 17, 2019
58afe2d
Upgrade to 1.9.1
oleksiyp Feb 17, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 0 additions & 19 deletions .idea/runConfigurations/Backend____Jetty.xml

This file was deleted.

2 changes: 1 addition & 1 deletion .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions backend/build.gradle
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
plugins {
id "jacoco"
}

group = 'org.jetbrains.demo.thinkter'
version = '0.0.1-SNAPSHOT'

apply plugin: 'kotlin'
apply plugin: 'application'

dependencies {

compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"

Expand All @@ -18,6 +23,8 @@ dependencies {
testCompile "org.jetbrains.ktor:ktor-test-host:$ktor_version"
testCompile "org.jsoup:jsoup:1.9.1"

testCompile "io.mockk:mockk:1.9.1.kotlin12"

compile "org.jetbrains.ktor:ktor-jetty:$ktor_version"
compile group: 'com.google.code.gson', name: 'gson', version: '2.8.0'
}
Expand All @@ -42,3 +49,12 @@ kotlin {
}

mainClassName = 'org.jetbrains.ktor.jetty.DevelopmentHost'

jacocoTestReport {
reports {
xml.enabled true
html.enabled true
}
}

check.dependsOn jacocoTestReport
3 changes: 1 addition & 2 deletions backend/src/org/jetbrains/demo/thinkter/Register.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ import org.jetbrains.ktor.util.*

fun Route.register(dao: ThinkterStorage, hashFunction: (String) -> String) {
post<Register> { form ->
val vm = call.request.content.get<ValuesMap>()

val user = call.sessionOrNull<Session>()?.let { dao.user(it.userId) }
if (user != null) {
call.redirect(LoginResponse(user))
Expand Down Expand Up @@ -40,6 +38,7 @@ fun Route.register(dao: ThinkterStorage, hashFunction: (String) -> String) {
application.environment.log.error("Failed to register user", e)
call.respond(LoginResponse(error = "Failed to register"))
}
return@post
}

call.session(Session(newUser.userId))
Expand Down
35 changes: 35 additions & 0 deletions backend/test/org/jetbrains/demo/thinkter/ApplicationPageTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.jetbrains.demo.thinkter

import io.mockk.*
import kotlinx.coroutines.experimental.runBlocking
import org.jetbrains.ktor.application.ApplicationCall
import org.jetbrains.ktor.cio.ByteBufferWriteChannel
import org.jetbrains.ktor.html.HtmlContent
import org.jetbrains.ktor.html.respondHtmlTemplate
import org.junit.Test
import java.nio.charset.Charset

class ApplicationPageTest {
val appCall = mockk<ApplicationCall>()

@Test
fun testRenderHTML() {
coEvery { appCall.respond(any()) } just Runs

runBlocking {
appCall.respondHtmlTemplate(ApplicationPage()) {
caption { +"caption" }
}
}

coVerify {
appCall.respond(coAssert<HtmlContent> {
val channel = ByteBufferWriteChannel()
it.writeTo(channel)
val html = channel.toString(Charset.defaultCharset())
html.contains("caption") && html.contains("yui.yahooapis.com")
})
}

}
}
96 changes: 96 additions & 0 deletions backend/test/org/jetbrains/demo/thinkter/Common.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package org.jetbrains.demo.thinkter

import io.mockk.*
import org.jetbrains.demo.thinkter.dao.ThinkterStorage
import org.jetbrains.demo.thinkter.model.Thought
import org.jetbrains.demo.thinkter.model.User
import org.jetbrains.ktor.application.ApplicationCall
import org.jetbrains.ktor.http.HttpHeaders
import org.jetbrains.ktor.http.HttpStatusCode
import org.jetbrains.ktor.request.host
import org.jetbrains.ktor.sessions.SessionConfig
import org.jetbrains.ktor.util.AttributeKey
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter

fun MockKMatcherScope.sessionMatcher(): AttributeKey<Session> =
match { it.name == "Session" }

fun MockKMatcherScope.sessionConfigMatcher(): AttributeKey<SessionConfig<*>> =
match { it.name == "SessionConfig" }


fun ApplicationCall.mockSessionReturningUser(dao: ThinkterStorage) {
every { attributes.contains(sessionMatcher()) } returns true

every {
attributes
.get(sessionMatcher())
} returns Session("userId")

every { dao.user("userId") } returns User("userId",
"email",
"User",
"pwd")
}


fun ApplicationCall.mockSessionReturningNothing() {
every { attributes.contains(sessionMatcher()) } returns false
}


fun ApplicationCall.checkForbiddenIfSesionReturningNothing(handle: () -> Unit) {
mockSessionReturningNothing()

coEvery { respond(any()) } just Runs

handle()

coVerify { respond(HttpStatusCode.Forbidden) }
}

fun ApplicationCall.mockHostReferrerHash(hash: (String) -> String) {
every { request.host() } returns "host"

every { request.headers[HttpHeaders.Referrer] } returns "http://abc/referrer"

every { hash(any()) } answers { firstArg<String>().reversed() }
}


fun mockGetThought(dao: ThinkterStorage, ts: Long) {
every {
dao.getThought(any())
} answers { Thought(firstArg(),
"userId",
"text",
formatDate(ts + firstArg<Int>()),
null) }
}

private fun formatDate(date: Long): String {
return Instant.ofEpochMilli(date)
.atZone(ZoneId.systemDefault())
.toOffsetDateTime()
.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
}


fun mockUser(dao: ThinkterStorage, pwdHash: String? = null): User {
val user = User("abcdef", "abc@def", "Abc Def", pwdHash ?: "")
every { dao.user("abcdef", pwdHash) } returns user
return user
}

fun ApplicationCall.mockPutSession() {
every {
attributes
.get(sessionConfigMatcher())
.sessionType
} returns Session::class

every { attributes.put(sessionMatcher(), any()) } just Runs
}

104 changes: 104 additions & 0 deletions backend/test/org/jetbrains/demo/thinkter/DeleteKtTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package org.jetbrains.demo.thinkter

import io.mockk.*
import org.jetbrains.demo.thinkter.dao.ThinkterStorage
import org.jetbrains.demo.thinkter.model.PostThoughtToken
import org.jetbrains.demo.thinkter.model.RpcData
import org.jetbrains.demo.thinkter.model.Thought
import org.jetbrains.ktor.http.HttpMethod
import org.jetbrains.ktor.locations.Locations
import org.jetbrains.ktor.routing.HttpMethodRouteSelector
import org.jetbrains.ktor.routing.Routing
import org.junit.Before
import org.junit.Test

class DeleteKtTest {
val route = mockk<Routing>()
val dao = mockk<ThinkterStorage>()
val hash = mockk<(String) -> String>()
val locations = mockk<Locations>()

val getThoughtDelete = RouteBlockSlot()
val postThoughtDelete = RouteBlockSlot()

@Before
fun setUp() {
route.mockDsl(locations) {
mockObj<ThoughtDelete> {
mockSelect(HttpMethodRouteSelector(HttpMethod.Get)) {
captureBlock(getThoughtDelete)
}
mockSelect(HttpMethodRouteSelector(HttpMethod.Post)) {
captureBlock(postThoughtDelete)
}
}
}

route.delete(dao, hash)
}

@Test
fun testGetThoughtDeleteOk() {
val data = ThoughtDelete(1, System.currentTimeMillis() - 6000, "abc")
getThoughtDelete.invokeBlock(locations, data) { handle ->
mockSessionReturningUser(dao)
mockHostReferrerHash(hash)

coEvery { respond(any()) } just Runs

handle()

coVerify {
respond(assert<PostThoughtToken> {
it.user == "userId" &&
it.code.contains("cba:tsoh:dIresu")
})
}
}
}

@Test
fun testGetThoughtDeleteNotLoggedIn() {
val data = ThoughtDelete(0, 0, "abc")
getThoughtDelete.invokeBlock(locations, data) { handle ->
checkForbiddenIfSesionReturningNothing(handle)
}
}

@Test
fun testPostThoughtDeleteOk() {
val ts = System.currentTimeMillis() - 6000
val data = ThoughtDelete(1, ts, "cba:tsoh:dIresu:" + ts.toString().reversed())
postThoughtDelete.invokeBlock(locations, data) { handle ->
mockSessionReturningUser(dao)
mockHostReferrerHash(hash)
mockGetThought(dao, ts)

every {
dao.deleteThought(1)
} just Runs

coEvery { respond(any()) } just Runs

handle()

coVerify {
respond(ofType(RpcData::class))
}
}
}

@Test
fun testPostThoughtDeleteNotLoggedIn() {
val data = ThoughtDelete(1, 0, "abc")
val ts = System.currentTimeMillis()
postThoughtDelete.invokeBlock(locations, data) { handle ->
every {
dao.getThought(1)
} answers { Thought(1, "userId", "text", ts.toString(), null) }

checkForbiddenIfSesionReturningNothing(handle)
}
}

}
Loading