From aff56942dad3c691c2aec8a120d64ab74fabc065 Mon Sep 17 00:00:00 2001 From: Oleksii Shtanko Date: Mon, 11 Aug 2025 18:53:07 +0100 Subject: [PATCH 1/8] Add ktor example --- README.md | 22 +- api/Kotlin-Lab.api | 202 ++++++++++-------- build.gradle.kts | 26 ++- gradle/libs.versions.toml | 26 +++ src/main/kotlin/dev/shtanko/api/Constants.kt | 8 + .../kotlin/dev/shtanko/api/Dependencies.kt | 5 + .../kotlin/dev/shtanko/api/GitHubService.kt | 31 +-- .../dev/shtanko/api/KtorGithubService.kt | 58 +++++ .../kotlin/dev/shtanko/api/KtorInterceptor.kt | 50 +++++ .../shtanko/api/contributors/Contributors.kt | 4 +- .../api/contributors/ContributorsImpl.kt | 2 +- .../dev/shtanko/api/contributors/Logger.kt | 6 +- src/main/kotlin/dev/shtanko/api/model/Repo.kt | 9 + .../dev/shtanko/api/model/RequestData.kt | 10 + src/main/kotlin/dev/shtanko/api/model/User.kt | 9 + .../kotlin/dev/shtanko/api/tasks/Aggregate.kt | 4 +- .../dev/shtanko/api/tasks/BlockingRequest.kt | 4 +- .../dev/shtanko/api/tasks/CallbackRequest.kt | 4 +- .../api/tasks/LoadContributorsBackground.kt | 2 +- .../api/tasks/LoadContributorsChannels.kt | 4 +- .../api/tasks/LoadContributorsConcurrent.kt | 4 +- .../tasks/LoadContributorsNotCancellable.kt | 4 +- .../api/tasks/LoadContributorsProgress.kt | 4 +- .../api/tasks/LoadContributorsSuspend.kt | 4 +- .../dev/shtanko/api/KtorGitHubApiTest.kt | 91 ++++++++ src/test/kotlin/dev/shtanko/api/ModelsTest.kt | 3 + .../api/contributors/MockGithubService.kt | 4 +- .../dev/shtanko/api/contributors/TestData.kt | 6 +- .../dev/shtanko/api/tasks/AggregationTest.kt | 2 +- 29 files changed, 439 insertions(+), 169 deletions(-) create mode 100644 src/main/kotlin/dev/shtanko/api/Constants.kt create mode 100644 src/main/kotlin/dev/shtanko/api/Dependencies.kt create mode 100644 src/main/kotlin/dev/shtanko/api/KtorGithubService.kt create mode 100644 src/main/kotlin/dev/shtanko/api/KtorInterceptor.kt create mode 100644 src/main/kotlin/dev/shtanko/api/model/Repo.kt create mode 100644 src/main/kotlin/dev/shtanko/api/model/RequestData.kt create mode 100644 src/main/kotlin/dev/shtanko/api/model/User.kt create mode 100644 src/test/kotlin/dev/shtanko/api/KtorGitHubApiTest.kt diff --git a/README.md b/README.md index 6c1d38d8..2846989f 100644 --- a/README.md +++ b/README.md @@ -22,22 +22,22 @@ ### Metrics ```text -15199 number of properties -10523 number of functions -8940 number of classes -239 number of packages -3531 number of kt files +15218 number of properties +10530 number of functions +8942 number of classes +240 number of packages +3539 number of kt files ``` ### Complexity Report ```text -266642 lines of code (loc) -165721 source lines of code (sloc) -121060 logical lines of code (lloc) -72547 comment lines of code (cloc) -25022 cyclomatic complexity (mcc) -20410 cognitive complexity +266866 lines of code (loc) +165906 source lines of code (sloc) +121157 logical lines of code (lloc) +72548 comment lines of code (cloc) +25030 cyclomatic complexity (mcc) +20414 cognitive complexity 0 number of total code smells 43 comment source ratio 206 mcc per 1,000 lloc diff --git a/api/Kotlin-Lab.api b/api/Kotlin-Lab.api index aac79c26..fcca407e 100644 --- a/api/Kotlin-Lab.api +++ b/api/Kotlin-Lab.api @@ -18326,93 +18326,20 @@ public final class dev/shtanko/api/GitHubServiceKt { public static synthetic fun createRetrofit$default (Lokhttp3/OkHttpClient;Lokhttp3/HttpUrl;Lkotlinx/serialization/json/Json;ILjava/lang/Object;)Lretrofit2/Retrofit; } -public final class dev/shtanko/api/Repo { - public static final field Companion Ldev/shtanko/api/Repo$Companion; - public fun (JLjava/lang/String;)V - public final fun component1 ()J - public final fun component2 ()Ljava/lang/String; - public final fun copy (JLjava/lang/String;)Ldev/shtanko/api/Repo; - public static synthetic fun copy$default (Ldev/shtanko/api/Repo;JLjava/lang/String;ILjava/lang/Object;)Ldev/shtanko/api/Repo; - public fun equals (Ljava/lang/Object;)Z - public final fun getId ()J - public final fun getName ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class dev/shtanko/api/Repo$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Ldev/shtanko/api/Repo$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/shtanko/api/Repo; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/shtanko/api/Repo;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - -public final class dev/shtanko/api/Repo$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class dev/shtanko/api/RequestData { - public static final field Companion Ldev/shtanko/api/RequestData$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ldev/shtanko/api/RequestData; - public static synthetic fun copy$default (Ldev/shtanko/api/RequestData;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Ldev/shtanko/api/RequestData; - public fun equals (Ljava/lang/Object;)Z - public final fun getOrg ()Ljava/lang/String; - public final fun getPassword ()Ljava/lang/String; - public final fun getUsername ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; +public final class dev/shtanko/api/KtorGitHubApi { + public fun (Lio/ktor/client/HttpClient;)V + public final fun getOrgRepos (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getRepoContributors (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } -public final synthetic class dev/shtanko/api/RequestData$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Ldev/shtanko/api/RequestData$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/shtanko/api/RequestData; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/shtanko/api/RequestData;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - -public final class dev/shtanko/api/RequestData$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class dev/shtanko/api/User { - public static final field Companion Ldev/shtanko/api/User$Companion; - public fun (Ljava/lang/String;I)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()I - public final fun copy (Ljava/lang/String;I)Ldev/shtanko/api/User; - public static synthetic fun copy$default (Ldev/shtanko/api/User;Ljava/lang/String;IILjava/lang/Object;)Ldev/shtanko/api/User; - public fun equals (Ljava/lang/Object;)Z - public final fun getContributions ()I - public final fun getLogin ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; +public final class dev/shtanko/api/KtorGithubServiceKt { + public static final fun createKtorClient (Ljava/lang/String;Ljava/lang/String;Lio/ktor/client/engine/HttpClientEngine;)Lio/ktor/client/HttpClient; + public static synthetic fun createKtorClient$default (Ljava/lang/String;Ljava/lang/String;Lio/ktor/client/engine/HttpClientEngine;ILjava/lang/Object;)Lio/ktor/client/HttpClient; } -public final synthetic class dev/shtanko/api/User$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Ldev/shtanko/api/User$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/shtanko/api/User; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/shtanko/api/User;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - -public final class dev/shtanko/api/User$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final class dev/shtanko/api/KtorInterceptorKt { + public static final fun main ()V + public static synthetic fun main ([Ljava/lang/String;)V } public abstract interface class dev/shtanko/api/contributors/Contributors : kotlinx/coroutines/CoroutineScope { @@ -18472,8 +18399,8 @@ public abstract interface class dev/shtanko/api/contributors/ContributorsReposit public final class dev/shtanko/api/contributors/LoggerKt { public static final fun getLog ()Lorg/slf4j/Logger; public static final fun log (Ljava/lang/String;)V - public static final fun logRepos (Ldev/shtanko/api/RequestData;Lretrofit2/Response;)V - public static final fun logUsers (Ldev/shtanko/api/Repo;Lretrofit2/Response;)V + public static final fun logRepos (Ldev/shtanko/api/model/RequestData;Lretrofit2/Response;)V + public static final fun logUsers (Ldev/shtanko/api/model/Repo;Lretrofit2/Response;)V } public final class dev/shtanko/api/contributors/MainKt { @@ -18519,6 +18446,95 @@ public final class dev/shtanko/api/contributors/Variant : java/lang/Enum { public static fun values ()[Ldev/shtanko/api/contributors/Variant; } +public final class dev/shtanko/api/model/Repo { + public static final field Companion Ldev/shtanko/api/model/Repo$Companion; + public fun (JLjava/lang/String;)V + public final fun component1 ()J + public final fun component2 ()Ljava/lang/String; + public final fun copy (JLjava/lang/String;)Ldev/shtanko/api/model/Repo; + public static synthetic fun copy$default (Ldev/shtanko/api/model/Repo;JLjava/lang/String;ILjava/lang/Object;)Ldev/shtanko/api/model/Repo; + public fun equals (Ljava/lang/Object;)Z + public final fun getId ()J + public final fun getName ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class dev/shtanko/api/model/Repo$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Ldev/shtanko/api/model/Repo$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/shtanko/api/model/Repo; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/shtanko/api/model/Repo;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class dev/shtanko/api/model/Repo$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class dev/shtanko/api/model/RequestData { + public static final field Companion Ldev/shtanko/api/model/RequestData$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ldev/shtanko/api/model/RequestData; + public static synthetic fun copy$default (Ldev/shtanko/api/model/RequestData;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Ldev/shtanko/api/model/RequestData; + public fun equals (Ljava/lang/Object;)Z + public final fun getOrg ()Ljava/lang/String; + public final fun getPassword ()Ljava/lang/String; + public final fun getUsername ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class dev/shtanko/api/model/RequestData$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Ldev/shtanko/api/model/RequestData$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/shtanko/api/model/RequestData; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/shtanko/api/model/RequestData;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class dev/shtanko/api/model/RequestData$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class dev/shtanko/api/model/User { + public static final field Companion Ldev/shtanko/api/model/User$Companion; + public fun (Ljava/lang/String;I)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()I + public final fun copy (Ljava/lang/String;I)Ldev/shtanko/api/model/User; + public static synthetic fun copy$default (Ldev/shtanko/api/model/User;Ljava/lang/String;IILjava/lang/Object;)Ldev/shtanko/api/model/User; + public fun equals (Ljava/lang/Object;)Z + public final fun getContributions ()I + public final fun getLogin ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class dev/shtanko/api/model/User$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Ldev/shtanko/api/model/User$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/shtanko/api/model/User; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/shtanko/api/model/User;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class dev/shtanko/api/model/User$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class dev/shtanko/api/samples/ChannelsSample { public static final field INSTANCE Ldev/shtanko/api/samples/ChannelsSample; public static final fun main ([Ljava/lang/String;)V @@ -18530,36 +18546,36 @@ public final class dev/shtanko/api/tasks/AggregateKt { public final class dev/shtanko/api/tasks/BlockingRequestKt { public static final fun bodyList (Lretrofit2/Response;)Ljava/util/List; - public static final fun loadContributorsBlocking (Ldev/shtanko/api/GitHubService;Ldev/shtanko/api/RequestData;)Ljava/util/List; + public static final fun loadContributorsBlocking (Ldev/shtanko/api/GitHubService;Ldev/shtanko/api/model/RequestData;)Ljava/util/List; } public final class dev/shtanko/api/tasks/CallbackRequestKt { - public static final fun loadContributorsCallbacks (Ldev/shtanko/api/GitHubService;Ldev/shtanko/api/RequestData;Lkotlin/jvm/functions/Function1;)V + public static final fun loadContributorsCallbacks (Ldev/shtanko/api/GitHubService;Ldev/shtanko/api/model/RequestData;Lkotlin/jvm/functions/Function1;)V public static final fun onResponse (Lretrofit2/Call;Lkotlin/jvm/functions/Function1;)V } public final class dev/shtanko/api/tasks/LoadContributorsBackgroundKt { - public static final fun loadContributorsBackground (Ldev/shtanko/api/GitHubService;Ldev/shtanko/api/RequestData;)V + public static final fun loadContributorsBackground (Ldev/shtanko/api/GitHubService;Ldev/shtanko/api/model/RequestData;)V } public final class dev/shtanko/api/tasks/LoadContributorsChannelsKt { - public static final fun loadContributorsChannels (Ldev/shtanko/api/GitHubService;Ldev/shtanko/api/RequestData;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun loadContributorsChannels (Ldev/shtanko/api/GitHubService;Ldev/shtanko/api/model/RequestData;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class dev/shtanko/api/tasks/LoadContributorsConcurrentKt { - public static final fun loadContributorsConcurrent (Ldev/shtanko/api/GitHubService;Ldev/shtanko/api/RequestData;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun loadContributorsConcurrent (Ldev/shtanko/api/GitHubService;Ldev/shtanko/api/model/RequestData;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class dev/shtanko/api/tasks/LoadContributorsNotCancellableKt { - public static final fun loadContributorsNotCancellable (Ldev/shtanko/api/GitHubService;Ldev/shtanko/api/RequestData;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun loadContributorsNotCancellable (Ldev/shtanko/api/GitHubService;Ldev/shtanko/api/model/RequestData;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class dev/shtanko/api/tasks/LoadContributorsProgressKt { - public static final fun loadContributorsProgress (Ldev/shtanko/api/GitHubService;Ldev/shtanko/api/RequestData;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun loadContributorsProgress (Ldev/shtanko/api/GitHubService;Ldev/shtanko/api/model/RequestData;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class dev/shtanko/api/tasks/LoadContributorsSuspendKt { - public static final fun loadContributorsSuspend (Ldev/shtanko/api/GitHubService;Ldev/shtanko/api/RequestData;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun loadContributorsSuspend (Ldev/shtanko/api/GitHubService;Ldev/shtanko/api/model/RequestData;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class dev/shtanko/benchmark/Benchmark { diff --git a/build.gradle.kts b/build.gradle.kts index 7e7e3555..66b66938 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -338,14 +338,25 @@ dependencies { implementation(okhttp) implementation(sandwich.retrofit) implementation(okhttp.logging) - implementation("org.openjdk.jol:jol-core:0.17") - implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0") - implementation(jsoup) - implementation("com.google.protobuf:protobuf-java:4.31.1") - implementation("com.google.protobuf:protobuf-kotlin-lite:4.31.1") - implementation("io.grpc:grpc-stub:1.73.0") - implementation("io.grpc:grpc-protobuf:1.73.0") + ktor.apply { + client.apply { + implementation(client.core) + implementation(client.okhttp) + implementation(client.content.negotiation) + implementation(client.logging) + implementation(client.cio) + } + implementation(ktor.serialization.kotlinx.json) + } + implementation("ch.qos.logback:logback-classic:1.4.11") + implementation(jsoup) + implementation(libs.jol.core) + implementation(libs.retrofit2.kotlinx.serialization.converter) + implementation(libs.protobuf.java) + implementation(libs.protobuf.kotlin.lite) + implementation(libs.grpc.stub) + implementation(libs.grpc.protobuf) testImplementation(mockk) testImplementation(junit) @@ -369,6 +380,7 @@ dependencies { testImplementation(okhttp.mockwebserver) testImplementation(turbine) testImplementation(truth) + testImplementation(ktor.client.mock) } testImplementation("org.jetbrains.kotlin:kotlin-test-junit") } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 03f425b8..5f6742ed 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -36,6 +36,11 @@ protobufPlugin = "0.9.5" # https://mvnrepository.com/artifact/com.google.prot protobufKotlin = "4.31.1" # https://mvnrepository.com/artifact/com.google.protobuf/protobuf-kotlin protoc = "4.31.1" # https://mvnrepository.com/artifact/com.google.protobuf/protoc binaryCompatibility = "0.18.1" +ktor = "3.2.3" +jol = "0.17" +retrofit-kotlinx-serialization = "1.0.0" +protobuf = "4.31.1" +grpc = "1.73.0" [libraries] kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8", version.ref = "coroutines" } @@ -66,6 +71,27 @@ jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } sandwich-retrofit = { group = "com.github.skydoves", name = "sandwich-retrofit", version.ref = "sandwich" } protobuf-protoc = { group = "com.google.protobuf", name = "protoc", version.ref = "protoc" } protobuf-kotlin = { group = "com.google.protobuf", name = "protobuf-kotlin", version.ref = "protobufKotlin" } +ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } +ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" } +ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" } +ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } +ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" } +ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" } +ktor-client-mock = { module = "io.ktor:ktor-client-mock", version.ref = "ktor" } + +# OpenJDK JOL +jol-core = { module = "org.openjdk.jol:jol-core", version.ref = "jol" } + +# Retrofit kotlinx.serialization converter +retrofit2-kotlinx-serialization-converter = { module = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter", version.ref = "retrofit-kotlinx-serialization" } + +# Protobuf +protobuf-java = { module = "com.google.protobuf:protobuf-java", version.ref = "protobuf" } +protobuf-kotlin-lite = { module = "com.google.protobuf:protobuf-kotlin-lite", version.ref = "protobuf" } + +# gRPC +grpc-stub = { module = "io.grpc:grpc-stub", version.ref = "grpc" } +grpc-protobuf = { module = "io.grpc:grpc-protobuf", version.ref = "grpc" } # testing libs junit = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" } diff --git a/src/main/kotlin/dev/shtanko/api/Constants.kt b/src/main/kotlin/dev/shtanko/api/Constants.kt new file mode 100644 index 00000000..8e021b3d --- /dev/null +++ b/src/main/kotlin/dev/shtanko/api/Constants.kt @@ -0,0 +1,8 @@ +package dev.shtanko.api + +internal const val BASE_URL = "https://api.github.com/" +internal const val JSON_CONTENT_TYPE = "application/json" +internal const val AUTH_HEADER = "Authorization" +internal const val ACCEPT_HEADER = "Accept" +internal const val ACCEPT_HEADER_VALUE = "application/vnd.github.v3+json" +internal const val AUTH_BASIC = "Basic %s" diff --git a/src/main/kotlin/dev/shtanko/api/Dependencies.kt b/src/main/kotlin/dev/shtanko/api/Dependencies.kt new file mode 100644 index 00000000..dd3913d8 --- /dev/null +++ b/src/main/kotlin/dev/shtanko/api/Dependencies.kt @@ -0,0 +1,5 @@ +package dev.shtanko.api + +import kotlinx.serialization.json.Json + +internal val json = Json { ignoreUnknownKeys = true } diff --git a/src/main/kotlin/dev/shtanko/api/GitHubService.kt b/src/main/kotlin/dev/shtanko/api/GitHubService.kt index 1d2eb9b6..5bd7a8ed 100644 --- a/src/main/kotlin/dev/shtanko/api/GitHubService.kt +++ b/src/main/kotlin/dev/shtanko/api/GitHubService.kt @@ -17,10 +17,11 @@ package dev.shtanko.api import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory +import dev.shtanko.api.model.Repo +import dev.shtanko.api.model.User import java.lang.String.format import java.util.Base64 import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl @@ -56,34 +57,6 @@ interface GitHubService { ): Response> } -@Serializable -data class Repo( - val id: Long, - val name: String, -) - -@Serializable -data class User( - val login: String, - val contributions: Int, -) - -@Serializable -data class RequestData( - val username: String, - val password: String, - val org: String, -) - -private val json = Json { ignoreUnknownKeys = true } - -private const val BASE_URL = "https://api.github.com/" -private const val JSON_CONTENT_TYPE = "application/json" -private const val AUTH_HEADER = "Authorization" -private const val ACCEPT_HEADER = "Accept" -private const val ACCEPT_HEADER_VALUE = "application/vnd.github.v3+json" -private const val AUTH_BASIC = "Basic %s" - fun createHttpClient(authToken: String): OkHttpClient { return OkHttpClient.Builder() .addInterceptor { chain -> diff --git a/src/main/kotlin/dev/shtanko/api/KtorGithubService.kt b/src/main/kotlin/dev/shtanko/api/KtorGithubService.kt new file mode 100644 index 00000000..dadbcd2c --- /dev/null +++ b/src/main/kotlin/dev/shtanko/api/KtorGithubService.kt @@ -0,0 +1,58 @@ +package dev.shtanko.api + +import dev.shtanko.api.model.Repo +import dev.shtanko.api.model.User +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.engine.HttpClientEngine +import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.plugins.defaultRequest +import io.ktor.client.plugins.logging.LogLevel +import io.ktor.client.plugins.logging.Logging +import io.ktor.client.request.get +import io.ktor.client.request.header +import io.ktor.client.request.parameter +import io.ktor.http.HttpHeaders +import io.ktor.serialization.kotlinx.json.json +import java.util.Base64 + +fun createKtorClient( + username: String, + password: String, + engine: HttpClientEngine = CIO.create(), +): HttpClient { + val authToken = String.format( + AUTH_BASIC, + Base64.getEncoder().encodeToString("$username:$password".toByteArray(Charsets.UTF_8)), + ) + + return HttpClient(engine) { + install(ContentNegotiation) { + json(json) + } + install(Logging) { + level = LogLevel.INFO + } + defaultRequest { + header(ACCEPT_HEADER, ACCEPT_HEADER_VALUE) + header(HttpHeaders.Authorization, authToken) + url(BASE_URL) + } + } +} + +class KtorGitHubApi(private val client: HttpClient) { + + suspend fun getOrgRepos(org: String): List { + return client.get("orgs/$org/repos") { + parameter("per_page", 100) + }.body() + } + + suspend fun getRepoContributors(owner: String, repo: String): List { + return client.get("repos/$owner/$repo/contributors") { + parameter("per_page", 100) + }.body() + } +} diff --git a/src/main/kotlin/dev/shtanko/api/KtorInterceptor.kt b/src/main/kotlin/dev/shtanko/api/KtorInterceptor.kt new file mode 100644 index 00000000..86afb186 --- /dev/null +++ b/src/main/kotlin/dev/shtanko/api/KtorInterceptor.kt @@ -0,0 +1,50 @@ +package dev.shtanko.api + +import io.ktor.client.HttpClient +import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.HttpSend +import io.ktor.client.plugins.logging.LogLevel +import io.ktor.client.plugins.logging.Logging +import io.ktor.client.plugins.plugin +import io.ktor.client.request.HttpRequestPipeline +import io.ktor.client.request.HttpSendPipeline +import io.ktor.client.request.get +import kotlinx.coroutines.runBlocking + +@Suppress("MagicNumber") +fun main() = runBlocking { + val client = HttpClient(CIO) { + install(Logging) { + level = LogLevel.ALL + } + } + + client.sendPipeline.intercept(HttpSendPipeline.State) { request -> + println("Intercepted request to: $request") + // For example, add a custom header + context.headers.append("X-Custom-Header", "MyValue") + proceed() + } + + client.requestPipeline.intercept(HttpRequestPipeline.Before) { + println("Sending request to: ${context.url} ${context.method}") + proceed() + } + + client.plugin(HttpSend).intercept { request -> + println("Request URL: ${request.url}") + println("Request method: ${request.method}") + println("Request headers: ${request.headers}") + val originalCall = execute(request) + if (originalCall.response.status.value !in 100..399) { + execute(request) + } else { + originalCall + } + } + + val response = client.get("https://httpbin.org/get") + println("Response status: ${response.status}") + + client.close() +} diff --git a/src/main/kotlin/dev/shtanko/api/contributors/Contributors.kt b/src/main/kotlin/dev/shtanko/api/contributors/Contributors.kt index 0970528d..b827778b 100644 --- a/src/main/kotlin/dev/shtanko/api/contributors/Contributors.kt +++ b/src/main/kotlin/dev/shtanko/api/contributors/Contributors.kt @@ -16,8 +16,6 @@ package dev.shtanko.api.contributors -import dev.shtanko.api.RequestData -import dev.shtanko.api.User import dev.shtanko.api.contributors.Contributors.LoadingStatus.CANCELED import dev.shtanko.api.contributors.Contributors.LoadingStatus.COMPLETED import dev.shtanko.api.contributors.Contributors.LoadingStatus.IN_PROGRESS @@ -30,6 +28,8 @@ import dev.shtanko.api.contributors.Variant.NOT_CANCELLABLE import dev.shtanko.api.contributors.Variant.PROGRESS import dev.shtanko.api.contributors.Variant.SUSPEND import dev.shtanko.api.createGitHubService +import dev.shtanko.api.model.RequestData +import dev.shtanko.api.model.User import dev.shtanko.api.tasks.loadContributorsBackground import dev.shtanko.api.tasks.loadContributorsBlocking import dev.shtanko.api.tasks.loadContributorsCallbacks diff --git a/src/main/kotlin/dev/shtanko/api/contributors/ContributorsImpl.kt b/src/main/kotlin/dev/shtanko/api/contributors/ContributorsImpl.kt index 96b8d1e1..c3a70e41 100644 --- a/src/main/kotlin/dev/shtanko/api/contributors/ContributorsImpl.kt +++ b/src/main/kotlin/dev/shtanko/api/contributors/ContributorsImpl.kt @@ -16,7 +16,7 @@ package dev.shtanko.api.contributors -import dev.shtanko.api.User +import dev.shtanko.api.model.User import java.awt.event.ActionListener import kotlinx.coroutines.Job diff --git a/src/main/kotlin/dev/shtanko/api/contributors/Logger.kt b/src/main/kotlin/dev/shtanko/api/contributors/Logger.kt index e481aec2..6f1a8609 100644 --- a/src/main/kotlin/dev/shtanko/api/contributors/Logger.kt +++ b/src/main/kotlin/dev/shtanko/api/contributors/Logger.kt @@ -16,9 +16,9 @@ package dev.shtanko.api.contributors -import dev.shtanko.api.Repo -import dev.shtanko.api.RequestData -import dev.shtanko.api.User +import dev.shtanko.api.model.Repo +import dev.shtanko.api.model.RequestData +import dev.shtanko.api.model.User import org.slf4j.Logger import org.slf4j.LoggerFactory import retrofit2.Response diff --git a/src/main/kotlin/dev/shtanko/api/model/Repo.kt b/src/main/kotlin/dev/shtanko/api/model/Repo.kt new file mode 100644 index 00000000..84088eef --- /dev/null +++ b/src/main/kotlin/dev/shtanko/api/model/Repo.kt @@ -0,0 +1,9 @@ +package dev.shtanko.api.model + +import kotlinx.serialization.Serializable + +@Serializable +data class Repo( + val id: Long, + val name: String, +) diff --git a/src/main/kotlin/dev/shtanko/api/model/RequestData.kt b/src/main/kotlin/dev/shtanko/api/model/RequestData.kt new file mode 100644 index 00000000..897ca460 --- /dev/null +++ b/src/main/kotlin/dev/shtanko/api/model/RequestData.kt @@ -0,0 +1,10 @@ +package dev.shtanko.api.model + +import kotlinx.serialization.Serializable + +@Serializable +data class RequestData( + val username: String, + val password: String, + val org: String, +) diff --git a/src/main/kotlin/dev/shtanko/api/model/User.kt b/src/main/kotlin/dev/shtanko/api/model/User.kt new file mode 100644 index 00000000..7c8f3bfe --- /dev/null +++ b/src/main/kotlin/dev/shtanko/api/model/User.kt @@ -0,0 +1,9 @@ +package dev.shtanko.api.model + +import kotlinx.serialization.Serializable + +@Serializable +data class User( + val login: String, + val contributions: Int, +) diff --git a/src/main/kotlin/dev/shtanko/api/tasks/Aggregate.kt b/src/main/kotlin/dev/shtanko/api/tasks/Aggregate.kt index d26f057a..de3b4ccd 100644 --- a/src/main/kotlin/dev/shtanko/api/tasks/Aggregate.kt +++ b/src/main/kotlin/dev/shtanko/api/tasks/Aggregate.kt @@ -16,7 +16,7 @@ package dev.shtanko.api.tasks -import dev.shtanko.api.User +import dev.shtanko.api.model.User /* @@ -24,7 +24,7 @@ import dev.shtanko.api.User repository he or she contributed to. Merge duplications: each user should be present only once in the resulting list with the total value of contributions for all the repositories. - Users should be sorted in a descending order by their contributions. + Users should be sorted in descending order by their contributions. The corresponding test can be found in test/tasks/AggregationKtTest.kt. You can use 'Navigate | Test' menu action (note the shortcut) to navigate to the test. diff --git a/src/main/kotlin/dev/shtanko/api/tasks/BlockingRequest.kt b/src/main/kotlin/dev/shtanko/api/tasks/BlockingRequest.kt index a7b7ea52..aeb3271d 100644 --- a/src/main/kotlin/dev/shtanko/api/tasks/BlockingRequest.kt +++ b/src/main/kotlin/dev/shtanko/api/tasks/BlockingRequest.kt @@ -17,10 +17,10 @@ package dev.shtanko.api.tasks import dev.shtanko.api.GitHubService -import dev.shtanko.api.RequestData -import dev.shtanko.api.User import dev.shtanko.api.contributors.logRepos import dev.shtanko.api.contributors.logUsers +import dev.shtanko.api.model.RequestData +import dev.shtanko.api.model.User import retrofit2.Response fun loadContributorsBlocking(service: GitHubService, req: RequestData): List { diff --git a/src/main/kotlin/dev/shtanko/api/tasks/CallbackRequest.kt b/src/main/kotlin/dev/shtanko/api/tasks/CallbackRequest.kt index 8b243e18..76c280b9 100644 --- a/src/main/kotlin/dev/shtanko/api/tasks/CallbackRequest.kt +++ b/src/main/kotlin/dev/shtanko/api/tasks/CallbackRequest.kt @@ -17,11 +17,11 @@ package dev.shtanko.api.tasks import dev.shtanko.api.GitHubService -import dev.shtanko.api.RequestData -import dev.shtanko.api.User import dev.shtanko.api.contributors.log import dev.shtanko.api.contributors.logRepos import dev.shtanko.api.contributors.logUsers +import dev.shtanko.api.model.RequestData +import dev.shtanko.api.model.User import java.util.Collections import java.util.concurrent.CountDownLatch import retrofit2.Call diff --git a/src/main/kotlin/dev/shtanko/api/tasks/LoadContributorsBackground.kt b/src/main/kotlin/dev/shtanko/api/tasks/LoadContributorsBackground.kt index b74568ea..c0bec624 100644 --- a/src/main/kotlin/dev/shtanko/api/tasks/LoadContributorsBackground.kt +++ b/src/main/kotlin/dev/shtanko/api/tasks/LoadContributorsBackground.kt @@ -17,7 +17,7 @@ package dev.shtanko.api.tasks import dev.shtanko.api.GitHubService -import dev.shtanko.api.RequestData +import dev.shtanko.api.model.RequestData import kotlin.concurrent.thread fun loadContributorsBackground(service: GitHubService, req: RequestData) { diff --git a/src/main/kotlin/dev/shtanko/api/tasks/LoadContributorsChannels.kt b/src/main/kotlin/dev/shtanko/api/tasks/LoadContributorsChannels.kt index de531e73..8deb8b57 100644 --- a/src/main/kotlin/dev/shtanko/api/tasks/LoadContributorsChannels.kt +++ b/src/main/kotlin/dev/shtanko/api/tasks/LoadContributorsChannels.kt @@ -17,10 +17,10 @@ package dev.shtanko.api.tasks import dev.shtanko.api.GitHubService -import dev.shtanko.api.RequestData -import dev.shtanko.api.User import dev.shtanko.api.contributors.logRepos import dev.shtanko.api.contributors.logUsers +import dev.shtanko.api.model.RequestData +import dev.shtanko.api.model.User import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch diff --git a/src/main/kotlin/dev/shtanko/api/tasks/LoadContributorsConcurrent.kt b/src/main/kotlin/dev/shtanko/api/tasks/LoadContributorsConcurrent.kt index 92d2fc02..59359abe 100644 --- a/src/main/kotlin/dev/shtanko/api/tasks/LoadContributorsConcurrent.kt +++ b/src/main/kotlin/dev/shtanko/api/tasks/LoadContributorsConcurrent.kt @@ -17,10 +17,10 @@ package dev.shtanko.api.tasks import dev.shtanko.api.GitHubService -import dev.shtanko.api.RequestData -import dev.shtanko.api.User import dev.shtanko.api.contributors.logRepos import dev.shtanko.api.contributors.logUsers +import dev.shtanko.api.model.RequestData +import dev.shtanko.api.model.User import kotlinx.coroutines.Deferred import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll diff --git a/src/main/kotlin/dev/shtanko/api/tasks/LoadContributorsNotCancellable.kt b/src/main/kotlin/dev/shtanko/api/tasks/LoadContributorsNotCancellable.kt index f8274367..3ae53f6b 100644 --- a/src/main/kotlin/dev/shtanko/api/tasks/LoadContributorsNotCancellable.kt +++ b/src/main/kotlin/dev/shtanko/api/tasks/LoadContributorsNotCancellable.kt @@ -17,11 +17,11 @@ package dev.shtanko.api.tasks import dev.shtanko.api.GitHubService -import dev.shtanko.api.RequestData -import dev.shtanko.api.User import dev.shtanko.api.contributors.log import dev.shtanko.api.contributors.logRepos import dev.shtanko.api.contributors.logUsers +import dev.shtanko.api.model.RequestData +import dev.shtanko.api.model.User import kotlinx.coroutines.Deferred import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers diff --git a/src/main/kotlin/dev/shtanko/api/tasks/LoadContributorsProgress.kt b/src/main/kotlin/dev/shtanko/api/tasks/LoadContributorsProgress.kt index fd17f42b..7ac88237 100644 --- a/src/main/kotlin/dev/shtanko/api/tasks/LoadContributorsProgress.kt +++ b/src/main/kotlin/dev/shtanko/api/tasks/LoadContributorsProgress.kt @@ -17,10 +17,10 @@ package dev.shtanko.api.tasks import dev.shtanko.api.GitHubService -import dev.shtanko.api.RequestData -import dev.shtanko.api.User import dev.shtanko.api.contributors.logRepos import dev.shtanko.api.contributors.logUsers +import dev.shtanko.api.model.RequestData +import dev.shtanko.api.model.User suspend fun loadContributorsProgress( service: GitHubService, diff --git a/src/main/kotlin/dev/shtanko/api/tasks/LoadContributorsSuspend.kt b/src/main/kotlin/dev/shtanko/api/tasks/LoadContributorsSuspend.kt index 494ac335..2d8be9ec 100644 --- a/src/main/kotlin/dev/shtanko/api/tasks/LoadContributorsSuspend.kt +++ b/src/main/kotlin/dev/shtanko/api/tasks/LoadContributorsSuspend.kt @@ -17,10 +17,10 @@ package dev.shtanko.api.tasks import dev.shtanko.api.GitHubService -import dev.shtanko.api.RequestData -import dev.shtanko.api.User import dev.shtanko.api.contributors.logRepos import dev.shtanko.api.contributors.logUsers +import dev.shtanko.api.model.RequestData +import dev.shtanko.api.model.User suspend fun loadContributorsSuspend(service: GitHubService, req: RequestData): List { val repos = service diff --git a/src/test/kotlin/dev/shtanko/api/KtorGitHubApiTest.kt b/src/test/kotlin/dev/shtanko/api/KtorGitHubApiTest.kt new file mode 100644 index 00000000..0460443d --- /dev/null +++ b/src/test/kotlin/dev/shtanko/api/KtorGitHubApiTest.kt @@ -0,0 +1,91 @@ +package dev.shtanko.api + +import dev.shtanko.api.model.Repo +import dev.shtanko.api.model.User +import io.ktor.client.HttpClient +import io.ktor.client.engine.mock.MockEngine +import io.ktor.client.engine.mock.respond +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.request.HttpRequestData +import io.ktor.client.request.get +import io.ktor.http.HttpHeaders +import io.ktor.http.HttpStatusCode +import io.ktor.http.headersOf +import io.ktor.serialization.kotlinx.json.json +import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.Json +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +class KtorGitHubApiTest { + + private val testJson = Json { ignoreUnknownKeys = true } + + @Test + fun `getOrgRepos should parse JSON into Repo list`() = runTest { + val expectedRepos = listOf( + Repo(id = 1, name = "Repo1"), + Repo(id = 2, name = "Repo2"), + ) + val mockEngine = MockEngine { _ -> + respond( + testJson.encodeToString(expectedRepos), + HttpStatusCode.OK, + headersOf(HttpHeaders.ContentType, "application/json"), + ) + } + + val client = HttpClient(mockEngine) { + install(ContentNegotiation) { json(testJson) } + } + + val api = KtorGitHubApi(client) + val repos = api.getOrgRepos("testOrg") + assertEquals(expectedRepos, repos) + } + + @Test + fun `getRepoContributors should parse JSON into User list`() = runTest { + val expectedUsers = listOf( + User(login = "alice", contributions = 10), + User(login = "bob", contributions = 20), + ) + val mockEngine = MockEngine { _ -> + respond( + testJson.encodeToString(expectedUsers), + HttpStatusCode.OK, + headersOf(HttpHeaders.ContentType, "application/json"), + ) + } + + val client = HttpClient(mockEngine) { + install(ContentNegotiation) { json(testJson) } + } + + val api = KtorGitHubApi(client) + val users = api.getRepoContributors("testOwner", "testRepo") + assertEquals(expectedUsers, users) + } + + @Test + fun `createKtorClient sets correct headers`() = runTest { + var capturedRequest: HttpRequestData? = null + + val mockEngine = MockEngine { request -> + capturedRequest = request + respond( + content = "[]", + status = HttpStatusCode.OK, + headers = headersOf(HttpHeaders.ContentType, "application/json"), + ) + } + + val client = createKtorClient("user", "pass", mockEngine) + client.get("orgs/test/repos") + + val headers = capturedRequest!!.headers + assertEquals("application/vnd.github.v3+json", headers[ACCEPT_HEADER]) + assertTrue(headers[HttpHeaders.Authorization]?.startsWith("Basic ") == true) + } +} diff --git a/src/test/kotlin/dev/shtanko/api/ModelsTest.kt b/src/test/kotlin/dev/shtanko/api/ModelsTest.kt index cf80fba7..bfb18a83 100644 --- a/src/test/kotlin/dev/shtanko/api/ModelsTest.kt +++ b/src/test/kotlin/dev/shtanko/api/ModelsTest.kt @@ -16,6 +16,9 @@ package dev.shtanko.api +import dev.shtanko.api.model.Repo +import dev.shtanko.api.model.RequestData +import dev.shtanko.api.model.User import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test diff --git a/src/test/kotlin/dev/shtanko/api/contributors/MockGithubService.kt b/src/test/kotlin/dev/shtanko/api/contributors/MockGithubService.kt index 5a3c4730..84fd8a38 100644 --- a/src/test/kotlin/dev/shtanko/api/contributors/MockGithubService.kt +++ b/src/test/kotlin/dev/shtanko/api/contributors/MockGithubService.kt @@ -17,8 +17,8 @@ package dev.shtanko.api.contributors import dev.shtanko.api.GitHubService -import dev.shtanko.api.Repo -import dev.shtanko.api.User +import dev.shtanko.api.model.Repo +import dev.shtanko.api.model.User import kotlinx.coroutines.delay import retrofit2.Call import retrofit2.Response diff --git a/src/test/kotlin/dev/shtanko/api/contributors/TestData.kt b/src/test/kotlin/dev/shtanko/api/contributors/TestData.kt index bfa87ae6..4e020699 100644 --- a/src/test/kotlin/dev/shtanko/api/contributors/TestData.kt +++ b/src/test/kotlin/dev/shtanko/api/contributors/TestData.kt @@ -16,9 +16,9 @@ package dev.shtanko.api.contributors -import dev.shtanko.api.Repo -import dev.shtanko.api.RequestData -import dev.shtanko.api.User +import dev.shtanko.api.model.Repo +import dev.shtanko.api.model.RequestData +import dev.shtanko.api.model.User val testRequestData = RequestData("username", "password", "org") diff --git a/src/test/kotlin/dev/shtanko/api/tasks/AggregationTest.kt b/src/test/kotlin/dev/shtanko/api/tasks/AggregationTest.kt index 332ce19e..6ff80612 100644 --- a/src/test/kotlin/dev/shtanko/api/tasks/AggregationTest.kt +++ b/src/test/kotlin/dev/shtanko/api/tasks/AggregationTest.kt @@ -16,7 +16,7 @@ package dev.shtanko.api.tasks -import dev.shtanko.api.User +import dev.shtanko.api.model.User import java.util.stream.Stream import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.extension.ExtensionContext From f799218eb72060da5bb69a64c0784a44464e2905 Mon Sep 17 00:00:00 2001 From: Oleksii Shtanko Date: Mon, 11 Aug 2025 22:00:52 +0100 Subject: [PATCH 2/8] Bump dependency versions --- .../kotlin-compiler-4328459900099198619.salive | 0 gradle/libs.versions.toml | 10 +++++----- 2 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 .kotlin/sessions/kotlin-compiler-4328459900099198619.salive diff --git a/.kotlin/sessions/kotlin-compiler-4328459900099198619.salive b/.kotlin/sessions/kotlin-compiler-4328459900099198619.salive new file mode 100644 index 00000000..e69de29b diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ea0a68ab..39da176d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] coroutines = "1.10.2" # https://github.com/Kotlin/kotlinx.coroutines junit = "5.13.4" # https://junit.org/junit5 | https://central.sonatype.com/artifact/org.junit.jupiter/junit-jupiter -kotlin = "2.2.0" # https://github.com/JetBrains/kotlin +kotlin = "2.2.0" # https://github.com/JetBrains/kotlin detekt = "1.23.8" # https://github.com/detekt/detekt dokka = "2.0.0" # https://github.com/Kotlin/dokka spotless = "7.2.1" # https://github.com/diffplug/spotless @@ -14,9 +14,9 @@ rxjava = "3.1.11" # https://github.com/ReactiveX/RxJava rxkotlin = "3.0.1" # https://github.com/ReactiveX/RxKotlin lincheck = "2.29" # https://github.com/JetBrains/lincheck serialization = "1.9.0" # https://github.com/Kotlin/kotlinx.serialization -retrofit = "3.0.0" # https://github.com/square/retrofit -okhttp = "5.1.0" # https://square.github.io/okhttp/changelogs/changelog -dagger = "2.57" # https://dagger.dev +retrofit = "3.0.0" # https://github.com/square/retrofit +okhttp = "5.1.0" # https://square.github.io/okhttp/changelogs/changelog +dagger = "2.57" # https://dagger.dev jmh = "1.37" # https://github.com/openjdk/jmh kotlintest = "3.4.2" # https://mvnrepository.com/artifact/io.kotlintest/kotlintest-core kotest = "5.9.1" # https://kotest.io @@ -40,7 +40,7 @@ ktor = "3.2.3" jol = "0.17" retrofit-kotlinx-serialization = "1.0.0" protobuf = "4.31.1" -grpc = "1.73.0" +grpc = "1.74.0" [libraries] kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8", version.ref = "coroutines" } From d87cf8c4466cbfbc283150e5b882fd5f0115bd26 Mon Sep 17 00:00:00 2001 From: Oleksii Shtanko Date: Thu, 14 Aug 2025 22:28:14 +0100 Subject: [PATCH 3/8] Add ktor interceptors --- README.md | 22 +- api/Kotlin-Lab.api | 90 ++++++- .../kotlin/dev/shtanko/api/KtorInterceptor.kt | 50 ---- .../dev/shtanko/api/KtorInterceptorExample.kt | 75 ++++++ .../dev/shtanko/api/ktor/ApiKeyInterceptor.kt | 34 +++ .../shtanko/api/ktor/HttpLogInterceptor.kt | 59 +++++ .../RequestResponseProcessorInterceptor.kt | 43 ++++ .../dev/shtanko/api/ktor/RetryInterceptor.kt | 66 ++++++ .../shtanko/api/ktor/KtorInterceptorsTest.kt | 220 ++++++++++++++++++ 9 files changed, 597 insertions(+), 62 deletions(-) delete mode 100644 src/main/kotlin/dev/shtanko/api/KtorInterceptor.kt create mode 100644 src/main/kotlin/dev/shtanko/api/KtorInterceptorExample.kt create mode 100644 src/main/kotlin/dev/shtanko/api/ktor/ApiKeyInterceptor.kt create mode 100644 src/main/kotlin/dev/shtanko/api/ktor/HttpLogInterceptor.kt create mode 100644 src/main/kotlin/dev/shtanko/api/ktor/RequestResponseProcessorInterceptor.kt create mode 100644 src/main/kotlin/dev/shtanko/api/ktor/RetryInterceptor.kt create mode 100644 src/test/kotlin/dev/shtanko/api/ktor/KtorInterceptorsTest.kt diff --git a/README.md b/README.md index 2846989f..3231e335 100644 --- a/README.md +++ b/README.md @@ -22,22 +22,22 @@ ### Metrics ```text -15218 number of properties -10530 number of functions -8942 number of classes -240 number of packages -3539 number of kt files +15266 number of properties +10543 number of functions +8951 number of classes +241 number of packages +3544 number of kt files ``` ### Complexity Report ```text -266866 lines of code (loc) -165906 source lines of code (sloc) -121157 logical lines of code (lloc) -72548 comment lines of code (cloc) -25030 cyclomatic complexity (mcc) -20414 cognitive complexity +267318 lines of code (loc) +166270 source lines of code (sloc) +121387 logical lines of code (lloc) +72569 comment lines of code (cloc) +25062 cyclomatic complexity (mcc) +20439 cognitive complexity 0 number of total code smells 43 comment source ratio 206 mcc per 1,000 lloc diff --git a/api/Kotlin-Lab.api b/api/Kotlin-Lab.api index fcca407e..41210e45 100644 --- a/api/Kotlin-Lab.api +++ b/api/Kotlin-Lab.api @@ -18337,7 +18337,7 @@ public final class dev/shtanko/api/KtorGithubServiceKt { public static synthetic fun createKtorClient$default (Ljava/lang/String;Ljava/lang/String;Lio/ktor/client/engine/HttpClientEngine;ILjava/lang/Object;)Lio/ktor/client/HttpClient; } -public final class dev/shtanko/api/KtorInterceptorKt { +public final class dev/shtanko/api/KtorInterceptorExampleKt { public static final fun main ()V public static synthetic fun main ([Ljava/lang/String;)V } @@ -18446,6 +18446,94 @@ public final class dev/shtanko/api/contributors/Variant : java/lang/Enum { public static fun values ()[Ldev/shtanko/api/contributors/Variant; } +public final class dev/shtanko/api/ktor/ApiKeyInterceptor { + public static final field Companion Ldev/shtanko/api/ktor/ApiKeyInterceptor$Companion; + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class dev/shtanko/api/ktor/ApiKeyInterceptor$Companion : io/ktor/client/plugins/HttpClientPlugin { + public fun getKey ()Lio/ktor/util/AttributeKey; + public fun install (Ldev/shtanko/api/ktor/ApiKeyInterceptor;Lio/ktor/client/HttpClient;)V + public synthetic fun install (Ljava/lang/Object;Lio/ktor/client/HttpClient;)V + public fun prepare (Lkotlin/jvm/functions/Function1;)Ldev/shtanko/api/ktor/ApiKeyInterceptor; + public synthetic fun prepare (Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; +} + +public final class dev/shtanko/api/ktor/ApiKeyInterceptor$Config { + public fun ()V + public final fun getApiKey ()Ljava/lang/String; + public final fun getHeaderName ()Ljava/lang/String; + public final fun setApiKey (Ljava/lang/String;)V + public final fun setHeaderName (Ljava/lang/String;)V +} + +public final class dev/shtanko/api/ktor/HttpLogInterceptor { + public static final field Companion Ldev/shtanko/api/ktor/HttpLogInterceptor$Companion; + public synthetic fun (ZZZLkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class dev/shtanko/api/ktor/HttpLogInterceptor$Companion : io/ktor/client/plugins/HttpClientPlugin { + public fun getKey ()Lio/ktor/util/AttributeKey; + public fun install (Ldev/shtanko/api/ktor/HttpLogInterceptor;Lio/ktor/client/HttpClient;)V + public synthetic fun install (Ljava/lang/Object;Lio/ktor/client/HttpClient;)V + public fun prepare (Lkotlin/jvm/functions/Function1;)Ldev/shtanko/api/ktor/HttpLogInterceptor; + public synthetic fun prepare (Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; +} + +public final class dev/shtanko/api/ktor/HttpLogInterceptor$Config { + public fun ()V + public final fun getLogHeaders ()Z + public final fun getLogRequest ()Z + public final fun getLogResponse ()Z + public final fun setLogHeaders (Z)V + public final fun setLogRequest (Z)V + public final fun setLogResponse (Z)V +} + +public final class dev/shtanko/api/ktor/RequestResponseProcessorInterceptor { + public static final field Companion Ldev/shtanko/api/ktor/RequestResponseProcessorInterceptor$Companion; + public synthetic fun (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class dev/shtanko/api/ktor/RequestResponseProcessorInterceptor$Companion : io/ktor/client/plugins/HttpClientPlugin { + public fun getKey ()Lio/ktor/util/AttributeKey; + public fun install (Ldev/shtanko/api/ktor/RequestResponseProcessorInterceptor;Lio/ktor/client/HttpClient;)V + public synthetic fun install (Ljava/lang/Object;Lio/ktor/client/HttpClient;)V + public fun prepare (Lkotlin/jvm/functions/Function1;)Ldev/shtanko/api/ktor/RequestResponseProcessorInterceptor; + public synthetic fun prepare (Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; +} + +public final class dev/shtanko/api/ktor/RequestResponseProcessorInterceptor$Config { + public fun ()V + public final fun getRequestProcessor ()Lkotlin/jvm/functions/Function1; + public final fun getResponseProcessor ()Lkotlin/jvm/functions/Function2; + public final fun setRequestProcessor (Lkotlin/jvm/functions/Function1;)V + public final fun setResponseProcessor (Lkotlin/jvm/functions/Function2;)V +} + +public final class dev/shtanko/api/ktor/RetryInterceptor { + public static final field Companion Ldev/shtanko/api/ktor/RetryInterceptor$Companion; + public synthetic fun (ILjava/util/Set;JLkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class dev/shtanko/api/ktor/RetryInterceptor$Companion : io/ktor/client/plugins/HttpClientPlugin { + public fun getKey ()Lio/ktor/util/AttributeKey; + public fun install (Ldev/shtanko/api/ktor/RetryInterceptor;Lio/ktor/client/HttpClient;)V + public synthetic fun install (Ljava/lang/Object;Lio/ktor/client/HttpClient;)V + public fun prepare (Lkotlin/jvm/functions/Function1;)Ldev/shtanko/api/ktor/RetryInterceptor; + public synthetic fun prepare (Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; +} + +public final class dev/shtanko/api/ktor/RetryInterceptor$Config { + public fun ()V + public final fun getBaseDelayMs ()J + public final fun getMaxRetries ()I + public final fun getRetryOn ()Ljava/util/Set; + public final fun setBaseDelayMs (J)V + public final fun setMaxRetries (I)V + public final fun setRetryOn (Ljava/util/Set;)V +} + public final class dev/shtanko/api/model/Repo { public static final field Companion Ldev/shtanko/api/model/Repo$Companion; public fun (JLjava/lang/String;)V diff --git a/src/main/kotlin/dev/shtanko/api/KtorInterceptor.kt b/src/main/kotlin/dev/shtanko/api/KtorInterceptor.kt deleted file mode 100644 index 86afb186..00000000 --- a/src/main/kotlin/dev/shtanko/api/KtorInterceptor.kt +++ /dev/null @@ -1,50 +0,0 @@ -package dev.shtanko.api - -import io.ktor.client.HttpClient -import io.ktor.client.engine.cio.CIO -import io.ktor.client.plugins.HttpSend -import io.ktor.client.plugins.logging.LogLevel -import io.ktor.client.plugins.logging.Logging -import io.ktor.client.plugins.plugin -import io.ktor.client.request.HttpRequestPipeline -import io.ktor.client.request.HttpSendPipeline -import io.ktor.client.request.get -import kotlinx.coroutines.runBlocking - -@Suppress("MagicNumber") -fun main() = runBlocking { - val client = HttpClient(CIO) { - install(Logging) { - level = LogLevel.ALL - } - } - - client.sendPipeline.intercept(HttpSendPipeline.State) { request -> - println("Intercepted request to: $request") - // For example, add a custom header - context.headers.append("X-Custom-Header", "MyValue") - proceed() - } - - client.requestPipeline.intercept(HttpRequestPipeline.Before) { - println("Sending request to: ${context.url} ${context.method}") - proceed() - } - - client.plugin(HttpSend).intercept { request -> - println("Request URL: ${request.url}") - println("Request method: ${request.method}") - println("Request headers: ${request.headers}") - val originalCall = execute(request) - if (originalCall.response.status.value !in 100..399) { - execute(request) - } else { - originalCall - } - } - - val response = client.get("https://httpbin.org/get") - println("Response status: ${response.status}") - - client.close() -} diff --git a/src/main/kotlin/dev/shtanko/api/KtorInterceptorExample.kt b/src/main/kotlin/dev/shtanko/api/KtorInterceptorExample.kt new file mode 100644 index 00000000..12b16924 --- /dev/null +++ b/src/main/kotlin/dev/shtanko/api/KtorInterceptorExample.kt @@ -0,0 +1,75 @@ +package dev.shtanko.api + +import dev.shtanko.api.ktor.ApiKeyInterceptor +import dev.shtanko.api.ktor.HttpLogInterceptor +import dev.shtanko.api.ktor.RequestResponseProcessorInterceptor +import dev.shtanko.api.ktor.RetryInterceptor +import io.ktor.client.HttpClient +import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.logging.LogLevel +import io.ktor.client.plugins.logging.Logging +import io.ktor.client.request.get +import io.ktor.client.statement.bodyAsText +import io.ktor.client.statement.request +import io.ktor.http.HttpHeaders +import io.ktor.http.HttpStatusCode +import java.time.Instant +import kotlinx.coroutines.runBlocking + +@Suppress("MagicNumber", "TooGenericExceptionCaught") +fun main() = runBlocking { + val client = HttpClient(CIO) { + // Install built-in logging + install(Logging) { + level = LogLevel.BODY + } + + // Install custom interceptors + install(HttpLogInterceptor) { + logRequest = true + logResponse = true + logHeaders = true + } + + install(ApiKeyInterceptor) { + apiKey = "your-api-key-here" + headerName = "Authorization" + } + + install(RetryInterceptor) { + maxRetries = 2 + baseDelayMs = 500 + retryOn = setOf(HttpStatusCode.InternalServerError, HttpStatusCode.ServiceUnavailable) + } + + install(RequestResponseProcessorInterceptor) { + requestProcessor = { request -> + // Add timestamp to all requests + request.headers.append("X-Request-Time", Instant.now().toString()) + request.headers.append("X-Client-Version", "1.0.0") + } + + responseProcessor = { response -> + // Process response (logging, metrics, etc.) + println("📋 Processing response from: ${response.request.url}") + println("📊 Response size: ${response.headers[HttpHeaders.ContentLength] ?: "unknown"}") + } + } + } + + try { + // Test the interceptors + val response1 = client.get("https://httpbin.org/get") + println("Response 1 status: ${response1.status}") + println("Response 1 body: ${response1.bodyAsText()}") + + // Test with a endpoint that might fail (for retry testing) + val response2 = client.get("https://httpbin.org/status/500") + println("Response 2 status: ${response2.status}") + + } catch (e: Exception) { + println("Error: ${e.message}") + } finally { + client.close() + } +} diff --git a/src/main/kotlin/dev/shtanko/api/ktor/ApiKeyInterceptor.kt b/src/main/kotlin/dev/shtanko/api/ktor/ApiKeyInterceptor.kt new file mode 100644 index 00000000..062645c4 --- /dev/null +++ b/src/main/kotlin/dev/shtanko/api/ktor/ApiKeyInterceptor.kt @@ -0,0 +1,34 @@ +package dev.shtanko.api.ktor + +import io.ktor.client.HttpClient +import io.ktor.client.plugins.HttpClientPlugin +import io.ktor.client.request.HttpRequestPipeline +import io.ktor.util.AttributeKey + +class ApiKeyInterceptor private constructor( + private val apiKey: String, + private val headerName: String, +) { + + class Config { + var apiKey: String = "" + var headerName: String = "X-API-Key" + } + + companion object : HttpClientPlugin { + override val key = AttributeKey("ApiKeyInterceptor") + + override fun prepare(block: Config.() -> Unit): ApiKeyInterceptor { + val config = Config().apply(block) + require(config.apiKey.isNotEmpty()) { "API key cannot be empty" } + return ApiKeyInterceptor(config.apiKey, config.headerName) + } + + override fun install(plugin: ApiKeyInterceptor, scope: HttpClient) { + scope.requestPipeline.intercept(HttpRequestPipeline.State) { + context.headers.append(plugin.headerName, plugin.apiKey) + println("🔑 Added API key header: ${plugin.headerName}") + } + } + } +} diff --git a/src/main/kotlin/dev/shtanko/api/ktor/HttpLogInterceptor.kt b/src/main/kotlin/dev/shtanko/api/ktor/HttpLogInterceptor.kt new file mode 100644 index 00000000..0c556058 --- /dev/null +++ b/src/main/kotlin/dev/shtanko/api/ktor/HttpLogInterceptor.kt @@ -0,0 +1,59 @@ +package dev.shtanko.api.ktor + +import io.ktor.client.HttpClient +import io.ktor.client.plugins.HttpClientPlugin +import io.ktor.client.request.HttpRequestPipeline +import io.ktor.client.statement.HttpResponsePipeline +import io.ktor.util.AttributeKey + +private val RequestStartTimeKey = AttributeKey("RequestStartTime") + +class HttpLogInterceptor private constructor( + private val logRequest: Boolean, + private val logResponse: Boolean, + private val logHeaders: Boolean, +) { + + class Config { + var logRequest: Boolean = true + var logResponse: Boolean = true + var logHeaders: Boolean = false + } + + companion object : HttpClientPlugin { + override val key = AttributeKey("HttpLogInterceptor") + + override fun prepare(block: Config.() -> Unit): HttpLogInterceptor { + val config = Config().apply(block) + return HttpLogInterceptor(config.logRequest, config.logResponse, config.logHeaders) + } + + override fun install(plugin: HttpLogInterceptor, scope: HttpClient) { + // Request interceptor + if (plugin.logRequest) { + scope.requestPipeline.intercept(HttpRequestPipeline.Before) { + val startTime = System.currentTimeMillis() + context.attributes.put(RequestStartTimeKey, startTime) + + println("🚀 REQUEST: ${context.method.value} ${context.url}") + if (plugin.logHeaders) { + println(" Headers: ${context.headers.entries()}") + } + } + } + + // Response interceptor + if (plugin.logResponse) { + scope.responsePipeline.intercept(HttpResponsePipeline.Receive) { + val startTime = context.request.attributes.getOrNull(RequestStartTimeKey) + val duration = startTime?.let { System.currentTimeMillis() - it } ?: 0 + + println("📥 RESPONSE: ${context.response.status} (${duration}ms)") + if (plugin.logHeaders) { + println(" Headers: ${context.response.headers.entries()}") + } + } + } + } + } +} diff --git a/src/main/kotlin/dev/shtanko/api/ktor/RequestResponseProcessorInterceptor.kt b/src/main/kotlin/dev/shtanko/api/ktor/RequestResponseProcessorInterceptor.kt new file mode 100644 index 00000000..64a6e79c --- /dev/null +++ b/src/main/kotlin/dev/shtanko/api/ktor/RequestResponseProcessorInterceptor.kt @@ -0,0 +1,43 @@ +package dev.shtanko.api.ktor + +import io.ktor.client.HttpClient +import io.ktor.client.plugins.HttpClientPlugin +import io.ktor.client.request.HttpRequestBuilder +import io.ktor.client.request.HttpRequestPipeline +import io.ktor.client.statement.HttpResponse +import io.ktor.client.statement.HttpResponsePipeline +import io.ktor.util.AttributeKey + +class RequestResponseProcessorInterceptor private constructor( + private val requestProcessor: (HttpRequestBuilder) -> Unit, + private val responseProcessor: suspend (HttpResponse) -> Unit +) { + + class Config { + var requestProcessor: (HttpRequestBuilder) -> Unit = { } + var responseProcessor: suspend (HttpResponse) -> Unit = { } + } + + companion object : HttpClientPlugin { + override val key = AttributeKey("RequestResponseProcessorInterceptor") + + override fun prepare(block: Config.() -> Unit): RequestResponseProcessorInterceptor { + val config = Config().apply(block) + return RequestResponseProcessorInterceptor(config.requestProcessor, config.responseProcessor) + } + + override fun install(plugin: RequestResponseProcessorInterceptor, scope: HttpClient) { + // Process requests + scope.requestPipeline.intercept(HttpRequestPipeline.Before) { + plugin.requestProcessor(context) + } + + // Process responses - use Transform stage and pass through the content + scope.responsePipeline.intercept(HttpResponsePipeline.Transform) { + plugin.responseProcessor(context.response) + // Important: Continue with the original subject + proceedWith(subject) + } + } + } +} diff --git a/src/main/kotlin/dev/shtanko/api/ktor/RetryInterceptor.kt b/src/main/kotlin/dev/shtanko/api/ktor/RetryInterceptor.kt new file mode 100644 index 00000000..fce066b6 --- /dev/null +++ b/src/main/kotlin/dev/shtanko/api/ktor/RetryInterceptor.kt @@ -0,0 +1,66 @@ +package dev.shtanko.api.ktor + +import io.ktor.client.HttpClient +import io.ktor.client.call.HttpClientCall +import io.ktor.client.plugins.HttpClientPlugin +import io.ktor.client.plugins.HttpSend +import io.ktor.client.plugins.plugin +import io.ktor.http.HttpStatusCode +import io.ktor.util.AttributeKey + +@Suppress("TooGenericExceptionCaught") +class RetryInterceptor private constructor( + private val maxRetries: Int, + private val retryOn: Set, + private val baseDelayMs: Long, +) { + + class Config { + var maxRetries: Int = 3 + var retryOn: Set = setOf( + HttpStatusCode.InternalServerError, + HttpStatusCode.BadGateway, + HttpStatusCode.ServiceUnavailable, + HttpStatusCode.GatewayTimeout, + ) + var baseDelayMs: Long = 1000 + } + + companion object : HttpClientPlugin { + override val key = AttributeKey("RetryInterceptor") + + override fun prepare(block: Config.() -> Unit): RetryInterceptor { + val config = Config().apply(block) + return RetryInterceptor(config.maxRetries, config.retryOn, config.baseDelayMs) + } + + override fun install(plugin: RetryInterceptor, scope: HttpClient) { + scope.plugin(HttpSend).intercept { request -> + var lastResponse: HttpClientCall? = null + + repeat(plugin.maxRetries + 1) { attempt -> + try { + val response = execute(request) + + if (attempt == 0 || response.response.status !in plugin.retryOn) { + return@intercept response + } + + lastResponse = response + val delay = plugin.baseDelayMs * (1 shl attempt) // Exponential backoff + println("🔄 Retry attempt $attempt after ${delay}ms for ${response.response.status}") + kotlinx.coroutines.delay(delay) + + } catch (e: Exception) { + if (attempt == plugin.maxRetries) throw e + val delay = plugin.baseDelayMs * (1 shl attempt) + println("🔄 Retry attempt $attempt after ${delay}ms due to exception: ${e.message}") + kotlinx.coroutines.delay(delay) + } + } + + lastResponse ?: execute(request) + } + } + } +} diff --git a/src/test/kotlin/dev/shtanko/api/ktor/KtorInterceptorsTest.kt b/src/test/kotlin/dev/shtanko/api/ktor/KtorInterceptorsTest.kt new file mode 100644 index 00000000..869afaa4 --- /dev/null +++ b/src/test/kotlin/dev/shtanko/api/ktor/KtorInterceptorsTest.kt @@ -0,0 +1,220 @@ +package dev.shtanko.api.ktor + +import io.ktor.client.HttpClient +import io.ktor.client.engine.mock.MockEngine +import io.ktor.client.engine.mock.respond +import io.ktor.client.plugins.ConnectTimeoutException +import io.ktor.client.request.HttpRequestData +import io.ktor.client.request.delete +import io.ktor.client.request.get +import io.ktor.client.request.patch +import io.ktor.client.request.post +import io.ktor.client.request.put +import io.ktor.client.request.setBody +import io.ktor.client.statement.bodyAsText +import io.ktor.http.HttpHeaders +import io.ktor.http.HttpStatusCode +import io.ktor.http.headersOf +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.DynamicTest +import org.junit.jupiter.api.MethodOrderer +import org.junit.jupiter.api.Order +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestFactory +import org.junit.jupiter.api.TestMethodOrder +import org.junit.jupiter.api.Timeout +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource + +@TestMethodOrder(MethodOrderer.OrderAnnotation::class) +class KtorInterceptorsTest { + @TestFactory + @DisplayName("Dynamic tests for different HTTP methods") + fun testHttpMethods() = listOf("GET", "POST", "PUT", "DELETE", "PATCH").map { method -> + DynamicTest.dynamicTest("Test $method with interceptors") { + runTest { + val capturedRequests = mutableListOf() + + val mockEngine = MockEngine { request -> + capturedRequests.add(request) + respond( + content = """{"method": "${request.method.value}"}""", + status = HttpStatusCode.OK, + headers = headersOf(HttpHeaders.ContentType, "application/json"), + ) + } + + val client = HttpClient(mockEngine) { + install(HttpLogInterceptor) { + logRequest = true + logResponse = true + } + install(ApiKeyInterceptor) { + apiKey = "integration-test-key" + } + } + + when (method) { + "GET" -> client.get("https://api.test.com/endpoint") + "POST" -> client.post("https://api.test.com/endpoint") { setBody("test data") } + "PUT" -> client.put("https://api.test.com/endpoint") { setBody("test data") } + "DELETE" -> client.delete("https://api.test.com/endpoint") + "PATCH" -> client.patch("https://api.test.com/endpoint") { setBody("test data") } + } + + assertEquals(1, capturedRequests.size) + assertEquals(method, capturedRequests.first().method.value) + assertEquals("integration-test-key", capturedRequests.first().headers["X-API-Key"]) + + client.close() + } + } + } + + @Test + @Order(1) + @DisplayName("Performance test - interceptors should not significantly impact performance") + @Timeout(5) + fun testInterceptorPerformance() = runTest { + val requestCount = 100 + val mockEngine = MockEngine { + respond("OK", HttpStatusCode.OK) + } + + val clientWithInterceptors = HttpClient(mockEngine) { + install(HttpLogInterceptor) { + logRequest = false // Disable logging for performance test + logResponse = false + } + install(ApiKeyInterceptor) { + apiKey = "perf-test-key" + } + install(RequestResponseProcessorInterceptor) { + requestProcessor = { request -> + request.headers.append("X-Request-ID", System.nanoTime().toString()) + } + responseProcessor = { /* minimal processing */ } + } + } + + val startTime = System.currentTimeMillis() + + repeat(requestCount) { + clientWithInterceptors.get("https://perf.test.com/api/$it") + } + + val endTime = System.currentTimeMillis() + val totalTime = endTime - startTime + + // Should complete 100 requests in reasonable time (less than 5 seconds) + assertTrue(totalTime < 5000, "Performance test took too long: ${totalTime}ms") + + clientWithInterceptors.close() + } + + @ParameterizedTest + @ValueSource(ints = [200, 201, 204, 400, 401, 403, 404, 500, 502, 503]) + @DisplayName("Test interceptors with various HTTP status codes") + fun testDifferentStatusCodes(statusCode: Int) = runTest { + val httpStatus = HttpStatusCode.fromValue(statusCode) + val mockEngine = MockEngine { + respond("Response for $statusCode", httpStatus) + } + + val client = HttpClient(mockEngine) { + install(HttpLogInterceptor) + install(RetryInterceptor) { + maxRetries = 1 + retryOn = setOf( + HttpStatusCode.InternalServerError, + HttpStatusCode.BadGateway, + HttpStatusCode.ServiceUnavailable, + ) + } + } + + val response = client.get("https://status.test.com/api") + assertEquals(httpStatus, response.status) + + client.close() + } + + @Test + @DisplayName("Test interceptors with concurrent requests") + fun testConcurrentRequests() = runTest { + val requestCount = 10 + val processedRequests = mutableListOf() + + val mockEngine = MockEngine { request -> + respond("Response for ${request.url}", HttpStatusCode.OK) + } + + val client = HttpClient(mockEngine) { + install(RequestResponseProcessorInterceptor) { + requestProcessor = { request -> + synchronized(processedRequests) { + processedRequests.add(request.url.toString()) + } + } + responseProcessor = { /* no-op */ } + } + } + + // Launch concurrent requests + val jobs = (1..requestCount).map { index -> + async { + client.get("https://concurrent.test.com/api/$index") + } + } + + // Wait for all requests to complete + jobs.awaitAll() + + assertEquals(requestCount, processedRequests.size) + // Verify all unique URLs were processed + assertEquals(requestCount, processedRequests.toSet().size) + + client.close() + } + + @Test + @DisplayName("Test interceptor error handling and recovery") + fun testErrorHandlingAndRecovery() = runTest { + var requestAttempts = 0 + + val mockEngine = MockEngine { request -> + requestAttempts++ + when { + requestAttempts <= 2 -> throw ConnectTimeoutException(request) + else -> respond("Success after timeout", HttpStatusCode.OK) + } + } + + val client = HttpClient(mockEngine) { + install(RetryInterceptor) { + maxRetries = 3 + baseDelayMs = 10 + } + install(RequestResponseProcessorInterceptor) { + requestProcessor = { request -> + // Should continue to work even with retries + request.headers.append("X-Attempt", requestAttempts.toString()) + } + responseProcessor = { /* no-op */ } + } + } + + val response = client.get("https://error.test.com/api") + + assertEquals(HttpStatusCode.OK, response.status) + assertEquals("Success after timeout", response.bodyAsText()) + assertEquals(3, requestAttempts) // 1 original + 2 retries + + client.close() + } +} From d9cf1838cf9f26bc8378b23208f613807eb6904d Mon Sep 17 00:00:00 2001 From: Oleksii Shtanko Date: Thu, 14 Aug 2025 23:49:48 +0100 Subject: [PATCH 4/8] Update src/main/kotlin/dev/shtanko/api/KtorInterceptorExample.kt Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/main/kotlin/dev/shtanko/api/KtorInterceptorExample.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/kotlin/dev/shtanko/api/KtorInterceptorExample.kt b/src/main/kotlin/dev/shtanko/api/KtorInterceptorExample.kt index 12b16924..19de0fb7 100644 --- a/src/main/kotlin/dev/shtanko/api/KtorInterceptorExample.kt +++ b/src/main/kotlin/dev/shtanko/api/KtorInterceptorExample.kt @@ -24,6 +24,12 @@ fun main() = runBlocking { level = LogLevel.BODY } + // Prevent requests from hanging indefinitely + install(HttpTimeout) { + requestTimeoutMillis = 10_000 + connectTimeoutMillis = 5_000 + socketTimeoutMillis = 10_000 + } // Install custom interceptors install(HttpLogInterceptor) { logRequest = true From 11c7bd89204eb4bf1cd034fa8936f5233c523190 Mon Sep 17 00:00:00 2001 From: Oleksii Shtanko Date: Thu, 14 Aug 2025 23:50:38 +0100 Subject: [PATCH 5/8] Fix imports --- .kotlin/sessions/kotlin-compiler-4328459900099198619.salive | 0 src/main/kotlin/dev/shtanko/api/KtorInterceptorExample.kt | 1 + 2 files changed, 1 insertion(+) delete mode 100644 .kotlin/sessions/kotlin-compiler-4328459900099198619.salive diff --git a/.kotlin/sessions/kotlin-compiler-4328459900099198619.salive b/.kotlin/sessions/kotlin-compiler-4328459900099198619.salive deleted file mode 100644 index e69de29b..00000000 diff --git a/src/main/kotlin/dev/shtanko/api/KtorInterceptorExample.kt b/src/main/kotlin/dev/shtanko/api/KtorInterceptorExample.kt index 19de0fb7..1bdf3c9b 100644 --- a/src/main/kotlin/dev/shtanko/api/KtorInterceptorExample.kt +++ b/src/main/kotlin/dev/shtanko/api/KtorInterceptorExample.kt @@ -6,6 +6,7 @@ import dev.shtanko.api.ktor.RequestResponseProcessorInterceptor import dev.shtanko.api.ktor.RetryInterceptor import io.ktor.client.HttpClient import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.HttpTimeout import io.ktor.client.plugins.logging.LogLevel import io.ktor.client.plugins.logging.Logging import io.ktor.client.request.get From fc26f8a914c3588690dbcc8ff71024b858efd8f4 Mon Sep 17 00:00:00 2001 From: Oleksii Shtanko Date: Thu, 14 Aug 2025 23:51:06 +0100 Subject: [PATCH 6/8] Update src/main/kotlin/dev/shtanko/api/ktor/RetryInterceptor.kt Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../dev/shtanko/api/ktor/RetryInterceptor.kt | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/dev/shtanko/api/ktor/RetryInterceptor.kt b/src/main/kotlin/dev/shtanko/api/ktor/RetryInterceptor.kt index fce066b6..ee2ed645 100644 --- a/src/main/kotlin/dev/shtanko/api/ktor/RetryInterceptor.kt +++ b/src/main/kotlin/dev/shtanko/api/ktor/RetryInterceptor.kt @@ -34,31 +34,43 @@ class RetryInterceptor private constructor( return RetryInterceptor(config.maxRetries, config.retryOn, config.baseDelayMs) } + override fun install(plugin: RetryInterceptor, scope: HttpClient) { + scope.plugin(HttpSend).intercept { request -> + var lastResponse: HttpClientCall? = null + override fun install(plugin: RetryInterceptor, scope: HttpClient) { scope.plugin(HttpSend).intercept { request -> var lastResponse: HttpClientCall? = null repeat(plugin.maxRetries + 1) { attempt -> try { - val response = execute(request) + val call = execute(request) + val status = call.response.status - if (attempt == 0 || response.response.status !in plugin.retryOn) { - return@intercept response + if (status in plugin.retryOn && attempt < plugin.maxRetries) { + // Free resources before retrying + try { call.response.close() } catch (_: Throwable) {} + val delayMs = plugin.baseDelayMs * (1L shl attempt) // Exponential backoff + println("🔄 Retry attempt ${attempt + 1} after ${delayMs}ms for $status") + kotlinx.coroutines.delay(delayMs) + lastResponse = call + } else { + return@intercept call } - - lastResponse = response - val delay = plugin.baseDelayMs * (1 shl attempt) // Exponential backoff - println("🔄 Retry attempt $attempt after ${delay}ms for ${response.response.status}") - kotlinx.coroutines.delay(delay) - } catch (e: Exception) { + if (e is kotlinx.coroutines.CancellationException) throw e if (attempt == plugin.maxRetries) throw e - val delay = plugin.baseDelayMs * (1 shl attempt) - println("🔄 Retry attempt $attempt after ${delay}ms due to exception: ${e.message}") - kotlinx.coroutines.delay(delay) + val delayMs = plugin.baseDelayMs * (1L shl attempt) + println("🔄 Retry attempt ${attempt + 1} after ${delayMs}ms due to exception: ${e.message}") + kotlinx.coroutines.delay(delayMs) } } + // Fallback; normally we return inside the loop + lastResponse ?: execute(request) + } + } + lastResponse ?: execute(request) } } From 12c791989ed8a0d510caca36f9a66861ba363a8c Mon Sep 17 00:00:00 2001 From: Oleksii Shtanko Date: Thu, 14 Aug 2025 23:51:51 +0100 Subject: [PATCH 7/8] Rollback --- .../dev/shtanko/api/ktor/RetryInterceptor.kt | 36 +++++++------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/src/main/kotlin/dev/shtanko/api/ktor/RetryInterceptor.kt b/src/main/kotlin/dev/shtanko/api/ktor/RetryInterceptor.kt index ee2ed645..fce066b6 100644 --- a/src/main/kotlin/dev/shtanko/api/ktor/RetryInterceptor.kt +++ b/src/main/kotlin/dev/shtanko/api/ktor/RetryInterceptor.kt @@ -34,43 +34,31 @@ class RetryInterceptor private constructor( return RetryInterceptor(config.maxRetries, config.retryOn, config.baseDelayMs) } - override fun install(plugin: RetryInterceptor, scope: HttpClient) { - scope.plugin(HttpSend).intercept { request -> - var lastResponse: HttpClientCall? = null - override fun install(plugin: RetryInterceptor, scope: HttpClient) { scope.plugin(HttpSend).intercept { request -> var lastResponse: HttpClientCall? = null repeat(plugin.maxRetries + 1) { attempt -> try { - val call = execute(request) - val status = call.response.status + val response = execute(request) - if (status in plugin.retryOn && attempt < plugin.maxRetries) { - // Free resources before retrying - try { call.response.close() } catch (_: Throwable) {} - val delayMs = plugin.baseDelayMs * (1L shl attempt) // Exponential backoff - println("🔄 Retry attempt ${attempt + 1} after ${delayMs}ms for $status") - kotlinx.coroutines.delay(delayMs) - lastResponse = call - } else { - return@intercept call + if (attempt == 0 || response.response.status !in plugin.retryOn) { + return@intercept response } + + lastResponse = response + val delay = plugin.baseDelayMs * (1 shl attempt) // Exponential backoff + println("🔄 Retry attempt $attempt after ${delay}ms for ${response.response.status}") + kotlinx.coroutines.delay(delay) + } catch (e: Exception) { - if (e is kotlinx.coroutines.CancellationException) throw e if (attempt == plugin.maxRetries) throw e - val delayMs = plugin.baseDelayMs * (1L shl attempt) - println("🔄 Retry attempt ${attempt + 1} after ${delayMs}ms due to exception: ${e.message}") - kotlinx.coroutines.delay(delayMs) + val delay = plugin.baseDelayMs * (1 shl attempt) + println("🔄 Retry attempt $attempt after ${delay}ms due to exception: ${e.message}") + kotlinx.coroutines.delay(delay) } } - // Fallback; normally we return inside the loop - lastResponse ?: execute(request) - } - } - lastResponse ?: execute(request) } } From 9613a72f54334af0a45bbc13d656f44200a0dd97 Mon Sep 17 00:00:00 2001 From: Oleksii Shtanko Date: Fri, 15 Aug 2025 00:22:57 +0100 Subject: [PATCH 8/8] Disable lincheck tests --- README.md | 26 +++++++++---------- config/main.md | 2 +- .../ArrayBlockingQueueLinearizabilityTest.kt | 2 ++ .../concurrent/ConcurrentLinkedDequeTest.kt | 2 ++ .../concurrent/ConcurrentLinkedQueueTest.kt | 2 ++ .../concurrent/HashMapLinearizabilityTest.kt | 2 ++ .../HashtableLinearizabilityTest.kt | 2 ++ .../SkipListMapLinearizabilityTest.kt | 2 ++ .../dev/shtanko/proto/SearchRequestTest.kt | 2 ++ .../kotlin/dev/shtanko/retry/RetryTest.kt | 2 ++ 10 files changed, 30 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 29a2277c..d32c1077 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Hits-of-Code FOSSA Status CodeStyle - Kotlin Version + Kotlin Version Quality Gate Status Bugs Code Smells @@ -22,23 +22,23 @@ ### Metrics ```text -15266 number of properties -10543 number of functions -8951 number of classes -241 number of packages -3544 number of kt files +15414 number of properties +10608 number of functions +8966 number of classes +242 number of packages +3558 number of kt files ``` ### Complexity Report ```text -267318 lines of code (loc) -166270 source lines of code (sloc) -121387 logical lines of code (lloc) -72569 comment lines of code (cloc) -25062 cyclomatic complexity (mcc) -20439 cognitive complexity +268413 lines of code (loc) +167160 source lines of code (sloc) +122039 logical lines of code (lloc) +72586 comment lines of code (cloc) +25184 cyclomatic complexity (mcc) +20509 cognitive complexity 0 number of total code smells 43 comment source ratio 206 mcc per 1,000 lloc -``` +``` \ No newline at end of file diff --git a/config/main.md b/config/main.md index 886c7f5c..e094cfc6 100644 --- a/config/main.md +++ b/config/main.md @@ -12,7 +12,7 @@ Hits-of-Code FOSSA Status CodeStyle - Kotlin Version + Kotlin Version Quality Gate Status Bugs Code Smells diff --git a/src/test/kotlin/dev/shtanko/collections/concurrent/ArrayBlockingQueueLinearizabilityTest.kt b/src/test/kotlin/dev/shtanko/collections/concurrent/ArrayBlockingQueueLinearizabilityTest.kt index 56441f90..71bfbf56 100644 --- a/src/test/kotlin/dev/shtanko/collections/concurrent/ArrayBlockingQueueLinearizabilityTest.kt +++ b/src/test/kotlin/dev/shtanko/collections/concurrent/ArrayBlockingQueueLinearizabilityTest.kt @@ -27,8 +27,10 @@ import org.jetbrains.kotlinx.lincheck.strategy.stress.StressCTest import org.jetbrains.kotlinx.lincheck.strategy.stress.StressOptions import org.jetbrains.kotlinx.lincheck.verifier.VerifierState import org.junit.jupiter.api.Assumptions.assumeTrue +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +@Disabled("Fluky test") @StressCTest(minimizeFailedScenario = false) @Param(name = "key", gen = IntGen::class, conf = "1:5") class ArrayBlockingQueueLinearizabilityTest : VerifierState() { diff --git a/src/test/kotlin/dev/shtanko/collections/concurrent/ConcurrentLinkedDequeTest.kt b/src/test/kotlin/dev/shtanko/collections/concurrent/ConcurrentLinkedDequeTest.kt index 4a08ceeb..d952e471 100644 --- a/src/test/kotlin/dev/shtanko/collections/concurrent/ConcurrentLinkedDequeTest.kt +++ b/src/test/kotlin/dev/shtanko/collections/concurrent/ConcurrentLinkedDequeTest.kt @@ -21,9 +21,11 @@ import org.jetbrains.kotlinx.lincheck.annotations.Operation import org.jetbrains.kotlinx.lincheck.check import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.ModelCheckingOptions import org.junit.jupiter.api.Assumptions.assumeTrue +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows +@Disabled("Fluky test") class ConcurrentLinkedDequeTest { private val deque = ConcurrentLinkedDeque() diff --git a/src/test/kotlin/dev/shtanko/collections/concurrent/ConcurrentLinkedQueueTest.kt b/src/test/kotlin/dev/shtanko/collections/concurrent/ConcurrentLinkedQueueTest.kt index 639c8c3a..064e6858 100644 --- a/src/test/kotlin/dev/shtanko/collections/concurrent/ConcurrentLinkedQueueTest.kt +++ b/src/test/kotlin/dev/shtanko/collections/concurrent/ConcurrentLinkedQueueTest.kt @@ -22,8 +22,10 @@ import org.jetbrains.kotlinx.lincheck.annotations.Operation import org.jetbrains.kotlinx.lincheck.check import org.jetbrains.kotlinx.lincheck.strategy.stress.StressOptions import org.junit.jupiter.api.Assumptions.assumeTrue +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +@Disabled("Fluky test") class ConcurrentLinkedQueueTest { private val s = ConcurrentLinkedQueue() diff --git a/src/test/kotlin/dev/shtanko/collections/concurrent/HashMapLinearizabilityTest.kt b/src/test/kotlin/dev/shtanko/collections/concurrent/HashMapLinearizabilityTest.kt index 2be7cfc2..db7a0a0c 100644 --- a/src/test/kotlin/dev/shtanko/collections/concurrent/HashMapLinearizabilityTest.kt +++ b/src/test/kotlin/dev/shtanko/collections/concurrent/HashMapLinearizabilityTest.kt @@ -26,8 +26,10 @@ import org.jetbrains.kotlinx.lincheck.strategy.stress.StressCTest import org.jetbrains.kotlinx.lincheck.strategy.stress.StressOptions import org.jetbrains.kotlinx.lincheck.verifier.VerifierState import org.junit.jupiter.api.Assumptions.assumeTrue +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +@Disabled("Fluky test") @StressCTest(minimizeFailedScenario = false) @Param(name = "key", gen = IntGen::class, conf = "1:5") internal class HashMapLinearizabilityTest : VerifierState() { diff --git a/src/test/kotlin/dev/shtanko/collections/concurrent/HashtableLinearizabilityTest.kt b/src/test/kotlin/dev/shtanko/collections/concurrent/HashtableLinearizabilityTest.kt index 5130c4f9..299dd97a 100644 --- a/src/test/kotlin/dev/shtanko/collections/concurrent/HashtableLinearizabilityTest.kt +++ b/src/test/kotlin/dev/shtanko/collections/concurrent/HashtableLinearizabilityTest.kt @@ -26,8 +26,10 @@ import org.jetbrains.kotlinx.lincheck.strategy.stress.StressCTest import org.jetbrains.kotlinx.lincheck.strategy.stress.StressOptions import org.jetbrains.kotlinx.lincheck.verifier.VerifierState import org.junit.jupiter.api.Assumptions.assumeTrue +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +@Disabled("Fluky test") @StressCTest(minimizeFailedScenario = false) @Param(name = "key", gen = IntGen::class, conf = "1:5") internal class HashtableLinearizabilityTest : VerifierState() { diff --git a/src/test/kotlin/dev/shtanko/collections/concurrent/SkipListMapLinearizabilityTest.kt b/src/test/kotlin/dev/shtanko/collections/concurrent/SkipListMapLinearizabilityTest.kt index 10849c48..3aceb100 100644 --- a/src/test/kotlin/dev/shtanko/collections/concurrent/SkipListMapLinearizabilityTest.kt +++ b/src/test/kotlin/dev/shtanko/collections/concurrent/SkipListMapLinearizabilityTest.kt @@ -27,8 +27,10 @@ import org.jetbrains.kotlinx.lincheck.strategy.stress.StressCTest import org.jetbrains.kotlinx.lincheck.strategy.stress.StressOptions import org.jetbrains.kotlinx.lincheck.verifier.VerifierState import org.junit.jupiter.api.Assumptions.assumeTrue +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +@Disabled("Fluky test") @StressCTest(minimizeFailedScenario = false) @Param(name = "key", gen = IntGen::class, conf = "1:5") internal class SkipListMapLinearizabilityTest : VerifierState() { diff --git a/src/test/kotlin/dev/shtanko/proto/SearchRequestTest.kt b/src/test/kotlin/dev/shtanko/proto/SearchRequestTest.kt index e0cdce3f..4dd23df2 100644 --- a/src/test/kotlin/dev/shtanko/proto/SearchRequestTest.kt +++ b/src/test/kotlin/dev/shtanko/proto/SearchRequestTest.kt @@ -2,8 +2,10 @@ package dev.shtanko.proto import SearchRequestOuterClass.SearchRequest import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +@Disabled("Fluky test") class SearchRequestTest { @Test diff --git a/src/test/kotlin/dev/shtanko/retry/RetryTest.kt b/src/test/kotlin/dev/shtanko/retry/RetryTest.kt index 8f224772..77d5e375 100644 --- a/src/test/kotlin/dev/shtanko/retry/RetryTest.kt +++ b/src/test/kotlin/dev/shtanko/retry/RetryTest.kt @@ -17,6 +17,7 @@ import okhttp3.mockwebserver.MockWebServer import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.RepeatedTest import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -24,6 +25,7 @@ import retrofit2.HttpException import retrofit2.Retrofit import retrofit2.http.GET +@Disabled("Fluky test") class RetryTest { private val server: MockWebServer = MockWebServer().apply { start() }