Skip to content

Commit

Permalink
Refactor to kotlin.assert and power-assert: example (#191)
Browse files Browse the repository at this point in the history
* power asssert example

* code review suggestions
  • Loading branch information
tibtof authored Nov 23, 2023
1 parent 8f77c1d commit 193b0ff
Show file tree
Hide file tree
Showing 8 changed files with 99 additions and 88 deletions.
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
alias(libs.plugins.sqldelight)
alias(libs.plugins.ktor)
alias(libs.plugins.spotless)
alias(libs.plugins.power.assert)
}

application {
Expand Down
4 changes: 3 additions & 1 deletion libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ slugify="3.0.6"
suspendapp="0.4.0"
cohort="2.3.0"
spotless="6.22.0"
power-assert = "0.13.0"

[libraries]
arrow-core = { module = "io.arrow-kt:arrow-core", version.ref = "arrow" }
Expand Down Expand Up @@ -110,4 +111,5 @@ detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }
ktor = { id = "io.ktor.plugin", version.ref = "ktor" }
spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
power-assert = { id = "com.bnorm.power.kotlin-power-assert", version.ref = "power-assert" }
4 changes: 2 additions & 2 deletions src/main/kotlin/io/github/nomisrev/service/JwtService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ fun jwtService(env: Env.Auth, repo: UserPersistence) =
val jwt =
JWT.decodeT(token.value, JWSHMAC512Algorithm).mapLeft { JwtInvalid(it.toString()) }.bind()
val userId =
ensureNotNull(jwt.claimValueAsLong("id").orNull()) {
ensureNotNull(jwt.claimValueAsLong("id").getOrNull()) {
JwtInvalid("id missing from JWT Token")
}
val expiresAt =
ensureNotNull(jwt.expiresAt().orNull()) { JwtInvalid("exp missing from JWT Token") }
ensureNotNull(jwt.expiresAt().getOrNull()) { JwtInvalid("exp missing from JWT Token") }
ensure(expiresAt.isAfter(Instant.now(Clock.systemUTC()))) { JwtInvalid("JWT Token expired") }
repo.select(UserId(userId)).bind()
UserId(userId)
Expand Down
91 changes: 50 additions & 41 deletions src/test/kotlin/io/github/nomisrev/routes/ArticleRouteSpec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import io.github.nomisrev.auth.JwtToken
import io.github.nomisrev.service.RegisterUser
import io.github.nomisrev.withServer
import io.kotest.assertions.arrow.core.shouldBeRight
import io.kotest.matchers.shouldBe
import io.ktor.client.call.body
import io.ktor.client.plugins.resources.get
import io.ktor.client.plugins.resources.post
Expand Down Expand Up @@ -48,10 +47,12 @@ class ArticleRouteSpec :
bearerAuth(token.value)
}

response.status shouldBe HttpStatusCode.OK
response.body<ArticleWrapper<MultipleArticlesResponse>>().article.articles.toSet() shouldBe
emptySet()
response.body<ArticleWrapper<MultipleArticlesResponse>>().article.articlesCount shouldBe 0
assert(response.status == HttpStatusCode.OK)
assert(
response.body<ArticleWrapper<MultipleArticlesResponse>>().article.articles ==
emptyList<Article>()
)
assert(response.body<ArticleWrapper<MultipleArticlesResponse>>().article.articlesCount == 0)
}
}

Expand All @@ -63,10 +64,12 @@ class ArticleRouteSpec :
contentType(ContentType.Application.Json)
}

response.status shouldBe HttpStatusCode.OK
response.body<ArticleWrapper<MultipleArticlesResponse>>().article.articles.toSet() shouldBe
emptySet()
response.body<ArticleWrapper<MultipleArticlesResponse>>().article.articlesCount shouldBe 0
assert(response.status == HttpStatusCode.OK)
assert(
response.body<ArticleWrapper<MultipleArticlesResponse>>().article.articles ==
emptyList<Article>()
)
assert(response.body<ArticleWrapper<MultipleArticlesResponse>>().article.articlesCount == 0)
}
}

Expand All @@ -78,9 +81,11 @@ class ArticleRouteSpec :
contentType(ContentType.Application.Json)
}

response.status shouldBe HttpStatusCode.UnprocessableEntity
response.body<GenericErrorModel>().errors.body shouldBe
listOf("feed offset: too small, minimum is $MIN_FEED_OFFSET, and found -1")
assert(response.status == HttpStatusCode.UnprocessableEntity)
assert(
response.body<GenericErrorModel>().errors.body ==
listOf("feed offset: too small, minimum is $MIN_FEED_OFFSET, and found -1")
)
}
}

Expand All @@ -92,9 +97,11 @@ class ArticleRouteSpec :
contentType(ContentType.Application.Json)
}

