Add skipped rounds info

This commit is contained in:
Claude Brisson
2023-05-16 12:46:13 +02:00
parent e347183b56
commit 100d28e483
8 changed files with 95 additions and 40 deletions

19
test.sh
View File

@@ -1,19 +1,4 @@
#!/bin/bash #!/bin/bash
curl -s --header "Accept: application/json" --header "Content-Type: application/json" \ #mvn -Dorg.slf4j.simpleLogger.log.test=trace package verify
--request POST \ mvn package verify
--data '{ "type":"INDIVIDUAL","name":"Mon Tournoi", "shortName": "mon-tournoi", "startDate": "2023-05-10", "endDate": "2023-05-12", "country": "FR", "location": "Marseille", "online": false, "timeSystem": { "type": "fisher", "mainTime": "1200", "increment": "10" }, "pairing": { "type": "ROUNDROBIN" } }' \
http://localhost:8080/api/tour
curl -s --header "Accept: application/json" http://localhost:8080/api/tour
curl -s --header "Accept: application/json" http://localhost:8080/api/tour/1
curl -s --header "Accept: application/json" --header "Content-Type: application/json" \
--request POST \
--data '{ "name": "Burma", "firstname": "Nestor", "rating": 1600, "rank": -2, "country": "FR", "club": "13Ma" }' \
http://localhost:8080/api/tour/1/part
curl -s --header "Accept: application/json" http://localhost:8080/api/tour/1/part

View File

@@ -97,6 +97,11 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<configuration>
<classpathDependencyExcludes>
<classpathDependencyExclude>com.republicate:webapp-slf4j-logger</classpathDependencyExclude>
</classpathDependencyExcludes>
</configuration>
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
@@ -169,6 +174,12 @@
<version>3.0</version> <version>3.0</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>${slf4j.version}</version>
<scope>test</scope>
</dependency>
<!-- <!--
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>

View File

