Skip to content

Commit

Permalink
Add kover and gh action for coverage (#2)
Browse files Browse the repository at this point in the history
* Add kover and gh action for coverage

* Add simple test for tracing interceptor

* Migrate to libs.versions.toml

* Add maven central

* Exclude some projects

* Add kover project

* Add tests for kotlet.json

* Add tests for kotlet.metrics

* Add tests for kotlet.metrics

* Add tests for kotlet.tracing

* Fix detekt
  • Loading branch information
turchenkoalex authored Jul 17, 2024
1 parent c9e2e0a commit ab991de
Show file tree
Hide file tree
Showing 36 changed files with 875 additions and 206 deletions.
19 changes: 14 additions & 5 deletions .github/workflows/verify.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:

env:
JDK_VERSION: 21
GRADLE_OPTS: -Dorg.gradle.daemon=false

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
Expand All @@ -16,8 +17,7 @@ jobs:
permissions:
contents: read
checks: write
env:
GRADLE_OPTS: -Dorg.gradle.daemon=false
pull-requests: write
steps:
- name: Checkout Code
uses: actions/checkout@v4
Expand All @@ -28,12 +28,23 @@ jobs:
java-version: '${{ env.JDK_VERSION }}'
cache: 'gradle'
- name: Build and Run Tests
run: ./gradlew test
run: ./gradlew test koverXmlReport
- name: Publish Test Report
uses: mikepenz/action-junit-report@v4
if: success() || failure() # always run even if the previous step fails
with:
report_paths: '**/build/test-results/test/TEST-*.xml'
- name: Publish Kover Report
uses: madrapps/[email protected]
if: success() || failure() # always run even if the previous step fails
with:
paths: |
${{ github.workspace }}/build/reports/kover/report.xml
token: ${{ secrets.GITHUB_TOKEN }}
min-coverage-overall: 50
min-coverage-changed-files: 60
- name: Coverage verify
run: ./gradlew koverVerify
detekt:
name: Detekt
runs-on: ubuntu-latest
Expand All @@ -44,8 +55,6 @@ jobs:
# only required for workflows in private repositories
actions: read
contents: read
env:
GRADLE_OPTS: -Dorg.gradle.daemon=false
steps:
- name: Checkout Code
uses: actions/checkout@v4
Expand Down
1 change: 1 addition & 0 deletions .idea/gradle.xml

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

6 changes: 3 additions & 3 deletions benchmarks/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
plugins {
kotlin("jvm")
id("me.champeau.jmh") version "0.7.2"
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.jmh)
}

dependencies {
Expand All @@ -20,4 +20,4 @@ tasks.test {

kotlin {
jvmToolchain(21)
}
}
45 changes: 38 additions & 7 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,25 @@ group = "com.ecwid"
version = "1.0-SNAPSHOT"

plugins {
kotlin("jvm") version libs.versions.kotlin.get() apply false
kotlin("plugin.serialization") version libs.versions.kotlin.get() apply false
id("io.gitlab.arturbosch.detekt") version libs.versions.detekt.get()
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.kotlinx.serialization) apply false
alias(libs.plugins.detekt)
alias(libs.plugins.kover)
}

allprojects {
repositories {
mavenCentral()
}
}

// register task before using in subprojects
val reportMerge by tasks.registering(ReportMergeTask::class) {
output.set(rootProject.layout.buildDirectory.file("reports/detekt/merge.xml"))
}

// Detekt configuration
subprojects {
repositories {
mavenCentral()
}

apply(plugin = "io.gitlab.arturbosch.detekt")

detekt {
Expand Down Expand Up @@ -47,3 +51,30 @@ subprojects {
)
}
}

val coverageExclusions = setOf(
"benchmarks",
"mocks",
"sample",
)

subprojects {
if (this.name !in coverageExclusions) {
apply(plugin = "org.jetbrains.kotlinx.kover")

// register kover for generating merged report from all subprojects
rootProject.dependencies {
kover(project)
}

kover {
reports {
verify {
rule("Minimal line coverage rate in percents") {
minBound(40)
}
}
}
}
}
}
6 changes: 3 additions & 3 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
plugins {
kotlin("jvm")
alias(libs.plugins.kotlin.jvm)
}

