feat: true-microservices (#40)

Reviewed-on: #40
This commit is contained in:
2026-04-29 22:06:01 +02:00
parent 67511fc649
commit 590924254e
328 changed files with 10672 additions and 2939 deletions
@@ -0,0 +1,5 @@
nowchess:
internal:
secret: test-secret
auth:
enabled: false
@@ -5,6 +5,7 @@ import de.nowchess.api.game.GameContext
import de.nowchess.api.io.GameContextExport
import de.nowchess.api.move.Move
import de.nowchess.io.json.{JsonExporter, JsonParser}
import org.scalactic.Prettifier.default
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers
@@ -128,6 +129,6 @@ class GameFileServiceSuite extends AnyFunSuite with Matchers:
val result = FileSystemGameService.saveGameToFile(context, tmpFile, faultyExporter)
assert(result.isLeft)
assert(result.left.toOption.get.contains("Failed to save file"))
assert(result.left.toOption.get.message.contains("Failed to save file"))
finally Files.deleteIfExists(tmpFile)
}
@@ -97,7 +97,7 @@ class FenExporterTest extends AnyFunSuite with Matchers:
val fen = FenExporter.gameContextToFen(gameContext)
FenParser.parseFen(fen) match
case Right(ctx) => ctx.halfMoveClock shouldBe 42
case Left(err) => fail(s"FEN parsing failed: $err")
case Left(err) => fail(s"FEN parsing failed: ${err.message}")
test("exportGameContext forwards to gameContextToFen"):
val ctx = GameContext.initial
@@ -95,13 +95,13 @@ class JsonParserTest extends AnyFunSuite with Matchers:
val invalidJson = "{ this is not valid json at all }"
val result = JsonParser.importGameContext(invalidJson)
assert(result.isLeft)
assert(result.left.toOption.get.contains("JSON parsing error"))
assert(result.left.toOption.get.message.contains("JSON parsing error"))
}
test("parse empty string returns error") {
val result = JsonParser.importGameContext("")
assert(result.isLeft)
assert(result.left.toOption.get.contains("JSON parsing error"))
assert(result.left.toOption.get.message.contains("JSON parsing error"))
}
test("parse number value returns error") {
@@ -113,7 +113,7 @@ class JsonParserTest extends AnyFunSuite with Matchers:
val malformed = """{"metadata": {"unclosed": """
val result = JsonParser.importGameContext(malformed)
assert(result.isLeft)
assert(result.left.toOption.get.contains("JSON parsing error"))
assert(result.left.toOption.get.message.contains("JSON parsing error"))
}
test("parse invalid JSON array returns error") {
@@ -137,7 +137,7 @@ class JsonParserTest extends AnyFunSuite with Matchers:
}"""
val result = JsonParser.importGameContext(json)
assert(result.isLeft)
assert(result.left.toOption.get.contains("Invalid turn color"))
assert(result.left.toOption.get.message.contains("Invalid turn color"))
}
test("parse invalid piece type filters it out") {
@@ -2,61 +2,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 de.nowchess.io.service.config.JacksonConfig
import de.nowchess.json.SquareKeyDeserializer
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)
val m = new ObjectMapper()
new JacksonConfig().customize(m)
m
private def readMap(json: String): Map[Square, Int] =
mapper.readValue(json, new TypeReference[Map[Square, Int]] {})
test("deserializes valid algebraic key") {
test("deserializes valid algebraic key"):
val result = readMap("""{"e4":1}""")
result(Square(File.E, Rank.R4)) shouldBe 1
}
test("deserializes a1 corner") {
test("deserializes a1 corner"):
val result = readMap("""{"a1":1}""")
result(Square(File.A, Rank.R1)) shouldBe 1
}
test("deserializes h8 corner") {
test("deserializes h8 corner"):
val result = readMap("""{"h8":1}""")
result(Square(File.H, Rank.R8)) shouldBe 1
}
test("deserializes multiple squares") {
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") {
test("deserializeKey returns null for invalid square"):
new SquareKeyDeserializer().deserializeKey("invalid", null) shouldBe null
}
test("deserializeKey returns null for wrong-length key") {
test("deserializeKey returns null for wrong-length key"):
new SquareKeyDeserializer().deserializeKey("e44", null) shouldBe null
}
test("deserializeKey returns null for bad file") {
test("deserializeKey returns null for bad file"):
new SquareKeyDeserializer().deserializeKey("z4", null) shouldBe null
}
test("deserializeKey returns null for bad rank") {
test("deserializeKey returns null for bad rank"):
new SquareKeyDeserializer().deserializeKey("e9", null) shouldBe null
}
// scalafix:on DisableSyntax.null
@@ -2,49 +2,32 @@ 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 de.nowchess.io.service.config.JacksonConfig
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)
val m = new ObjectMapper()
new JacksonConfig().customize(m)
m
test("serializes square as algebraic notation") {
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") {
test("serializes a1 corner"):
val json = mapper.writeValueAsString(Map(Square(File.A, Rank.R1) -> 1))
json should include("\"a1\"")
}
test("serializes h8 corner") {
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
}
test("round-trips with SquareKeyDeserializer"):
val original = Map(Square(File.D, Rank.R5) -> 99)
val json = rt.writeValueAsString(original)
val result = rt.readValue(json, new TypeReference[Map[Square, Int]] {})
val json = mapper.writeValueAsString(original)
val result = mapper.readValue(json, new TypeReference[Map[Square, Int]] {})
result shouldBe original
}
@@ -5,7 +5,7 @@ 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 de.nowchess.json.{SquareKeyDeserializer, SquareKeySerializer}
import io.quarkus.test.junit.QuarkusTest
import io.restassured.RestAssured
import io.restassured.http.ContentType