Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions a2a-client/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ kotlin {
sourceSets {
commonMain {
dependencies {
api(libs.ktor.client.content.negotiation)
api(project(":ai-mocks-a2a-models"))
api(libs.ktor.serialization.kotlinx.json)
api(libs.ktor.client.core)
api(libs.ktor.client.logging)
api(libs.ktor.serialization.kotlinx.json)
api(project(":ai-mocks-a2a-models"))
api(libs.ktor.client.content.negotiation)
api(project.dependencies.platform(libs.ktor.bom))
}
}
Expand Down
1 change: 0 additions & 1 deletion ai-mocks-a2a/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ kotlin {
api(libs.ktor.server.content.negotiation)
api(project(":ai-mocks-a2a-models"))
api(project(":ai-mocks-core"))
api(project.dependencies.platform(libs.ktor.bom))
implementation(libs.ktor.server.sse)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package me.kpavlov.mokksy.utils

import io.kotest.matchers.shouldBe
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.CsvSource

class StringsTest {
@ParameterizedTest
@CsvSource(
",",
"abcdefghij, abcdefghij",
"abcdefghi, abcdefghi",
"abcdefghijklmnopqrst, abcd...rst",
)
fun testShortStringUnchanged(input: String?, expected: String?) {
val result = input.ellipsizeMiddle(10)
result shouldBe expected
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package me.kpavlov.mokksy.utils.logger

import io.kotest.assertions.assertSoftly
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import io.ktor.http.ContentType
import me.kpavlov.mokksy.utils.logger.Highlighting.highlightBody
import kotlin.test.Test

internal class HighlightingTest {

@Test
fun `should highlight JSON key-value pairs with correct colors and retain spaces`() {
// language=json
val input = """
{
"name" : "Alice",
"age":42,
"active" : true,
"nickname" : null
}
""".trimIndent()

val result = highlightBody(input, ContentType.Application.Json)

assertSoftly {
result shouldContain colorize("\"name\"", AnsiColor.MAGENTA)
result shouldContain colorize("\"Alice\"", AnsiColor.GREEN)

result shouldContain colorize("\"age\"", AnsiColor.MAGENTA)
result shouldContain colorize("42", AnsiColor.BLUE)

result shouldContain colorize("true", AnsiColor.YELLOW)

result shouldContain colorize("null", AnsiColor.YELLOW)

// Check spacing is retained (e.g., double space before/after colon in "name")
result shouldContain "name\"\u001B[0m : "
}
}

@Test
fun `should highlight form parameters with correct colors and preserve format`() {
val input = "name=Alice&age=30&debug=true"
val result = highlightBody(input, ContentType.Application.FormUrlEncoded)

assertSoftly {
result shouldContain colorize("name", AnsiColor.YELLOW)
result shouldContain colorize("Alice", AnsiColor.GREEN)

result shouldContain colorize("age", AnsiColor.YELLOW)
result shouldContain colorize("30", AnsiColor.GREEN)

result shouldContain colorize("debug", AnsiColor.YELLOW)
result shouldContain colorize("true", AnsiColor.GREEN)

result.count { it == '&' } shouldBe 2
}
}

@Test
fun `should leave invalid pairs untouched`() {
val input = "incomplete&validKey=value"
val result = highlightBody(input, ContentType.Application.FormUrlEncoded)

assertSoftly {
result shouldContain "incomplete"
result shouldContain colorize("validKey", AnsiColor.YELLOW)
result shouldContain colorize("value", AnsiColor.GREEN)
}
}

private fun colorize(text: String, color: AnsiColor): String {
return "${color.code}$text\u001B[0m"
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import me.kpavlov.aimocks.openai.model.responses.InputImage
import me.kpavlov.aimocks.openai.model.responses.InputItems
import me.kpavlov.aimocks.openai.model.responses.InputText
import me.kpavlov.aimocks.openai.model.responses.Text
import me.kpavlov.mokksy.utils.ellipsizeMiddle

private const val IMAGE_URL_MAX_LENGTH = 256

/**
* OpenaiResponsesMatchers is a utility object that provides matchers for validating properties of
Expand Down Expand Up @@ -46,13 +49,22 @@ internal object OpenaiResponsesMatchers {
it as? T
}

/**
* Returns a matcher that checks whether a `CreateResponseRequest` contains an `InputImage` with the specified URL.
*
* The matcher succeeds if the request's input includes an `InputImage`
* whose `imageUrl` exactly matches the provided value.
*
* @param imageUrl The URL to match against the `InputImage` items in the request.
* @return A Kotest matcher for validating the presence of an image with the given URL.
*/
fun containsInputImageWithUrl(imageUrl: String): Matcher<CreateResponseRequest?> =
object : Matcher<CreateResponseRequest?> {
override fun test(value: CreateResponseRequest?): MatcherResult {
val passed =
if (value == null) {
false
} else if ((value.input is InputItems) == false) {
} else if (value.input !is InputItems) {
false
} else {
val images = extractInputItem<InputImage>(value)
Expand All @@ -61,21 +73,42 @@ internal object OpenaiResponsesMatchers {

return MatcherResult(
passed,
{ "Input should contain image with url \"$imageUrl\"" },
{ "Input should NOT contain image with url \"$imageUrl\"" },
{
"Input should contain image with url \"${
imageUrl.ellipsizeMiddle(
IMAGE_URL_MAX_LENGTH
)
}\""
},
{
"Input should NOT contain image with url \"${
imageUrl.ellipsizeMiddle(
IMAGE_URL_MAX_LENGTH
)
}\""
},
)
}

override fun toString(): String = "InputImage should have URL \"$imageUrl\""
override fun toString(): String =
"InputImage should have URL \"${imageUrl.ellipsizeMiddle(IMAGE_URL_MAX_LENGTH)}\""
}

/**
* Returns a matcher that checks if a `CreateResponseRequest` contains an input file with the specified filename.
*
* The matcher succeeds if any `InputFile` in the request's input has a filename matching the provided value.
*
* @param filename The name of the file to search for in the request input.
* @return A matcher that verifies the presence of a file with the given filename.
*/
fun containsInputFileNamed(filename: String): Matcher<CreateResponseRequest?> =
object : Matcher<CreateResponseRequest?> {
override fun test(value: CreateResponseRequest?): MatcherResult {
val passed =
if (value == null) {
false
} else if ((value.input is InputItems) == false) {
} else if (value.input !is InputItems) {
false
} else {
val files = extractInputItem<InputFile>(value)
Expand All @@ -98,7 +131,7 @@ internal object OpenaiResponsesMatchers {
val passed =
if (value == null) {
false
} else if ((value.input is InputItems) == false) {
} else if (value.input !is InputItems) {
false
} else {
val files = extractInputItem<InputFile>(value)
Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ spotless = "7.2.1"
spring = "6.2.9"
spring-ai = "1.0.1"
system-stubs = "2.1.8"
jansi = "2.4.2"

[libraries]
anthropic-java = { group = "com.anthropic", name = "anthropic-java", version.ref = "anthropic-java" }
Expand All @@ -40,6 +41,7 @@ datafaker = { module = "net.datafaker:datafaker", version.ref = "datafaker" }
finchly = { module = "me.kpavlov.finchly:finchly", version.ref = "finchly" }
google-adk = { group = "com.google.adk", name = "google-adk", version.ref = "google-adk" }
google-genai = { group = "com.google.genai", name = "google-genai", version.ref = "google-genai" }
jansi = { module = "org.fusesource.jansi:jansi", version.ref = "jansi" }
junit-jupiter-params = { group = "org.junit.jupiter", name = "junit-jupiter-params", version.ref = "junitJupiter" }
kotest-assertions-core = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" }
kotest-assertions-json = { module = "io.kotest:kotest-assertions-json", version.ref = "kotest" }
Expand Down
1 change: 1 addition & 0 deletions mokksy/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ kotlin {
jvmMain {
dependencies {
api(project.dependencies.platform(libs.netty.bom))
implementation(libs.jansi)
implementation(libs.ktor.server.call.logging)
implementation(libs.ktor.server.netty)
}
Expand Down
Loading