test(rules): Rules as a microservice
Added tests to rules
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
package de.nowchess.rules.config
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
class JacksonConfigTest extends AnyFunSuite with Matchers:
|
||||
|
||||
test("customize registers DefaultScalaModule enabling Option serialization"):
|
||||
val config = new JacksonConfig()
|
||||
val mapper = new ObjectMapper()
|
||||
config.customize(mapper)
|
||||
mapper.writeValueAsString(None) shouldBe "null"
|
||||
mapper.writeValueAsString(Some("hello")) shouldBe """"hello""""
|
||||
@@ -138,6 +138,12 @@ class DtoMapperTest extends AnyFunSuite with Matchers:
|
||||
val ctx = GameContext.initial.copy(enPassantSquare = Some(Square(File.E, Rank.R3)))
|
||||
DtoMapper.fromGameContext(ctx).enPassantSquare shouldBe Some("e3")
|
||||
|
||||
test("toGameContext round-trips a valid en passant square"):
|
||||
val ctx = GameContext.initial.copy(enPassantSquare = Some(Square(File.E, Rank.R3)))
|
||||
DtoMapper.toGameContext(DtoMapper.fromGameContext(ctx)).map(_.enPassantSquare) shouldBe Right(
|
||||
Some(Square(File.E, Rank.R3)),
|
||||
)
|
||||
|
||||
// ── fromMove ──────────────────────────────────────────────────────
|
||||
|
||||
test("fromMove converts all move types"):
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
package de.nowchess.rules.resource
|
||||
|
||||
import de.nowchess.api.board.{Board, CastlingRights, Color, File, Piece, PieceType, Rank, Square}
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.api.move.{Move, MoveType}
|
||||
import de.nowchess.rules.dto.{ContextMoveRequest, ContextRequest, ContextSquareRequest, DtoMapper}
|
||||
import de.nowchess.rules.sets.DefaultRules
|
||||
import jakarta.ws.rs.BadRequestException
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
class RuleSetResourceUnitTest extends AnyFunSuite with Matchers:
|
||||
|
||||
private val resource = new RuleSetResource()
|
||||
private val rules = DefaultRules
|
||||
|
||||
private def ctx(g: GameContext) = ContextRequest(DtoMapper.fromGameContext(g))
|
||||
private def ctxSq(g: GameContext, sq: String) = ContextSquareRequest(DtoMapper.fromGameContext(g), sq)
|
||||
private def ctxMv(g: GameContext, m: Move) = ContextMoveRequest(DtoMapper.fromGameContext(g), DtoMapper.fromMove(m))
|
||||
|
||||
// ── position builders ─────────────────────────────────────────────
|
||||
|
||||
private def checkContext(): GameContext =
|
||||
val board = Board(Map(
|
||||
Square(File.E, Rank.R1) -> Piece(Color.White, PieceType.King),
|
||||
Square(File.E, Rank.R8) -> Piece(Color.Black, PieceType.King),
|
||||
Square(File.E, Rank.R3) -> Piece(Color.Black, PieceType.Rook),
|
||||
))
|
||||
GameContext(board, Color.White, CastlingRights.None, None, 0, List.empty, initialBoard = board)
|
||||
|
||||
private def foolsMate(): GameContext =
|
||||
val moves = List(("f2", "f3"), ("e7", "e5"), ("g2", "g4"), ("d8", "h4"))
|
||||
moves.foldLeft(GameContext.initial) { (c, ft) =>
|
||||
val from = Square.fromAlgebraic(ft._1).get
|
||||
val to = Square.fromAlgebraic(ft._2).get
|
||||
rules.legalMoves(c)(from).find(_.to == to).fold(c)(rules.applyMove(c))
|
||||
}
|
||||
|
||||
private def stalemateContext(): GameContext =
|
||||
val board = Board(Map(
|
||||
Square(File.H, Rank.R8) -> Piece(Color.Black, PieceType.King),
|
||||
Square(File.F, Rank.R7) -> Piece(Color.White, PieceType.Queen),
|
||||
Square(File.G, Rank.R6) -> Piece(Color.White, PieceType.King),
|
||||
))
|
||||
GameContext(board, Color.Black, CastlingRights.None, None, 0, List.empty, initialBoard = board)
|
||||
|
||||
private def kingsOnlyContext(): GameContext =
|
||||
val board = Board(Map(
|
||||
Square(File.E, Rank.R1) -> Piece(Color.White, PieceType.King),
|
||||
Square(File.E, Rank.R8) -> Piece(Color.Black, PieceType.King),
|
||||
))
|
||||
GameContext(board, Color.White, CastlingRights.None, None, 0, List.empty, initialBoard = board)
|
||||
|
||||
private def threefoldContext(): GameContext =
|
||||
val g1 = Square(File.G, Rank.R1)
|
||||
val f3 = Square(File.F, Rank.R3)
|
||||
val g8 = Square(File.G, Rank.R8)
|
||||
val f6 = Square(File.F, Rank.R6)
|
||||
def mv(c: GameContext, from: Square, to: Square): GameContext =
|
||||
rules.legalMoves(c)(from).find(_.to == to).fold(c)(rules.applyMove(c))
|
||||
val c1 = mv(GameContext.initial, g1, f3)
|
||||
val c2 = mv(c1, g8, f6)
|
||||
val c3 = mv(c2, f3, g1)
|
||||
val c4 = mv(c3, f6, g8)
|
||||
val c5 = mv(c4, g1, f3)
|
||||
val c6 = mv(c5, g8, f6)
|
||||
val c7 = mv(c6, f3, g1)
|
||||
mv(c7, f6, g8)
|
||||
|
||||
// ── allLegalMoves ─────────────────────────────────────────────────
|
||||
|
||||
test("allLegalMoves returns 20 moves for initial position"):
|
||||
resource.allLegalMoves(ctx(GameContext.initial)).moves should have size 20
|
||||
|
||||
// ── legalMoves ────────────────────────────────────────────────────
|
||||
|
||||
test("legalMoves returns 2 moves for e2 pawn"):
|
||||
resource.legalMoves(ctxSq(GameContext.initial, "e2")).moves should have size 2
|
||||
|
||||
test("legalMoves throws BadRequestException for invalid square"):
|
||||
an[BadRequestException] should be thrownBy
|
||||
resource.legalMoves(ctxSq(GameContext.initial, "z9"))
|
||||
|
||||
// ── candidateMoves ────────────────────────────────────────────────
|
||||
|
||||
test("candidateMoves returns moves for e2 pawn"):
|
||||
resource.candidateMoves(ctxSq(GameContext.initial, "e2")).moves should not be empty
|
||||
|
||||
test("candidateMoves throws BadRequestException for invalid square"):
|
||||
an[BadRequestException] should be thrownBy
|
||||
resource.candidateMoves(ctxSq(GameContext.initial, "z9"))
|
||||
|
||||
// ── isCheck ───────────────────────────────────────────────────────
|
||||
|
||||
test("isCheck returns false for initial position"):
|
||||
resource.isCheck(ctx(GameContext.initial)).result shouldBe false
|
||||
|
||||
test("isCheck returns true when king is attacked"):
|
||||
resource.isCheck(ctx(checkContext())).result shouldBe true
|
||||
|
||||
test("isCheck throws BadRequestException for invalid context"):
|
||||
an[BadRequestException] should be thrownBy
|
||||
resource.isCheck(ctx(GameContext.initial).copy(context =
|
||||
DtoMapper.fromGameContext(GameContext.initial).copy(turn = "Red"),
|
||||
))
|
||||
|
||||
// ── isCheckmate ───────────────────────────────────────────────────
|
||||
|
||||
test("isCheckmate returns false for initial position"):
|
||||
resource.isCheckmate(ctx(GameContext.initial)).result shouldBe false
|
||||
|
||||
test("isCheckmate returns true for Fool's mate"):
|
||||
resource.isCheckmate(ctx(foolsMate())).result shouldBe true
|
||||
|
||||
// ── isStalemate ───────────────────────────────────────────────────
|
||||
|
||||
test("isStalemate returns false for initial position"):
|
||||
resource.isStalemate(ctx(GameContext.initial)).result shouldBe false
|
||||
|
||||
test("isStalemate returns true for stalemate position"):
|
||||
resource.isStalemate(ctx(stalemateContext())).result shouldBe true
|
||||
|
||||
// ── isInsufficientMaterial ────────────────────────────────────────
|
||||
|
||||
test("isInsufficientMaterial returns false for initial position"):
|
||||
resource.isInsufficientMaterial(ctx(GameContext.initial)).result shouldBe false
|
||||
|
||||
test("isInsufficientMaterial returns true for kings only"):
|
||||
resource.isInsufficientMaterial(ctx(kingsOnlyContext())).result shouldBe true
|
||||
|
||||
// ── isFiftyMoveRule ───────────────────────────────────────────────
|
||||
|
||||
test("isFiftyMoveRule returns false for initial position"):
|
||||
resource.isFiftyMoveRule(ctx(GameContext.initial)).result shouldBe false
|
||||
|
||||
test("isFiftyMoveRule returns true when halfMoveClock is 100"):
|
||||
resource.isFiftyMoveRule(ctx(GameContext.initial.copy(halfMoveClock = 100))).result shouldBe true
|
||||
|
||||
// ── isThreefoldRepetition ─────────────────────────────────────────
|
||||
|
||||
test("isThreefoldRepetition returns false for initial position"):
|
||||
resource.isThreefoldRepetition(ctx(GameContext.initial)).result shouldBe false
|
||||
|
||||
test("isThreefoldRepetition returns true after repeated moves"):
|
||||
resource.isThreefoldRepetition(ctx(threefoldContext())).result shouldBe true
|
||||
|
||||
// ── applyMove ─────────────────────────────────────────────────────
|
||||
|
||||
test("applyMove returns updated context with switched turn"):
|
||||
val move = rules
|
||||
.legalMoves(GameContext.initial)(Square(File.E, Rank.R2))
|
||||
.find(_.to == Square(File.E, Rank.R4))
|
||||
.get
|
||||
resource.applyMove(ctxMv(GameContext.initial, move)).turn shouldBe "Black"
|
||||
|
||||
test("applyMove throws BadRequestException for invalid move"):
|
||||
val badMove = DtoMapper.fromMove(Move(Square(File.E, Rank.R2), Square(File.E, Rank.R4)))
|
||||
.copy(moveType = "unknown")
|
||||
an[BadRequestException] should be thrownBy
|
||||
resource.applyMove(ContextMoveRequest(DtoMapper.fromGameContext(GameContext.initial), badMove))
|
||||
Reference in New Issue
Block a user