From 03fa9d68c1de2290e41cf605163732cfb9d9a3fb Mon Sep 17 00:00:00 2001 From: Claude Brisson Date: Sat, 20 May 2023 18:01:07 +0200 Subject: [PATCH] Opengotha export and basic test --- .../org/jeudego/pairgoth/api/ApiHandler.kt | 5 +- .../jeudego/pairgoth/api/PairingHandler.kt | 3 +- .../org/jeudego/pairgoth/api/PlayerHandler.kt | 3 +- .../jeudego/pairgoth/api/ResultsHandler.kt | 5 +- .../jeudego/pairgoth/api/TournamentHandler.kt | 17 ++- .../org/jeudego/pairgoth/ext/OpenGotha.kt | 108 ++++++++++++++++++ .../org/jeudego/pairgoth/web/ApiServlet.kt | 6 +- .../{ImportTests.kt => ImportExportTests.kt} | 5 +- webapp/src/test/kotlin/TestUtils.kt | 15 +-- 9 files changed, 146 insertions(+), 21 deletions(-) rename webapp/src/test/kotlin/{ImportTests.kt => ImportExportTests.kt} (88%) diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/ApiHandler.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/ApiHandler.kt index b11395f..c7b3d85 100644 --- a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/ApiHandler.kt +++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/ApiHandler.kt @@ -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() } diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/PairingHandler.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/PairingHandler.kt index e997637..9ba2727 100644 --- a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/PairingHandler.kt +++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/PairingHandler.kt @@ -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 { diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/PlayerHandler.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/PlayerHandler.kt index 70691f5..d6ac610 100644 --- a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/PlayerHandler.kt +++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/PlayerHandler.kt @@ -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() diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/ResultsHandler.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/ResultsHandler.kt index ed3b869..bac6b0a 100644 --- a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/ResultsHandler.kt +++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/ResultsHandler.kt @@ -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() diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/TournamentHandler.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/TournamentHandler.kt index 715ee7a..c1c612b 100644 --- a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/TournamentHandler.kt +++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/TournamentHandler.kt @@ -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") + } } } diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/ext/OpenGotha.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/ext/OpenGotha.kt index 70377c5..861dfe2 100644 --- a/webapp/src/main/kotlin/org/jeudego/pairgoth/ext/OpenGotha.kt +++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/ext/OpenGotha.kt @@ -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 = """ + + + + ${tournament.pairables.values.map { player -> + player as Player + }.joinToString("\n") { player -> + """""" + } + } + + + ${tournament.games.flatMapIndexed { round, games -> + games.values.mapIndexed { table, game -> + Triple(round, table , game) + } + }.joinToString("\n") { (round, table, game) -> + """ "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 É + } + }"/>""" + } + } + + + + + "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}"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + """.trimIndent() + return xml + } } diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/web/ApiServlet.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/web/ApiServlet.kt index 229e972..2d0834a 100644 --- a/webapp/src/main/kotlin/org/jeudego/pairgoth/web/ApiServlet.kt +++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/web/ApiServlet.kt @@ -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") } } diff --git a/webapp/src/test/kotlin/ImportTests.kt b/webapp/src/test/kotlin/ImportExportTests.kt similarity index 88% rename from webapp/src/test/kotlin/ImportTests.kt rename to webapp/src/test/kotlin/ImportExportTests.kt index ceaab92..1874442 100644 --- a/webapp/src/test/kotlin/ImportTests.kt +++ b/webapp/src/test/kotlin/ImportExportTests.kt @@ -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) } } } diff --git a/webapp/src/test/kotlin/TestUtils.kt b/webapp/src/test/kotlin/TestUtils.kt index 7d6dd78..da44a2b 100644 --- a/webapp/src/test/kotlin/TestUtils.kt +++ b/webapp/src/test/kotlin/TestUtils.kt @@ -34,7 +34,7 @@ object TestAPI { private val apiServlet = ApiServlet() private val sseServlet = SSEServlet() - private fun testRequest(reqMethod: String, uri: String, payload: T? = null): Json { + private fun 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("GET", uri) - fun post(uri: String, payload: T) = testRequest("POST", uri, payload) - fun put(uri: String, payload: T) = testRequest("PUT", uri, payload) - fun delete(uri: String, payload: T) = testRequest("DELETE", uri, payload) + fun get(uri: String): Json = Json.parse(testRequest("GET", uri)) ?: throw Error("no payload") + fun getXml(uri: String): String = testRequest("GET", uri, "application/xml") + fun post(uri: String, payload: T) = Json.parse(testRequest("POST", uri, payload = payload)) ?: throw Error("no payload") + fun put(uri: String, payload: T) = Json.parse(testRequest("PUT", uri, payload = payload)) ?: throw Error("no payload") + fun delete(uri: String, payload: T) = Json.parse(testRequest("DELETE", uri, payload = payload)) ?: throw Error("no payload") } // Get a list of resources