feat: NCS-25 Add linters to keep quality up (#27)
Build & Test (NowChessSystems) TeamCity build finished

Reviewed-on: #27
Reviewed-by: Leon Hermann <lq@blackhole.local>
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
This commit was merged in pull request #27.
This commit is contained in:
2026-04-12 20:58:39 +02:00
committed by Janis
parent 3cb3160731
commit fd4e67d4f7
79 changed files with 1671 additions and 1457 deletions
@@ -7,11 +7,11 @@ object Board:
def apply(pieces: Map[Square, Piece]): Board = pieces
extension (b: Board)
def pieceAt(sq: Square): Option[Piece] = b.get(sq)
def pieceAt(sq: Square): Option[Piece] = b.get(sq)
def updated(sq: Square, piece: Piece): Board = b.updated(sq, piece)
def removed(sq: Square): Board = b.removed(sq)
def removed(sq: Square): Board = b.removed(sq)
def withMove(from: Square, to: Square): (Board, Option[Piece]) =
val captured = b.get(to)
val captured = b.get(to)
val updatedBoard = b.removed(from).updated(to, b(from))
(updatedBoard, captured)
def applyMove(move: de.nowchess.api.move.Move): Board =
@@ -21,8 +21,14 @@ object Board:
val initial: Board =
val backRank: Vector[PieceType] = Vector(
PieceType.Rook, PieceType.Knight, PieceType.Bishop, PieceType.Queen,
PieceType.King, PieceType.Bishop, PieceType.Knight, PieceType.Rook
PieceType.Rook,
PieceType.Knight,
PieceType.Bishop,
PieceType.Queen,
PieceType.King,
PieceType.Bishop,
PieceType.Knight,
PieceType.Rook,
)
val entries = for
fileIdx <- 0 until 8
@@ -30,7 +36,7 @@ object Board:
(Color.White, Rank.R1, backRank(fileIdx)),
(Color.White, Rank.R2, PieceType.Pawn),
(Color.Black, Rank.R8, backRank(fileIdx)),
(Color.Black, Rank.R7, PieceType.Pawn)
(Color.Black, Rank.R7, PieceType.Pawn),
)
yield Square(File.values(fileIdx), rank) -> Piece(color, pieceType)
Board(entries.toMap)
@@ -1,50 +1,48 @@
package de.nowchess.api.board
/**
* Unified castling rights tracker for all four sides.
* Tracks whether castling is still available for each side and direction.
*
* @param whiteKingSide White's king-side castling (0-0) still legally available
* @param whiteQueenSide White's queen-side castling (0-0-0) still legally available
* @param blackKingSide Black's king-side castling (0-0) still legally available
* @param blackQueenSide Black's queen-side castling (0-0-0) still legally available
*/
/** Unified castling rights tracker for all four sides. Tracks whether castling is still available for each side and
* direction.
*
* @param whiteKingSide
* White's king-side castling (0-0) still legally available
* @param whiteQueenSide
* White's queen-side castling (0-0-0) still legally available
* @param blackKingSide
* Black's king-side castling (0-0) still legally available
* @param blackQueenSide
* Black's queen-side castling (0-0-0) still legally available
*/
final case class CastlingRights(
whiteKingSide: Boolean,
whiteQueenSide: Boolean,
blackKingSide: Boolean,
blackQueenSide: Boolean
whiteKingSide: Boolean,
whiteQueenSide: Boolean,
blackKingSide: Boolean,
blackQueenSide: Boolean,
):
/**
* Check if either side has any castling rights remaining.
*/
/** Check if either side has any castling rights remaining.
*/
def hasAnyRights: Boolean =
whiteKingSide || whiteQueenSide || blackKingSide || blackQueenSide
/**
* Check if a specific color has any castling rights remaining.
*/
/** Check if a specific color has any castling rights remaining.
*/
def hasRights(color: Color): Boolean = color match
case Color.White => whiteKingSide || whiteQueenSide
case Color.Black => blackKingSide || blackQueenSide
/**
* Revoke all castling rights for a specific color.
*/
/** Revoke all castling rights for a specific color.
*/
def revokeColor(color: Color): CastlingRights = color match
case Color.White => copy(whiteKingSide = false, whiteQueenSide = false)
case Color.Black => copy(blackKingSide = false, blackQueenSide = false)
/**
* Revoke a specific castling right.
*/
/** Revoke a specific castling right.
*/
def revokeKingSide(color: Color): CastlingRights = color match
case Color.White => copy(whiteKingSide = false)
case Color.Black => copy(blackKingSide = false)
/**
* Revoke a specific castling right.
*/
/** Revoke a specific castling right.
*/
def revokeQueenSide(color: Color): CastlingRights = color match
case Color.White => copy(whiteQueenSide = false)
case Color.Black => copy(blackQueenSide = false)
@@ -55,7 +53,7 @@ object CastlingRights:
whiteKingSide = false,
whiteQueenSide = false,
blackKingSide = false,
blackQueenSide = false
blackQueenSide = false,
)
/** All castling rights available. */
@@ -63,7 +61,7 @@ object CastlingRights:
whiteKingSide = true,
whiteQueenSide = true,
blackKingSide = true,
blackQueenSide = true
blackQueenSide = true,
)
/** Standard starting position castling rights (both sides can castle both ways). */
@@ -5,16 +5,16 @@ final case class Piece(color: Color, pieceType: PieceType)
object Piece:
// Convenience constructors
val WhitePawn: Piece = Piece(Color.White, PieceType.Pawn)
val WhitePawn: Piece = Piece(Color.White, PieceType.Pawn)
val WhiteKnight: Piece = Piece(Color.White, PieceType.Knight)
val WhiteBishop: Piece = Piece(Color.White, PieceType.Bishop)
val WhiteRook: Piece = Piece(Color.White, PieceType.Rook)
val WhiteQueen: Piece = Piece(Color.White, PieceType.Queen)
val WhiteKing: Piece = Piece(Color.White, PieceType.King)
val WhiteRook: Piece = Piece(Color.White, PieceType.Rook)
val WhiteQueen: Piece = Piece(Color.White, PieceType.Queen)
val WhiteKing: Piece = Piece(Color.White, PieceType.King)
val BlackPawn: Piece = Piece(Color.Black, PieceType.Pawn)
val BlackPawn: Piece = Piece(Color.Black, PieceType.Pawn)
val BlackKnight: Piece = Piece(Color.Black, PieceType.Knight)
val BlackBishop: Piece = Piece(Color.Black, PieceType.Bishop)
val BlackRook: Piece = Piece(Color.Black, PieceType.Rook)
val BlackQueen: Piece = Piece(Color.Black, PieceType.Queen)
val BlackKing: Piece = Piece(Color.Black, PieceType.King)
val BlackRook: Piece = Piece(Color.Black, PieceType.Rook)
val BlackQueen: Piece = Piece(Color.Black, PieceType.Queen)
val BlackKing: Piece = Piece(Color.Black, PieceType.King)
@@ -1,43 +1,38 @@
package de.nowchess.api.board
/**
* A file (column) on the chess board, ah.
* Ordinal values 07 correspond to ah.
*/
/** A file (column) on the chess board, ah. Ordinal values 07 correspond to ah.
*/
enum File:
case A, B, C, D, E, F, G, H
/**
* A rank (row) on the chess board, 18.
* Ordinal values 07 correspond to ranks 18.
*/
/** A rank (row) on the chess board, 18. Ordinal values 07 correspond to ranks 18.
*/
enum Rank:
case R1, R2, R3, R4, R5, R6, R7, R8
/**
* A unique square on the board, identified by its file and rank.
*
* @param file the column, ah
* @param rank the row, 18
*/
/** A unique square on the board, identified by its file and rank.
*
* @param file
* the column, ah
* @param rank
* the row, 18
*/
final case class Square(file: File, rank: Rank):
/** Algebraic notation string, e.g. "e4". */
override def toString: String =
s"${file.toString.toLowerCase}${rank.ordinal + 1}"
object Square:
/** Parse a square from algebraic notation (e.g. "e4").
* Returns None if the input is not a valid square name. */
/** Parse a square from algebraic notation (e.g. "e4"). Returns None if the input is not a valid square name.
*/
def fromAlgebraic(s: String): Option[Square] =
if s.length != 2 then None
else
val fileChar = s.charAt(0)
val rankChar = s.charAt(1)
val fileOpt = File.values.find(_.toString.equalsIgnoreCase(fileChar.toString))
val fileOpt = File.values.find(_.toString.equalsIgnoreCase(fileChar.toString))
val rankOpt =
rankChar.toString.toIntOption.flatMap(n =>
if n >= 1 && n <= 8 then Some(Rank.values(n - 1)) else None
)
rankChar.toString.toIntOption.flatMap(n => if n >= 1 && n <= 8 then Some(Rank.values(n - 1)) else None)
for f <- fileOpt; r <- rankOpt yield Square(f, r)
val all: IndexedSeq[Square] =
@@ -46,12 +41,13 @@ object Square:
f <- File.values.toIndexedSeq
yield Square(f, r)
/** Compute a target square by offsetting file and rank.
* Returns None if the resulting square is outside the board (0-7 range). */
/** Compute a target square by offsetting file and rank. Returns None if the resulting square is outside the board
* (0-7 range).
*/
extension (sq: Square)
def offset(fileDelta: Int, rankDelta: Int): Option[Square] =
val newFileOrd = sq.file.ordinal + fileDelta
val newRankOrd = sq.rank.ordinal + rankDelta
if newFileOrd >= 0 && newFileOrd < 8 && newRankOrd >= 0 && newRankOrd < 8 then
Some(Square(File.values(newFileOrd), Rank.values(newRankOrd)))
else None
else None
@@ -1,18 +1,17 @@
package de.nowchess.api.game
import de.nowchess.api.board.{Board, Color, Square, CastlingRights}
import de.nowchess.api.board.{Board, CastlingRights, Color, Square}
import de.nowchess.api.move.Move
/** Immutable bundle of complete game state.
* All state changes produce new GameContext instances.
*/
/** Immutable bundle of complete game state. All state changes produce new GameContext instances.
*/
case class GameContext(
board: Board,
turn: Color,
castlingRights: CastlingRights,
enPassantSquare: Option[Square],
halfMoveClock: Int,
moves: List[Move]
board: Board,
turn: Color,
castlingRights: CastlingRights,
enPassantSquare: Option[Square],
halfMoveClock: Int,
moves: List[Move],
):
/** Create new context with updated board. */
def withBoard(newBoard: Board): GameContext = copy(board = newBoard)
@@ -40,5 +39,5 @@ object GameContext:
castlingRights = CastlingRights.Initial,
enPassantSquare = None,
halfMoveClock = 0,
moves = List.empty
moves = List.empty,
)
@@ -10,24 +10,30 @@ enum PromotionPiece:
enum MoveType:
/** A normal move or capture with no special rule. */
case Normal(isCapture: Boolean = false)
/** Kingside castling (O-O). */
case CastleKingside
/** Queenside castling (O-O-O). */
case CastleQueenside
/** En-passant pawn capture. */
case EnPassant
/** Pawn promotion; carries the chosen promotion piece. */
case Promotion(piece: PromotionPiece)
/**
* A half-move (ply) in a chess game.
*
* @param from origin square
* @param to destination square
* @param moveType special semantics; defaults to Normal
*/
/** A half-move (ply) in a chess game.
*
* @param from
* origin square
* @param to
* destination square
* @param moveType
* special semantics; defaults to Normal
*/
final case class Move(
from: Square,
to: Square,
moveType: MoveType = MoveType.Normal()
from: Square,
to: Square,
moveType: MoveType = MoveType.Normal(),
)
@@ -1,27 +1,26 @@
package de.nowchess.api.player
/**
* An opaque player identifier.
*
* Wraps a plain String so that IDs are not accidentally interchanged with
* other String values at compile time.
*/
/** An opaque player identifier.
*
* Wraps a plain String so that IDs are not accidentally interchanged with other String values at compile time.
*/
opaque type PlayerId = String
object PlayerId:
def apply(value: String): PlayerId = value
def apply(value: String): PlayerId = value
extension (id: PlayerId) def value: String = id
/**
* The minimal cross-service identity stub for a player.
*
* Full profile data (email, rating history, etc.) lives in the user-management
* service. Only what every service needs is held here.
*
* @param id unique identifier
* @param displayName human-readable name shown in the UI
*/
/** The minimal cross-service identity stub for a player.
*
* Full profile data (email, rating history, etc.) lives in the user-management service. Only what every service needs
* is held here.
*
* @param id
* unique identifier
* @param displayName
* human-readable name shown in the UI
*/
final case class PlayerInfo(
id: PlayerId,
displayName: String
id: PlayerId,
displayName: String,
)
@@ -1,13 +1,12 @@
package de.nowchess.api.response
/**
* A standardised envelope for every API response.
*
* Success and failure are modelled as subtypes so that callers
* can pattern-match exhaustively.
*
* @tparam A the payload type for a successful response
*/
/** A standardised envelope for every API response.
*
* Success and failure are modelled as subtypes so that callers can pattern-match exhaustively.
*
* @tparam A
* the payload type for a successful response
*/
sealed trait ApiResponse[+A]
object ApiResponse:
@@ -20,43 +19,49 @@ object ApiResponse:
/** Convenience constructor for a single-error failure. */
def error(err: ApiError): Failure = Failure(List(err))
/**
* A structured error descriptor.
*
* @param code machine-readable error code (e.g. "INVALID_MOVE", "NOT_FOUND")
* @param message human-readable explanation
* @param field optional field name when the error relates to a specific input
*/
/** A structured error descriptor.
*
* @param code
* machine-readable error code (e.g. "INVALID_MOVE", "NOT_FOUND")
* @param message
* human-readable explanation
* @param field
* optional field name when the error relates to a specific input
*/
final case class ApiError(
code: String,
message: String,
field: Option[String] = None
code: String,
message: String,
field: Option[String] = None,
)
/**
* Pagination metadata for list responses.
*
* @param page current 0-based page index
* @param pageSize number of items per page
* @param totalItems total number of items across all pages
*/
/** Pagination metadata for list responses.
*
* @param page
* current 0-based page index
* @param pageSize
* number of items per page
* @param totalItems
* total number of items across all pages
*/
final case class Pagination(
page: Int,
pageSize: Int,
totalItems: Long
page: Int,
pageSize: Int,
totalItems: Long,
):
def totalPages: Int =
if pageSize <= 0 then 0
else Math.ceil(totalItems.toDouble / pageSize).toInt
/**
* A paginated list response envelope.
*
* @param items the items on the current page
* @param pagination pagination metadata
* @tparam A the item type
*/
/** A paginated list response envelope.
*
* @param items
* the items on the current page
* @param pagination
* pagination metadata
* @tparam A
* the item type
*/
final case class PagedResponse[A](
items: List[A],
pagination: Pagination
items: List[A],
pagination: Pagination,
)
@@ -22,9 +22,9 @@ class BoardTest extends AnyFunSuite with Matchers:
}
test("withMove returns captured piece when destination is occupied") {
val from = Square(File.A, Rank.R1)
val to = Square(File.A, Rank.R8)
val b = Board(Map(from -> Piece.WhiteRook, to -> Piece.BlackRook))
val from = Square(File.A, Rank.R1)
val to = Square(File.A, Rank.R8)
val b = Board(Map(from -> Piece.WhiteRook, to -> Piece.BlackRook))
val (board, captured) = b.withMove(from, to)
captured shouldBe Some(Piece.BlackRook)
board.pieceAt(to) shouldBe Some(Piece.WhiteRook)
@@ -51,8 +51,14 @@ class BoardTest extends AnyFunSuite with Matchers:
test("initial board white back rank") {
val expectedBackRank = Vector(
PieceType.Rook, PieceType.Knight, PieceType.Bishop, PieceType.Queen,
PieceType.King, PieceType.Bishop, PieceType.Knight, PieceType.Rook
PieceType.Rook,
PieceType.Knight,
PieceType.Bishop,
PieceType.Queen,
PieceType.King,
PieceType.Bishop,
PieceType.Knight,
PieceType.Rook,
)
File.values.zipWithIndex.foreach { (file, i) =>
Board.initial.pieceAt(Square(file, Rank.R1)) shouldBe
@@ -62,8 +68,14 @@ class BoardTest extends AnyFunSuite with Matchers:
test("initial board black back rank") {
val expectedBackRank = Vector(
PieceType.Rook, PieceType.Knight, PieceType.Bishop, PieceType.Queen,
PieceType.King, PieceType.Bishop, PieceType.Knight, PieceType.Rook
PieceType.Rook,
PieceType.Knight,
PieceType.Bishop,
PieceType.Queen,
PieceType.King,
PieceType.Bishop,
PieceType.Knight,
PieceType.Rook,
)
File.values.zipWithIndex.foreach { (file, i) =>
Board.initial.pieceAt(Square(file, Rank.R8)) shouldBe
@@ -76,12 +88,11 @@ class BoardTest extends AnyFunSuite with Matchers:
for
rank <- emptyRanks
file <- File.values
do
Board.initial.pieceAt(Square(file, rank)) shouldBe None
do Board.initial.pieceAt(Square(file, rank)) shouldBe None
}
test("updated adds and replaces piece at squares") {
val b = Board(Map(e2 -> Piece.WhitePawn))
val b = Board(Map(e2 -> Piece.WhitePawn))
val added = b.updated(e4, Piece.WhiteKnight)
added.pieceAt(e2) shouldBe Some(Piece.WhitePawn)
added.pieceAt(e4) shouldBe Some(Piece.WhiteKnight)
@@ -91,7 +102,7 @@ class BoardTest extends AnyFunSuite with Matchers:
}
test("removed deletes piece from board") {
val b = Board(Map(e2 -> Piece.WhitePawn, e4 -> Piece.WhiteKnight))
val b = Board(Map(e2 -> Piece.WhitePawn, e4 -> Piece.WhiteKnight))
val removed = b.removed(e2)
removed.pieceAt(e2) shouldBe None
removed.pieceAt(e4) shouldBe Some(Piece.WhiteKnight)
@@ -105,4 +116,3 @@ class BoardTest extends AnyFunSuite with Matchers:
moved.pieceAt(e4) shouldBe Some(Piece.WhitePawn)
moved.pieceAt(e2) shouldBe None
}
@@ -10,7 +10,7 @@ class CastlingRightsTest extends AnyFunSuite with Matchers:
whiteKingSide = true,
whiteQueenSide = false,
blackKingSide = false,
blackQueenSide = true
blackQueenSide = true,
)
rights.hasAnyRights shouldBe true
@@ -54,4 +54,3 @@ class CastlingRightsTest extends AnyFunSuite with Matchers:
val blackQueenSideRevoked = all.revokeQueenSide(Color.Black)
blackQueenSideRevoked.blackKingSide shouldBe true
blackQueenSideRevoked.blackQueenSide shouldBe false
@@ -8,7 +8,7 @@ class ColorTest extends AnyFunSuite with Matchers:
test("Color values expose opposite and label consistently"):
val cases = List(
(Color.White, Color.Black, "White"),
(Color.Black, Color.White, "Black")
(Color.Black, Color.White, "Black"),
)
cases.foreach { (color, opposite, label) =>
@@ -7,24 +7,24 @@ class PieceTest extends AnyFunSuite with Matchers:
test("Piece holds color and pieceType") {
val p = Piece(Color.White, PieceType.Queen)
p.color shouldBe Color.White
p.color shouldBe Color.White
p.pieceType shouldBe PieceType.Queen
}
test("all convenience constants map to expected color and piece type") {
val expected = List(
Piece.WhitePawn -> Piece(Color.White, PieceType.Pawn),
Piece.WhitePawn -> Piece(Color.White, PieceType.Pawn),
Piece.WhiteKnight -> Piece(Color.White, PieceType.Knight),
Piece.WhiteBishop -> Piece(Color.White, PieceType.Bishop),
Piece.WhiteRook -> Piece(Color.White, PieceType.Rook),
Piece.WhiteQueen -> Piece(Color.White, PieceType.Queen),
Piece.WhiteKing -> Piece(Color.White, PieceType.King),
Piece.BlackPawn -> Piece(Color.Black, PieceType.Pawn),
Piece.WhiteRook -> Piece(Color.White, PieceType.Rook),
Piece.WhiteQueen -> Piece(Color.White, PieceType.Queen),
Piece.WhiteKing -> Piece(Color.White, PieceType.King),
Piece.BlackPawn -> Piece(Color.Black, PieceType.Pawn),
Piece.BlackKnight -> Piece(Color.Black, PieceType.Knight),
Piece.BlackBishop -> Piece(Color.Black, PieceType.Bishop),
Piece.BlackRook -> Piece(Color.Black, PieceType.Rook),
Piece.BlackQueen -> Piece(Color.Black, PieceType.Queen),
Piece.BlackKing -> Piece(Color.Black, PieceType.King)
Piece.BlackRook -> Piece(Color.Black, PieceType.Rook),
Piece.BlackQueen -> Piece(Color.Black, PieceType.Queen),
Piece.BlackKing -> Piece(Color.Black, PieceType.King),
)
expected.foreach { case (actual, wanted) =>
@@ -7,12 +7,12 @@ class PieceTypeTest extends AnyFunSuite with Matchers:
test("PieceType values expose the expected labels"):
val expectedLabels = List(
PieceType.Pawn -> "Pawn",
PieceType.Pawn -> "Pawn",
PieceType.Knight -> "Knight",
PieceType.Bishop -> "Bishop",
PieceType.Rook -> "Rook",
PieceType.Queen -> "Queen",
PieceType.King -> "King"
PieceType.Rook -> "Rook",
PieceType.Queen -> "Queen",
PieceType.King -> "King",
)
expectedLabels.foreach { (pieceType, expectedLabel) =>
@@ -16,7 +16,7 @@ class SquareTest extends AnyFunSuite with Matchers:
"a1" -> Square(File.A, Rank.R1),
"e4" -> Square(File.E, Rank.R4),
"h8" -> Square(File.H, Rank.R8),
"E4" -> Square(File.E, Rank.R4)
"E4" -> Square(File.E, Rank.R4),
)
expected.foreach { case (raw, sq) =>
Square.fromAlgebraic(raw) shouldBe Some(sq)
@@ -34,4 +34,3 @@ class SquareTest extends AnyFunSuite with Matchers:
Square(File.A, Rank.R1).offset(-1, 0) shouldBe None
Square(File.H, Rank.R8).offset(0, 1) shouldBe None
}
@@ -18,9 +18,9 @@ class GameContextTest extends AnyFunSuite with Matchers:
initial.moves shouldBe List.empty
test("withBoard updates only board"):
val square = Square(File.E, Rank.R4)
val square = Square(File.E, Rank.R4)
val updatedBoard = Board.initial.updated(square, de.nowchess.api.board.Piece.WhiteQueen)
val updated = GameContext.initial.withBoard(updatedBoard)
val updated = GameContext.initial.withBoard(updatedBoard)
updated.board shouldBe updatedBoard
updated.turn shouldBe GameContext.initial.turn
updated.castlingRights shouldBe GameContext.initial.castlingRights
@@ -34,13 +34,13 @@ class GameContextTest extends AnyFunSuite with Matchers:
whiteKingSide = true,
whiteQueenSide = false,
blackKingSide = false,
blackQueenSide = true
blackQueenSide = true,
)
val square = Some(Square(File.E, Rank.R3))
val updatedTurn = initial.withTurn(Color.Black)
val square = Some(Square(File.E, Rank.R3))
val updatedTurn = initial.withTurn(Color.Black)
val updatedRights = initial.withCastlingRights(rights)
val updatedEp = initial.withEnPassantSquare(square)
val updatedClock = initial.withHalfMoveClock(17)
val updatedEp = initial.withEnPassantSquare(square)
val updatedClock = initial.withHalfMoveClock(17)
updatedTurn.turn shouldBe Color.Black
updatedTurn.board shouldBe initial.board
@@ -57,4 +57,3 @@ class GameContextTest extends AnyFunSuite with Matchers:
test("withMove appends move to history"):
val move = Move(Square(File.E, Rank.R2), Square(File.E, Rank.R4))
GameContext.initial.withMove(move).moves shouldBe List(move)
@@ -25,7 +25,7 @@ class MoveTest extends AnyFunSuite with Matchers:
MoveType.Promotion(PromotionPiece.Queen),
MoveType.Promotion(PromotionPiece.Rook),
MoveType.Promotion(PromotionPiece.Bishop),
MoveType.Promotion(PromotionPiece.Knight)
MoveType.Promotion(PromotionPiece.Knight),
)
moveTypes.foreach { moveType =>
@@ -7,12 +7,12 @@ class PlayerInfoTest extends AnyFunSuite with Matchers:
test("PlayerId and PlayerInfo preserve constructor values") {
val raw = "player-123"
val id = PlayerId(raw)
val id = PlayerId(raw)
id.value shouldBe raw
val playerId = PlayerId("p1")
val info = PlayerInfo(playerId, "Magnus")
info.id.value shouldBe "p1"
info.displayName shouldBe "Magnus"
val info = PlayerInfo(playerId, "Magnus")
info.id.value shouldBe "p1"
info.displayName shouldBe "Magnus"
}
@@ -14,9 +14,9 @@ class ApiResponseTest extends AnyFunSuite with Matchers:
ApiResponse.error(err) shouldBe ApiResponse.Failure(List(err))
val e = ApiError("CODE", "message")
e.code shouldBe "CODE"
e.code shouldBe "CODE"
e.message shouldBe "message"
e.field shouldBe None
e.field shouldBe None
ApiError("INVALID", "bad value", Some("email")).field shouldBe Some("email")
}
@@ -31,6 +31,6 @@ class ApiResponseTest extends AnyFunSuite with Matchers:
test("PagedResponse holds items and pagination") {
val pagination = Pagination(page = 1, pageSize = 5, totalItems = 20)
val pr = PagedResponse(List("a", "b"), pagination)
pr.items shouldBe List("a", "b")
pr.items shouldBe List("a", "b")
pr.pagination shouldBe pagination
}