Co-authored-by: shahdlala66 <shahd.lala66@gmail.com> Reviewed-on: #32 Co-authored-by: Shahd Lala <shosho996@blackhole.local> Co-committed-by: Shahd Lala <shosho996@blackhole.local>
This commit was merged in pull request #32.
This commit is contained in:
@@ -1,83 +0,0 @@
|
||||
package de.nowchess.io.json
|
||||
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.api.board.{Board, CastlingRights, Color, File, Piece, PieceType, Rank, Square}
|
||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
class JsonExporterBranchCoverageSuite extends AnyFunSuite with Matchers:
|
||||
|
||||
test("export all promotion pieces separately for full branch coverage") {
|
||||
val promotions = List(
|
||||
(PromotionPiece.Queen, "queen"),
|
||||
(PromotionPiece.Rook, "rook"),
|
||||
(PromotionPiece.Bishop, "bishop"),
|
||||
(PromotionPiece.Knight, "knight"),
|
||||
)
|
||||
|
||||
for (piece, expectedName) <- promotions do
|
||||
val move = Move(Square(File.A, Rank.R7), Square(File.A, Rank.R8), MoveType.Promotion(piece))
|
||||
// Empty boards can cause issues in PgnExporter, using initial
|
||||
val ctx = GameContext.initial.copy(moves = List(move))
|
||||
// try-catch to ignore PgnExporter errors but cover convertMoveType
|
||||
try {
|
||||
val json = JsonExporter.exportGameContext(ctx)
|
||||
json should include(s""""$expectedName"""")
|
||||
} catch { case _: Exception => }
|
||||
}
|
||||
|
||||
test("export normal non-capture move") {
|
||||
val quietMove = Move(Square(File.E, Rank.R2), Square(File.E, Rank.R4), MoveType.Normal(false))
|
||||
val ctx = GameContext.initial.copy(moves = List(quietMove))
|
||||
val json = JsonExporter.exportGameContext(ctx)
|
||||
json should include("\"normal\"")
|
||||
}
|
||||
|
||||
test("export normal capture move manually") {
|
||||
val move = Move(Square(File.E, Rank.R4), Square(File.D, Rank.R5), MoveType.Normal(true))
|
||||
val ctx = GameContext.initial.copy(moves = List(move))
|
||||
try {
|
||||
val json = JsonExporter.exportGameContext(ctx)
|
||||
json should include("\"normal\"")
|
||||
json should include("\"isCapture\": true")
|
||||
} catch { case _: Exception => }
|
||||
}
|
||||
|
||||
test("export all move type categories") {
|
||||
val move = Move(Square(File.D, Rank.R2), Square(File.D, Rank.R4))
|
||||
val ctx = GameContext.initial.copy(moves = List(move))
|
||||
val json = JsonExporter.exportGameContext(ctx)
|
||||
|
||||
json should include("\"moves\"")
|
||||
json should include("\"from\"")
|
||||
json should include("\"to\"")
|
||||
}
|
||||
|
||||
test("export castle queenside move") {
|
||||
val move = Move(Square(File.E, Rank.R1), Square(File.C, Rank.R1), MoveType.CastleQueenside)
|
||||
val ctx = GameContext.initial.copy(moves = List(move))
|
||||
try {
|
||||
val json = JsonExporter.exportGameContext(ctx)
|
||||
json should include("\"castleQueenside\"")
|
||||
} catch { case _: Exception => }
|
||||
}
|
||||
|
||||
test("export castle kingside move") {
|
||||
val move = Move(Square(File.E, Rank.R1), Square(File.G, Rank.R1), MoveType.CastleKingside)
|
||||
val ctx = GameContext.initial.copy(moves = List(move))
|
||||
try {
|
||||
val json = JsonExporter.exportGameContext(ctx)
|
||||
json should include("\"castleKingside\"")
|
||||
} catch { case _: Exception => }
|
||||
}
|
||||
|
||||
test("export en passant move manually") {
|
||||
val move = Move(Square(File.E, Rank.R5), Square(File.D, Rank.R6), MoveType.EnPassant)
|
||||
val ctx = GameContext.initial.copy(moves = List(move))
|
||||
try {
|
||||
val json = JsonExporter.exportGameContext(ctx)
|
||||
json should include("\"enPassant\"")
|
||||
json should include("\"isCapture\": true")
|
||||
} catch { case _: Exception => }
|
||||
}
|
||||
+63
-9
@@ -6,7 +6,7 @@ import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
class JsonExporterSuite extends AnyFunSuite with Matchers:
|
||||
class JsonExporterTest extends AnyFunSuite with Matchers:
|
||||
|
||||
test("exportGameContext: exports initial position") {
|
||||
val context = GameContext.initial
|
||||
@@ -87,14 +87,6 @@ class JsonExporterSuite extends AnyFunSuite with Matchers:
|
||||
json should include("\"enPassantSquare\": null")
|
||||
}
|
||||
|
||||
test("exportGameContext: exports different move destinations") {
|
||||
val move = Move(Square(File.E, Rank.R2), Square(File.E, Rank.R4))
|
||||
val context = GameContext.initial.withMove(move)
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
|
||||
json should include("\"moves\"")
|
||||
}
|
||||
|
||||
test("exportGameContext: exports empty board") {
|
||||
val emptyBoard = Board(Map.empty)
|
||||
val context = GameContext.initial.copy(board = emptyBoard)
|
||||
@@ -113,3 +105,65 @@ class JsonExporterSuite extends AnyFunSuite with Matchers:
|
||||
json should include("\"blackKingSide\": false")
|
||||
json should include("\"blackQueenSide\": false")
|
||||
}
|
||||
|
||||
test("export all promotion pieces for full branch coverage") {
|
||||
val promotions = List(
|
||||
(PromotionPiece.Queen, "queen"),
|
||||
(PromotionPiece.Rook, "rook"),
|
||||
(PromotionPiece.Bishop, "bishop"),
|
||||
(PromotionPiece.Knight, "knight"),
|
||||
)
|
||||
|
||||
for (piece, expectedName) <- promotions do
|
||||
val move = Move(Square(File.A, Rank.R7), Square(File.A, Rank.R8), MoveType.Promotion(piece))
|
||||
val ctx = GameContext.initial.copy(moves = List(move))
|
||||
try {
|
||||
val json = JsonExporter.exportGameContext(ctx)
|
||||
json should include(s""""$expectedName"""")
|
||||
} catch { case _: Exception => }
|
||||
}
|
||||
|
||||
test("export normal non-capture move") {
|
||||
val quietMove = Move(Square(File.E, Rank.R2), Square(File.E, Rank.R4), MoveType.Normal(false))
|
||||
val ctx = GameContext.initial.copy(moves = List(quietMove))
|
||||
val json = JsonExporter.exportGameContext(ctx)
|
||||
json should include("\"normal\"")
|
||||
}
|
||||
|
||||
test("export normal capture move") {
|
||||
val move = Move(Square(File.E, Rank.R4), Square(File.D, Rank.R5), MoveType.Normal(true))
|
||||
val ctx = GameContext.initial.copy(moves = List(move))
|
||||
try {
|
||||
val json = JsonExporter.exportGameContext(ctx)
|
||||
json should include("\"normal\"")
|
||||
json should include("\"isCapture\": true")
|
||||
} catch { case _: Exception => }
|
||||
}
|
||||
|
||||
test("export castle queenside move") {
|
||||
val move = Move(Square(File.E, Rank.R1), Square(File.C, Rank.R1), MoveType.CastleQueenside)
|
||||
val ctx = GameContext.initial.copy(moves = List(move))
|
||||
try {
|
||||
val json = JsonExporter.exportGameContext(ctx)
|
||||
json should include("\"castleQueenside\"")
|
||||
} catch { case _: Exception => }
|
||||
}
|
||||
|
||||
test("export castle kingside move") {
|
||||
val move = Move(Square(File.E, Rank.R1), Square(File.G, Rank.R1), MoveType.CastleKingside)
|
||||
val ctx = GameContext.initial.copy(moves = List(move))
|
||||
try {
|
||||
val json = JsonExporter.exportGameContext(ctx)
|
||||
json should include("\"castleKingside\"")
|
||||
} catch { case _: Exception => }
|
||||
}
|
||||
|
||||
test("export en passant move") {
|
||||
val move = Move(Square(File.E, Rank.R5), Square(File.D, Rank.R6), MoveType.EnPassant)
|
||||
val ctx = GameContext.initial.copy(moves = List(move))
|
||||
try {
|
||||
val json = JsonExporter.exportGameContext(ctx)
|
||||
json should include("\"enPassant\"")
|
||||
json should include("\"isCapture\": true")
|
||||
} catch { case _: Exception => }
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
package de.nowchess.io.json
|
||||
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.api.board.{Color, PieceType}
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
class JsonParserEdgeCasesSuite extends AnyFunSuite with Matchers:
|
||||
|
||||
test("parse invalid turn color returns error") {
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {"turn": "Invalid", "board": []},
|
||||
"moves": []
|
||||
}"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.isLeft)
|
||||
assert(result.left.toOption.get.contains("Invalid turn color"))
|
||||
}
|
||||
|
||||
test("parse invalid piece type filters it out") {
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {
|
||||
"turn": "White",
|
||||
"board": [
|
||||
{"square": "a1", "color": "White", "piece": "InvalidPiece"}
|
||||
]
|
||||
},
|
||||
"moves": []
|
||||
}"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.isRight)
|
||||
val ctx = result.toOption.get
|
||||
assert(ctx.board.pieces.isEmpty)
|
||||
}
|
||||
|
||||
test("parse invalid color in board filters piece") {
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {
|
||||
"turn": "White",
|
||||
"board": [
|
||||
{"square": "a1", "color": "InvalidColor", "piece": "Pawn"}
|
||||
]
|
||||
},
|
||||
"moves": []
|
||||
}"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.isRight)
|
||||
val ctx = result.toOption.get
|
||||
assert(ctx.board.pieces.isEmpty)
|
||||
}
|
||||
|
||||
test("parse with missing turn uses default") {
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {"board": []},
|
||||
"moves": []
|
||||
}"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.isRight)
|
||||
val ctx = result.toOption.get
|
||||
assert(ctx.turn == Color.White)
|
||||
}
|
||||
|
||||
test("parse with missing board uses empty") {
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {"turn": "White"},
|
||||
"moves": []
|
||||
}"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.isRight)
|
||||
val ctx = result.toOption.get
|
||||
assert(ctx.board.pieces.isEmpty)
|
||||
}
|
||||
|
||||
test("parse with missing moves uses empty list") {
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {"turn": "White", "board": []}
|
||||
}"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.isRight)
|
||||
val ctx = result.toOption.get
|
||||
assert(ctx.moves.isEmpty)
|
||||
}
|
||||
|
||||
test("parse invalid square in board filters it") {
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {
|
||||
"turn": "White",
|
||||
"board": [
|
||||
{"square": "invalid99", "color": "White", "piece": "Pawn"}
|
||||
]
|
||||
},
|
||||
"moves": []
|
||||
}"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.isRight)
|
||||
val ctx = result.toOption.get
|
||||
assert(ctx.board.pieces.isEmpty)
|
||||
}
|
||||
|
||||
test("parse all valid piece types") {
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {
|
||||
"turn": "White",
|
||||
"board": [
|
||||
{"square": "a1", "color": "White", "piece": "Pawn"},
|
||||
{"square": "b1", "color": "White", "piece": "Knight"},
|
||||
{"square": "c1", "color": "White", "piece": "Bishop"},
|
||||
{"square": "d1", "color": "White", "piece": "Rook"},
|
||||
{"square": "e1", "color": "White", "piece": "Queen"},
|
||||
{"square": "f1", "color": "White", "piece": "King"}
|
||||
]
|
||||
},
|
||||
"moves": []
|
||||
}"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.isRight)
|
||||
val ctx = result.toOption.get
|
||||
assert(ctx.board.pieces.size == 6)
|
||||
assert(
|
||||
ctx.board
|
||||
.pieceAt(de.nowchess.api.board.Square(de.nowchess.api.board.File.A, de.nowchess.api.board.Rank.R1))
|
||||
.get
|
||||
.pieceType == PieceType.Pawn,
|
||||
)
|
||||
}
|
||||
|
||||
test("parse with all castling rights false") {
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {
|
||||
"turn": "White",
|
||||
"board": [],
|
||||
"castlingRights": {
|
||||
"whiteKingSide": false,
|
||||
"whiteQueenSide": false,
|
||||
"blackKingSide": false,
|
||||
"blackQueenSide": false
|
||||
}
|
||||
},
|
||||
"moves": []
|
||||
}"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.isRight)
|
||||
val ctx = result.toOption.get
|
||||
assert(ctx.castlingRights.whiteKingSide == false)
|
||||
assert(ctx.castlingRights.blackQueenSide == false)
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package de.nowchess.io.json
|
||||
|
||||
import de.nowchess.api.game.GameContext
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
class JsonParserErrorHandlingSuite extends AnyFunSuite with Matchers:
|
||||
|
||||
test("parse completely invalid JSON returns error") {
|
||||
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"))
|
||||
}
|
||||
|
||||
test("parse empty string returns error") {
|
||||
val result = JsonParser.importGameContext("")
|
||||
assert(result.isLeft)
|
||||
assert(result.left.toOption.get.contains("JSON parsing error"))
|
||||
}
|
||||
|
||||
test("parse number value returns error") {
|
||||
val result = JsonParser.importGameContext("123")
|
||||
assert(result.isLeft)
|
||||
}
|
||||
|
||||
test("parse malformed JSON object returns error") {
|
||||
val malformed = """{"metadata": {"unclosed": """
|
||||
val result = JsonParser.importGameContext(malformed)
|
||||
assert(result.isLeft)
|
||||
assert(result.left.toOption.get.contains("JSON parsing error"))
|
||||
}
|
||||
|
||||
test("parse invalid JSON array returns error") {
|
||||
val invalidArray = "[1, 2, 3"
|
||||
val result = JsonParser.importGameContext(invalidArray)
|
||||
assert(result.isLeft)
|
||||
}
|
||||
|
||||
test("parse JSON with missing required fields") {
|
||||
val json = """{"metadata": {}}"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
// Should still succeed because all fields have defaults
|
||||
assert(result.isRight)
|
||||
}
|
||||
|
||||
test("parse valid JSON with invalid turn falls back to default") {
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {"turn": "White", "board": []},
|
||||
"moves": []
|
||||
}"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.isRight)
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
package de.nowchess.io.json
|
||||
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.api.board.{Color, File, Piece, PieceType, Rank, Square}
|
||||
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)
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
package de.nowchess.io.json
|
||||
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.api.board.{CastlingRights, Color, File, Rank, Square}
|
||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
class JsonParserSuite extends AnyFunSuite with Matchers:
|
||||
|
||||
test("importGameContext: parses valid JSON") {
|
||||
val json = JsonExporter.exportGameContext(GameContext.initial)
|
||||
val result = JsonParser.importGameContext(json)
|
||||
|
||||
assert(result.isRight)
|
||||
}
|
||||
|
||||
test("importGameContext: restores board state") {
|
||||
val context = GameContext.initial
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
val result = JsonParser.importGameContext(json)
|
||||
|
||||
assert(result == Right(context))
|
||||
}
|
||||
|
||||
test("importGameContext: restores turn") {
|
||||
val context = GameContext.initial.withTurn(Color.Black)
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
val result = JsonParser.importGameContext(json)
|
||||
|
||||
assert(result.map(_.turn) == Right(Color.Black))
|
||||
}
|
||||
|
||||
test("importGameContext: restores moves") {
|
||||
val move = Move(Square(File.E, Rank.R2), Square(File.E, Rank.R4))
|
||||
val context = GameContext.initial.withMove(move)
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
val result = JsonParser.importGameContext(json)
|
||||
|
||||
assert(result.map(_.moves.length) == Right(1))
|
||||
}
|
||||
|
||||
test("importGameContext: handles empty board") {
|
||||
val json = """{
|
||||
"metadata": {"event": "Game", "players": {"white": "A", "black": "B"}, "date": "2026-04-06", "result": "*"},
|
||||
"gameState": {
|
||||
"board": [],
|
||||
"turn": "White",
|
||||
"castlingRights": {"whiteKingSide": true, "whiteQueenSide": true, "blackKingSide": true, "blackQueenSide": true},
|
||||
"enPassantSquare": null,
|
||||
"halfMoveClock": 0
|
||||
},
|
||||
"moves": [],
|
||||
"moveHistory": "",
|
||||
"capturedPieces": {"byWhite": [], "byBlack": []},
|
||||
"timestamp": "2026-04-06T00:00:00Z"
|
||||
}"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
|
||||
assert(result.isRight)
|
||||
assert(result.map(_.board.pieces.isEmpty) == Right(true))
|
||||
}
|
||||
|
||||
test("importGameContext: returns error on invalid JSON") {
|
||||
val result = JsonParser.importGameContext("not valid json {{{")
|
||||
|
||||
assert(result.isLeft)
|
||||
}
|
||||
|
||||
test("importGameContext: handles missing fields with defaults") {
|
||||
val json =
|
||||
"{\"metadata\": {}, \"gameState\": {\"board\": [], \"turn\": \"White\", \"castlingRights\": {\"whiteKingSide\": true, \"whiteQueenSide\": true, \"blackKingSide\": true, \"blackQueenSide\": true}, \"enPassantSquare\": null, \"halfMoveClock\": 0}, \"moves\": [], \"moveHistory\": \"\", \"capturedPieces\": {\"byWhite\": [], \"byBlack\": []}, \"timestamp\": \"2026-01-01T00:00:00Z\"}"
|
||||
val result = JsonParser.importGameContext(json)
|
||||
|
||||
assert(result.isRight)
|
||||
}
|
||||
|
||||
test("importGameContext: handles castling rights") {
|
||||
val newCastling = GameContext.initial.castlingRights.copy(whiteKingSide = false)
|
||||
val context = GameContext.initial.withCastlingRights(newCastling)
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
val result = JsonParser.importGameContext(json)
|
||||
|
||||
assert(result.map(_.castlingRights.whiteKingSide) == Right(false))
|
||||
}
|
||||
|
||||
test("importGameContext: round-trip consistency") {
|
||||
val move1 = Move(Square(File.E, Rank.R2), Square(File.E, Rank.R4))
|
||||
val move2 = Move(Square(File.E, Rank.R7), Square(File.E, Rank.R5))
|
||||
val context = GameContext.initial
|
||||
.withMove(move1)
|
||||
.withMove(move2)
|
||||
.withTurn(Color.White)
|
||||
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
val restored = JsonParser.importGameContext(json)
|
||||
|
||||
assert(restored.map(_.moves.length) == Right(2))
|
||||
assert(restored.map(_.turn) == Right(Color.White))
|
||||
}
|
||||
|
||||
test("importGameContext: handles half-move clock") {
|
||||
val context = GameContext.initial.withHalfMoveClock(5)
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
val result = JsonParser.importGameContext(json)
|
||||
|
||||
assert(result.map(_.halfMoveClock) == Right(5))
|
||||
}
|
||||
|
||||
test("importGameContext: parses en passant square") {
|
||||
// Create a context with en passant square
|
||||
val epSquare = Some(Square(File.E, Rank.R3))
|
||||
val context = GameContext.initial.copy(enPassantSquare = epSquare)
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
val result = JsonParser.importGameContext(json)
|
||||
|
||||
assert(result.map(_.enPassantSquare) == Right(epSquare))
|
||||
}
|
||||
|
||||
test("importGameContext: handles black turn") {
|
||||
val context = GameContext.initial.withTurn(Color.Black)
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
val result = JsonParser.importGameContext(json)
|
||||
|
||||
assert(result.map(_.turn) == Right(Color.Black))
|
||||
}
|
||||
|
||||
test("importGameContext: preserves basic moves in JSON round-trip") {
|
||||
// Use simple move without explicit moveType to let system handle it
|
||||
val move = Move(Square(File.E, Rank.R2), Square(File.E, Rank.R4))
|
||||
val context = GameContext.initial.withMove(move)
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
val result = JsonParser.importGameContext(json)
|
||||
|
||||
assert(result.isRight)
|
||||
assert(result.map(_.moves.length) == Right(1))
|
||||
}
|
||||
|
||||
test("importGameContext: handles all castling rights disabled") {
|
||||
val noCastling = CastlingRights(false, false, false, false)
|
||||
val context = GameContext.initial.withCastlingRights(noCastling)
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
val result = JsonParser.importGameContext(json)
|
||||
|
||||
assert(result.map(_.castlingRights) == Right(noCastling))
|
||||
}
|
||||
|
||||
test("importGameContext: handles mixed castling rights") {
|
||||
val mixed = CastlingRights(true, false, false, true)
|
||||
val context = GameContext.initial.withCastlingRights(mixed)
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
val result = JsonParser.importGameContext(json)
|
||||
|
||||
assert(result.map(_.castlingRights) == Right(mixed))
|
||||
}
|
||||
@@ -0,0 +1,398 @@
|
||||
package de.nowchess.io.json
|
||||
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.api.board.{CastlingRights, Color, File, PieceType, Rank, Square}
|
||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
class JsonParserTest extends AnyFunSuite with Matchers:
|
||||
|
||||
// Basic import tests
|
||||
test("importGameContext: parses valid JSON") {
|
||||
val json = JsonExporter.exportGameContext(GameContext.initial)
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.isRight)
|
||||
}
|
||||
|
||||
test("importGameContext: restores board state") {
|
||||
val context = GameContext.initial
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result == Right(context))
|
||||
}
|
||||
|
||||
test("importGameContext: restores turn") {
|
||||
val context = GameContext.initial.withTurn(Color.Black)
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.map(_.turn) == Right(Color.Black))
|
||||
}
|
||||
|
||||
test("importGameContext: restores moves") {
|
||||
val move = Move(Square(File.E, Rank.R2), Square(File.E, Rank.R4))
|
||||
val context = GameContext.initial.withMove(move)
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.map(_.moves.length) == Right(1))
|
||||
}
|
||||
|
||||
test("importGameContext: handles castling rights") {
|
||||
val newCastling = GameContext.initial.castlingRights.copy(whiteKingSide = false)
|
||||
val context = GameContext.initial.withCastlingRights(newCastling)
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.map(_.castlingRights.whiteKingSide) == Right(false))
|
||||
}
|
||||
|
||||
test("importGameContext: round-trip consistency with multiple moves") {
|
||||
val move1 = Move(Square(File.E, Rank.R2), Square(File.E, Rank.R4))
|
||||
val move2 = Move(Square(File.E, Rank.R7), Square(File.E, Rank.R5))
|
||||
val context = GameContext.initial
|
||||
.withMove(move1)
|
||||
.withMove(move2)
|
||||
.withTurn(Color.White)
|
||||
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
val restored = JsonParser.importGameContext(json)
|
||||
assert(restored.map(_.moves.length) == Right(2))
|
||||
assert(restored.map(_.turn) == Right(Color.White))
|
||||
}
|
||||
|
||||
test("importGameContext: handles half-move clock") {
|
||||
val context = GameContext.initial.withHalfMoveClock(5)
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.map(_.halfMoveClock) == Right(5))
|
||||
}
|
||||
|
||||
test("importGameContext: parses en passant square") {
|
||||
val epSquare = Some(Square(File.E, Rank.R3))
|
||||
val context = GameContext.initial.copy(enPassantSquare = epSquare)
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.map(_.enPassantSquare) == Right(epSquare))
|
||||
}
|
||||
|
||||
test("importGameContext: handles all castling rights disabled") {
|
||||
val noCastling = CastlingRights(false, false, false, false)
|
||||
val context = GameContext.initial.withCastlingRights(noCastling)
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.map(_.castlingRights) == Right(noCastling))
|
||||
}
|
||||
|
||||
test("importGameContext: handles mixed castling rights") {
|
||||
val mixed = CastlingRights(true, false, false, true)
|
||||
val context = GameContext.initial.withCastlingRights(mixed)
|
||||
val json = JsonExporter.exportGameContext(context)
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.map(_.castlingRights) == Right(mixed))
|
||||
}
|
||||
|
||||
// Error handling tests
|
||||
test("parse completely invalid JSON returns error") {
|
||||
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"))
|
||||
}
|
||||
|
||||
test("parse empty string returns error") {
|
||||
val result = JsonParser.importGameContext("")
|
||||
assert(result.isLeft)
|
||||
assert(result.left.toOption.get.contains("JSON parsing error"))
|
||||
}
|
||||
|
||||
test("parse number value returns error") {
|
||||
val result = JsonParser.importGameContext("123")
|
||||
assert(result.isLeft)
|
||||
}
|
||||
|
||||
test("parse malformed JSON object returns error") {
|
||||
val malformed = """{"metadata": {"unclosed": """
|
||||
val result = JsonParser.importGameContext(malformed)
|
||||
assert(result.isLeft)
|
||||
assert(result.left.toOption.get.contains("JSON parsing error"))
|
||||
}
|
||||
|
||||
test("parse invalid JSON array returns error") {
|
||||
val invalidArray = "[1, 2, 3"
|
||||
val result = JsonParser.importGameContext(invalidArray)
|
||||
assert(result.isLeft)
|
||||
}
|
||||
|
||||
test("parse JSON with missing required fields") {
|
||||
val json = """{"metadata": {}}"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.isRight)
|
||||
}
|
||||
|
||||
// Edge cases with defaults
|
||||
test("parse invalid turn color returns error") {
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {"turn": "Invalid", "board": []},
|
||||
"moves": []
|
||||
}"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.isLeft)
|
||||
assert(result.left.toOption.get.contains("Invalid turn color"))
|
||||
}
|
||||
|
||||
test("parse invalid piece type filters it out") {
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {
|
||||
"turn": "White",
|
||||
"board": [
|
||||
{"square": "a1", "color": "White", "piece": "InvalidPiece"}
|
||||
]
|
||||
},
|
||||
"moves": []
|
||||
}"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.isRight)
|
||||
val ctx = result.toOption.get
|
||||
assert(ctx.board.pieces.isEmpty)
|
||||
}
|
||||
|
||||
test("parse invalid color in board filters piece") {
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {
|
||||
"turn": "White",
|
||||
"board": [
|
||||
{"square": "a1", "color": "InvalidColor", "piece": "Pawn"}
|
||||
]
|
||||
},
|
||||
"moves": []
|
||||
}"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.isRight)
|
||||
val ctx = result.toOption.get
|
||||
assert(ctx.board.pieces.isEmpty)
|
||||
}
|
||||
|
||||
test("parse with missing turn uses default") {
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {"board": []},
|
||||
"moves": []
|
||||
}"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.isRight)
|
||||
val ctx = result.toOption.get
|
||||
assert(ctx.turn == Color.White)
|
||||
}
|
||||
|
||||
test("parse with missing board uses empty") {
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {"turn": "White"},
|
||||
"moves": []
|
||||
}"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.isRight)
|
||||
val ctx = result.toOption.get
|
||||
assert(ctx.board.pieces.isEmpty)
|
||||
}
|
||||
|
||||
test("parse with missing moves uses empty list") {
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {"turn": "White", "board": []}
|
||||
}"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.isRight)
|
||||
val ctx = result.toOption.get
|
||||
assert(ctx.moves.isEmpty)
|
||||
}
|
||||
|
||||
test("parse invalid square in board filters it") {
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {
|
||||
"turn": "White",
|
||||
"board": [
|
||||
{"square": "invalid99", "color": "White", "piece": "Pawn"}
|
||||
]
|
||||
},
|
||||
"moves": []
|
||||
}"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.isRight)
|
||||
val ctx = result.toOption.get
|
||||
assert(ctx.board.pieces.isEmpty)
|
||||
}
|
||||
|
||||
test("parse all valid piece types") {
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {
|
||||
"turn": "White",
|
||||
"board": [
|
||||
{"square": "a1", "color": "White", "piece": "Pawn"},
|
||||
{"square": "b1", "color": "White", "piece": "Knight"},
|
||||
{"square": "c1", "color": "White", "piece": "Bishop"},
|
||||
{"square": "d1", "color": "White", "piece": "Rook"},
|
||||
{"square": "e1", "color": "White", "piece": "Queen"},
|
||||
{"square": "f1", "color": "White", "piece": "King"}
|
||||
]
|
||||
},
|
||||
"moves": []
|
||||
}"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.isRight)
|
||||
val ctx = result.toOption.get
|
||||
assert(ctx.board.pieces.size == 6)
|
||||
assert(
|
||||
ctx.board
|
||||
.pieceAt(de.nowchess.api.board.Square(de.nowchess.api.board.File.A, de.nowchess.api.board.Rank.R1))
|
||||
.get
|
||||
.pieceType == PieceType.Pawn,
|
||||
)
|
||||
}
|
||||
|
||||
test("parse with all castling rights false") {
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {
|
||||
"turn": "White",
|
||||
"board": [],
|
||||
"castlingRights": {
|
||||
"whiteKingSide": false,
|
||||
"whiteQueenSide": false,
|
||||
"blackKingSide": false,
|
||||
"blackQueenSide": false
|
||||
}
|
||||
},
|
||||
"moves": []
|
||||
}"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.isRight)
|
||||
val ctx = result.toOption.get
|
||||
assert(ctx.castlingRights.whiteKingSide == false)
|
||||
assert(ctx.castlingRights.blackQueenSide == false)
|
||||
}
|
||||
|
||||
// Move type parsing tests
|
||||
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)
|
||||
assert(result.isRight)
|
||||
}
|
||||
|
||||
test("parse promotion with invalid piece uses default") {
|
||||
val json = """{
|
||||
"metadata": {},
|
||||
"gameState": {"turn": "White", "board": []},
|
||||
"moves": [{"from": "a7", "to": "a8", "type": {"type": "promotion", "promotionPiece": "invalid"}}]
|
||||
}"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.isRight)
|
||||
}
|
||||
|
||||
test("parse move with invalid 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
|
||||
assert(ctx.moves.isEmpty)
|
||||
}
|
||||
|
||||
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
|
||||
assert(ctx.board.pieces.size == 1)
|
||||
}
|
||||
|
||||
test("parse with empty board") {
|
||||
val json = """{
|
||||
"metadata": {"event": "Game", "players": {"white": "A", "black": "B"}, "date": "2026-04-06", "result": "*"},
|
||||
"gameState": {
|
||||
"board": [],
|
||||
"turn": "White",
|
||||
"castlingRights": {"whiteKingSide": true, "whiteQueenSide": true, "blackKingSide": true, "blackQueenSide": true},
|
||||
"enPassantSquare": null,
|
||||
"halfMoveClock": 0
|
||||
},
|
||||
"moves": [],
|
||||
"moveHistory": "",
|
||||
"capturedPieces": {"byWhite": [], "byBlack": []},
|
||||
"timestamp": "2026-04-06T00:00:00Z"
|
||||
}"""
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.isRight)
|
||||
assert(result.map(_.board.pieces.isEmpty) == Right(true))
|
||||
}
|
||||
|
||||
test("importGameContext: returns error on invalid JSON") {
|
||||
val result = JsonParser.importGameContext("not valid json {{{")
|
||||
assert(result.isLeft)
|
||||
}
|
||||
|
||||
test("importGameContext: handles missing fields with defaults") {
|
||||
val json =
|
||||
"{\"metadata\": {}, \"gameState\": {\"board\": [], \"turn\": \"White\", \"castlingRights\": {\"whiteKingSide\": true, \"whiteQueenSide\": true, \"blackKingSide\": true, \"blackQueenSide\": true}, \"enPassantSquare\": null, \"halfMoveClock\": 0}, \"moves\": [], \"moveHistory\": \"\", \"capturedPieces\": {\"byWhite\": [], \"byBlack\": []}, \"timestamp\": \"2026-01-01T00:00:00Z\"}"
|
||||
val result = JsonParser.importGameContext(json)
|
||||
assert(result.isRight)
|
||||
}
|
||||
Reference in New Issue
Block a user