This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
package de.nowchess.io.json
|
||||
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.api.board.{Piece, Square, File, Rank, Board, CastlingRights, Color, PieceType}
|
||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
class JsonExporterMoveSuite extends AnyFunSuite with Matchers:
|
||||
|
||||
test("export initial position has empty moves") {
|
||||
val context = GameContext.initial
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
|
||||
assert(json.contains("\"moves\": []"))
|
||||
}
|
||||
|
||||
test("export move using withMove applies it validly") {
|
||||
val move = Move(Square(File.E, Rank.R2), Square(File.E, Rank.R4))
|
||||
val context = GameContext.initial.withMove(move)
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
|
||||
assert(json.contains("\"e2\""))
|
||||
assert(json.contains("\"e4\""))
|
||||
}
|
||||
|
||||
test("export castling rights after kingside castling") {
|
||||
val move = Move(Square(File.E, Rank.R1), Square(File.G, Rank.R1), MoveType.CastleKingside)
|
||||
// Note: We can't apply kingside castling without proper board state, so test structure
|
||||
val cr = CastlingRights(false, false, true, true)
|
||||
val context = GameContext.initial.copy(castlingRights = cr)
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
|
||||
assert(json.contains("\"whiteKingSide\": false"))
|
||||
assert(json.contains("\"blackKingSide\": true"))
|
||||
}
|
||||
|
||||
test("export en passant square if set") {
|
||||
val epSquare = Some(Square(File.E, Rank.R3))
|
||||
val context = GameContext.initial.copy(enPassantSquare = epSquare)
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
|
||||
assert(json.contains("\"enPassantSquare\": \"e3\""))
|
||||
}
|
||||
|
||||
test("export null en passant square") {
|
||||
val context = GameContext.initial.copy(enPassantSquare = None)
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
|
||||
assert(json.contains("\"enPassantSquare\": null"))
|
||||
}
|
||||
|
||||
test("export move JSON structure contains required fields") {
|
||||
val move = Move(Square(File.D, Rank.R2), Square(File.D, Rank.R4))
|
||||
val context = GameContext.initial.withMove(move)
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
|
||||
assert(json.contains("\"d2\""))
|
||||
assert(json.contains("\"d4\""))
|
||||
assert(json.contains("\"moves\""))
|
||||
}
|
||||
|
||||
test("export complex board with custom pieces") {
|
||||
val board = Board(Map(
|
||||
Square(File.A, Rank.R1) -> Piece(Color.White, PieceType.Rook),
|
||||
Square(File.E, Rank.R1) -> Piece(Color.White, PieceType.King),
|
||||
Square(File.H, Rank.R1) -> Piece(Color.White, PieceType.Rook),
|
||||
Square(File.A, Rank.R8) -> Piece(Color.Black, PieceType.Rook),
|
||||
Square(File.E, Rank.R8) -> Piece(Color.Black, PieceType.King),
|
||||
Square(File.H, Rank.R8) -> Piece(Color.Black, PieceType.Rook)
|
||||
))
|
||||
val context = GameContext.initial.copy(board = board)
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
|
||||
assert(json.contains("\"a1\""))
|
||||
assert(json.contains("\"e1\""))
|
||||
assert(json.contains("\"h1\""))
|
||||
assert(json.contains("\"Rook\""))
|
||||
}
|
||||
|
||||
test("export with no castling rights") {
|
||||
val cr = CastlingRights(false, false, false, false)
|
||||
val context = GameContext.initial.copy(castlingRights = cr)
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
|
||||
assert(json.contains("\"whiteKingSide\": false"))
|
||||
assert(json.contains("\"whiteQueenSide\": false"))
|
||||
assert(json.contains("\"blackKingSide\": false"))
|
||||
assert(json.contains("\"blackQueenSide\": false"))
|
||||
}
|
||||
|
||||
test("export with partial castling rights") {
|
||||
val cr = CastlingRights(true, false, true, false)
|
||||
val context = GameContext.initial.copy(castlingRights = cr)
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
|
||||
assert(json.contains("\"whiteKingSide\": true"))
|
||||
assert(json.contains("\"whiteQueenSide\": false"))
|
||||
}
|
||||
|
||||
test("export normal move type") {
|
||||
val normalMove = Move(Square(File.C, Rank.R2), Square(File.C, Rank.R4), MoveType.Normal(false))
|
||||
val context = GameContext.initial.withMove(normalMove)
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
|
||||
assert(json.contains("\"c2\""))
|
||||
assert(json.contains("\"c4\""))
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package de.nowchess.io.json
|
||||
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
class JsonModelExtraTestSuite extends AnyFunSuite with Matchers:
|
||||
|
||||
test("JsonMetadata with all fields") {
|
||||
val meta = JsonMetadata(Some("Event"), Some(Map("a" -> "b")), Some("2026-04-08"), Some("1-0"))
|
||||
assert(meta.event.contains("Event"))
|
||||
assert(meta.players.exists(_.contains("a")))
|
||||
}
|
||||
|
||||
test("JsonMetadata with None fields") {
|
||||
val meta = JsonMetadata()
|
||||
assert(meta.event.isEmpty)
|
||||
assert(meta.players.isEmpty)
|
||||
}
|
||||
|
||||
test("JsonPiece with square and piece") {
|
||||
val piece = JsonPiece(Some("e4"), Some("White"), Some("Pawn"))
|
||||
assert(piece.square.contains("e4"))
|
||||
assert(piece.color.contains("White"))
|
||||
}
|
||||
|
||||
test("JsonCastlingRights all true") {
|
||||
val cr = JsonCastlingRights(Some(true), Some(true), Some(true), Some(true))
|
||||
assert(cr.whiteKingSide.contains(true))
|
||||
assert(cr.blackQueenSide.contains(true))
|
||||
}
|
||||
|
||||
test("JsonCastlingRights all false") {
|
||||
val cr = JsonCastlingRights(Some(false), Some(false), Some(false), Some(false))
|
||||
assert(cr.whiteKingSide.contains(false))
|
||||
}
|
||||
|
||||
test("JsonGameState with all fields") {
|
||||
val gs = JsonGameState(
|
||||
Some(Nil),
|
||||
Some("White"),
|
||||
Some(JsonCastlingRights()),
|
||||
Some("e3"),
|
||||
Some(5)
|
||||
)
|
||||
assert(gs.board.contains(Nil))
|
||||
assert(gs.halfMoveClock.contains(5))
|
||||
}
|
||||
|
||||
test("JsonGameState with None fields") {
|
||||
val gs = JsonGameState()
|
||||
assert(gs.board.isEmpty)
|
||||
assert(gs.halfMoveClock.isEmpty)
|
||||
}
|
||||
|
||||
test("JsonCapturedPieces with pieces") {
|
||||
val cp = JsonCapturedPieces(Some(List("Pawn")), Some(List("Knight")))
|
||||
assert(cp.byWhite.exists(_.contains("Pawn")))
|
||||
assert(cp.byBlack.exists(_.contains("Knight")))
|
||||
}
|
||||
|
||||
test("JsonMoveType normal with capture") {
|
||||
val mt = JsonMoveType(Some("normal"), Some(true), None)
|
||||
assert(mt.`type`.contains("normal"))
|
||||
assert(mt.isCapture.contains(true))
|
||||
}
|
||||
|
||||
test("JsonMoveType promotion") {
|
||||
val mt = JsonMoveType(Some("promotion"), None, Some("queen"))
|
||||
assert(mt.`type`.contains("promotion"))
|
||||
assert(mt.promotionPiece.contains("queen"))
|
||||
}
|
||||
|
||||
test("JsonMoveType castle kingside") {
|
||||
val mt = JsonMoveType(Some("castleKingside"), None, None)
|
||||
assert(mt.`type`.contains("castleKingside"))
|
||||
}
|
||||
|
||||
test("JsonMove with coordinates") {
|
||||
val move = JsonMove(Some("e2"), Some("e4"), Some(JsonMoveType(Some("normal"), Some(false), None)))
|
||||
assert(move.from.contains("e2"))
|
||||
assert(move.to.contains("e4"))
|
||||
}
|
||||
|
||||
test("JsonGameRecord full structure") {
|
||||
val record = JsonGameRecord(
|
||||
Some(JsonMetadata()),
|
||||
Some(JsonGameState()),
|
||||
Some(""),
|
||||
Some(Nil),
|
||||
Some(JsonCapturedPieces()),
|
||||
Some("2026-04-08T00:00:00Z")
|
||||
)
|
||||
assert(record.metadata.nonEmpty)
|
||||
assert(record.timestamp.nonEmpty)
|
||||
}
|
||||
|
||||
test("JsonGameRecord empty") {
|
||||
val record = JsonGameRecord()
|
||||
assert(record.metadata.isEmpty)
|
||||
assert(record.moves.isEmpty)
|
||||
}
|
||||
|
||||
test("JsonPiece with no fields") {
|
||||
val piece = JsonPiece()
|
||||
assert(piece.square.isEmpty)
|
||||
assert(piece.color.isEmpty)
|
||||
assert(piece.piece.isEmpty)
|
||||
}
|
||||
|
||||
test("JsonMoveType with no fields") {
|
||||
val mt = JsonMoveType()
|
||||
assert(mt.`type`.isEmpty)
|
||||
assert(mt.isCapture.isEmpty)
|
||||
assert(mt.promotionPiece.isEmpty)
|
||||
}
|
||||
|
||||
test("JsonMove with empty fields") {
|
||||
val move = JsonMove()
|
||||
assert(move.from.isEmpty)
|
||||
assert(move.to.isEmpty)
|
||||
assert(move.`type`.isEmpty)
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package de.nowchess.io.json
|
||||
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.api.board.{Color, PieceType, Piece, Square, File, Rank}
|
||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
class JsonParserMoveTypeSuite extends AnyFunSuite with Matchers:
|
||||
|
||||
test("parse all move type variations") {
|
||||
val json = """{
|
||||
"metadata": {"event": "Game", "result": "*"},
|
||||
"gameState": {"turn": "White", "board": []},
|
||||
"moves": [
|
||||
{"from": "e2", "to": "e4", "type": {"type": "normal", "isCapture": false}},
|
||||
{"from": "e1", "to": "g1", "type": {"type": "castleKingside"}},
|
||||
{"from": "e1", "to": "c1", "type": {"type": "castleQueenside"}},
|
||||
{"from": "e5", "to": "d4", "type": {"type": "enPassant"}},
|
||||
{"from": "a7", "to": "a8", "type": {"type": "promotion", "promotionPiece": "queen"}},
|
||||
{"from": "b7", "to": "b8", "type": {"type": "promotion", "promotionPiece": "rook"}},
|
||||
{"from": "c7", "to": "c8", "type": {"type": "promotion", "promotionPiece": "bishop"}},
|
||||
{"from": "d7", "to": "d8", "type": {"type": "promotion", "promotionPiece": "knight"}}
|
||||
]
|
||||
}"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.isRight)
|
||||
val ctx = result.toOption.get
|
||||
assert(ctx.moves.length == 8)
|
||||
assert(ctx.moves(0).moveType == MoveType.Normal(false))
|
||||
assert(ctx.moves(1).moveType == MoveType.CastleKingside)
|
||||
assert(ctx.moves(2).moveType == MoveType.CastleQueenside)
|
||||
assert(ctx.moves(3).moveType == MoveType.EnPassant)
|
||||
}
|
||||
|
||||
test("parse invalid move type defaults to None") {
|
||||
val json = """{
|
||||
"metadata": {"event": "Game"},
|
||||
"gameState": {"turn": "White", "board": []},
|
||||
"moves": [{"from": "e2", "to": "e4", "type": {"type": "unknown"}}]
|
||||
}"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
// Invalid move type is skipped, so moves list should be empty
|
||||
assert(result.isRight)
|
||||
}
|
||||
|
||||
test("parse promotion with default piece") {
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {"turn": "White", "board": []},
|
||||
"moves": [{"from": "a7", "to": "a8", "type": {"type": "promotion", "promotionPiece": "invalid"}}]
|
||||
}"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
// Invalid promotion piece should use default
|
||||
assert(result.isRight)
|
||||
}
|
||||
|
||||
test("parse move with missing from/to skips it") {
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {"turn": "White", "board": []},
|
||||
"moves": [{"from": "e2", "to": "invalid", "type": {"type": "normal"}}]
|
||||
}"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.isRight)
|
||||
val ctx = result.toOption.get
|
||||
// Invalid square should be filtered out
|
||||
assert(ctx.moves.isEmpty)
|
||||
}
|
||||
|
||||
test("parse with invalid JSON returns error") {
|
||||
val json = """{"invalid json"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.isLeft)
|
||||
}
|
||||
|
||||
test("parse normal move with isCapture true") {
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {"turn": "White", "board": []},
|
||||
"moves": [{"from": "e4", "to": "d5", "type": {"type": "normal", "isCapture": true}}]
|
||||
}"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.isRight)
|
||||
val ctx = result.toOption.get
|
||||
val move = ctx.moves.head
|
||||
assert(move.moveType == MoveType.Normal(true))
|
||||
}
|
||||
|
||||
test("parse board with invalid pieces filters them") {
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {
|
||||
"turn": "White",
|
||||
"board": [
|
||||
{"square": "a1", "color": "White", "piece": "Rook"},
|
||||
{"square": "invalid", "color": "White", "piece": "King"},
|
||||
{"square": "a2", "color": "Invalid", "piece": "Pawn"}
|
||||
]
|
||||
}
|
||||
}"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.isRight)
|
||||
val ctx = result.toOption.get
|
||||
// Only valid piece should be in board
|
||||
assert(ctx.board.pieces.size == 1)
|
||||
}
|
||||
Reference in New Issue
Block a user