From a792fad342ff77f22fe6919b88d16d85d2cf88b0 Mon Sep 17 00:00:00 2001 From: Kaz Date: Thu, 27 Jun 2024 22:10:57 +0200 Subject: [PATCH] data input validation for OpenRollCall + constructor tests --- .../message/data/rollcall/OpenRollCall.kt | 27 ++++++ .../popstellar/utility/MessageValidator.kt | 14 ++++ .../message/data/rollcall/OpenRollCallTest.kt | 84 ++++++++++++++++++- 3 files changed, 123 insertions(+), 2 deletions(-) diff --git a/fe2-android/app/src/main/java/com/github/dedis/popstellar/model/network/method/message/data/rollcall/OpenRollCall.kt b/fe2-android/app/src/main/java/com/github/dedis/popstellar/model/network/method/message/data/rollcall/OpenRollCall.kt index 46fa3d4eb0..8244e256dc 100644 --- a/fe2-android/app/src/main/java/com/github/dedis/popstellar/model/network/method/message/data/rollcall/OpenRollCall.kt +++ b/fe2-android/app/src/main/java/com/github/dedis/popstellar/model/network/method/message/data/rollcall/OpenRollCall.kt @@ -6,6 +6,7 @@ import com.github.dedis.popstellar.model.network.method.message.data.Data import com.github.dedis.popstellar.model.network.method.message.data.Objects import com.github.dedis.popstellar.model.objects.RollCall import com.github.dedis.popstellar.model.objects.event.EventState +import com.github.dedis.popstellar.utility.MessageValidator import com.google.gson.annotations.SerializedName /** Data sent to open a roll call */ @@ -28,6 +29,8 @@ class OpenRollCall : Data { * @param state the state in which the roll call is when this instance is created */ constructor(laoId: String, opens: String, openedAt: Long, state: EventState) { + validate(laoId, "laoId", opens, openedAt) + this.updateId = RollCall.generateOpenRollCallId(laoId, opens, openedAt) this.opens = opens this.openedAt = openedAt @@ -40,7 +43,19 @@ class OpenRollCall : Data { } } + /** + * Constructor of a data Open Roll-Call + * + * @param updateId id of the update + * @param opens The 'update_id' of the latest roll call close, or in its absence, the 'id' field + * of the roll call creation + * @param openedAt timestamp corresponding to roll call open. Must be one of + * ["open", "reopen"] + */ constructor(updateId: String, opens: String, openedAt: Long, action: String) { + validate(updateId, "updateId", opens, openedAt) + .elementIsOneOf(action, "action", Action.OPEN.action, Action.REOPEN.action) + this.updateId = updateId this.opens = opens this.openedAt = openedAt @@ -71,4 +86,16 @@ class OpenRollCall : Data { override fun toString(): String { return "OpenRollCall{updateId='$updateId', opens='$opens', openedAt=$openedAt, action='$action'}" } + + private fun validate( + id: String, + idLabel: String, + opens: String, + openedAt: Long + ): MessageValidator.MessageValidatorBuilder { + return MessageValidator.verify() + .isNotEmptyBase64(id, idLabel) + .isNotEmptyBase64(opens, "opens") + .greaterOrEqualThan(openedAt, 0, "openedAt") + } } diff --git a/fe2-android/app/src/main/java/com/github/dedis/popstellar/utility/MessageValidator.kt b/fe2-android/app/src/main/java/com/github/dedis/popstellar/utility/MessageValidator.kt index 537599f83e..ba379f0776 100644 --- a/fe2-android/app/src/main/java/com/github/dedis/popstellar/utility/MessageValidator.kt +++ b/fe2-android/app/src/main/java/com/github/dedis/popstellar/utility/MessageValidator.kt @@ -216,6 +216,20 @@ object MessageValidator { return this } + /** + * Helper method to check that a value is one of a given list of values + * + * @param input the value to check + * @param field name of the field (to print in case of error) + * @param values the list of values to compare to (modular number of arguments) + * @throws IllegalArgumentException if the value is not one of the given values + */ + fun elementIsOneOf(input: Any, field: String, vararg values: Any): MessageValidatorBuilder { + require(values.isNotEmpty()) { "Values cannot be empty" } + require(values.contains(input)) { "$field must be one of $values" } + return this + } + /** * Helper method to check that a list is not empty. * diff --git a/fe2-android/app/src/test/unit/java/com/github/dedis/popstellar/model/network/method/message/data/rollcall/OpenRollCallTest.kt b/fe2-android/app/src/test/unit/java/com/github/dedis/popstellar/model/network/method/message/data/rollcall/OpenRollCallTest.kt index 0ed2597b92..c7722a6d37 100644 --- a/fe2-android/app/src/test/unit/java/com/github/dedis/popstellar/model/network/method/message/data/rollcall/OpenRollCallTest.kt +++ b/fe2-android/app/src/test/unit/java/com/github/dedis/popstellar/model/network/method/message/data/rollcall/OpenRollCallTest.kt @@ -6,7 +6,9 @@ import com.github.dedis.popstellar.model.network.method.message.data.Action import com.github.dedis.popstellar.model.network.method.message.data.Objects import com.github.dedis.popstellar.model.objects.event.EventState import com.github.dedis.popstellar.model.objects.event.EventType +import com.github.dedis.popstellar.model.objects.security.Base64URLData import com.github.dedis.popstellar.utility.security.HashSHA256.hash +import junit.framework.TestCase.assertNotNull import java.time.Instant import org.hamcrest.CoreMatchers import org.hamcrest.MatcherAssert @@ -53,6 +55,82 @@ class OpenRollCallTest { MatcherAssert.assertThat(REOPEN_ROLL_CALL.opens, CoreMatchers.`is`(CREATE_ROLL_CALL.id)) } + @Test + fun constructor1SucceedsWithValidData() { + val openRollCall = OpenRollCall(LAO_ID, CREATE_ROLL_CALL.id, TIME, EventState.CREATED) + assertNotNull(openRollCall) + } + + @Test(expected = IllegalArgumentException::class) + fun constructor1FailsWhenLaoIdEmpty() { + OpenRollCall(EMPTY_B64, CREATE_ROLL_CALL.id, TIME, EventState.CREATED) + } + + @Test(expected = IllegalArgumentException::class) + fun constructor1FailsWhenLaoIdNotBase64() { + OpenRollCall(INVALID_B64, CREATE_ROLL_CALL.id, TIME, EventState.CREATED) + } + + @Test(expected = IllegalArgumentException::class) + fun constructor1FailsWhenOpensNotBase64() { + OpenRollCall(LAO_ID, INVALID_B64, TIME, EventState.CREATED) + } + + @Test(expected = IllegalArgumentException::class) + fun constructor1FailsWhenOpensEmpty() { + OpenRollCall(LAO_ID, EMPTY_B64, TIME, EventState.CREATED) + } + + @Test(expected = IllegalArgumentException::class) + fun constructor1FailsWhenOpenedAtNegative() { + OpenRollCall(LAO_ID, CREATE_ROLL_CALL.id, -1, EventState.CREATED) + } + + @Test + fun constructor2SucceedsWithValidDataOPENAction() { + val openRollCall = OpenRollCall(ID, CREATE_ROLL_CALL.id, TIME, Action.OPEN.action) + assertNotNull(openRollCall) + } + + fun constructor2SucceedsWithValidDataREOPENAction() { + val openRollCall = OpenRollCall(ID, CREATE_ROLL_CALL.id, TIME, Action.REOPEN.action) + assertNotNull(openRollCall) + } + + @Test(expected = IllegalArgumentException::class) + fun constructor2FailsWhenUpdateIdEmpty() { + OpenRollCall(EMPTY_B64, CREATE_ROLL_CALL.id, TIME, Action.OPEN.action) + } + + @Test(expected = IllegalArgumentException::class) + fun constructor2FailsWhenUpdateIdNotBase64() { + OpenRollCall(INVALID_B64, CREATE_ROLL_CALL.id, TIME, Action.OPEN.action) + } + + @Test(expected = IllegalArgumentException::class) + fun constructor2FailsWhenOpensEmpty() { + OpenRollCall(ID, EMPTY_B64, TIME, Action.OPEN.action) + } + + @Test(expected = IllegalArgumentException::class) + fun constructor2FailsWhenOpensNotBase64() { + OpenRollCall(ID, INVALID_B64, TIME, Action.OPEN.action) + } + + @Test(expected = IllegalArgumentException::class) + fun constructor2FailsWhenOpenedAtNegative() { + OpenRollCall(ID, CREATE_ROLL_CALL.id, -1, Action.OPEN.action) + } + + @Test(expected = IllegalArgumentException::class) + fun constructor2FailsWhenActionInvalid() { + for (action in Action.values()) { + if (action != Action.OPEN && action != Action.REOPEN) { + OpenRollCall(ID, CREATE_ROLL_CALL.id, TIME, action.action) + } + } + } + @Test fun jsonValidationTest() { testData(REOPEN_ROLL_CALL) @@ -81,8 +159,10 @@ class OpenRollCallTest { private const val LOCATION = "Location" private val CREATE_ROLL_CALL = CreateRollCall(NAME, TIME, TIME, TIME, LOCATION, null, LAO_ID) private val OPEN_ROLL_CALL = OpenRollCall(LAO_ID, CREATE_ROLL_CALL.id, TIME, EventState.CREATED) - private val REOPEN_ROLL_CALL = - OpenRollCall(LAO_ID, CREATE_ROLL_CALL.id, TIME, EventState.CLOSED) + private val REOPEN_ROLL_CALL = OpenRollCall(LAO_ID, CREATE_ROLL_CALL.id, TIME, EventState.CLOSED) private val ID = hash(EventType.ROLL_CALL.suffix, LAO_ID, CREATE_ROLL_CALL.id, TIME.toString()) + + private const val INVALID_B64 = "invalidBase64String" + private val EMPTY_B64 = Base64URLData("".toByteArray()).encoded } }