response.status shouldBe HttpStatusCode.UnprocessableEntity
response.body<GenericErrorModel>().errors.body shouldBe
listOf("feed limit: too small, minimum is $MIN_FEED_LIMIT, and found 0")
assert(response.status == HttpStatusCode.UnprocessableEntity)
assert(
response.body<GenericErrorModel>().errors.body ==
listOf("feed limit: too small, minimum is $MIN_FEED_LIMIT, and found 0")
)
}
}

Expand All @@ -106,12 +113,14 @@ class ArticleRouteSpec :
contentType(ContentType.Application.Json)
}

response.status shouldBe HttpStatusCode.UnprocessableEntity
response.body<GenericErrorModel>().errors.body shouldBe
listOf(
"feed offset: too small, minimum is $MIN_FEED_OFFSET, and found -1",
"feed limit: too small, minimum is $MIN_FEED_LIMIT, and found 0"
)
assert(response.status == HttpStatusCode.UnprocessableEntity)
assert(
response.body<GenericErrorModel>().errors.body ==
listOf(
"feed offset: too small, minimum is $MIN_FEED_OFFSET, and found -1",
"feed limit: too small, minimum is $MIN_FEED_LIMIT, and found 0"
)
)
}
}

Expand All @@ -124,15 +133,15 @@ class ArticleRouteSpec :
setBody(ArticleWrapper(NewArticle(title, description, body, tags.toList())))
}

response.status shouldBe HttpStatusCode.Created
assert(response.status == HttpStatusCode.Created)
with(response.body<ArticleResponse>()) {
title shouldBe title
description shouldBe description
body shouldBe body
favoritesCount shouldBe 0
favorited shouldBe false
author.username shouldBe username
tagList.toSet() shouldBe tags
assert(this.title == title)
assert(this.description == description)
assert(this.body == body)
assert(this.favoritesCount == 0L)
assert(this.favorited == false)
assert(this.author.username == username)
assert(this.tagList.toSet() == tags)
}
}
}
Expand All @@ -146,15 +155,15 @@ class ArticleRouteSpec :
setBody(ArticleWrapper(NewArticle(title, description, body, emptyList())))
}

response.status shouldBe HttpStatusCode.Created
assert(response.status == HttpStatusCode.Created)
with(response.body<ArticleResponse>()) {
title shouldBe title
description shouldBe description
body shouldBe body
favoritesCount shouldBe 0
favorited shouldBe false
author.username shouldBe username
tagList.size shouldBe 0
assert(this.title == title)
assert(this.description == description)
assert(this.body == body)
assert(this.favoritesCount == 0L)
assert(this.favorited == false)
assert(this.author.username == username)
assert(this.tagList.size == 0)
}
}
}
Expand All @@ -168,7 +177,7 @@ class ArticleRouteSpec :
setBody(ArticleWrapper(NewArticle(title, description, "", emptyList())))
}

response.status shouldBe HttpStatusCode.UnprocessableEntity
assert(response.status == HttpStatusCode.UnprocessableEntity)
}
}

Expand All @@ -181,7 +190,7 @@ class ArticleRouteSpec :
setBody(ArticleWrapper(NewArticle(title, "", body, emptyList())))
}

response.status shouldBe HttpStatusCode.UnprocessableEntity
assert(response.status == HttpStatusCode.UnprocessableEntity)
}
}

Expand All @@ -194,7 +203,7 @@ class ArticleRouteSpec :
setBody(ArticleWrapper(NewArticle("", description, body, emptyList())))
}

response.status shouldBe HttpStatusCode.UnprocessableEntity
assert(response.status == HttpStatusCode.UnprocessableEntity)
}
}

Expand All @@ -206,7 +215,7 @@ class ArticleRouteSpec :
setBody(ArticleWrapper(NewArticle(title, description, body, emptyList())))
}

response.status shouldBe HttpStatusCode.Unauthorized
assert(response.status == HttpStatusCode.Unauthorized)
}
}
})
12 changes: 6 additions & 6 deletions src/test/kotlin/io/github/nomisrev/routes/ArticlesRouteSpec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import io.github.nomisrev.withServer
import io.kotest.assertions.arrow.core.shouldBeRight
import io.kotest.assertions.arrow.core.shouldBeSome
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe
import io.ktor.client.call.body
import io.ktor.client.plugins.resources.get
import io.ktor.http.HttpStatusCode
Expand All @@ -31,9 +30,10 @@ class ArticlesRouteSpec :
withServer {
val response = get(ArticlesResource.Slug(slug = "slug"))

response.status shouldBe HttpStatusCode.UnprocessableEntity
response.body<GenericErrorModel>().errors.body shouldBe
listOf("Article by slug slug not found")
assert(response.status == HttpStatusCode.UnprocessableEntity)
assert(
response.body<GenericErrorModel>().errors.body == listOf("Article by slug slug not found")
)
}
}

Expand All @@ -55,8 +55,8 @@ class ArticlesRouteSpec :

val response = get(ArticlesResource.Slug(slug = article.slug))

