Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e7d556fa06 | |||
| 9a36555f1a | |||
| e5a6cc30eb | |||
| d7f7c37111 | |||
| 2f1e3aded0 | |||
| 1401297e7f |
@@ -9,6 +9,7 @@ Scala 3.5.1 · Gradle 9
|
|||||||
./compile # Compile all modules — always run
|
./compile # Compile all modules — always run
|
||||||
./test # Run all tests
|
./test # Run all tests
|
||||||
./coverage # Check coverage
|
./coverage # Check coverage
|
||||||
|
./lint # Run linters
|
||||||
```
|
```
|
||||||
Use consistently.
|
Use consistently.
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ Scala 3.5.1 · Gradle 9
|
|||||||
./compile # Compile all modules — always run
|
./compile # Compile all modules — always run
|
||||||
./test # Run all tests
|
./test # Run all tests
|
||||||
./coverage # Check coverage
|
./coverage # Check coverage
|
||||||
|
./lint # Run linters
|
||||||
```
|
```
|
||||||
Try to stick to these commands for consistency.
|
Try to stick to these commands for consistency.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -72,7 +72,7 @@ sonar {
|
|||||||
val versions = mapOf(
|
val versions = mapOf(
|
||||||
"QUARKUS_SCALA3" to "1.0.0",
|
"QUARKUS_SCALA3" to "1.0.0",
|
||||||
"SCALA3" to "3.5.1",
|
"SCALA3" to "3.5.1",
|
||||||
"SCALA_LIBRARY" to "2.13.18",
|
"SCALA_LIBRARY" to "2.13.16",
|
||||||
"SCALATEST" to "3.2.19",
|
"SCALATEST" to "3.2.19",
|
||||||
"SCALATEST_JUNIT" to "0.1.11",
|
"SCALATEST_JUNIT" to "0.1.11",
|
||||||
"SCOVERAGE" to "2.1.1",
|
"SCOVERAGE" to "2.1.1",
|
||||||
|
|||||||
@@ -60,3 +60,13 @@
|
|||||||
* NCS-21 Write Scripts to automate certain tasks ([#15](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/15)) ([8051871](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/80518719d536a087d339fe02530825dc07f8b388))
|
* NCS-21 Write Scripts to automate certain tasks ([#15](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/15)) ([8051871](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/80518719d536a087d339fe02530825dc07f8b388))
|
||||||
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
|
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
|
||||||
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
|
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
|
||||||
|
## (2026-04-21)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* NCS-13 Implement Threefold Repetition ([#31](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/31)) ([767d305](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/767d3051a76c266050b6335774d66e2db2273c16))
|
||||||
|
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
|
||||||
|
* NCS-21 Write Scripts to automate certain tasks ([#15](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/15)) ([8051871](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/80518719d536a087d339fe02530825dc07f8b388))
|
||||||
|
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
|
||||||
|
* NCS-37 Quarkus integration ([#35](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/35)) ([5ad5efb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5ad5efb41e9df9e3dccb48f96a69f06217ab98e1))
|
||||||
|
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
MAJOR=0
|
MAJOR=0
|
||||||
MINOR=8
|
MINOR=9
|
||||||
PATCH=0
|
PATCH=0
|
||||||
|
|||||||
@@ -49,10 +49,10 @@ dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
implementation(project(":modules:api"))
|
implementation(project(":modules:api"))
|
||||||
|
implementation(project(":modules:io"))
|
||||||
implementation(project(":modules:rule"))
|
implementation(project(":modules:rule"))
|
||||||
implementation("com.microsoft.onnxruntime:onnxruntime:${versions["ONNXRUNTIME"]!!}")
|
implementation("com.microsoft.onnxruntime:onnxruntime:${versions["ONNXRUNTIME"]!!}")
|
||||||
|
|
||||||
testImplementation(project(":modules:io"))
|
|
||||||
testImplementation(platform("org.junit:junit-bom:${versions["JUNIT_BOM"]!!}"))
|
testImplementation(platform("org.junit:junit-bom:${versions["JUNIT_BOM"]!!}"))
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||||
testImplementation("org.scalatest:scalatest_3:${versions["SCALATEST"]!!}")
|
testImplementation("org.scalatest:scalatest_3:${versions["SCALATEST"]!!}")
|
||||||
|
|||||||
@@ -341,3 +341,32 @@
|
|||||||
* correct test board positions and captureOutput/withInput interaction ([f0481e2](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/f0481e2561b779df00925b46ee281dc36a795150))
|
* correct test board positions and captureOutput/withInput interaction ([f0481e2](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/f0481e2561b779df00925b46ee281dc36a795150))
|
||||||
* update main class path in build configuration and adjust VCS directory mapping ([7b1f8b1](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/7b1f8b117623d327232a1a92a8a44d18582e0189))
|
* update main class path in build configuration and adjust VCS directory mapping ([7b1f8b1](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/7b1f8b117623d327232a1a92a8a44d18582e0189))
|
||||||
* update move validation to check for king safety ([#13](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/13)) ([e5e20c5](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/e5e20c566e368b12ca1dc59680c34e9112bf6762))
|
* update move validation to check for king safety ([#13](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/13)) ([e5e20c5](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/e5e20c566e368b12ca1dc59680c34e9112bf6762))
|
||||||
|
## (2026-04-21)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add GameRules stub with PositionStatus enum ([76d4168](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/76d4168038de23e5d6083d4e8f0504fbf31d15a3))
|
||||||
|
* add MovedInCheck/Checkmate/Stalemate MoveResult variants (stub dispatch) ([8b7ec57](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/8b7ec57e5ea6ee1615a1883848a426dc07d26364))
|
||||||
|
* implement GameRules with isInCheck, legalMoves, gameStatus ([94a02ff](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/94a02ff6849436d9496c70a0f16c21666dae8e4e))
|
||||||
|
* implement legal castling ([#1](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/1)) ([00d326c](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/00d326c1ba67711fbe180f04e1100c3f01dd0254))
|
||||||
|
* NCS-10 Implement Pawn Promotion ([#12](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/12)) ([13bfc16](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/13bfc16cfe25db78ec607db523ca6d993c13430c))
|
||||||
|
* NCS-11 50-move rule ([#9](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/9)) ([412ed98](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/412ed986a95703a3b282276540153480ceed229d))
|
||||||
|
* NCS-13 Implement Threefold Repetition ([#31](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/31)) ([767d305](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/767d3051a76c266050b6335774d66e2db2273c16))
|
||||||
|
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
|
||||||
|
* NCS-16 Core Separation via Patterns ([#10](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/10)) ([1361dfc](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/1361dfc89553b146864fb8ff3526cf12cf3f293a))
|
||||||
|
* NCS-17 Implement basic ScalaFX UI ([#14](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/14)) ([3ff8031](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/3ff80318b4f16c59733a46498581a5c27f048287))
|
||||||
|
* NCS-21 Write Scripts to automate certain tasks ([#15](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/15)) ([8051871](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/80518719d536a087d339fe02530825dc07f8b388))
|
||||||
|
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
|
||||||
|
* NCS-37 Quarkus integration ([#35](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/35)) ([5ad5efb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5ad5efb41e9df9e3dccb48f96a69f06217ab98e1))
|
||||||
|
* NCS-40 Rework Draw System ([#34](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/34)) ([0091d50](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/0091d50467e9f955f23570128b96c977c01bc51b))
|
||||||
|
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
|
||||||
|
* NCS-6 Implementing FEN & PGN ([#7](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/7)) ([f28e69d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/f28e69dc181416aa2f221fdc4b45c2cda5efbf07))
|
||||||
|
* NCS-9 En passant implementation ([#8](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/8)) ([919beb3](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/919beb3b4bfa8caf2f90976a415fe9b19b7e9747))
|
||||||
|
* wire check/checkmate/stalemate into processMove and gameLoop ([5264a22](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5264a225418b885c5e6ea6411b96f85e38837f6c))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add missing kings to gameLoop capture test board ([aedd787](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/aedd787b77203c2af934751dba7b784eaf165032))
|
||||||
|
* correct test board positions and captureOutput/withInput interaction ([f0481e2](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/f0481e2561b779df00925b46ee281dc36a795150))
|
||||||
|
* update main class path in build configuration and adjust VCS directory mapping ([7b1f8b1](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/7b1f8b117623d327232a1a92a8a44d18582e0189))
|
||||||
|
* update move validation to check for king safety ([#13](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/13)) ([e5e20c5](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/e5e20c566e368b12ca1dc59680c34e9112bf6762))
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ dependencies {
|
|||||||
testImplementation("org.scalatest:scalatest_3:${versions["SCALATEST"]!!}")
|
testImplementation("org.scalatest:scalatest_3:${versions["SCALATEST"]!!}")
|
||||||
testImplementation("co.helmethair:scalatest-junit-runner:${versions["SCALATEST_JUNIT"]!!}")
|
testImplementation("co.helmethair:scalatest-junit-runner:${versions["SCALATEST_JUNIT"]!!}")
|
||||||
testImplementation("io.quarkus:quarkus-junit5")
|
testImplementation("io.quarkus:quarkus-junit5")
|
||||||
|
testImplementation("io.quarkus:quarkus-junit5-mockito")
|
||||||
testImplementation("io.rest-assured:rest-assured")
|
testImplementation("io.rest-assured:rest-assured")
|
||||||
|
|
||||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||||
|
|||||||
+22
-1
@@ -1,11 +1,19 @@
|
|||||||
package de.nowchess.chess.resource
|
package de.nowchess.chess.resource
|
||||||
|
|
||||||
import de.nowchess.api.dto.*
|
import de.nowchess.api.dto.*
|
||||||
|
import de.nowchess.api.game.GameContext
|
||||||
|
import de.nowchess.chess.client.IoServiceClient
|
||||||
import de.nowchess.chess.exception.BadRequestException
|
import de.nowchess.chess.exception.BadRequestException
|
||||||
|
import de.nowchess.io.fen.FenExporter
|
||||||
|
import de.nowchess.io.pgn.PgnParser
|
||||||
|
import io.quarkus.test.InjectMock
|
||||||
import io.quarkus.test.junit.QuarkusTest
|
import io.quarkus.test.junit.QuarkusTest
|
||||||
import jakarta.inject.Inject
|
import jakarta.inject.Inject
|
||||||
import org.junit.jupiter.api.{DisplayName, Test}
|
import org.eclipse.microprofile.rest.client.inject.RestClient
|
||||||
|
import org.junit.jupiter.api.{BeforeEach, DisplayName, Test}
|
||||||
import org.junit.jupiter.api.Assertions.*
|
import org.junit.jupiter.api.Assertions.*
|
||||||
|
import org.mockito.ArgumentMatchers.any
|
||||||
|
import org.mockito.Mockito.when
|
||||||
|
|
||||||
import scala.compiletime.uninitialized
|
import scala.compiletime.uninitialized
|
||||||
|
|
||||||
@@ -17,6 +25,19 @@ class GameResourceIntegrationTest:
|
|||||||
@Inject
|
@Inject
|
||||||
var resource: GameResource = uninitialized
|
var resource: GameResource = uninitialized
|
||||||
|
|
||||||
|
@InjectMock
|
||||||
|
@RestClient
|
||||||
|
var ioClient: IoServiceClient = uninitialized
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
def setupMocks(): Unit =
|
||||||
|
when(ioClient.importFen(any())).thenReturn(GameContext.initial)
|
||||||
|
when(ioClient.importPgn(any())).thenReturn(
|
||||||
|
PgnParser.importGameContext("1. e4 c5").toOption.get,
|
||||||
|
)
|
||||||
|
when(ioClient.exportFen(any())).thenReturn(FenExporter.exportGameContext(GameContext.initial))
|
||||||
|
when(ioClient.exportPgn(any())).thenReturn("1. e4 c5")
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("createGame returns 201")
|
@DisplayName("createGame returns 201")
|
||||||
def testCreateGame(): Unit =
|
def testCreateGame(): Unit =
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
MAJOR=0
|
MAJOR=0
|
||||||
MINOR=16
|
MINOR=17
|
||||||
PATCH=0
|
PATCH=0
|
||||||
|
|||||||
@@ -76,3 +76,14 @@
|
|||||||
* NCS-30 FEN Parser using ParserCombinators ([#21](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/21)) ([b4bc72f](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b4bc72f7e49f94d6e1bc805c68680e5fe8ef8e36))
|
* NCS-30 FEN Parser using ParserCombinators ([#21](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/21)) ([b4bc72f](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b4bc72f7e49f94d6e1bc805c68680e5fe8ef8e36))
|
||||||
* NCS-31 FastParse FEN ([#22](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/22)) ([7a045d3](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/7a045d31d757bbc5aa6f4bad2664ebe8b8519cac))
|
* NCS-31 FastParse FEN ([#22](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/22)) ([7a045d3](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/7a045d31d757bbc5aa6f4bad2664ebe8b8519cac))
|
||||||
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
|
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
|
||||||
|
## (2026-04-21)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
|
||||||
|
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
|
||||||
|
* NCS-29 JSON - Cherry Picked ([#28](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/28)) ([dbcafd2](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dbcafd286993e0604a6fa286c5543581a149439e))
|
||||||
|
* NCS-30 FEN Parser using ParserCombinators ([#21](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/21)) ([b4bc72f](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b4bc72f7e49f94d6e1bc805c68680e5fe8ef8e36))
|
||||||
|
* NCS-31 FastParse FEN ([#22](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/22)) ([7a045d3](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/7a045d31d757bbc5aa6f4bad2664ebe8b8519cac))
|
||||||
|
* NCS-37 Quarkus integration ([#35](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/35)) ([5ad5efb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5ad5efb41e9df9e3dccb48f96a69f06217ab98e1))
|
||||||
|
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ dependencies {
|
|||||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
|
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
configurations.matching { !it.name.startsWith("scoverage") }.configureEach {
|
configurations.matching { !it.name.startsWith("scoverage") }.configureEach {
|
||||||
resolutionStrategy.force("org.scala-lang:scala-library:${versions["SCALA_LIBRARY"]!!}")
|
resolutionStrategy.force("org.scala-lang:scala-library:${versions["SCALA_LIBRARY"]!!}")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.funsuite.AnyFunSuite
|
||||||
import org.scalatest.matchers.should.Matchers
|
import org.scalatest.matchers.should.Matchers
|
||||||
|
|
||||||
class JsonExporterSuite extends AnyFunSuite with Matchers:
|
class JsonExporterTest extends AnyFunSuite with Matchers:
|
||||||
|
|
||||||
test("exportGameContext: exports initial position") {
|
test("exportGameContext: exports initial position") {
|
||||||
val context = GameContext.initial
|
val context = GameContext.initial
|
||||||
@@ -87,14 +87,6 @@ class JsonExporterSuite extends AnyFunSuite with Matchers:
|
|||||||
json should include("\"enPassantSquare\": null")
|
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") {
|
test("exportGameContext: exports empty board") {
|
||||||
val emptyBoard = Board(Map.empty)
|
val emptyBoard = Board(Map.empty)
|
||||||
val context = GameContext.initial.copy(board = emptyBoard)
|
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("\"blackKingSide\": false")
|
||||||
json should include("\"blackQueenSide\": 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)
|
||||||
|
}
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
MAJOR=0
|
MAJOR=0
|
||||||
MINOR=10
|
MINOR=11
|
||||||
PATCH=0
|
PATCH=0
|
||||||
|
|||||||
@@ -79,3 +79,16 @@
|
|||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* NCS-32 Queenside Castle doesn't care about pieces in the way ([#23](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/23)) ([fe8e3c0](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fe8e3c05397f433bfa34d1999e9738c82790adf7))
|
* NCS-32 Queenside Castle doesn't care about pieces in the way ([#23](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/23)) ([fe8e3c0](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fe8e3c05397f433bfa34d1999e9738c82790adf7))
|
||||||
|
## (2026-04-21)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* NCS-13 Implement Threefold Repetition ([#31](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/31)) ([767d305](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/767d3051a76c266050b6335774d66e2db2273c16))
|
||||||
|
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
|
||||||
|
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
|
||||||
|
* NCS-37 Quarkus integration ([#35](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/35)) ([5ad5efb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5ad5efb41e9df9e3dccb48f96a69f06217ab98e1))
|
||||||
|
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* NCS-32 Queenside Castle doesn't care about pieces in the way ([#23](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/23)) ([fe8e3c0](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fe8e3c05397f433bfa34d1999e9738c82790adf7))
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
MAJOR=0
|
MAJOR=0
|
||||||
MINOR=5
|
MINOR=6
|
||||||
PATCH=0
|
PATCH=0
|
||||||
|
|||||||
Reference in New Issue
Block a user