diff --git a/fe2-android/app/src/main/java/com/github/dedis/popstellar/model/network/method/message/data/election/QuestionResult.kt b/fe2-android/app/src/main/java/com/github/dedis/popstellar/model/network/method/message/data/election/QuestionResult.kt index bd0ca42bf0..d05a7275ae 100644 --- a/fe2-android/app/src/main/java/com/github/dedis/popstellar/model/network/method/message/data/election/QuestionResult.kt +++ b/fe2-android/app/src/main/java/com/github/dedis/popstellar/model/network/method/message/data/election/QuestionResult.kt @@ -11,7 +11,9 @@ class QuestionResult(ballotOption: String?, count: Int) { val count: Int init { - verify().stringNotEmpty(ballotOption, "ballot option").greaterOrEqualThan(count, 0, "count") + verify() + .stringNotEmpty(ballotOption, "ballot option") + .greaterOrEqualThan(count.toLong(), 0, "count") ballot = ballotOption!! this.count = count diff --git a/fe2-android/app/src/main/java/com/github/dedis/popstellar/model/network/method/message/data/rollcall/CloseRollCall.kt b/fe2-android/app/src/main/java/com/github/dedis/popstellar/model/network/method/message/data/rollcall/CloseRollCall.kt index 41308a917f..701b676789 100644 --- a/fe2-android/app/src/main/java/com/github/dedis/popstellar/model/network/method/message/data/rollcall/CloseRollCall.kt +++ b/fe2-android/app/src/main/java/com/github/dedis/popstellar/model/network/method/message/data/rollcall/CloseRollCall.kt @@ -4,8 +4,9 @@ import com.github.dedis.popstellar.model.Immutable import com.github.dedis.popstellar.model.network.method.message.data.Action 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.RollCall.Companion.generateCloseRollCallId import com.github.dedis.popstellar.model.objects.security.PublicKey +import com.github.dedis.popstellar.utility.MessageValidator.verify import com.google.gson.annotations.SerializedName /** Data sent to close a Roll-Call */ @@ -25,12 +26,23 @@ class CloseRollCall( @field:SerializedName("closed_at") val closedAt: Long, attendees: List ) : Data { - @SerializedName("update_id") - val updateId: String = RollCall.generateCloseRollCallId(laoId, closes, closedAt) + @SerializedName("update_id") val updateId: String - val attendees: List = ArrayList(attendees) + var attendees: List = ArrayList() get() = ArrayList(field) + init { + verify() + .stringListIsSorted(attendees, "attendees") + .validPastTimes(closedAt) + .greaterOrEqualThan(closedAt, 0, "closedAt") + .isNotEmptyBase64(laoId, "laoId") + .isNotEmptyBase64(closes, "closes") + + this.updateId = generateCloseRollCallId(laoId, closes, closedAt) + this.attendees = ArrayList(attendees) + } + override val `object`: String get() = Objects.ROLL_CALL.`object` diff --git a/fe2-android/app/src/main/java/com/github/dedis/popstellar/model/objects/event/RollCallBuilder.kt b/fe2-android/app/src/main/java/com/github/dedis/popstellar/model/objects/event/RollCallBuilder.kt index 4028f33ab8..598bc919eb 100644 --- a/fe2-android/app/src/main/java/com/github/dedis/popstellar/model/objects/event/RollCallBuilder.kt +++ b/fe2-android/app/src/main/java/com/github/dedis/popstellar/model/objects/event/RollCallBuilder.kt @@ -30,7 +30,7 @@ class RollCallBuilder { start = rollCall.startTimestamp end = rollCall.end state = rollCall.state - attendees = HashSet(rollCall.attendees) + attendees = LinkedHashSet(rollCall.attendees) location = rollCall.location description = rollCall.description } @@ -66,12 +66,12 @@ class RollCallBuilder { } fun setAttendees(attendees: Set): RollCallBuilder { - this.attendees = HashSet(attendees) + this.attendees = LinkedHashSet(attendees) return this } fun setEmptyAttendees(): RollCallBuilder { - attendees = HashSet() + attendees = LinkedHashSet() return this } @@ -102,7 +102,7 @@ class RollCallBuilder { start, end, state!!, - attendees!!, + LinkedHashSet(attendees), location!!, description!!) } diff --git a/fe2-android/app/src/main/java/com/github/dedis/popstellar/ui/lao/event/rollcall/RollCallArrayAdapter.kt b/fe2-android/app/src/main/java/com/github/dedis/popstellar/ui/lao/event/rollcall/RollCallArrayAdapter.kt index b69162085c..74f098b098 100644 --- a/fe2-android/app/src/main/java/com/github/dedis/popstellar/ui/lao/event/rollcall/RollCallArrayAdapter.kt +++ b/fe2-android/app/src/main/java/com/github/dedis/popstellar/ui/lao/event/rollcall/RollCallArrayAdapter.kt @@ -14,8 +14,13 @@ class RollCallArrayAdapter( private val layout: Int, private val attendeesList: List, private val myToken: PoPToken?, + private val fragment: RollCallFragment ) : ArrayAdapter(context, layout, attendeesList) { + init { + fragment.isAttendeeListSorted(attendeesList, context) + } + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { val view = super.getView(position, convertView, parent) diff --git a/fe2-android/app/src/main/java/com/github/dedis/popstellar/ui/lao/event/rollcall/RollCallFragment.kt b/fe2-android/app/src/main/java/com/github/dedis/popstellar/ui/lao/event/rollcall/RollCallFragment.kt index 9abe2c818a..e23865a64f 100644 --- a/fe2-android/app/src/main/java/com/github/dedis/popstellar/ui/lao/event/rollcall/RollCallFragment.kt +++ b/fe2-android/app/src/main/java/com/github/dedis/popstellar/ui/lao/event/rollcall/RollCallFragment.kt @@ -1,6 +1,7 @@ package com.github.dedis.popstellar.ui.lao.event.rollcall import android.annotation.SuppressLint +import android.content.Context import android.content.pm.ActivityInfo import android.graphics.Color import android.os.Bundle @@ -9,6 +10,7 @@ import android.view.View import android.view.ViewGroup import androidx.annotation.VisibleForTesting import androidx.appcompat.content.res.AppCompatResources +import androidx.lifecycle.MutableLiveData import com.github.dedis.popstellar.R import com.github.dedis.popstellar.databinding.RollCallFragmentBinding import com.github.dedis.popstellar.model.objects.RollCall @@ -51,6 +53,8 @@ class RollCallFragment : AbstractEventFragment { private val managementTextMap = buildManagementTextMap() private val managementIconMap = buildManagementIconMap() + private val deAnonymizationWarned = MutableLiveData(false) + constructor() override fun onCreateView( @@ -253,6 +257,7 @@ class RollCallFragment : AbstractEventFragment { .getAttendees() .stream() .map(PublicKey::encoded) + .sorted(compareBy(String::toString)) .collect(Collectors.toList()) binding.rollCallAttendeesText.text = @@ -260,8 +265,8 @@ class RollCallFragment : AbstractEventFragment { resources.getString(R.string.roll_call_scanned), rollCallViewModel.getAttendees().size) } else if (rollCall.isClosed) { - attendeesList = - rollCall.attendees.stream().map(PublicKey::encoded).collect(Collectors.toList()) + val orderedAttendees: MutableSet = LinkedHashSet(rollCall.attendees) + attendeesList = orderedAttendees.stream().map(PublicKey::encoded).collect(Collectors.toList()) // Show the list of attendees if the roll call has ended binding.rollCallAttendeesText.text = @@ -271,11 +276,7 @@ class RollCallFragment : AbstractEventFragment { if (attendeesList != null) { binding.listViewAttendees.adapter = RollCallArrayAdapter( - requireContext(), - android.R.layout.simple_list_item_1, - attendeesList, - popToken, - ) + requireContext(), android.R.layout.simple_list_item_1, attendeesList, popToken, this) } } @@ -335,6 +336,17 @@ class RollCallFragment : AbstractEventFragment { return map } + fun isAttendeeListSorted(attendeesList: List, context: Context): Boolean { + if (attendeesList.isNotEmpty() && + attendeesList != attendeesList.sorted() && + deAnonymizationWarned.value == false) { + deAnonymizationWarned.value = true + logAndShow(context, TAG, R.string.roll_call_attendees_list_not_sorted) + return false + } + return true + } + @VisibleForTesting(otherwise = VisibleForTesting.NONE) constructor(rollCall: RollCall) { this.rollCall = rollCall @@ -349,7 +361,6 @@ class RollCallFragment : AbstractEventFragment { val bundle = Bundle(1) bundle.putString(ROLL_CALL_ID, persistentId) fragment.arguments = bundle - return fragment } diff --git a/fe2-android/app/src/main/java/com/github/dedis/popstellar/ui/lao/event/rollcall/RollCallViewModel.kt b/fe2-android/app/src/main/java/com/github/dedis/popstellar/ui/lao/event/rollcall/RollCallViewModel.kt index 68afe7d9a4..4a18a8de13 100644 --- a/fe2-android/app/src/main/java/com/github/dedis/popstellar/ui/lao/event/rollcall/RollCallViewModel.kt +++ b/fe2-android/app/src/main/java/com/github/dedis/popstellar/ui/lao/event/rollcall/RollCallViewModel.kt @@ -33,6 +33,8 @@ import io.reactivex.Completable import io.reactivex.Observable import io.reactivex.Single import java.time.Instant +import java.util.ArrayList +import java.util.TreeSet import java.util.stream.Collectors import javax.inject.Inject import timber.log.Timber @@ -52,7 +54,7 @@ constructor( ) : AndroidViewModel(application), QRCodeScanningViewModel { private lateinit var laoId: String - private val attendees: MutableSet = HashSet() + private val attendees: TreeSet = TreeSet(compareBy { it.toString() }) override val nbScanned = MutableLiveData() lateinit var attendedRollCalls: Observable> private set @@ -261,6 +263,7 @@ constructor( nbScanned.postValue(attendees.size) } + @Inject fun getAttendees(): Set { return attendees } 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 b5d39bda37..537599f83e 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 @@ -211,7 +211,7 @@ object MessageValidator { * @param value the value to compare to * @throws IllegalArgumentException if the value is not greater or equal than the given value */ - fun greaterOrEqualThan(input: Int, value: Int, field: String): MessageValidatorBuilder { + fun greaterOrEqualThan(input: Long, value: Long, field: String): MessageValidatorBuilder { require(input >= value) { "$field must be greater or equal than $value" } return this } @@ -305,6 +305,12 @@ object MessageValidator { return this } + /** Helper method to check that a string list is sorted */ + fun stringListIsSorted(list: List<*>, field: String): MessageValidatorBuilder { + require(list == list.sortedBy { it.toString() }) { "$field must be sorted" } + return this + } + private fun validBallotOptions(ballotOptions: List?): MessageValidatorBuilder { requireNotNull(ballotOptions) { "Ballot options cannot be null" } listNotEmpty(ballotOptions) diff --git a/fe2-android/app/src/main/java/com/github/dedis/popstellar/utility/handler/data/RollCallHandler.kt b/fe2-android/app/src/main/java/com/github/dedis/popstellar/utility/handler/data/RollCallHandler.kt index 5d24e57512..ff7680df58 100644 --- a/fe2-android/app/src/main/java/com/github/dedis/popstellar/utility/handler/data/RollCallHandler.kt +++ b/fe2-android/app/src/main/java/com/github/dedis/popstellar/utility/handler/data/RollCallHandler.kt @@ -125,8 +125,18 @@ constructor( val updateId = closeRollCall.updateId val closes = closeRollCall.closes val existingRollCall = rollCallRepo.getRollCallWithId(laoView.id, closes) - val currentAttendees = existingRollCall.attendees - currentAttendees.addAll(closeRollCall.attendees) + + val currentAttendees: Set + if (closeRollCall.attendees.containsAll(existingRollCall.attendees)) { + // closeRollCall.attendees is sorted, so we prefer to use it if we can + currentAttendees = closeRollCall.attendees.toMutableSet() + } else { + // if both lists have different attendees, we merge them even though we lose the order + // We are not ordering it because it is important to keep the order that we received to know + // if we face de-anonymization + currentAttendees = existingRollCall.attendees + currentAttendees.addAll(closeRollCall.attendees) + } val builder = RollCallBuilder() builder @@ -142,7 +152,6 @@ constructor( .setEnd(closeRollCall.closedAt) val laoId = laoView.id val rollCall = builder.build() - witnessingRepo.addWitnessMessage(laoId, closeRollCallWitnessMessage(messageId, rollCall)) if (witnessingRepo.areWitnessesEmpty(laoId)) { addRollCallRoutine(rollCallRepo, digitalCashRepo, laoId, rollCall) diff --git a/fe2-android/app/src/main/res/values/strings.xml b/fe2-android/app/src/main/res/values/strings.xml index aa7253eec5..b32b592019 100644 --- a/fe2-android/app/src/main/res/values/strings.xml +++ b/fe2-android/app/src/main/res/values/strings.xml @@ -176,6 +176,8 @@ Scanned tokens : %1$d Description Location +Attendees list is not sorted, risk of de-anonymization" + Meeting diff --git a/fe2-android/app/src/test/framework/common/java/com/github/dedis/popstellar/testutils/pages/lao/event/rollcall/RollCallFragmentPageObject.java b/fe2-android/app/src/test/framework/common/java/com/github/dedis/popstellar/testutils/pages/lao/event/rollcall/RollCallFragmentPageObject.java index 84d372151b..56f271b7ae 100644 --- a/fe2-android/app/src/test/framework/common/java/com/github/dedis/popstellar/testutils/pages/lao/event/rollcall/RollCallFragmentPageObject.java +++ b/fe2-android/app/src/test/framework/common/java/com/github/dedis/popstellar/testutils/pages/lao/event/rollcall/RollCallFragmentPageObject.java @@ -17,7 +17,6 @@ private RollCallFragmentPageObject() { public static ViewInteraction rollCallTitle() { return onView(withId(R.id.roll_call_fragment_title)); } - public static ViewInteraction rollCallStatusText() { return onView(withId(R.id.roll_call_status)); } diff --git a/fe2-android/app/src/test/framework/robolectric/java/com/github/dedis/popstellar/testutils/UITestUtils.kt b/fe2-android/app/src/test/framework/robolectric/java/com/github/dedis/popstellar/testutils/UITestUtils.kt index c3c5febb67..dd4bbb0932 100644 --- a/fe2-android/app/src/test/framework/robolectric/java/com/github/dedis/popstellar/testutils/UITestUtils.kt +++ b/fe2-android/app/src/test/framework/robolectric/java/com/github/dedis/popstellar/testutils/UITestUtils.kt @@ -43,6 +43,24 @@ object UITestUtils { Assert.assertEquals(expected, ShadowToast.getTextOfLatestToast()) } + /** + * Assert that the latest toast was shown with the expected text + * + * @param resId expected + * @param args arguments to the resource + */ + @JvmStatic + fun assertToastDisplayedHasNotText(@StringRes resId: Int, vararg args: Any?) { + MatcherAssert.assertThat( + "No toast was displayed", + ShadowToast.getLatestToast(), + Matchers.notNullValue() + ) + + val expected = ApplicationProvider.getApplicationContext().getString(resId, *args) + Assert.assertNotEquals(expected, ShadowToast.getTextOfLatestToast()) + } + @JvmStatic fun assertToastIsDisplayedContainsText(@StringRes resId: Int, vararg args: Any?) { MatcherAssert.assertThat( diff --git a/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/InviteFragmentTest.kt b/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/InviteFragmentTest.kt index dbeb74ee95..350ce58274 100644 --- a/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/InviteFragmentTest.kt +++ b/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/InviteFragmentTest.kt @@ -2,10 +2,7 @@ package com.github.dedis.popstellar.ui.lao import android.content.ClipboardManager import android.content.Context -import android.widget.Button import androidx.arch.core.executor.testing.InstantTaskExecutorRule -import androidx.test.espresso.Espresso -import androidx.test.espresso.EspressoException import androidx.test.espresso.action.ViewActions import androidx.test.espresso.action.ViewActions.scrollTo import androidx.test.espresso.assertion.ViewAssertions @@ -20,13 +17,11 @@ import com.github.dedis.popstellar.testutils.BundleBuilder import com.github.dedis.popstellar.testutils.fragment.ActivityFragmentScenarioRule import com.github.dedis.popstellar.testutils.pages.lao.InviteFragmentPageObject import com.github.dedis.popstellar.testutils.pages.lao.LaoActivityPageObject -import com.github.dedis.popstellar.ui.lao.event.election.CastVoteOpenBallotFragmentTest import com.github.dedis.popstellar.utility.security.KeyManager import dagger.hilt.android.testing.BindValue import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest import junit.framework.TestCase -import javax.inject.Inject import org.junit.Rule import org.junit.Test import org.junit.rules.ExternalResource @@ -35,6 +30,7 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoTestRule +import javax.inject.Inject @SmallTest @HiltAndroidTest diff --git a/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/event/election/CastVoteOpenBallotFragmentTest.kt b/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/event/election/CastVoteOpenBallotFragmentTest.kt index 6177ee4f61..86360bda81 100644 --- a/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/event/election/CastVoteOpenBallotFragmentTest.kt +++ b/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/event/election/CastVoteOpenBallotFragmentTest.kt @@ -202,7 +202,7 @@ class CastVoteOpenBallotFragmentTest { private const val PLURALITY = "Plurality" private val laoSubject = BehaviorSubject.createDefault(LaoView(LAO)) private val ROLL_CALL = - RollCall("id", "id", "rc", 0L, 1L, 2L, EventState.CLOSED, HashSet(), "nowhere", "none") + RollCall("id", "id", "rc", 0L, 1L, 2L, EventState.CLOSED, LinkedHashSet(), "nowhere", "none") private val ELECTION_ID = generateElectionSetupId(LAO_ID, CREATION, TITLE) private val ELECTION_QUESTION_1 = ElectionQuestion( diff --git a/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/event/election/CastVoteSecretBallotFragmentTest.kt b/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/event/election/CastVoteSecretBallotFragmentTest.kt index a80a0943cd..530c7cf7dc 100644 --- a/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/event/election/CastVoteSecretBallotFragmentTest.kt +++ b/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/event/election/CastVoteSecretBallotFragmentTest.kt @@ -136,7 +136,7 @@ class CastVoteSecretBallotFragmentTest { private const val PLURALITY = "Plurality" private val laoSubject = BehaviorSubject.createDefault(LaoView(LAO)) private val ROLL_CALL = - RollCall("id", "id", "rc", 0L, 1L, 2L, EventState.CLOSED, HashSet(), "nowhere", "none") + RollCall("id", "id", "rc", 0L, 1L, 2L, EventState.CLOSED, LinkedHashSet(), "nowhere", "none") private val ELECTION_ID = generateElectionSetupId(LAO_ID, CREATION, TITLE) private val ELECTION_QUESTION_1 = ElectionQuestion( diff --git a/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/event/election/ElectionFragmentTest.kt b/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/event/election/ElectionFragmentTest.kt index a846b86a91..dabf57c127 100644 --- a/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/event/election/ElectionFragmentTest.kt +++ b/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/event/election/ElectionFragmentTest.kt @@ -66,7 +66,7 @@ class ElectionFragmentTest { START, END, EventState.CLOSED, - HashSet(), + LinkedHashSet(), LOCATION, ROLL_CALL_DESC ) diff --git a/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/event/eventlist/EventListAdapterTest.kt b/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/event/eventlist/EventListAdapterTest.kt index f3092831aa..017216528f 100644 --- a/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/event/eventlist/EventListAdapterTest.kt +++ b/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/event/eventlist/EventListAdapterTest.kt @@ -44,12 +44,12 @@ class EventListAdapterTest { 1L, 2L, EventState.CREATED, - HashSet(), + LinkedHashSet(), "not lausanne", "no" ) private val ROLL_CALL2 = - RollCall("12345", "12345", "Name", 2L, 3L, 4L, EventState.CREATED, HashSet(), "nowhere", "foo") + RollCall("12345", "12345", "Name", 2L, 3L, 4L, EventState.CREATED, LinkedHashSet(), "nowhere", "foo") private lateinit var events: Subject> diff --git a/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/event/rollcall/RollCallArrayAdapterTest.kt b/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/event/rollcall/RollCallArrayAdapterTest.kt index 15b69dda93..9846703e28 100644 --- a/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/event/rollcall/RollCallArrayAdapterTest.kt +++ b/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/event/rollcall/RollCallArrayAdapterTest.kt @@ -44,21 +44,21 @@ class RollCallArrayAdapterTest { val myToken = PoPToken(MY_PRIVATE_KEY, MY_PUBLIC_KEY) val otherToken = PoPToken(OTHER_PRIVATE_KEY, OTHER_PUBLIC_KEY) attendeesList = listOf(myToken.publicKey.encoded, otherToken.publicKey.encoded) - adapter = RollCallArrayAdapter(context, R.id.valid_token_layout_text, attendeesList, myToken) + adapter = RollCallArrayAdapter(context, R.id.valid_token_layout_text, attendeesList, myToken, mock(RollCallFragment::class.java)) mockView = TextView(context) val colorAccent = ContextCompat.getColor(context, R.color.textOnBackground) (mockView as TextView).setTextColor(colorAccent) } @Test - fun verify_our_token_is_highlighted() { + fun verifyOurTokenIsHighlighted() { val view = adapter.getView(0, mockView, mock(ViewGroup::class.java)) as TextView val color = ContextCompat.getColor(context, R.color.colorAccent) Assert.assertEquals(color, view.currentTextColor) } @Test - fun verify_other_token_is_not_highlighted() { + fun verifyOtherTokenIsNotHighlighted() { val view = adapter.getView(1, mockView, mock(ViewGroup::class.java)) as TextView val color = ContextCompat.getColor(context, R.color.textOnBackground) Assert.assertEquals(color, view.currentTextColor) diff --git a/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/event/rollcall/RollCallFragmentTest.kt b/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/event/rollcall/RollCallFragmentTest.kt index e1ecdd73af..06fd5b5e36 100644 --- a/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/event/rollcall/RollCallFragmentTest.kt +++ b/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/event/rollcall/RollCallFragmentTest.kt @@ -6,6 +6,7 @@ import androidx.test.espresso.assertion.ViewAssertions import androidx.test.espresso.matcher.ViewMatchers import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry +import com.github.dedis.popstellar.R import com.github.dedis.popstellar.model.objects.Lao import com.github.dedis.popstellar.model.objects.RollCall import com.github.dedis.popstellar.model.objects.RollCall.Companion.closeRollCall @@ -19,6 +20,7 @@ import com.github.dedis.popstellar.testutils.Base64DataUtils import com.github.dedis.popstellar.testutils.BundleBuilder import com.github.dedis.popstellar.testutils.MessageSenderHelper import com.github.dedis.popstellar.testutils.MockitoKotlinHelpers +import com.github.dedis.popstellar.testutils.UITestUtils import com.github.dedis.popstellar.testutils.fragment.ActivityFragmentScenarioRule import com.github.dedis.popstellar.testutils.pages.lao.LaoActivityPageObject import com.github.dedis.popstellar.testutils.pages.lao.event.rollcall.RollCallFragmentPageObject @@ -29,15 +31,11 @@ import com.github.dedis.popstellar.utility.Constants import com.github.dedis.popstellar.utility.error.UnknownLaoException import com.github.dedis.popstellar.utility.error.keys.KeyException import com.github.dedis.popstellar.utility.security.KeyManager +import com.google.android.gms.common.util.CollectionUtils.listOf import dagger.hilt.android.testing.BindValue import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest import io.reactivex.subjects.BehaviorSubject -import java.text.DateFormat -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale -import javax.inject.Inject import org.junit.Assert import org.junit.Rule import org.junit.Test @@ -48,6 +46,12 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoTestRule +import java.text.DateFormat +import java.text.SimpleDateFormat +import java.util.Collections +import java.util.Date +import java.util.Locale +import javax.inject.Inject @HiltAndroidTest @RunWith(AndroidJUnit4::class) @@ -61,9 +65,9 @@ class RollCallFragmentTest { ROLL_CALL_START, ROLL_CALL_END, EventState.CREATED, - HashSet(), + LinkedHashSet(), LOCATION, - ROLL_CALL_EMPTY_DESC + ROLL_CALL_EMPTY_DESC, ) private val ROLL_CALL_2 = RollCall( @@ -74,9 +78,37 @@ class RollCallFragmentTest { ROLL_CALL_START + 3, ROLL_CALL_END + 3, EventState.CREATED, - HashSet(), + LinkedHashSet(), + LOCATION, + ROLL_CALL_DESC, + ) + + private val ROLL_CALL_SORTED_ATTENDEES = + RollCall( + LAO.id + "3", + LAO.id + "3", + ROLL_CALL_TITLE + "3", + CREATION, + ROLL_CALL_START, + ROLL_CALL_END, + EventState.OPENED, + validAttendees, LOCATION, - ROLL_CALL_DESC + ROLL_CALL_EMPTY_DESC, + ) + + private val ROLL_CALL_UNSORTED_ATTENDEES = + RollCall( + LAO.id + "4", + LAO.id + "4", + ROLL_CALL_TITLE + "4", + CREATION, + ROLL_CALL_START, + ROLL_CALL_END, + EventState.OPENED, + LinkedHashSet(unSortedAttendees), + LOCATION, + ROLL_CALL_EMPTY_DESC, ) @Inject lateinit var rollCallRepo: RollCallRepository @@ -488,6 +520,44 @@ class RollCallFragmentTest { ) } + @Test + fun sortedAttendeeListShowsNoToast() { + rollCallRepo.updateRollCall(LAO_ID, ROLL_CALL_SORTED_ATTENDEES) + rollCallRepo.updateRollCall(LAO_ID, closeRollCall(ROLL_CALL_SORTED_ATTENDEES)) + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + openRollCallWithDescription(ROLL_CALL_SORTED_ATTENDEES) + + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + + RollCallFragmentPageObject.rollCallListAttendees() + .check( + ViewAssertions.matches( + ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE) + ) + ) + + UITestUtils.assertToastDisplayedHasNotText(R.string.roll_call_attendees_list_not_sorted) + } + + @Test + fun unSortedAttendeeListShowsToast() { + rollCallRepo.updateRollCall(LAO_ID, ROLL_CALL_UNSORTED_ATTENDEES) + rollCallRepo.updateRollCall(LAO_ID, closeRollCall(ROLL_CALL_UNSORTED_ATTENDEES)) + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + openRollCallWithDescription(ROLL_CALL_UNSORTED_ATTENDEES) + + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + + RollCallFragmentPageObject.rollCallListAttendees() + .check( + ViewAssertions.matches( + ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE) + ) + ) + + UITestUtils.assertToastIsDisplayedWithText(R.string.roll_call_attendees_list_not_sorted) + } + /** Utility function to create a LAO when the user is not the organizer */ @Throws(UnknownLaoException::class) private fun fakeClientLao() { @@ -495,14 +565,20 @@ class RollCallFragmentTest { Mockito.`when`(laoRepo.getLaoView(MockitoKotlinHelpers.any())).thenAnswer { LaoView(LAO_2) } rollCallRepo.updateRollCall(LAO_ID2, ROLL_CALL) rollCallRepo.updateRollCall(LAO_ID2, ROLL_CALL_2) + rollCallRepo.updateRollCall(LAO_ID2, ROLL_CALL_SORTED_ATTENDEES) + rollCallRepo.updateRollCall(LAO_ID2, ROLL_CALL_UNSORTED_ATTENDEES) Mockito.`when`(keyManager.mainPublicKey).thenReturn(SENDER_2) } /** Utility function to open the fragment of an alternative roll call */ private fun openRollCallWithDescription() { + openRollCallWithDescription(ROLL_CALL_2) + } + + private fun openRollCallWithDescription(rollCall: RollCall) { activityScenarioRule.scenario.onActivity { activity: LaoActivity -> setCurrentFragment(activity.supportFragmentManager, RollCallFragmentPageObject.fragmentId()) { - newInstance(ROLL_CALL_2.persistentId) + newInstance(rollCall.persistentId) } } } @@ -527,5 +603,22 @@ class RollCallFragmentTest { private val laoSubject2 = BehaviorSubject.createDefault(LaoView(LAO_2)) private val DATE_FORMAT: DateFormat = SimpleDateFormat("dd/MM/yyyy HH:mm z", Locale.ENGLISH) private val POP_TOKEN = Base64DataUtils.generatePoPToken() + + private val attendees = listOf( + Base64DataUtils.generatePublicKey(), + Base64DataUtils.generatePublicKey(), + Base64DataUtils.generatePublicKey(), + Base64DataUtils.generatePublicKey(), + Base64DataUtils.generatePublicKey(), + Base64DataUtils.generatePublicKey() + ) + + private val validAttendees = LinkedHashSet(attendees.sortedBy { it.toString() }) + + private val unSortedAttendees = ArrayList(validAttendees).toMutableList().apply { } + + init { + Collections.swap(unSortedAttendees, 0, 1) + } } } diff --git a/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/popcha/PoPCHAHomeFragmentTest.kt b/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/popcha/PoPCHAHomeFragmentTest.kt index 88189baf9f..681309e56a 100644 --- a/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/popcha/PoPCHAHomeFragmentTest.kt +++ b/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/popcha/PoPCHAHomeFragmentTest.kt @@ -95,7 +95,7 @@ class PoPCHAHomeFragmentTest { 1632204910, 1632204900, EventState.CLOSED, - attendees, + LinkedHashSet(attendees), "bc", "" ) diff --git a/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/socialmedia/ChirpListAdapterTest.kt b/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/socialmedia/ChirpListAdapterTest.kt index 5661fd8f46..23bbdbc216 100644 --- a/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/socialmedia/ChirpListAdapterTest.kt +++ b/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/socialmedia/ChirpListAdapterTest.kt @@ -62,7 +62,7 @@ class ChirpListAdapterTest { TIMESTAMP_1, TIMESTAMP_2, EventState.CLOSED, - HashSet(), + LinkedHashSet(), "", "", ) diff --git a/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/socialmedia/ChirpListFragmentTest.kt b/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/socialmedia/ChirpListFragmentTest.kt index fc6b394570..8ae049821c 100644 --- a/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/socialmedia/ChirpListFragmentTest.kt +++ b/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/socialmedia/ChirpListFragmentTest.kt @@ -64,7 +64,7 @@ class ChirpListFragmentTest { TIMESTAMP_1, TIMESTAMP_2, EventState.CLOSED, - HashSet(), + LinkedHashSet(), "", "", ) diff --git a/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/socialmedia/SocialMediaHomeFragmentTest.kt b/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/socialmedia/SocialMediaHomeFragmentTest.kt index d4c6da10a4..3d4f78cfd0 100644 --- a/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/socialmedia/SocialMediaHomeFragmentTest.kt +++ b/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/socialmedia/SocialMediaHomeFragmentTest.kt @@ -59,7 +59,7 @@ class SocialMediaHomeFragmentTest { CREATION_TIME, CREATION_TIME, EventState.CLOSED, - HashSet(), + LinkedHashSet(), "", "", ) diff --git a/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/token/TokenFragmentTest.kt b/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/token/TokenFragmentTest.kt index 6aeaf372eb..f721fa8766 100644 --- a/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/token/TokenFragmentTest.kt +++ b/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/token/TokenFragmentTest.kt @@ -51,7 +51,7 @@ class TokenFragmentTest { ROLL_CALL_START, ROLL_CALL_END, EventState.CLOSED, - HashSet(), + LinkedHashSet(), LOCATION, ROLL_CALL_DESC ) diff --git a/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/token/TokenListFragmentTest.kt b/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/token/TokenListFragmentTest.kt index b06cfb8d13..08a949d60e 100644 --- a/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/token/TokenListFragmentTest.kt +++ b/fe2-android/app/src/test/ui/robolectric/com/github/dedis/popstellar/ui/lao/token/TokenListFragmentTest.kt @@ -54,7 +54,7 @@ class TokenListFragmentTest { ROLL_CALL_START, ROLL_CALL_END, EventState.CREATED, - HashSet(), + LinkedHashSet(), LOCATION, ROLL_CALL_DESC ) diff --git a/fe2-android/app/src/test/unit/java/com/github/dedis/popstellar/model/network/method/message/data/rollcall/CloseRollCallTest.kt b/fe2-android/app/src/test/unit/java/com/github/dedis/popstellar/model/network/method/message/data/rollcall/CloseRollCallTest.kt index 376ea63c77..d6360945c0 100644 --- a/fe2-android/app/src/test/unit/java/com/github/dedis/popstellar/model/network/method/message/data/rollcall/CloseRollCallTest.kt +++ b/fe2-android/app/src/test/unit/java/com/github/dedis/popstellar/model/network/method/message/data/rollcall/CloseRollCallTest.kt @@ -6,13 +6,20 @@ 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.testutils.Base64DataUtils +import com.github.dedis.popstellar.utility.MessageValidator import com.github.dedis.popstellar.utility.security.HashSHA256.hash -import java.time.Instant +import com.google.android.gms.common.util.CollectionUtils.listOf +import junit.framework.TestCase.assertNotNull import org.hamcrest.CoreMatchers import org.hamcrest.MatcherAssert import org.junit.Assert import org.junit.Test import org.junit.runner.RunWith +import java.time.Instant +import java.util.ArrayList +import java.util.Collections @RunWith(AndroidJUnit4::class) class CloseRollCallTest { @@ -80,6 +87,55 @@ class CloseRollCallTest { ) } + @Test + fun constructorSucceedsWithValidData() { + val closeRollCall = CloseRollCall(validBase64LaoId, validBase64Closes, pastTime, validAttendees) + assertNotNull(closeRollCall) + } + + @Test(expected = IllegalArgumentException::class) + fun constructorFailsWhenAttendeesNotSorted() { + val unsortedAttendees = ArrayList(attendees).toMutableList() + Collections.swap(unsortedAttendees, 0, 1) + + CloseRollCall(validBase64LaoId, validBase64Closes, pastTime, unsortedAttendees) + } + + @Test(expected = IllegalArgumentException::class) + fun constructorFailsWhenClosedAtInFuture() { + CloseRollCall(validBase64LaoId, validBase64Closes, futureTime, validAttendees) + } + + @Test(expected = IllegalArgumentException::class) + fun constructorFailsWhenTooLongPastTime() { + CloseRollCall(validBase64LaoId, validBase64Closes, tooLongPastTime, validAttendees) + } + + @Test(expected = IllegalArgumentException::class) + fun constructorFailsWhenLaoIdNotBase64() { + CloseRollCall(invalidBase64, validBase64Closes, pastTime, validAttendees) + } + + @Test(expected = IllegalArgumentException::class) + fun constructorFailsWhenLaoIdEmpty() { + CloseRollCall(emptyBase64, validBase64Closes, pastTime, validAttendees) + } + + @Test(expected = IllegalArgumentException::class) + fun constructorFailsWhenClosesNotBase64() { + CloseRollCall(validBase64LaoId, invalidBase64, pastTime, validAttendees) + } + + @Test(expected = IllegalArgumentException::class) + fun constructorFailsWhenClosesEmpty() { + CloseRollCall(validBase64LaoId, emptyBase64, pastTime, validAttendees) + } + + @Test(expected = IllegalArgumentException::class) + fun constructorFailsWhenClosedAtNegative() { + CloseRollCall(validBase64LaoId, validBase64Closes, negativeClosedAt, validAttendees) + } + companion object { private val LAO_ID = hash("LAO_ID") private const val NAME = "NAME" @@ -88,5 +144,27 @@ class CloseRollCallTest { 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 CLOSE_ROLL_CALL = CloseRollCall(LAO_ID, OPEN_ROLL_CALL.updateId, TIME, ArrayList()) + + private val attendees = listOf( + Base64DataUtils.generatePublicKey(), + Base64DataUtils.generatePublicKey(), + Base64DataUtils.generatePublicKey(), + Base64DataUtils.generatePublicKey(), + Base64DataUtils.generatePublicKey(), + Base64DataUtils.generatePublicKey() + ) + + private val validAttendees = attendees.sortedBy { it.toString() } + + private val pastTime = Instant.now().epochSecond - 1000 + private val futureTime = Instant.now().epochSecond + 10000 + private val tooLongPastTime = Instant.now().epochSecond - MessageValidator.MessageValidatorBuilder.VALID_PAST_DELAY + private val negativeClosedAt = (-1).toLong() + + private val invalidBase64 = "invalidBase64String" + private val emptyBase64 = Base64URLData("".toByteArray()).encoded + private val validBase64LaoId = Base64URLData("validLaoId".toByteArray()).encoded + private val validBase64Closes = Base64URLData("validCloses".toByteArray()).encoded + } } diff --git a/fe2-android/app/src/test/unit/java/com/github/dedis/popstellar/repository/WitnessingRepositoryTest.kt b/fe2-android/app/src/test/unit/java/com/github/dedis/popstellar/repository/WitnessingRepositoryTest.kt index b2475b164b..73dc487768 100644 --- a/fe2-android/app/src/test/unit/java/com/github/dedis/popstellar/repository/WitnessingRepositoryTest.kt +++ b/fe2-android/app/src/test/unit/java/com/github/dedis/popstellar/repository/WitnessingRepositoryTest.kt @@ -194,7 +194,7 @@ class WitnessingRepositoryTest { CREATION + 10, CREATION + 20, EventState.CREATED, - HashSet(), + LinkedHashSet(), "loc", "" ) diff --git a/fe2-android/app/src/test/unit/java/com/github/dedis/popstellar/repository/database/RollCallDatabaseTest.kt b/fe2-android/app/src/test/unit/java/com/github/dedis/popstellar/repository/database/RollCallDatabaseTest.kt index af39117bb0..5784cbcd07 100644 --- a/fe2-android/app/src/test/unit/java/com/github/dedis/popstellar/repository/database/RollCallDatabaseTest.kt +++ b/fe2-android/app/src/test/unit/java/com/github/dedis/popstellar/repository/database/RollCallDatabaseTest.kt @@ -65,7 +65,7 @@ class RollCallDatabaseTest { CREATION + 10, CREATION + 20, EventState.CREATED, - HashSet(), + LinkedHashSet(), "loc", "" ) diff --git a/fe2-android/app/src/test/unit/java/com/github/dedis/popstellar/repository/database/WitnessDatabaseTest.kt b/fe2-android/app/src/test/unit/java/com/github/dedis/popstellar/repository/database/WitnessDatabaseTest.kt index 597490f2d2..a633c07289 100644 --- a/fe2-android/app/src/test/unit/java/com/github/dedis/popstellar/repository/database/WitnessDatabaseTest.kt +++ b/fe2-android/app/src/test/unit/java/com/github/dedis/popstellar/repository/database/WitnessDatabaseTest.kt @@ -212,7 +212,7 @@ class WitnessDatabaseTest { CREATION + 10, CREATION + 20, EventState.CREATED, - HashSet(), + LinkedHashSet(), "loc", "" ) diff --git a/fe2-android/app/src/test/unit/java/com/github/dedis/popstellar/utility/handler/RollCallHandlerTest.kt b/fe2-android/app/src/test/unit/java/com/github/dedis/popstellar/utility/handler/RollCallHandlerTest.kt index baab9bdaf7..2f7284a074 100644 --- a/fe2-android/app/src/test/unit/java/com/github/dedis/popstellar/utility/handler/RollCallHandlerTest.kt +++ b/fe2-android/app/src/test/unit/java/com/github/dedis/popstellar/utility/handler/RollCallHandlerTest.kt @@ -185,7 +185,7 @@ class RollCallHandlerTest { now + 1, now + 2, EventState.CREATED, - HashSet(), + LinkedHashSet(), "somewhere", "desc" ) diff --git a/fe2-android/app/src/test/unit/java/com/github/dedis/popstellar/utility/security/KeyManagerTest.kt b/fe2-android/app/src/test/unit/java/com/github/dedis/popstellar/utility/security/KeyManagerTest.kt index 7ff8a0264a..949ce5edd8 100644 --- a/fe2-android/app/src/test/unit/java/com/github/dedis/popstellar/utility/security/KeyManagerTest.kt +++ b/fe2-android/app/src/test/unit/java/com/github/dedis/popstellar/utility/security/KeyManagerTest.kt @@ -106,7 +106,7 @@ class KeyManagerTest { creation1 + 1, creation1 + 75, EventState.CLOSED, - HashSet(), + LinkedHashSet(), "location", "desc" ) @@ -119,7 +119,7 @@ class KeyManagerTest { creation2 + 1, creation2 + 75, EventState.CLOSED, - HashSet(), + LinkedHashSet(), "EPFL", "do not come" ) @@ -160,7 +160,7 @@ class KeyManagerTest { (5421364 + 1).toLong(), (5421364 + 145).toLong(), EventState.CLOSED, - HashSet(), + LinkedHashSet(), "ETHZ", "do come" )