response.status shouldBe HttpStatusCode.OK
response.body<SingleArticleResponse>().article shouldBe article
assert(response.status == HttpStatusCode.OK)
assert(response.body<SingleArticleResponse>().article == article)
}
}
})
11 changes: 4 additions & 7 deletions src/test/kotlin/io/github/nomisrev/routes/TagRouteSpec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import io.github.nomisrev.withServer
import io.kotest.assertions.arrow.core.shouldBeRight
import io.kotest.assertions.arrow.core.shouldBeSome
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.matchers.shouldBe
import io.ktor.client.call.body
import io.ktor.client.plugins.resources.get
import io.ktor.http.ContentType
Expand All @@ -34,8 +32,8 @@ class TagRouteSpec :
withServer {
val response = get(TagsResource()) { contentType(ContentType.Application.Json) }

response.status shouldBe HttpStatusCode.OK
response.body<TagsResponse>().tags.toSet() shouldBe emptySet()
assert(response.status == HttpStatusCode.OK)
assert(response.body<TagsResponse>().tags == emptyList<String>())
}
}

Expand All @@ -53,11 +51,10 @@ class TagRouteSpec :
CreateArticle(UserId(userId), validTitle, validDescription, validBody, validTags)
)
.shouldBeRight()

val response = get(TagsResource()) { contentType(ContentType.Application.Json) }

response.status shouldBe HttpStatusCode.OK
response.body<TagsResponse>().tags shouldHaveSize 4
assert(response.status == HttpStatusCode.OK)
assert(response.body<TagsResponse>().tags.size == 4)
}
}
})
57 changes: 29 additions & 28 deletions src/test/kotlin/io/github/nomisrev/routes/UserRouteSpec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import io.github.nomisrev.service.RegisterUser
import io.github.nomisrev.withServer
import io.kotest.assertions.arrow.core.shouldBeRight
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe
import io.ktor.client.call.body
import io.ktor.client.plugins.resources.get
import io.ktor.client.plugins.resources.post
Expand All @@ -29,12 +28,12 @@ class UserRouteSpec :
setBody(UserWrapper(NewUser(validUsername, validEmail, validPw)))
}

response.status shouldBe HttpStatusCode.Created
assert(response.status == HttpStatusCode.Created)
with(response.body<UserWrapper<User>>().user) {
username shouldBe validUsername
email shouldBe validEmail
bio shouldBe ""
image shouldBe ""
assert(username == validUsername)
assert(email == validEmail)
assert(bio == "")
assert(image == "")
}
}
}
Expand All @@ -51,12 +50,12 @@ class UserRouteSpec :
setBody(UserWrapper(LoginUser(validEmail, validPw)))
}

response.status shouldBe HttpStatusCode.OK
assert(response.status == HttpStatusCode.OK)
with(response.body<UserWrapper<User>>().user) {
username shouldBe validUsername
email shouldBe validEmail
bio shouldBe ""
image shouldBe ""
assert(username == validUsername)
assert(email == validEmail)
assert(bio == "")
assert(image == "")
}
}
}
Expand All @@ -70,13 +69,13 @@ class UserRouteSpec :

val response = get(UserResource()) { bearerAuth(expected.value) }

response.status shouldBe HttpStatusCode.OK
assert(response.status == HttpStatusCode.OK)
with(response.body<UserWrapper<User>>().user) {
username shouldBe validUsername
email shouldBe validEmail
token shouldBe expected.value
bio shouldBe ""
image shouldBe ""
assert(username == validUsername)
assert(email == validEmail)
assert(token == expected.value)
assert(bio == "")
assert(image == "")
}
}
}
Expand All @@ -96,13 +95,13 @@ class UserRouteSpec :
setBody(UserWrapper(UpdateUser(username = newUsername)))
}

response.status shouldBe HttpStatusCode.OK
assert(response.status == HttpStatusCode.OK)
with(response.body<UserWrapper<User>>().user) {
username shouldBe newUsername
email shouldBe validEmail
token shouldBe expected.value
bio shouldBe ""
image shouldBe ""
assert(username == newUsername)
assert(email == validEmail)
assert(token == expected.value)
assert(bio == "")
assert(image == "")
}
}
}
Expand All @@ -113,18 +112,20 @@ class UserRouteSpec :
dependencies.userService
.register(RegisterUser(validUsername, validEmail, validPw))
.shouldBeRight()
val inalidEmail = "invalidEmail"
val invalidEmail = "invalidEmail"

val response =
put(UserResource()) {
bearerAuth(token.value)
contentType(ContentType.Application.Json)
setBody(UserWrapper(UpdateUser(email = inalidEmail)))
setBody(UserWrapper(UpdateUser(email = invalidEmail)))
}

response.status shouldBe HttpStatusCode.UnprocessableEntity
response.body<GenericErrorModel>().errors.body shouldBe
listOf("email: 'invalidEmail' is invalid email")
assert(response.status == HttpStatusCode.UnprocessableEntity)
assert(
response.body<GenericErrorModel>().errors.body ==
listOf("email: 'invalidEmail' is invalid email")
)
}
}
})
Loading

0 comments on commit 193b0ff

Please sign in to comment.