Opengotha export and basic test
This commit is contained in:
@@ -9,15 +9,16 @@ import javax.servlet.http.HttpServletResponse
|
||||
interface ApiHandler {
|
||||
|
||||
fun route(request: HttpServletRequest, response: HttpServletResponse) =
|
||||
// for now, only get() needed the response object ; other methods shall be reengineered as well if needed
|
||||
when (request.method) {
|
||||
"GET" -> get(request)
|
||||
"GET" -> get(request, response)
|
||||
"POST" -> post(request)
|
||||
"PUT" -> put(request)
|
||||
"DELETE" -> delete(request)
|
||||
else -> notImplemented()
|
||||
}
|
||||
|
||||
fun get(request: HttpServletRequest): Json {
|
||||
fun get(request: HttpServletRequest, response: HttpServletResponse): Json? {
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
|
@@ -8,10 +8,11 @@ import org.jeudego.pairgoth.model.toJson
|
||||
import org.jeudego.pairgoth.web.Event
|
||||
import org.jeudego.pairgoth.web.Event.*
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import javax.servlet.http.HttpServletResponse
|
||||
|
||||
object PairingHandler: PairgothApiHandler {
|
||||
|
||||
override fun get(request: HttpServletRequest): Json {
|
||||
override fun get(request: HttpServletRequest, response: HttpServletResponse): Json? {
|
||||
val tournament = getTournament(request)
|
||||
val round = getSubSelector(request)?.toIntOrNull() ?: badRequest("invalid round number")
|
||||
val playing = (tournament.games.getOrNull(round)?.values ?: emptyList()).flatMap {
|
||||
|
@@ -8,10 +8,11 @@ import org.jeudego.pairgoth.model.fromJson
|
||||
import org.jeudego.pairgoth.web.Event
|
||||
import org.jeudego.pairgoth.web.Event.*
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import javax.servlet.http.HttpServletResponse
|
||||
|
||||
object PlayerHandler: PairgothApiHandler {
|
||||
|
||||
override fun get(request: HttpServletRequest): Json {
|
||||
override fun get(request: HttpServletRequest, response: HttpServletResponse): Json? {
|
||||
val tournament = getTournament(request) ?: badRequest("invalid tournament")
|
||||
return when (val pid = getSubSelector(request)?.toIntOrNull()) {
|
||||
null -> tournament.pairables.values.map { it.toJson() }.toJsonArray()
|
||||
|
@@ -4,15 +4,14 @@ import com.republicate.kson.Json
|
||||
import com.republicate.kson.toJsonArray
|
||||
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
|
||||
import org.jeudego.pairgoth.model.Game
|
||||
import org.jeudego.pairgoth.model.Tournament
|
||||
import org.jeudego.pairgoth.model.toJson
|
||||
import org.jeudego.pairgoth.store.Store
|
||||
import org.jeudego.pairgoth.web.Event
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import javax.servlet.http.HttpServletResponse
|
||||
|
||||
object ResultsHandler: PairgothApiHandler {
|
||||
|
||||
override fun get(request: HttpServletRequest): Json {
|
||||
override fun get(request: HttpServletRequest, response: HttpServletResponse): Json? {
|
||||
val tournament = getTournament(request)
|
||||
val round = getSubSelector(request)?.toIntOrNull() ?: badRequest("invalid round number")
|
||||
val games = tournament.games.getOrNull(round)?.values ?: emptyList()
|
||||
|
@@ -8,17 +8,30 @@ import org.jeudego.pairgoth.model.Tournament
|
||||
import org.jeudego.pairgoth.model.fromJson
|
||||
import org.jeudego.pairgoth.model.toJson
|
||||
import org.jeudego.pairgoth.store.Store
|
||||
import org.jeudego.pairgoth.web.ApiServlet
|
||||
import org.jeudego.pairgoth.web.Event
|
||||
import org.jeudego.pairgoth.web.Event.*
|
||||
import org.w3c.dom.Element
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import javax.servlet.http.HttpServletResponse
|
||||
|
||||
object TournamentHandler: PairgothApiHandler {
|
||||
|
||||
override fun get(request: HttpServletRequest): Json {
|
||||
override fun get(request: HttpServletRequest, response: HttpServletResponse): Json? {
|
||||
val accept = request.getHeader("Accept")
|
||||
return when (val id = getSelector(request)?.toIntOrNull()) {
|
||||
null -> Json.Array(Store.getTournamentsIDs())
|
||||
else -> Store.getTournament(id)?.toJson() ?: badRequest("no tournament with id #${id}")
|
||||
else ->
|
||||
when {
|
||||
ApiServlet.isJson(accept) -> Store.getTournament(id)?.toJson() ?: badRequest("no tournament with id #${id}")
|
||||
ApiServlet.isXml(accept) -> {
|
||||
val export = Store.getTournament(id)?.let { OpenGotha.export(it) } ?: badRequest("no tournament with id #${id}")
|
||||
response.contentType = "application/xml; charset=UTF-8"
|
||||
response.writer.write(export)
|
||||
null // return null to indicate that we handled the response ourself
|
||||
}
|
||||
else -> badRequest("unhandled Accept header: $accept")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -9,7 +9,9 @@ import org.jeudego.pairgoth.model.Player
|
||||
import org.jeudego.pairgoth.model.StandardByoyomi
|
||||
import org.jeudego.pairgoth.model.SuddenDeath
|
||||
import org.jeudego.pairgoth.model.Swiss
|
||||
import org.jeudego.pairgoth.model.TimeSystem
|
||||
import org.jeudego.pairgoth.model.Tournament
|
||||
import org.jeudego.pairgoth.model.displayRank
|
||||
import org.jeudego.pairgoth.model.parseRank
|
||||
import org.jeudego.pairgoth.store.Store
|
||||
import org.jeudego.pairgoth.util.XmlFormat
|
||||
@@ -173,4 +175,110 @@ object OpenGotha {
|
||||
tournament.games.addAll(gamesPerRound)
|
||||
return tournament
|
||||
}
|
||||
|
||||
// TODO - bye player(s)
|
||||
fun export(tournament: Tournament): String {
|
||||
val xml = """
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<Tournament dataVersion="201" externalIPAddress="88.122.144.219" fullVersionNumber="3.51" runningMode="SAL" saveDT="20210111180800">
|
||||
<Players>
|
||||
${tournament.pairables.values.map { player ->
|
||||
player as Player
|
||||
}.joinToString("\n") { player ->
|
||||
"""<Player agaExpirationDate="" agaId="" club="${
|
||||
player.club
|
||||
}" country="${
|
||||
player.country
|
||||
}" egfPin="" ffgLicence="" ffgLicenceStatus="" firstName="${
|
||||
player.firstname
|
||||
}" grade="${
|
||||
player.displayRank()
|
||||
}" name="${
|
||||
player.name
|
||||
}" participating="${
|
||||
(1..20).map {
|
||||
if (player.skip.contains(it)) 0 else 1
|
||||
}.joinToString("")
|
||||
}" rank="${
|
||||
player.displayRank()
|
||||
}" rating="${
|
||||
player.rating
|
||||
}" ratingOrigin="" registeringStatus="FIN" smmsCorrection="0"/>"""
|
||||
}
|
||||
}
|
||||
</Players>
|
||||
<Games>
|
||||
${tournament.games.flatMapIndexed { round, games ->
|
||||
games.values.mapIndexed { table, game ->
|
||||
Triple(round, table , game)
|
||||
}
|
||||
}.joinToString("\n") { (round, table, game) ->
|
||||
"""<Game blackPlayer="${
|
||||
(tournament.pairables[game.black]!! as Player).let { black ->
|
||||
"${black.name}${black.firstname}".uppercase(Locale.ENGLISH) // Use Locale.ENGLISH to transform é to É
|
||||
}
|
||||
}" handicap="0" knownColor="true" result="${
|
||||
when (game.result) {
|
||||
Game.Result.UNKNOWN, Game.Result.CANCELLED -> "RESULT_UNKNOWN"
|
||||
Game.Result.BLACK -> "RESULT_BLACKWINS"
|
||||
Game.Result.WHITE -> "RESULT_WHITEWINS"
|
||||
Game.Result.JIGO -> "RESULT_EQUAL"
|
||||
Game.Result.BOTHWIN -> "RESULT_BOTHWIN"
|
||||
Game.Result.BOTHLOOSE -> "RESULT_BOTHLOOSE"
|
||||
else -> throw Error("unhandled game result")
|
||||
}
|
||||
}" roundNumber="${
|
||||
round + 1
|
||||
}" tableNumber="${
|
||||
table + 1
|
||||
}" whitePlayer="${
|
||||
(tournament.pairables[game.white]!! as Player).let { white ->
|
||||
"${white.name}${white.firstname}".uppercase(Locale.ENGLISH) // Use Locale.ENGLISH to transform é to É
|
||||
}
|
||||
}"/>"""
|
||||
}
|
||||
}
|
||||
</Games>
|
||||
<ByePlayer>
|
||||
</ByePlayer>
|
||||
<TournamentParameterSet>
|
||||
<GeneralParameterSet bInternet="${tournament.online}" basicTime="${tournament.timeSystem.mainTime}" beginDate="${tournament.startDate}" canByoYomiTime="${tournament.timeSystem.byoyomi}" complementaryTimeSystem="${when(tournament.timeSystem.type) {
|
||||
TimeSystem.TimeSystemType.SUDDEN_DEATH -> "SUDDENDEATH"
|
||||
TimeSystem.TimeSystemType.STANDARD -> "STDBYOYOMI"
|
||||
TimeSystem.TimeSystemType.CANADIAN -> "CANBYOYOMI"
|
||||
TimeSystem.TimeSystemType.FISCHER -> "FISCHER"
|
||||
} }" director="" endDate="${tournament.endDate}" fischerTime="${tournament.timeSystem.increment}" genCountNotPlayedGamesAsHalfPoint="false" genMMBar="9D" genMMFloor="30K" genMMS2ValueAbsent="1" genMMS2ValueBye="2" genMMZero="30K" genNBW2ValueAbsent="0" genNBW2ValueBye="2" genRoundDownNBWMMS="true" komi="${tournament.komi}" location="${tournament.location}" name="${tournament.name}" nbMovesCanTime="${tournament.timeSystem.stones}" numberOfCategories="1" numberOfRounds="${tournament.rounds}" shortName="${tournament.shortName}" size="${tournament.gobanSize}" stdByoYomiTime="${tournament.timeSystem.byoyomi}"/>
|
||||
<HandicapParameterSet hdBasedOnMMS="false" hdCeiling="0" hdCorrection="0" hdNoHdRankThreshold="30K"/>
|
||||
<PlacementParameterSet>
|
||||
<PlacementCriteria>
|
||||
<PlacementCriterion name="NBW" number="1"/>
|
||||
<PlacementCriterion name="SOSW" number="2"/>
|
||||
<PlacementCriterion name="SOSOSW" number="3"/>
|
||||
<PlacementCriterion name="NULL" number="4"/>
|
||||
<PlacementCriterion name="NULL" number="5"/>
|
||||
<PlacementCriterion name="NULL" number="6"/>
|
||||
</PlacementCriteria>
|
||||
</PlacementParameterSet>
|
||||
<PairingParameterSet paiBaAvoidDuplGame="500000000000000" paiBaBalanceWB="1000000" paiBaDeterministic="true" paiBaRandom="0" paiMaAdditionalPlacementCritSystem1="Rating" paiMaAdditionalPlacementCritSystem2="Rating" paiMaAvoidMixingCategories="0" paiMaCompensateDUDD="true" paiMaDUDDLowerMode="MID" paiMaDUDDUpperMode="MID" paiMaDUDDWeight="100000000" paiMaLastRoundForSeedSystem1="2" paiMaMaximizeSeeding="5000000" paiMaMinimizeScoreDifference="100000000000" paiMaSeedSystem1="SPLITANDSLIP" paiMaSeedSystem2="SPLITANDSLIP" paiSeAvoidSameGeo="0" paiSeBarThresholdActive="true" paiSeDefSecCrit="20000000000000" paiSeMinimizeHandicap="0" paiSeNbWinsThresholdActive="true" paiSePreferMMSDiffRatherThanSameClub="0" paiSePreferMMSDiffRatherThanSameCountry="0" paiSeRankThreshold="30K" paiStandardNX1Factor="0.5"/>
|
||||
<DPParameterSet displayClCol="true" displayCoCol="true" displayIndGamesInMatches="true" displayNPPlayers="false" displayNumCol="true" displayPlCol="true" gameFormat="short" playerSortType="name" showByePlayer="true" showNotFinallyRegisteredPlayers="true" showNotPairedPlayers="true" showNotParticipatingPlayers="false" showPlayerClub="true" showPlayerCountry="false" showPlayerGrade="true"/>
|
||||
<PublishParameterSet exportToLocalFile="true" htmlAutoScroll="false" print="false"/>
|
||||
</TournamentParameterSet>
|
||||
<TeamTournamentParameterSet>
|
||||
<TeamGeneralParameterSet teamSize="4"/>
|
||||
<TeamPlacementParameterSet>
|
||||
<PlacementCriteria>
|
||||
<PlacementCriterion name="TEAMP" number="1"/>
|
||||
<PlacementCriterion name="BDW" number="2"/>
|
||||
<PlacementCriterion name="BDW3U" number="3"/>
|
||||
<PlacementCriterion name="BDW2U" number="4"/>
|
||||
<PlacementCriterion name="BDW1U" number="5"/>
|
||||
<PlacementCriterion name="MNR" number="6"/>
|
||||
</PlacementCriteria>
|
||||
</TeamPlacementParameterSet>
|
||||
</TeamTournamentParameterSet>
|
||||
</Tournament>
|
||||
|
||||
""".trimIndent()
|
||||
return xml
|
||||
}
|
||||
}
|
||||
|
@@ -213,7 +213,7 @@ class ApiServlet : HttpServlet() {
|
||||
HttpServletResponse.SC_BAD_REQUEST,
|
||||
"Missing 'Accept' header"
|
||||
)
|
||||
if (!isJson(accept)) throw ApiException(
|
||||
if (!isJson(accept) && (!isXml(accept) || !request.requestURI.matches(Regex("/api/tour/\\d+")))) throw ApiException(
|
||||
HttpServletResponse.SC_BAD_REQUEST,
|
||||
"Invalid 'Accept' header"
|
||||
)
|
||||
@@ -260,7 +260,7 @@ class ApiServlet : HttpServlet() {
|
||||
private const val EXPECTED_CHARSET = "utf8"
|
||||
const val AUTH_HEADER = "Authorization"
|
||||
const val AUTH_PREFIX = "Bearer"
|
||||
private fun isJson(mimeType: String) = "text/json" == mimeType || "application/json" == mimeType || mimeType.endsWith("+json")
|
||||
private fun isXml(mimeType: String) = "text/xml" == mimeType || "application/xml" == mimeType || mimeType.endsWith("+xml")
|
||||
fun isJson(mimeType: String) = "text/json" == mimeType || "application/json" == mimeType || mimeType.endsWith("+json")
|
||||
fun isXml(mimeType: String) = "text/xml" == mimeType || "application/xml" == mimeType || mimeType.endsWith("+xml")
|
||||
}
|
||||
}
|
||||
|
@@ -1,10 +1,9 @@
|
||||
package org.jeudego.pairgoth.test
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.io.InputStreamReader
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
class ImportTests: TestBase() {
|
||||
class ImportExportTests: TestBase() {
|
||||
|
||||
@Test
|
||||
fun `001 test imports`() {
|
||||
@@ -21,6 +20,8 @@ class ImportTests: TestBase() {
|
||||
val games = TestAPI.get("/api/tour/$id/res/1").asArray()
|
||||
logger.info("games for round $round: {}", games.toString())
|
||||
}
|
||||
val xml = TestAPI.getXml("/api/tour/$id")
|
||||
logger.info(xml)
|
||||
}
|
||||
}
|
||||
}
|
@@ -34,7 +34,7 @@ object TestAPI {
|
||||
private val apiServlet = ApiServlet()
|
||||
private val sseServlet = SSEServlet()
|
||||
|
||||
private fun <T> testRequest(reqMethod: String, uri: String, payload: T? = null): Json {
|
||||
private fun <T> testRequest(reqMethod: String, uri: String, accept: String = "application/json", payload: T? = null): String {
|
||||
|
||||
WebappManager.properties["webapp.env"] = "test"
|
||||
|
||||
@@ -64,7 +64,7 @@ object TestAPI {
|
||||
else -> throw Error("unhandled case")
|
||||
}
|
||||
on { headerNames } doReturn Collections.enumeration(myHeaderNames)
|
||||
on { getHeader(eq("Accept")) } doReturn "application/json"
|
||||
on { getHeader(eq("Accept")) } doReturn accept
|
||||
}
|
||||
|
||||
// mock response
|
||||
@@ -83,13 +83,14 @@ object TestAPI {
|
||||
"DELETE" -> apiServlet.doDelete(req, resp)
|
||||
}
|
||||
|
||||
return Json.parse(buffer.toString()) ?: throw Error("no response payload")
|
||||
return buffer.toString() ?: throw Error("no response payload")
|
||||
}
|
||||
|
||||
fun get(uri: String) = testRequest<Void>("GET", uri)
|
||||
fun <T> post(uri: String, payload: T) = testRequest("POST", uri, payload)
|
||||
fun <T> put(uri: String, payload: T) = testRequest("PUT", uri, payload)
|
||||
fun <T> delete(uri: String, payload: T) = testRequest("DELETE", uri, payload)
|
||||
fun get(uri: String): Json = Json.parse(testRequest<Void>("GET", uri)) ?: throw Error("no payload")
|
||||
fun getXml(uri: String): String = testRequest<Void>("GET", uri, "application/xml")
|
||||
fun <T> post(uri: String, payload: T) = Json.parse(testRequest("POST", uri, payload = payload)) ?: throw Error("no payload")
|
||||
fun <T> put(uri: String, payload: T) = Json.parse(testRequest("PUT", uri, payload = payload)) ?: throw Error("no payload")
|
||||
fun <T> delete(uri: String, payload: T) = Json.parse(testRequest("DELETE", uri, payload = payload)) ?: throw Error("no payload")
|
||||
}
|
||||
|
||||
// Get a list of resources
|
||||
|
Reference in New Issue
Block a user