diff --git a/modules/core/src/test/scala/de/nowchess/chess/adapter/RuleSetRestAdapterTest.scala b/modules/core/src/test/scala/de/nowchess/chess/adapter/RuleSetRestAdapterTest.scala new file mode 100644 index 0000000..27147e2 --- /dev/null +++ b/modules/core/src/test/scala/de/nowchess/chess/adapter/RuleSetRestAdapterTest.scala @@ -0,0 +1,109 @@ +package de.nowchess.chess.adapter + +import de.nowchess.api.board.{File, Rank, Square} +import de.nowchess.api.game.GameContext +import de.nowchess.api.move.{Move, MoveType} +import de.nowchess.chess.client.{RuleMoveRequest, RuleServiceClient, RuleSquareRequest} +import io.quarkus.test.InjectMock +import io.quarkus.test.junit.QuarkusTest +import jakarta.inject.Inject +import org.eclipse.microprofile.rest.client.inject.RestClient +import org.junit.jupiter.api.{BeforeEach, DisplayName, Test} +import org.junit.jupiter.api.Assertions.* +import org.mockito.Mockito.{verify, when} + +import scala.compiletime.uninitialized + +// scalafix:off +@QuarkusTest +@DisplayName("RuleSetRestAdapter") +class RuleSetRestAdapterTest: + + @Inject + var adapter: RuleSetRestAdapter = uninitialized + + @InjectMock + @RestClient + var client: RuleServiceClient = uninitialized + + private val ctx = GameContext.initial + private val sq = Square(File.E, Rank.R2) + private val move = Move(Square(File.E, Rank.R2), Square(File.E, Rank.R4), MoveType.Normal(false)) + + @BeforeEach + def setup(): Unit = + when(client.candidateMoves(RuleSquareRequest(ctx, sq.toString))).thenReturn(List(move)) + when(client.legalMoves(RuleSquareRequest(ctx, sq.toString))).thenReturn(List(move)) + when(client.allLegalMoves(ctx)).thenReturn(List(move)) + when(client.isCheck(ctx)).thenReturn(false) + when(client.isCheckmate(ctx)).thenReturn(false) + when(client.isStalemate(ctx)).thenReturn(false) + when(client.isInsufficientMaterial(ctx)).thenReturn(false) + when(client.isFiftyMoveRule(ctx)).thenReturn(false) + when(client.isThreefoldRepetition(ctx)).thenReturn(false) + when(client.applyMove(RuleMoveRequest(ctx, move))).thenReturn(ctx) + + @Test + @DisplayName("candidateMoves delegates to client") + def testCandidateMoves(): Unit = + val result = adapter.candidateMoves(ctx)(sq) + assertEquals(List(move), result) + verify(client).candidateMoves(RuleSquareRequest(ctx, sq.toString)) + + @Test + @DisplayName("legalMoves delegates to client") + def testLegalMoves(): Unit = + val result = adapter.legalMoves(ctx)(sq) + assertEquals(List(move), result) + verify(client).legalMoves(RuleSquareRequest(ctx, sq.toString)) + + @Test + @DisplayName("allLegalMoves delegates to client") + def testAllLegalMoves(): Unit = + val result = adapter.allLegalMoves(ctx) + assertEquals(List(move), result) + verify(client).allLegalMoves(ctx) + + @Test + @DisplayName("isCheck delegates to client") + def testIsCheck(): Unit = + assertFalse(adapter.isCheck(ctx)) + verify(client).isCheck(ctx) + + @Test + @DisplayName("isCheckmate delegates to client") + def testIsCheckmate(): Unit = + assertFalse(adapter.isCheckmate(ctx)) + verify(client).isCheckmate(ctx) + + @Test + @DisplayName("isStalemate delegates to client") + def testIsStalemate(): Unit = + assertFalse(adapter.isStalemate(ctx)) + verify(client).isStalemate(ctx) + + @Test + @DisplayName("isInsufficientMaterial delegates to client") + def testIsInsufficientMaterial(): Unit = + assertFalse(adapter.isInsufficientMaterial(ctx)) + verify(client).isInsufficientMaterial(ctx) + + @Test + @DisplayName("isFiftyMoveRule delegates to client") + def testIsFiftyMoveRule(): Unit = + assertFalse(adapter.isFiftyMoveRule(ctx)) + verify(client).isFiftyMoveRule(ctx) + + @Test + @DisplayName("isThreefoldRepetition delegates to client") + def testIsThreefoldRepetition(): Unit = + assertFalse(adapter.isThreefoldRepetition(ctx)) + verify(client).isThreefoldRepetition(ctx) + + @Test + @DisplayName("applyMove delegates to client") + def testApplyMove(): Unit = + val result = adapter.applyMove(ctx)(move) + assertEquals(ctx, result) + verify(client).applyMove(RuleMoveRequest(ctx, move)) +// scalafix:on diff --git a/modules/core/src/test/scala/de/nowchess/chess/json/JsonSerializersTest.scala b/modules/core/src/test/scala/de/nowchess/chess/json/JsonSerializersTest.scala new file mode 100644 index 0000000..a834aa1 --- /dev/null +++ b/modules/core/src/test/scala/de/nowchess/chess/json/JsonSerializersTest.scala @@ -0,0 +1,87 @@ +package de.nowchess.chess.json + +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.api.move.{MoveType, PromotionPiece} +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.should.Matchers + +class JsonSerializersTest extends AnyFunSuite with Matchers: + + private val mapper: ObjectMapper = + val m = new ObjectMapper() + val mod = new SimpleModule() + m.registerModule(DefaultScalaModule) + mod.addSerializer(classOf[Square], new SquareSerializer()) + mod.addDeserializer(classOf[Square], new SquareDeserializer()) + mod.addSerializer(classOf[MoveType], new MoveTypeSerializer()) + mod.addDeserializer(classOf[MoveType], new MoveTypeDeserializer()) + m.registerModule(mod) + m + + private val e4 = Square(File.E, Rank.R4) + + // ── SquareSerializer ────────────────────────────────────────────── + + test("SquareSerializer writes square as string"): + mapper.writeValueAsString(e4) shouldBe """"e4"""" + + // ── SquareDeserializer ──────────────────────────────────────────── + + test("SquareDeserializer reads valid square string"): + mapper.readValue(""""e4"""", classOf[Square]) shouldBe e4 + + // scalafix:off DisableSyntax.null + test("SquareDeserializer returns null for invalid square string"): + mapper.readValue(""""z9"""", classOf[Square]) shouldBe null + // scalafix:on DisableSyntax.null + + // ── MoveTypeSerializer ──────────────────────────────────────────── + + test("MoveTypeSerializer serializes Normal non-capture"): + mapper.writeValueAsString(MoveType.Normal(false)) shouldBe """{"type":"normal","isCapture":false}""" + + test("MoveTypeSerializer serializes Normal capture"): + mapper.writeValueAsString(MoveType.Normal(true)) shouldBe """{"type":"normal","isCapture":true}""" + + test("MoveTypeSerializer serializes CastleKingside"): + mapper.writeValueAsString(MoveType.CastleKingside) shouldBe """{"type":"castleKingside"}""" + + test("MoveTypeSerializer serializes CastleQueenside"): + mapper.writeValueAsString(MoveType.CastleQueenside) shouldBe """{"type":"castleQueenside"}""" + + test("MoveTypeSerializer serializes EnPassant"): + mapper.writeValueAsString(MoveType.EnPassant) shouldBe """{"type":"enPassant"}""" + + test("MoveTypeSerializer serializes Promotion"): + mapper.writeValueAsString(MoveType.Promotion(PromotionPiece.Queen)) shouldBe + """{"type":"promotion","piece":"Queen"}""" + + // ── MoveTypeDeserializer ────────────────────────────────────────── + + test("MoveTypeDeserializer deserializes normal non-capture"): + mapper.readValue("""{"type":"normal","isCapture":false}""", classOf[MoveType]) shouldBe + MoveType.Normal(false) + + test("MoveTypeDeserializer deserializes normal capture"): + mapper.readValue("""{"type":"normal","isCapture":true}""", classOf[MoveType]) shouldBe + MoveType.Normal(true) + + test("MoveTypeDeserializer deserializes castleKingside"): + mapper.readValue("""{"type":"castleKingside"}""", classOf[MoveType]) shouldBe MoveType.CastleKingside + + test("MoveTypeDeserializer deserializes castleQueenside"): + mapper.readValue("""{"type":"castleQueenside"}""", classOf[MoveType]) shouldBe MoveType.CastleQueenside + + test("MoveTypeDeserializer deserializes enPassant"): + mapper.readValue("""{"type":"enPassant"}""", classOf[MoveType]) shouldBe MoveType.EnPassant + + test("MoveTypeDeserializer deserializes promotion"): + mapper.readValue("""{"type":"promotion","piece":"Rook"}""", classOf[MoveType]) shouldBe + MoveType.Promotion(PromotionPiece.Rook) + + test("MoveTypeDeserializer throws for unknown type"): + an[Exception] should be thrownBy + mapper.readValue("""{"type":"unknown"}""", classOf[MoveType])