Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b5a2966ada | |||
| 74a4fce0ca | |||
| 8fc97bde02 | |||
| 2d75b2e80e | |||
| f088c4e9ff | |||
| 8a1cf909d4 | |||
| 33e785d22a | |||
| d16cec176b | |||
| 8744bee2dd |
+5
-1
@@ -36,7 +36,11 @@ val coverageExclusions = listOf(
|
||||
"**/core/src/main/scala/de/nowchess/chess/registry/GameEntry.scala",
|
||||
"**/core/src/main/scala/de/nowchess/chess/registry/GameRegistryImpl.scala",
|
||||
// GameResource — REST integration layer with @Inject var fields; mocking dependencies for unit tests is infeasible with Quarkus DI; integration tests would require @QuarkusTest which Scoverage doesn't instrument
|
||||
"**/core/src/main/scala/de/nowchess/chess/resource/GameResource.scala"
|
||||
"**/core/src/main/scala/de/nowchess/chess/resource/GameResource.scala",
|
||||
// IoResource — same rationale as GameResource; @QuarkusTest not instrumented by Scoverage
|
||||
"**/io/src/main/scala/de/nowchess/io/service/resource/IoResource.scala",
|
||||
// JacksonConfig — Quarkus lifecycle hook, no testable logic beyond ObjectMapper registration
|
||||
"**/io/src/main/scala/de/nowchess/io/service/config/JacksonConfig.scala",
|
||||
)
|
||||
|
||||
// Converts a Sonar-style glob to a scoverage regex (matched against full source path).
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,66 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"created": "2026-04-13T19:58:38.629943",
|
||||
"total_positions": 3022562,
|
||||
"stockfish_depth": 12,
|
||||
"sources": [
|
||||
{
|
||||
"type": "legacy_import",
|
||||
"path": "data/training_data.jsonl",
|
||||
"count": 2009355,
|
||||
"note": "Migrated from data/training_data.jsonl"
|
||||
},
|
||||
{
|
||||
"type": "test_extend",
|
||||
"count": 4,
|
||||
"actual_count": 3
|
||||
},
|
||||
{
|
||||
"type": "test_new_positions",
|
||||
"count": 3,
|
||||
"actual_count": 3
|
||||
},
|
||||
{
|
||||
"type": "test_mixed",
|
||||
"count": 5,
|
||||
"actual_count": 0
|
||||
},
|
||||
{
|
||||
"type": "test_all_dups",
|
||||
"count": 2,
|
||||
"actual_count": 0
|
||||
},
|
||||
{
|
||||
"type": "guaranteed_unique",
|
||||
"count": 10,
|
||||
"actual_count": 8
|
||||
},
|
||||
{
|
||||
"type": "merged_sources",
|
||||
"count": 600000,
|
||||
"sources": [
|
||||
{
|
||||
"type": "tactical",
|
||||
"count": 600000,
|
||||
"max_puzzles": 600000
|
||||
}
|
||||
],
|
||||
"actual_count": 599993
|
||||
},
|
||||
{
|
||||
"type": "merged_sources",
|
||||
"count": 500000,
|
||||
"sources": [
|
||||
{
|
||||
"type": "lichess",
|
||||
"count": 500000,
|
||||
"params": {
|
||||
"min_depth": 20,
|
||||
"max_positions": 500000
|
||||
}
|
||||
}
|
||||
],
|
||||
"actual_count": 500000
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -21,7 +21,6 @@ import io.quarkus.runtime.annotations.RegisterForReflection
|
||||
classOf[LegalMovesResponseDto],
|
||||
classOf[OkResponseDto],
|
||||
classOf[PlayerInfoDto],
|
||||
|
||||
classOf[GameContext],
|
||||
classOf[Color],
|
||||
classOf[Piece],
|
||||
@@ -35,7 +34,6 @@ import io.quarkus.runtime.annotations.RegisterForReflection
|
||||
classOf[PromotionPiece],
|
||||
classOf[GameResult],
|
||||
classOf[DrawReason],
|
||||
|
||||
),
|
||||
)
|
||||
class NativeReflectionConfig
|
||||
|
||||
@@ -32,7 +32,7 @@ class IoResource:
|
||||
def importFen(body: ImportFenRequest): Uni[Response] =
|
||||
Uni.createFrom().item {
|
||||
FenParser.parseFen(body.fen) match
|
||||
case Left(err) =>
|
||||
case Left(err) =>
|
||||
Response.status(400).entity(IoErrorDto("INVALID_FEN", err)).build()
|
||||
case Right(ctx) =>
|
||||
Response.ok(ctx).build()
|
||||
@@ -52,7 +52,7 @@ class IoResource:
|
||||
def importPgn(body: ImportPgnRequest): Uni[Response] =
|
||||
Uni.createFrom().item {
|
||||
PgnParser.importGameContext(body.pgn) match
|
||||
case Left(err) =>
|
||||
case Left(err) =>
|
||||
Response.status(400).entity(IoErrorDto("INVALID_PGN", err)).build()
|
||||
case Right(ctx) =>
|
||||
Response.ok(ctx).build()
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package de.nowchess.io.json
|
||||
|
||||
import com.fasterxml.jackson.core.`type`.TypeReference
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||
import com.fasterxml.jackson.module.scala.DefaultScalaModule
|
||||
import de.nowchess.api.board.{File, Rank, Square}
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
class SquareKeyDeserializerTest extends AnyFunSuite with Matchers:
|
||||
|
||||
private def mapper: ObjectMapper =
|
||||
val m = new ObjectMapper()
|
||||
val mod = new SimpleModule()
|
||||
mod.addKeyDeserializer(classOf[Square], new SquareKeyDeserializer())
|
||||
m.registerModule(DefaultScalaModule)
|
||||
m.registerModule(mod)
|
||||
m
|
||||
|
||||
private def readMap(json: String): Map[Square, Int] =
|
||||
mapper.readValue(json, new TypeReference[Map[Square, Int]] {})
|
||||
|
||||
test("deserializes valid algebraic key") {
|
||||
val result = readMap("""{"e4":1}""")
|
||||
result(Square(File.E, Rank.R4)) shouldBe 1
|
||||
}
|
||||
|
||||
test("deserializes a1 corner") {
|
||||
val result = readMap("""{"a1":1}""")
|
||||
result(Square(File.A, Rank.R1)) shouldBe 1
|
||||
}
|
||||
|
||||
test("deserializes h8 corner") {
|
||||
val result = readMap("""{"h8":1}""")
|
||||
result(Square(File.H, Rank.R8)) shouldBe 1
|
||||
}
|
||||
|
||||
test("deserializes multiple squares") {
|
||||
val result = readMap("""{"a1":1,"h8":2,"e4":3}""")
|
||||
result(Square(File.A, Rank.R1)) shouldBe 1
|
||||
result(Square(File.H, Rank.R8)) shouldBe 2
|
||||
result(Square(File.E, Rank.R4)) shouldBe 3
|
||||
}
|
||||
|
||||
// scalafix:off DisableSyntax.null
|
||||
test("deserializeKey returns null for invalid square") {
|
||||
new SquareKeyDeserializer().deserializeKey("invalid", null) shouldBe null
|
||||
}
|
||||
|
||||
test("deserializeKey returns null for wrong-length key") {
|
||||
new SquareKeyDeserializer().deserializeKey("e44", null) shouldBe null
|
||||
}
|
||||
|
||||
test("deserializeKey returns null for bad file") {
|
||||
new SquareKeyDeserializer().deserializeKey("z4", null) shouldBe null
|
||||
}
|
||||
|
||||
test("deserializeKey returns null for bad rank") {
|
||||
new SquareKeyDeserializer().deserializeKey("e9", null) shouldBe null
|
||||
}
|
||||
// scalafix:on DisableSyntax.null
|
||||
@@ -0,0 +1,50 @@
|
||||
package de.nowchess.io.json
|
||||
|
||||
import com.fasterxml.jackson.core.`type`.TypeReference
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||
import com.fasterxml.jackson.module.scala.DefaultScalaModule
|
||||
import de.nowchess.api.board.{File, Rank, Square}
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
class SquareKeySerializerTest extends AnyFunSuite with Matchers:
|
||||
|
||||
private def mapper: ObjectMapper =
|
||||
val m = new ObjectMapper()
|
||||
val mod = new SimpleModule()
|
||||
mod.addKeySerializer(classOf[Square], new SquareKeySerializer())
|
||||
m.registerModule(DefaultScalaModule)
|
||||
m.registerModule(mod)
|
||||
m
|
||||
|
||||
test("serializes square as algebraic notation") {
|
||||
val json = mapper.writeValueAsString(Map(Square(File.E, Rank.R4) -> 1))
|
||||
json should include("\"e4\"")
|
||||
}
|
||||
|
||||
test("serializes a1 corner") {
|
||||
val json = mapper.writeValueAsString(Map(Square(File.A, Rank.R1) -> 1))
|
||||
json should include("\"a1\"")
|
||||
}
|
||||
|
||||
test("serializes h8 corner") {
|
||||
val json = mapper.writeValueAsString(Map(Square(File.H, Rank.R8) -> 1))
|
||||
json should include("\"h8\"")
|
||||
}
|
||||
|
||||
test("round-trips with SquareKeyDeserializer") {
|
||||
val rt = {
|
||||
val m = new ObjectMapper()
|
||||
val mod = new SimpleModule()
|
||||
mod.addKeySerializer(classOf[Square], new SquareKeySerializer())
|
||||
mod.addKeyDeserializer(classOf[Square], new SquareKeyDeserializer())
|
||||
m.registerModule(DefaultScalaModule)
|
||||
m.registerModule(mod)
|
||||
m
|
||||
}
|
||||
val original = Map(Square(File.D, Rank.R5) -> 99)
|
||||
val json = rt.writeValueAsString(original)
|
||||
val result = rt.readValue(json, new TypeReference[Map[Square, Int]] {})
|
||||
result shouldBe original
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package de.nowchess.io.service.resource
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||
import com.fasterxml.jackson.module.scala.DefaultScalaModule
|
||||
import de.nowchess.api.board.Square
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.io.json.{SquareKeyDeserializer, SquareKeySerializer}
|
||||
import io.quarkus.test.junit.QuarkusTest
|
||||
import io.restassured.RestAssured
|
||||
import io.restassured.http.ContentType
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@QuarkusTest
|
||||
class IoResourceTest:
|
||||
private lazy val testMapper: ObjectMapper =
|
||||
val m = new ObjectMapper()
|
||||
val mod = new SimpleModule()
|
||||
mod.addKeySerializer(classOf[Square], new SquareKeySerializer())
|
||||
mod.addKeyDeserializer(classOf[Square], new SquareKeyDeserializer())
|
||||
m.registerModule(new DefaultScalaModule())
|
||||
m.registerModule(mod)
|
||||
m
|
||||
|
||||
private def contextJson(ctx: GameContext): String = testMapper.writeValueAsString(ctx)
|
||||
|
||||
@Test
|
||||
def importFenReturns200(): Unit =
|
||||
val resp = RestAssured
|
||||
.`given`()
|
||||
.contentType(ContentType.JSON)
|
||||
.body("""{"fen":"rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1"}""")
|
||||
.post("/io/import/fen")
|
||||
assertEquals(200, resp.statusCode())
|
||||
|
||||
@Test
|
||||
def importFenInvalidReturns400(): Unit =
|
||||
val resp = RestAssured
|
||||
.`given`()
|
||||
.contentType(ContentType.JSON)
|
||||
.body("""{"fen":"not-a-fen"}""")
|
||||
.post("/io/import/fen")
|
||||
assertEquals(400, resp.statusCode())
|
||||
|
||||
@Test
|
||||
def importPgnReturns200(): Unit =
|
||||
val resp = RestAssured
|
||||
.`given`()
|
||||
.contentType(ContentType.JSON)
|
||||
.body("""{"pgn":"1. e4 e5"}""")
|
||||
.post("/io/import/pgn")
|
||||
assertEquals(200, resp.statusCode())
|
||||
|
||||
@Test
|
||||
def importPgnInvalidReturns400(): Unit =
|
||||
val resp = RestAssured
|
||||
.`given`()
|
||||
.contentType(ContentType.JSON)
|
||||
.body("""{"pgn":"not valid pgn !!!###"}""")
|
||||
.post("/io/import/pgn")
|
||||
assertEquals(400, resp.statusCode())
|
||||
|
||||
@Test
|
||||
def exportFenReturns200WithFen(): Unit =
|
||||
val resp = RestAssured
|
||||
.`given`()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(contextJson(GameContext.initial))
|
||||
.post("/io/export/fen")
|
||||
assertEquals(200, resp.statusCode())
|
||||
assertTrue(resp.getBody.asString().contains("rnbqkbnr"))
|
||||
|
||||
@Test
|
||||
def exportPgnReturns200(): Unit =
|
||||
val resp = RestAssured
|
||||
.`given`()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(contextJson(GameContext.initial))
|
||||
.post("/io/export/pgn")
|
||||
assertEquals(200, resp.statusCode())
|
||||
Reference in New Issue
Block a user