@@ -8,7 +8,7 @@ import kotlin.math.roundToInt
// Pairable // Pairable
sealed class Pairable(val id: Int, val name: String, open val rating: Double, open val rank: Int) { sealed class Pairable(val id: Int, val name: String, open val rating: Int, open val rank: Int) {
abstract fun toJson(): Json.Object abstract fun toJson(): Json.Object
val skip = mutableSetOf<Int>() // skipped rounds val skip = mutableSetOf<Int>() // skipped rounds
} }
@@ -40,7 +40,7 @@ class Player(
id: Int, id: Int,
name: String, name: String,
var firstname: String, var firstname: String,
rating: Double, rating: Int,
rank: Int, rank: Int,
var country: String, var country: String,
var club: String var club: String
@@ -48,7 +48,7 @@ class Player(
companion object companion object
// used to store external IDs ("FFG" => FFG ID, "EGF" => EGF PIN, "AGA" => AGA ID ...) // used to store external IDs ("FFG" => FFG ID, "EGF" => EGF PIN, "AGA" => AGA ID ...)
val externalIds = mutableMapOf<String, String>() val externalIds = mutableMapOf<String, String>()
override fun toJson() = Json.Object( override fun toJson(): Json.Object = Json.MutableObject(
"id" to id, "id" to id,
"name" to name, "name" to name,
"firstname" to firstname, "firstname" to firstname,
@@ -56,25 +56,32 @@ class Player(
"rank" to rank, "rank" to rank,
"country" to country, "country" to country,
"club" to club "club" to club
) ).also {
if (skip.isNotEmpty()) it["skip"] = Json.Array(skip)
}
} }
fun Player.Companion.fromJson(json: Json.Object, default: Player? = null) = Player( fun Player.Companion.fromJson(json: Json.Object, default: Player? = null) = Player(
id = json.getInt("id") ?: default?.id ?: Store.nextPlayerId, id = json.getInt("id") ?: default?.id ?: Store.nextPlayerId,
name = json.getString("name") ?: default?.name ?: badRequest("missing name"), name = json.getString("name") ?: default?.name ?: badRequest("missing name"),
firstname = json.getString("firstname") ?: default?.firstname ?: badRequest("missing firstname"), firstname = json.getString("firstname") ?: default?.firstname ?: badRequest("missing firstname"),
rating = json.getDouble("rating") ?: default?.rating ?: badRequest("missing rating"), rating = json.getInt("rating") ?: default?.rating ?: badRequest("missing rating"),
rank = json.getInt("rank") ?: default?.rank ?: badRequest("missing rank"), rank = json.getInt("rank") ?: default?.rank ?: badRequest("missing rank"),
country = json.getString("country") ?: default?.country ?: badRequest("missing country"), country = json.getString("country") ?: default?.country ?: badRequest("missing country"),
club = json.getString("club") ?: default?.club ?: badRequest("missing club") club = json.getString("club") ?: default?.club ?: badRequest("missing club")
) ).also { player ->
player.skip.clear()
json.getArray("skip")?.let {
if (it.isNotEmpty()) player.skip.addAll(it.map { id -> (id as Number).toInt() })
}
}
// Team // Team
class Team(id: Int, name: String): Pairable(id, name, 0.0, 0) { class Team(id: Int, name: String): Pairable(id, name, 0, 0) {
companion object {} companion object {}
val players = mutableSetOf<Player>() val players = mutableSetOf<Player>()
override val rating: Double get() = if (players.isEmpty()) super.rating else players.sumOf { player -> player.rating } / players.size override val rating: Int get() = if (players.isEmpty()) super.rating else (players.sumOf { player -> player.rating.toDouble() } / players.size).roundToInt()
override val rank: Int get() = if (players.isEmpty()) super.rank else (players.sumOf { player -> player.rank.toDouble() } / players.size).roundToInt() override val rank: Int get() = if (players.isEmpty()) super.rank else (players.sumOf { player -> player.rank.toDouble() } / players.size).roundToInt()
override fun toJson() = Json.Object( override fun toJson() = Json.Object(
"id" to id, "id" to id,

View File

@@ -114,9 +114,9 @@ class ApiServlet : HttpServlet() {
builder.append(response.status).append(' ') builder.append(response.status).append(' ')
.append(reason) .append(reason)
if (response.status == HttpServletResponse.SC_INTERNAL_SERVER_ERROR) { if (response.status == HttpServletResponse.SC_INTERNAL_SERVER_ERROR) {
logger.info(red(">> {}"), builder.toString()) logger.trace(red(">> {}"), builder.toString())
} else { } else {
logger.info(green(">> {}"), builder.toString()) logger.trace(green(">> {}"), builder.toString())
} }
// CB TODO - should be bufferized and asynchronously written in synchronous chunks // CB TODO - should be bufferized and asynchronously written in synchronous chunks

View File

@@ -25,7 +25,7 @@ fun Logger.logRequest(req: HttpServletRequest, logHeaders: Boolean = false) {
} }
// builder.append(' ').append(req.getProtocol()); // builder.append(' ').append(req.getProtocol());
info(blue("<< {}"), builder.toString()) info(blue("<< {}"), builder.toString())
if (logHeaders) { if (isTraceEnabled && logHeaders) {
// CB TODO - should be bufferized and asynchronously written in synchronous chunks // CB TODO - should be bufferized and asynchronously written in synchronous chunks
// so that header lines from parallel requests are not mixed up in the logs ; // so that header lines from parallel requests are not mixed up in the logs ;
// synchronizing the whole request log is not desirable // synchronizing the whole request log is not desirable
@@ -33,7 +33,7 @@ fun Logger.logRequest(req: HttpServletRequest, logHeaders: Boolean = false) {
while (headerNames.hasMoreElements()) { while (headerNames.hasMoreElements()) {
val name = headerNames.nextElement() val name = headerNames.nextElement()
val value = req.getHeader(name) val value = req.getHeader(name)
info(blue("<< {}: {}"), name, value) trace(blue("<< {}: {}"), name, value)
} }
} }
} }

View File

@@ -11,7 +11,7 @@ import kotlin.test.assertTrue
@TestMethodOrder(Alphanumeric::class) @TestMethodOrder(Alphanumeric::class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
class BasicTests() { class BasicTests: TestBase() {
val aTournament = Json.Object( val aTournament = Json.Object(
"type" to "INDIVIDUAL", "type" to "INDIVIDUAL",
@@ -32,20 +32,47 @@ class BasicTests() {
) )
) )
val aPlayer = Json.Object(
"name" to "Burma",
"firstname" to "Nestor",
"rating" to 1600,
"rank" to -2,
"country" to "FR",
"club" to "13Ma"
)
@Test @Test
fun `001 create tournament`() { fun `001 create tournament`() {
val resp = TestAPI.post("/api/tour", aTournament) val resp = TestAPI.post("/api/tour", aTournament) as Json.Object
assertTrue(resp.isObject, "Json object expected") assertTrue(resp.getBoolean("success") == true, "expecting success")
assertTrue(resp.asObject().getBoolean("success") == true, "expecting success")
} }
@Test @Test
fun `002 get tournament`() { fun `002 get tournament`() {
val resp = TestAPI.get("/api/tour/1") val resp = TestAPI.get("/api/tour/1") as Json.Object
assertTrue(resp.isObject, "Json object expected") assertEquals(1, resp.getInt("id"), "First tournament should have id #1")
assertEquals(1, resp.asObject().getInt("id"), "First tournament should have id #1")
// filter out "id", and also "komi", "rules" and "gobanSize" which were provided by default // filter out "id", and also "komi", "rules" and "gobanSize" which were provided by default
val cmp = Json.Object(*resp.asObject().entries.filter { it.key !in listOf("id", "komi", "rules", "gobanSize") }.map { Pair(it.key, it.value) }.toTypedArray()) val cmp = Json.Object(*resp.entries.filter { it.key !in listOf("id", "komi", "rules", "gobanSize") }.map { Pair(it.key, it.value) }.toTypedArray())
assertEquals(aTournament.toString(), cmp.toString(), "tournament differs") assertEquals(aTournament.toString(), cmp.toString(), "tournament differs")
} }
@Test
fun `003 register user`() {
val resp = TestAPI.post("/api/tour/1/part", aPlayer) as Json.Object
assertTrue(resp.getBoolean("success") == true, "expecting success")
val players = TestAPI.get("/api/tour/1/part") as Json.Array
val player = players[0] as Json.Object
assertEquals(1, player.getInt("id"), "First player should have id #1")
// filter out "id"
val cmp = Json.Object(*player.entries.filter { it.key != "id" }.map { Pair(it.key, it.value) }.toTypedArray())
assertEquals(aPlayer.toString(), cmp.toString(), "player differs")
}
@Test
fun `004 modify user`() {
val resp = TestAPI.put("/api/tour/1/part/1", Json.Object("skip" to Json.Array(1))) as Json.Object
assertTrue(resp.getBoolean("success") == true, "expecting success")
val player = TestAPI.get("/api/tour/1/part/1") as Json.Object
assertEquals("[1]", player.getArray("skip").toString(), "First player should have id #1")
}
} }

View File

@@ -0,0 +1,25 @@
package org.jeudego.pairgoth.test
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.TestInfo
import org.slf4j.LoggerFactory
abstract class TestBase {
companion object {
val logger = LoggerFactory.getLogger("test")
private var testClassName: String? = null
@BeforeAll
@JvmStatic
fun prepare() {
testClassName = this::class.simpleName
}
}
@BeforeEach
fun before(testInfo: TestInfo) {
val testName = testInfo.displayName.removeSuffix("()")
logger.info("===== Running $testClassName.$testName =====")
}
}

View File

@@ -40,9 +40,9 @@ object TestAPI {
on { setAttribute(eq(ApiHandler.SELECTOR_KEY), selector.capture()) } doAnswer {} on { setAttribute(eq(ApiHandler.SELECTOR_KEY), selector.capture()) } doAnswer {}
on { setAttribute(eq(ApiHandler.SUBSELECTOR_KEY), subSelector.capture()) } doAnswer {} on { setAttribute(eq(ApiHandler.SUBSELECTOR_KEY), subSelector.capture()) } doAnswer {}
on { setAttribute(eq(ApiHandler.PAYLOAD_KEY), reqPayload.capture()) } doAnswer {} on { setAttribute(eq(ApiHandler.PAYLOAD_KEY), reqPayload.capture()) } doAnswer {}
on { getAttribute(ApiHandler.SELECTOR_KEY) } doAnswer { selector.lastValue } on { getAttribute(ApiHandler.SELECTOR_KEY) } doAnswer { selector.allValues.lastOrNull() }
on { getAttribute(ApiHandler.SUBSELECTOR_KEY) } doAnswer { subSelector.lastValue } on { getAttribute(ApiHandler.SUBSELECTOR_KEY) } doAnswer { subSelector.allValues.lastOrNull() }
on { getAttribute(ApiHandler.PAYLOAD_KEY) } doAnswer { reqPayload.lastValue } on { getAttribute(ApiHandler.PAYLOAD_KEY) } doAnswer { reqPayload.allValues.lastOrNull() }
on { reader } doReturn myReader on { reader } doReturn myReader
on { scheme } doReturn "http" on { scheme } doReturn "http"
on { localName } doReturn "pairgoth" on { localName } doReturn "pairgoth"