dependencies {
compileOnly(libs.jakarta.api)

testImplementation(kotlin("test"))
testImplementation(libs.kotlin.test)
testImplementation(libs.mockk)
testImplementation(libs.jakarta.api)
}
Expand All @@ -16,4 +16,4 @@ tasks.test {

kotlin {
jvmToolchain(21)
}
}
2 changes: 1 addition & 1 deletion core/src/main/kotlin/kotlet/HttpCall.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface HttpCall {
val httpMethod: HttpMethod

/**
* Route path of the request configured in the [routing].
* Route path of the request configured in the [Kotlet.routing].
*/
val routePath: String

Expand Down
6 changes: 3 additions & 3 deletions cors/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
plugins {
kotlin("jvm")
alias(libs.plugins.kotlin.jvm)
}

dependencies {
implementation(project(":core"))
compileOnly(libs.jakarta.api)

testImplementation(kotlin("test"))
testImplementation(libs.kotlin.test)
testImplementation(libs.mockk)
testImplementation(libs.jakarta.api)
}
Expand All @@ -16,4 +16,4 @@ tasks.test {
}
kotlin {
jvmToolchain(21)
}
}
50 changes: 50 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
[versions]
auth0-jwt = "4.4.0"
detekt = "1.23.6"
jakarta = "6.1.0"
jetty = "12.0.10"
kotlin = "2.0.0"
kotlinx-serialization = "1.7.1"
kover = "0.8.2"
mockk = "1.13.12"
opentelemetry = "1.39.0"
opentelemetry-instrumentation-api = "2.5.0"
opentelemetry-semconv = "1.25.0-alpha"
prometheus = "1.3.1"

[plugins]
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
jmh = { id = "me.champeau.jmh", version = "0.7.2" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" }

[libraries]

# Jakarta
jakarta-api = { group = "jakarta.servlet", name = "jakarta.servlet-api", version.ref = "jakarta" }

# JWT
auth0-jwt = { group = "com.auth0", name = "java-jwt", version.ref = "auth0-jwt" }

# Kotlin
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
kotlin-test = { group = "org.jetbrains.kotlin", name = "kotlin-test", version.ref = "kotlin" }

# Prometheus metrics
prometheus-metrics-core = { group = "io.prometheus", name = "prometheus-metrics-core", version.ref = "prometheus" }
prometheus-metrics-exporter-servlet-jakarta = { group = "io.prometheus", name = "prometheus-metrics-exporter-servlet-jakarta", version.ref = "prometheus" }

# OpenTelemetry
opentelemetry-api = { group = "io.opentelemetry", name = "opentelemetry-api", version.ref = "opentelemetry" }
opentelemetry-sdk = { group = "io.opentelemetry", name = "opentelemetry-sdk", version.ref = "opentelemetry" }
opentelemetry-exporter-otlp = { group = "io.opentelemetry", name = "opentelemetry-exporter-otlp", version.ref = "opentelemetry" }
opentelemetry-instrumentation-api = { group = "io.opentelemetry.instrumentation", name = "opentelemetry-instrumentation-api", version.ref = "opentelemetry-instrumentation-api" }
opentelemetry-semconv = { group = "io.opentelemetry.semconv", name = "opentelemetry-semconv", version.ref = "opentelemetry-semconv" }

# Jetty
jetty-server = { group = "org.eclipse.jetty", name = "jetty-server", version.ref = "jetty" }
jetty-ee10-servlet = { group = "org.eclipse.jetty.ee10", name = "jetty-ee10-servlet", version.ref = "jetty" }

# Testing
mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }
10 changes: 8 additions & 2 deletions json/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
plugins {
kotlin("jvm")
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.kotlinx.serialization)
}

dependencies {
implementation(project(":core"))
compileOnly(libs.jakarta.api)
implementation(libs.kotlinx.serialization.json)

testImplementation(project(":mocks"))
testImplementation(libs.kotlin.test)
testImplementation(libs.mockk)
testImplementation(libs.jakarta.api)
}

tasks.test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(21)
}
}
35 changes: 35 additions & 0 deletions json/src/test/kotlin/kotlet/HttpCallUnitTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package kotlet

import io.mockk.verify
import kotlet.mocks.Mocks
import kotlinx.serialization.Serializable
import org.junit.jupiter.api.Assertions.assertEquals
import kotlin.test.Test

class HttpCallUnitTest {
@Test
fun `receiveBody must returns deserialized object`() {
val data = "{\"name\":\"test\"}".toByteArray(Charsets.UTF_8)
val call = Mocks.httpCall(method = HttpMethod.GET, data = data)
val obj = call.receiveBody<SimpleBody>()
assertEquals(SimpleBody(name = "test"), obj)
}

@Test
fun `respondJson must write json`() {
val call = Mocks.httpCall(method = HttpMethod.GET)
call.respondJson(SimpleBody(name = "test"))

assertEquals("application/json", call.rawResponse.contentType)
assertEquals("{\"name\":\"test\"}", String(call.responseData))

verify {
call.rawResponse.contentType = "application/json"
}
}

@Serializable
private data class SimpleBody(val name: String)
}


29 changes: 29 additions & 0 deletions json/src/test/kotlin/kotlet/json/SerializerUnitTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package kotlet.json

import kotlinx.serialization.Serializable
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import kotlin.test.Test
import kotlin.test.assertEquals

class SerializerUnitTest {
@Test
fun `serialize test`() {
val obj = SimpleObject(name = "test", number = 99)
val stream = ByteArrayOutputStream()
Serializer.serializeToStream(obj, stream)

val json = stream.toString(Charsets.UTF_8)
assertEquals("{\"name\":\"test\",\"number\":99}", json)
}

@Test
fun `deserialize test`() {
val stream = ByteArrayInputStream("{\"name\":\"test\",\"number\":99}".toByteArray(Charsets.UTF_8))
val obj = Serializer.deserialize(stream, SimpleObject::class.java)
assertEquals(SimpleObject(name = "test", number = 99), obj)
}
}

@Serializable
private data class SimpleObject(val name: String, val number: Int)
5 changes: 3 additions & 2 deletions jwt/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
plugins {
kotlin("jvm")
alias(libs.plugins.kotlin.jvm)
}

dependencies {
implementation(project(":core"))
compileOnly(libs.jakarta.api)
implementation(libs.auth0.jwt)

testImplementation(kotlin("test"))
testImplementation(project(":mocks"))
testImplementation(libs.kotlin.test)
testImplementation(libs.mockk)
testImplementation(libs.jakarta.api)
}
Expand Down
6 changes: 6 additions & 0 deletions jwt/src/main/kotlin/kotlet/jwt/IdentityBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,10 @@ package kotlet.jwt

import com.auth0.jwt.interfaces.DecodedJWT

/**
* Identity builder
*
* This method is used to convert a DecodedJWT to a custom identity,
* then it can be accessed from the HttpCall, by calling `call.identity<T>()`
*/
typealias IdentityBuilder<T> = (DecodedJWT) -> T
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,15 @@ internal class JWTAuthenticationInterceptor(

override fun afterCall(call: HttpCall): HttpCall {
if (call.rawRequest.isAsyncStarted) {
call.rawRequest.asyncContext.addListener(JWTAttributeCleaner)
call.rawRequest.asyncContext.addListener(JWTAttributeAsyncCleaner)
} else {
call.rawRequest.removeAttribute(IDENTITY_PARAMETER_NAME)
}
return call
}
}

private object JWTAttributeCleaner : AsyncListener {
private object JWTAttributeAsyncCleaner : AsyncListener {
override fun onComplete(event: jakarta.servlet.AsyncEvent) {
event.asyncContext.request.removeAttribute(IDENTITY_PARAMETER_NAME)
}
Expand Down
Loading

0 comments on commit ab991de

Please sign in to comment.