Merge branch 'develop' into feature/aris/thread_live_thread_list

This commit is contained in:
ariskotsomitopoulos 2022-03-14 12:02:04 +01:00
commit d215f03798
26 changed files with 385 additions and 153 deletions

View file

@ -14,50 +14,6 @@ env:
-Porg.gradle.jvmargs=-Xmx4g
-Porg.gradle.parallel=false
jobs:
# Build Android Tests [Matrix SDK]
build-android-test-matrix-sdk:
name: Matrix SDK - Build Android Tests
runs-on: macos-latest
# No concurrency required, runs every time on a schedule.
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: 11
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Build Android Tests for matrix-sdk-android
run: ./gradlew clean matrix-sdk-android:assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES --stacktrace
# Build Android Tests [Matrix APP]
build-android-test-app:
name: App - Build Android Tests
runs-on: macos-latest
# No concurrency required, runs every time on a schedule.
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: 11
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Build Android Tests for vector
run: ./gradlew clean vector:assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES --stacktrace
# Run Android Tests
integration-tests:
name: Matrix SDK - Running Integration Tests
@ -367,9 +323,6 @@ jobs:
needs:
- integration-tests
- ui-tests
# - unit-tests
- build-android-test-matrix-sdk
- build-android-test-app
- sonarqube
if: always() && github.event_name != 'workflow_dispatch'
# No concurrency required, runs every time on a schedule.

View file

@ -12,6 +12,30 @@ env:
-Porg.gradle.parallel=false
jobs:
# Build Android Tests
build-android-tests:
name: Build Android Tests
runs-on: ubuntu-latest
concurrency:
group: ${{ github.ref == 'refs/heads/main' && format('unit-tests-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('unit-tests-develop-{0}', github.sha) || format('build-android-tests-{0}', github.ref) }}
cancel-in-progress: true
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: 11
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Build Android Tests
run: ./gradlew clean assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES --stacktrace
unit-tests:
name: Run Unit Tests
runs-on: ubuntu-latest
@ -41,3 +65,20 @@ jobs:
( github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository )
with:
files: ./**/build/test-results/**/*.xml
# Notify the channel about runs against develop or main that have failures, as PRs should have caught these first.
notify:
runs-on: ubuntu-latest
needs:
- unit-tests
- build-android-tests
if: ${{ (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/main' ) && failure() }}
steps:
- uses: michaelkaye/matrix-hookshot-action@v0.3.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
matrix_access_token: ${{ secrets.ELEMENT_ANDROID_NOTIFICATION_ACCESS_TOKEN }}
matrix_room_id: ${{ secrets.ELEMENT_ANDROID_INTERNAL_ROOM_ID }}
text_template: "Build is broken for ${{ github.ref }}: {{#each job_statuses }}{{#with this }}{{#if completed }}{{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}"
html_template: "Build is broken for ${{ github.ref }}: {{#each job_statuses }}{{#with this }}{{#if completed }}<br />{{icon conclusion }} {{name}} <font color='{{color conclusion }}'>{{conclusion}} at {{completed_at}} <a href=\"{{html_url}}\">[details]</a></font>{{/if}}{{/with}}{{/each}}"

View file

@ -11,6 +11,7 @@
<w>emoji</w>
<w>emojis</w>
<w>fdroid</w>
<w>ganfra</w>
<w>gplay</w>
<w>hmac</w>
<w>homeserver</w>
@ -18,6 +19,7 @@
<w>ktlint</w>
<w>linkified</w>
<w>linkify</w>
<w>manu</w>
<w>megolm</w>
<w>msisdn</w>
<w>msisdns</w>

1
changelog.d/4860.bugfix Normal file
View file

@ -0,0 +1 @@
Add colors for shield vector drawable

1
changelog.d/5443.misc Normal file
View file

@ -0,0 +1 @@
Adds stable room hierarchy endpoint with a fallback to the unstable one

View file

@ -126,4 +126,10 @@
<attr name="vctr_live_location" format="color" />
<color name="vctr_live_location_light">@color/palette_prune</color>
<color name="vctr_live_location_dark">@color/palette_prune</color>
<!-- Shield colors -->
<color name="shield_color_trust">#0DBD8B</color>
<color name="shield_color_black">#17191C</color>
<color name="shield_color_warning">#FF4B55</color>
</resources>

View file

@ -166,7 +166,7 @@ dependencies {
implementation libs.apache.commonsImaging
// Phone number https://github.com/google/libphonenumber
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.44'
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.45'
testImplementation libs.tests.junit
testImplementation 'org.robolectric:robolectric:4.7.3'
@ -174,7 +174,7 @@ dependencies {
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
testImplementation libs.mockk.mockk
testImplementation libs.tests.kluent
implementation libs.jetbrains.coroutinesAndroid
testImplementation libs.jetbrains.coroutinesTest
// Plant Timber tree for test
testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
// Transitively required for mocking realm as monarchy doesn't expose Rx

View file

@ -41,6 +41,7 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.CryptoTestHelper
import org.matrix.android.sdk.common.SessionTestParams
import org.matrix.android.sdk.common.TestConstants
import org.matrix.android.sdk.common.TestMatrixCallback
import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
@ -99,7 +100,6 @@ class E2eeSanityTests : InstrumentedTest {
ensureMembersHaveJoined(aliceSession, otherAccounts, e2eRoomID)
Log.v("#E2E TEST", "All users have joined the room")
Log.v("#E2E TEST", "Alice is sending the message")
val text = "This is my message"
@ -172,7 +172,7 @@ class E2eeSanityTests : InstrumentedTest {
}
timelineEvent != null &&
timelineEvent.root.getClearType() == EventType.MESSAGE &&
secondMessage.equals(timelineEvent.root.getClearContent().toModel<MessageContent>()?.body)
secondMessage == timelineEvent.root.getClearContent().toModel<MessageContent>()?.body
}
}
}
@ -186,11 +186,11 @@ class E2eeSanityTests : InstrumentedTest {
}
/**
* Quick test for basic keybackup
* Quick test for basic key backup
* 1. Create e2e between Alice and Bob
* 2. Alice sends 3 messages, using 3 different sessions
* 3. Ensure bob can decrypt
* 4. Create backup for bob and uplaod keys
* 4. Create backup for bob and upload keys
*
* 5. Sign out alice and bob to ensure no gossiping will happen
*
@ -206,14 +206,14 @@ class E2eeSanityTests : InstrumentedTest {
val bobSession = cryptoTestData.secondSession!!
val e2eRoomID = cryptoTestData.roomId
Log.v("#E2E TEST", "Create and start keybackup for bob ...")
val keysBackupService = bobSession.cryptoService().keysBackupService()
Log.v("#E2E TEST", "Create and start key backup for bob ...")
val bobKeysBackupService = bobSession.cryptoService().keysBackupService()
val keyBackupPassword = "FooBarBaz"
val megolmBackupCreationInfo = testHelper.doSync<MegolmBackupCreationInfo> {
keysBackupService.prepareKeysBackupVersion(keyBackupPassword, null, it)
bobKeysBackupService.prepareKeysBackupVersion(keyBackupPassword, null, it)
}
val version = testHelper.doSync<KeysVersion> {
keysBackupService.createKeysBackupVersion(megolmBackupCreationInfo, it)
bobKeysBackupService.createKeysBackupVersion(megolmBackupCreationInfo, it)
}
Log.v("#E2E TEST", "... Key backup started and enabled for bob")
// Bob session should now have
@ -249,12 +249,12 @@ class E2eeSanityTests : InstrumentedTest {
Log.v("#E2E TEST", "Force key backup for Bob...")
testHelper.waitWithLatch { latch ->
keysBackupService.backupAllGroupSessions(
bobKeysBackupService.backupAllGroupSessions(
null,
TestMatrixCallback(latch, true)
)
}
Log.v("#E2E TEST", "... Keybackup done for Bob")
Log.v("#E2E TEST", "... Key backup done for Bob")
// Now lets logout both alice and bob to ensure that we won't have any gossiping
@ -397,7 +397,7 @@ class E2eeSanityTests : InstrumentedTest {
}
/**
* Test that if a better key is forwared (lower index, it is then used)
* Test that if a better key is forwarded (lower index, it is then used)
*/
@Test
fun testForwardBetterKey() {
@ -525,15 +525,16 @@ class E2eeSanityTests : InstrumentedTest {
private fun sendMessageInRoom(aliceRoomPOV: Room, text: String): String? {
aliceRoomPOV.sendTextMessage(text)
var sentEventId: String? = null
testHelper.waitWithLatch(4 * 60_000) {
testHelper.waitWithLatch(4 * TestConstants.timeOutMillis) { latch ->
val timeline = aliceRoomPOV.createTimeline(null, TimelineSettings(60))
timeline.start()
testHelper.retryPeriodicallyWithLatch(it) {
testHelper.retryPeriodicallyWithLatch(latch) {
val decryptedMsg = timeline.getSnapshot()
.filter { it.root.getClearType() == EventType.MESSAGE }
.also {
Log.v("#E2E TEST", "Timeline snapshot is ${it.map { "${it.root.type}|${it.root.sendState}" }.joinToString(",", "[", "]")}")
.also { list ->
val message = list.joinToString(",", "[", "]") { "${it.root.type}|${it.root.sendState}" }
Log.v("#E2E TEST", "Timeline snapshot is $message")
}
.filter { it.root.sendState == SendState.SYNCED }
.firstOrNull { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(text) == true }
@ -547,8 +548,8 @@ class E2eeSanityTests : InstrumentedTest {
}
private fun ensureMembersHaveJoined(aliceSession: Session, otherAccounts: List<Session>, e2eRoomID: String) {
testHelper.waitWithLatch {
testHelper.retryPeriodicallyWithLatch(it) {
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
otherAccounts.map {
aliceSession.getRoomMember(it.myUserId, e2eRoomID)?.membership
}.all {
@ -559,8 +560,8 @@ class E2eeSanityTests : InstrumentedTest {
}
private fun waitForAndAcceptInviteInRoom(otherSession: Session, e2eRoomID: String) {
testHelper.waitWithLatch {
testHelper.retryPeriodicallyWithLatch(it) {
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
val roomSummary = otherSession.getRoomSummary(e2eRoomID)
(roomSummary != null && roomSummary.membership == Membership.INVITE).also {
if (it) {

View file

@ -21,6 +21,7 @@ internal object NetworkConstants {
private const val URI_API_PREFIX_PATH = "_matrix/client"
const val URI_API_PREFIX_PATH_ = "$URI_API_PREFIX_PATH/"
const val URI_API_PREFIX_PATH_R0 = "$URI_API_PREFIX_PATH/r0/"
const val URI_API_PREFIX_PATH_V1 = "$URI_API_PREFIX_PATH/v1/"
const val URI_API_PREFIX_PATH_UNSTABLE = "$URI_API_PREFIX_PATH/unstable/"
// Media

View file

@ -113,71 +113,108 @@ internal class DefaultSpaceService @Inject constructor(
return peekSpaceTask.execute(PeekSpaceTask.Params(spaceId))
}
override suspend fun querySpaceChildren(spaceId: String,
suggestedOnly: Boolean?,
limit: Int?,
from: String?,
knownStateList: List<Event>?): SpaceHierarchyData {
return resolveSpaceInfoTask.execute(
ResolveSpaceInfoTask.Params(
spaceId = spaceId, limit = limit, maxDepth = 1, from = from, suggestedOnly = suggestedOnly
)
).let { response ->
val spaceDesc = response.rooms?.firstOrNull { it.roomId == spaceId }
val root = RoomSummary(
roomId = spaceDesc?.roomId ?: spaceId,
roomType = spaceDesc?.roomType,
name = spaceDesc?.name ?: "",
displayName = spaceDesc?.name ?: "",
topic = spaceDesc?.topic ?: "",
joinedMembersCount = spaceDesc?.numJoinedMembers,
avatarUrl = spaceDesc?.avatarUrl ?: "",
encryptionEventTs = null,
typingUsers = emptyList(),
isEncrypted = false,
flattenParentIds = emptyList(),
canonicalAlias = spaceDesc?.canonicalAlias,
joinRules = RoomJoinRules.PUBLIC.takeIf { spaceDesc?.worldReadable == true }
)
val children = response.rooms
?.filter { it.roomId != spaceId }
?.flatMap { childSummary ->
(spaceDesc?.childrenState ?: knownStateList)
?.filter { it.stateKey == childSummary.roomId && it.type == EventType.STATE_SPACE_CHILD }
?.mapNotNull { childStateEv ->
// create a child entry for everytime this room is the child of a space
// beware that a room could appear then twice in this list
childStateEv.content.toModel<SpaceChildContent>()?.let { childStateEvContent ->
SpaceChildInfo(
childRoomId = childSummary.roomId,
isKnown = true,
roomType = childSummary.roomType,
name = childSummary.name,
topic = childSummary.topic,
avatarUrl = childSummary.avatarUrl,
order = childStateEvContent.order,
// autoJoin = childStateEvContent.autoJoin ?: false,
viaServers = childStateEvContent.via.orEmpty(),
activeMemberCount = childSummary.numJoinedMembers,
parentRoomId = childStateEv.roomId,
suggested = childStateEvContent.suggested,
canonicalAlias = childSummary.canonicalAlias,
aliases = childSummary.aliases,
worldReadable = childSummary.worldReadable
)
}
}.orEmpty()
}
.orEmpty()
SpaceHierarchyData(
rootSummary = root,
children = children,
childrenState = spaceDesc?.childrenState.orEmpty(),
nextToken = response.nextBatch
)
}
override suspend fun querySpaceChildren(
spaceId: String,
suggestedOnly: Boolean?,
limit: Int?,
from: String?,
knownStateList: List<Event>?
): SpaceHierarchyData {
val spacesResponse = getSpacesResponse(spaceId, suggestedOnly, limit, from)
val spaceRootResponse = spacesResponse.getRoot(spaceId)
val spaceRoot = spaceRootResponse?.toRoomSummary() ?: createBlankRoomSummary(spaceId)
val spaceChildren = spacesResponse.rooms.mapSpaceChildren(spaceId, spaceRootResponse, knownStateList)
return SpaceHierarchyData(
rootSummary = spaceRoot,
children = spaceChildren,
childrenState = spaceRootResponse?.childrenState.orEmpty(),
nextToken = spacesResponse.nextBatch
)
}
private suspend fun getSpacesResponse(spaceId: String, suggestedOnly: Boolean?, limit: Int?, from: String?) =
resolveSpaceInfoTask.execute(
ResolveSpaceInfoTask.Params(spaceId = spaceId, limit = limit, maxDepth = 1, from = from, suggestedOnly = suggestedOnly)
)
private fun SpacesResponse.getRoot(spaceId: String) = rooms?.firstOrNull { it.roomId == spaceId }
private fun SpaceChildSummaryResponse.toRoomSummary() = RoomSummary(
roomId = roomId,
roomType = roomType,
name = name ?: "",
displayName = name ?: "",
topic = topic ?: "",
joinedMembersCount = numJoinedMembers,
avatarUrl = avatarUrl ?: "",
encryptionEventTs = null,
typingUsers = emptyList(),
isEncrypted = false,
flattenParentIds = emptyList(),
canonicalAlias = canonicalAlias,
joinRules = RoomJoinRules.PUBLIC.takeIf { isWorldReadable }
)
private fun createBlankRoomSummary(spaceId: String) = RoomSummary(
roomId = spaceId,
joinedMembersCount = null,
encryptionEventTs = null,
typingUsers = emptyList(),
isEncrypted = false,
flattenParentIds = emptyList(),
canonicalAlias = null,
joinRules = null
)
private fun List<SpaceChildSummaryResponse>?.mapSpaceChildren(
spaceId: String,
spaceRootResponse: SpaceChildSummaryResponse?,
knownStateList: List<Event>?,
) = this?.filterIdIsNot(spaceId)
?.toSpaceChildInfoList(spaceId, spaceRootResponse, knownStateList)
.orEmpty()
private fun List<SpaceChildSummaryResponse>.filterIdIsNot(spaceId: String) = filter { it.roomId != spaceId }
private fun List<SpaceChildSummaryResponse>.toSpaceChildInfoList(
spaceId: String,
rootRoomResponse: SpaceChildSummaryResponse?,
knownStateList: List<Event>?,
) = flatMap { spaceChildSummary ->
(rootRoomResponse?.childrenState ?: knownStateList)
?.filter { it.isChildOf(spaceChildSummary) }
?.mapNotNull { childStateEvent -> childStateEvent.toSpaceChildInfo(spaceId, spaceChildSummary) }
.orEmpty()
}
private fun Event.isChildOf(space: SpaceChildSummaryResponse) = stateKey == space.roomId && type == EventType.STATE_SPACE_CHILD
private fun Event.toSpaceChildInfo(spaceId: String, summary: SpaceChildSummaryResponse) = content.toModel<SpaceChildContent>()?.let { content ->
createSpaceChildInfo(spaceId, summary, content)
}
private fun createSpaceChildInfo(
spaceId: String,
summary: SpaceChildSummaryResponse,
content: SpaceChildContent
) = SpaceChildInfo(
childRoomId = summary.roomId,
isKnown = true,
roomType = summary.roomType,
name = summary.name,
topic = summary.topic,
avatarUrl = summary.avatarUrl,
order = content.order,
viaServers = content.via.orEmpty(),
activeMemberCount = summary.numJoinedMembers,
parentRoomId = spaceId,
suggested = content.suggested,
canonicalAlias = summary.canonicalAlias,
aliases = summary.aliases,
worldReadable = summary.isWorldReadable
)
override suspend fun joinSpace(spaceIdOrAlias: String,
reason: String?,
viaServers: List<String>): JoinSpaceResult {
@ -192,10 +229,6 @@ internal class DefaultSpaceService @Inject constructor(
leaveRoomTask.execute(LeaveRoomTask.Params(spaceId, reason))
}
// override fun getSpaceParentsOfRoom(roomId: String): List<SpaceSummary> {
// return spaceSummaryDataSource.getParentsOfRoom(roomId)
// }
override suspend fun setSpaceParent(childRoomId: String, parentSpaceId: String, canonical: Boolean, viaServers: List<String>) {
// Should we perform some validation here?,
// and if client want to bypass, it could use sendStateEvent directly?

View file

@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.space
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.task.Task
import retrofit2.HttpException
import javax.inject.Inject
internal interface ResolveSpaceInfoTask : Task<ResolveSpaceInfoTask.Params, SpacesResponse> {
@ -28,7 +29,6 @@ internal interface ResolveSpaceInfoTask : Task<ResolveSpaceInfoTask.Params, Spac
val maxDepth: Int?,
val from: String?,
val suggestedOnly: Boolean?
// val autoJoinOnly: Boolean?
)
}
@ -36,14 +36,30 @@ internal class DefaultResolveSpaceInfoTask @Inject constructor(
private val spaceApi: SpaceApi,
private val globalErrorReceiver: GlobalErrorReceiver
) : ResolveSpaceInfoTask {
override suspend fun execute(params: ResolveSpaceInfoTask.Params): SpacesResponse {
return executeRequest(globalErrorReceiver) {
override suspend fun execute(params: ResolveSpaceInfoTask.Params) = executeRequest(globalErrorReceiver) {
try {
getSpaceHierarchy(params)
} catch (e: HttpException) {
getUnstableSpaceHierarchy(params)
}
}
private suspend fun getSpaceHierarchy(params: ResolveSpaceInfoTask.Params) =
spaceApi.getSpaceHierarchy(
spaceId = params.spaceId,
suggestedOnly = params.suggestedOnly,
limit = params.limit,
maxDepth = params.maxDepth,
from = params.from)
}
}
from = params.from,
)
private suspend fun getUnstableSpaceHierarchy(params: ResolveSpaceInfoTask.Params) =
spaceApi.getSpaceHierarchyUnstable(
spaceId = params.spaceId,
suggestedOnly = params.suggestedOnly,
limit = params.limit,
maxDepth = params.maxDepth,
from = params.from,
)
}

View file

@ -31,11 +31,22 @@ internal interface SpaceApi {
* @param from: Optional. Pagination token given to retrieve the next set of rooms.
* Note that if a pagination token is provided, then the parameters given for suggested_only and max_depth must be the same.
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2946/rooms/{roomId}/hierarchy")
@GET(NetworkConstants.URI_API_PREFIX_PATH_V1 + "rooms/{roomId}/hierarchy")
suspend fun getSpaceHierarchy(
@Path("roomId") spaceId: String,
@Query("suggested_only") suggestedOnly: Boolean?,
@Query("limit") limit: Int?,
@Query("max_depth") maxDepth: Int?,
@Query("from") from: String?): SpacesResponse
/**
* Unstable version of [getSpaceHierarchy]
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2946/rooms/{roomId}/hierarchy")
suspend fun getSpaceHierarchyUnstable(
@Path("roomId") spaceId: String,
@Query("suggested_only") suggestedOnly: Boolean?,
@Query("limit") limit: Int?,
@Query("max_depth") maxDepth: Int?,
@Query("from") from: String?): SpacesResponse
}

View file

@ -81,7 +81,7 @@ internal data class SpaceChildSummaryResponse(
* Required. Whether the room may be viewed by guest users without joining.
*/
@Json(name = "world_readable")
val worldReadable: Boolean = false,
val isWorldReadable: Boolean = false,
/**
* Required. Whether guest users may join the room and participate in it. If they can,

View file

@ -0,0 +1,60 @@
/*
* Copyright 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.space
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runBlockingTest
import okhttp3.ResponseBody.Companion.toResponseBody
import org.amshove.kluent.shouldBeEqualTo
import org.junit.Test
import org.matrix.android.sdk.test.fakes.FakeGlobalErrorReceiver
import org.matrix.android.sdk.test.fakes.FakeSpaceApi
import org.matrix.android.sdk.test.fixtures.SpacesResponseFixture.aSpacesResponse
import retrofit2.HttpException
import retrofit2.Response
@ExperimentalCoroutinesApi
internal class DefaultResolveSpaceInfoTaskTest {
private val spaceApi = FakeSpaceApi()
private val globalErrorReceiver = FakeGlobalErrorReceiver()
private val resolveSpaceInfoTask = DefaultResolveSpaceInfoTask(spaceApi.instance, globalErrorReceiver)
@Test
fun `given stable endpoint works, when execute, then return stable api data`() = runBlockingTest {
spaceApi.givenStableEndpointReturns(response)
val result = resolveSpaceInfoTask.execute(spaceApi.params)
result shouldBeEqualTo response
}
@Test
fun `given stable endpoint fails, when execute, then fallback to unstable endpoint`() = runBlockingTest {
spaceApi.givenStableEndpointThrows(httpException)
spaceApi.givenUnstableEndpointReturns(response)
val result = resolveSpaceInfoTask.execute(spaceApi.params)
result shouldBeEqualTo response
}
companion object {
private val response = aSpacesResponse()
private val httpException = HttpException(Response.error<SpacesResponse>(500, "".toResponseBody()))
}
}

View file

@ -0,0 +1,41 @@
/*
* Copyright 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.test.fakes
import io.mockk.coEvery
import io.mockk.mockk
import org.matrix.android.sdk.internal.session.space.SpaceApi
import org.matrix.android.sdk.internal.session.space.SpacesResponse
import org.matrix.android.sdk.test.fixtures.ResolveSpaceInfoTaskParamsFixture
internal class FakeSpaceApi {
val instance: SpaceApi = mockk()
val params = ResolveSpaceInfoTaskParamsFixture.aResolveSpaceInfoTaskParams()
fun givenStableEndpointReturns(response: SpacesResponse) {
coEvery { instance.getSpaceHierarchy(params.spaceId, params.suggestedOnly, params.limit, params.maxDepth, params.from) } returns response
}
fun givenStableEndpointThrows(throwable: Throwable) {
coEvery { instance.getSpaceHierarchy(params.spaceId, params.suggestedOnly, params.limit, params.maxDepth, params.from) } throws throwable
}
fun givenUnstableEndpointReturns(response: SpacesResponse) {
coEvery { instance.getSpaceHierarchyUnstable(params.spaceId, params.suggestedOnly, params.limit, params.maxDepth, params.from) } returns response
}
}

View file

@ -0,0 +1,35 @@
/*
* Copyright 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.test.fixtures
import org.matrix.android.sdk.internal.session.space.ResolveSpaceInfoTask
internal object ResolveSpaceInfoTaskParamsFixture {
fun aResolveSpaceInfoTaskParams(
spaceId: String = "",
limit: Int? = null,
maxDepth: Int? = null,
from: String? = null,
suggestedOnly: Boolean? = null,
) = ResolveSpaceInfoTask.Params(
spaceId,
limit,
maxDepth,
from,
suggestedOnly,
)
}

View file

@ -0,0 +1,30 @@
/*
* Copyright 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.test.fixtures
import org.matrix.android.sdk.internal.session.space.SpaceChildSummaryResponse
import org.matrix.android.sdk.internal.session.space.SpacesResponse
internal object SpacesResponseFixture {
fun aSpacesResponse(
nextBatch: String? = null,
rooms: List<SpaceChildSummaryResponse>? = null,
) = SpacesResponse(
nextBatch,
rooms,
)
}

View file

@ -367,7 +367,7 @@ dependencies {
implementation 'com.facebook.stetho:stetho:1.6.0'
// Phone number https://github.com/google/libphonenumber
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.44'
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.45'
// FlowBinding
implementation libs.github.flowBinding

View file

@ -6,6 +6,6 @@
<path
android:strokeWidth="1"
android:pathData="M12.0077,23.4869C12.0051,23.4875 12.0025,23.4881 12,23.4886C11.9975,23.4881 11.9949,23.4875 11.9923,23.4869C11.9204,23.4706 11.8129,23.4452 11.6749,23.4092C11.3989,23.3373 11.0015,23.2235 10.5233,23.0575C9.5654,22.725 8.2921,22.186 7.0225,21.3608C4.4897,19.7145 2,16.954 2,12.405V3.4496L12,0.521L22,3.4496V12.405C22,16.954 19.5103,19.7145 16.9775,21.3608C15.7079,22.186 14.4346,22.725 13.4767,23.0575C12.9985,23.2235 12.6011,23.3373 12.3251,23.4092C12.1871,23.4452 12.0796,23.4706 12.0077,23.4869Z"
android:fillColor="#17191C"
android:fillColor="@color/shield_color_black"
android:strokeColor="#ffffff"/>
</vector>

View file

@ -4,6 +4,6 @@
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#17191C"
android:fillColor="@color/shield_color_black"
android:pathData="M12.0077,23.4869C12.0051,23.4875 12.0025,23.4881 12,23.4886C11.9975,23.4881 11.9949,23.4875 11.9923,23.4869C11.9204,23.4706 11.8129,23.4452 11.6749,23.4092C11.3989,23.3373 11.0015,23.2235 10.5233,23.0575C9.5654,22.725 8.2921,22.186 7.0225,21.3608C4.4897,19.7145 2,16.954 2,12.405V3.4496L12,0.521L22,3.4496V12.405C22,16.954 19.5103,19.7145 16.9775,21.3608C15.7079,22.186 14.4346,22.725 13.4767,23.0575C12.9985,23.2235 12.6011,23.3373 12.3251,23.4092C12.1871,23.4452 12.0796,23.4706 12.0077,23.4869Z" />
</vector>

View file

@ -4,7 +4,7 @@
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#03B381"
android:fillColor="@color/shield_color_trust"
android:pathData="M12.0077,23.4869C12.0051,23.4875 12.0025,23.4881 12,23.4886C11.9975,23.4881 11.9949,23.4875 11.9923,23.4869C11.9204,23.4706 11.8129,23.4452 11.6749,23.4092C11.3989,23.3373 11.0015,23.2235 10.5233,23.0575C9.5654,22.725 8.2921,22.186 7.0225,21.3608C4.4897,19.7145 2,16.954 2,12.405V3.4496L12,0.521L22,3.4496V12.405C22,16.954 19.5103,19.7145 16.9775,21.3608C15.7079,22.186 14.4346,22.725 13.4767,23.0575C12.9985,23.2235 12.6011,23.3373 12.3251,23.4092C12.1871,23.4452 12.0796,23.4706 12.0077,23.4869Z"
android:strokeWidth="1"
android:strokeColor="#ffffff" />

View file

@ -6,7 +6,7 @@
<path
android:strokeWidth="1"
android:pathData="M12.0077,23.4869C12.0051,23.4875 12.0025,23.4881 12,23.4886C11.9975,23.4881 11.9949,23.4875 11.9923,23.4869C11.9204,23.4706 11.8129,23.4452 11.6749,23.4092C11.3989,23.3373 11.0015,23.2235 10.5233,23.0575C9.5654,22.725 8.2921,22.186 7.0225,21.3608C4.4897,19.7145 2,16.954 2,12.405V3.4496L12,0.521L22,3.4496V12.405C22,16.954 19.5103,19.7145 16.9775,21.3608C15.7079,22.186 14.4346,22.725 13.4767,23.0575C12.9985,23.2235 12.6011,23.3373 12.3251,23.4092C12.1871,23.4452 12.0796,23.4706 12.0077,23.4869Z"
android:fillColor="#0DBD8B"
android:fillColor="@color/shield_color_trust"
android:strokeColor="#ffffff"/>
<path
android:pathData="M17.4157,6.8094C17.1457,6.5244 16.6957,6.5094 16.4107,6.7794L10.0057,12.7794L7.6507,11.1294C7.3357,10.9194 6.9007,10.9194 6.6007,11.1894C6.2407,11.4894 6.2107,12.0294 6.5107,12.3894L9.2257,15.5094C9.2707,15.5544 9.3157,15.6144 9.3757,15.6444C9.8857,16.0644 10.6507,15.9894 11.0707,15.4794L11.1157,15.4194L17.4457,7.7544C17.6557,7.4844 17.6557,7.0794 17.4157,6.8094Z"

View file

@ -5,7 +5,7 @@
android:viewportHeight="24">
<path
android:pathData="M1.5,12.405V3.075L12,0L22.5,3.075V12.405C22.5,21.945 12,24 12,24C12,24 1.5,21.945 1.5,12.405Z"
android:fillColor="#0DBD8B"/>
android:fillColor="@color/shield_color_trust"/>
<path
android:pathData="M17.4157,6.8094C17.1457,6.5244 16.6957,6.5094 16.4107,6.7794L10.0057,12.7794L7.6507,11.1294C7.3357,10.9194 6.9007,10.9194 6.6007,11.1894C6.2407,11.4894 6.2107,12.0294 6.5107,12.3894L9.2257,15.5094C9.2707,15.5544 9.3157,15.6144 9.3757,15.6444C9.8857,16.0644 10.6507,15.9894 11.0707,15.4794L11.1157,15.4194L17.4457,7.7544C17.6557,7.4844 17.6557,7.0794 17.4157,6.8094Z"
android:fillColor="#ffffff"/>

View file

@ -6,7 +6,7 @@
<path
android:strokeWidth="1"
android:pathData="M12.0077,23.4869C12.0051,23.4875 12.0025,23.4881 12,23.4886C11.9975,23.4881 11.9949,23.4875 11.9923,23.4869C11.9204,23.4706 11.8129,23.4452 11.6749,23.4092C11.3989,23.3373 11.0015,23.2235 10.5233,23.0575C9.5654,22.725 8.2921,22.186 7.0225,21.3608C4.4897,19.7145 2,16.954 2,12.405V3.4496L12,0.521L22,3.4496V12.405C22,16.954 19.5103,19.7145 16.9775,21.3608C15.7079,22.186 14.4346,22.725 13.4767,23.0575C12.9985,23.2235 12.6011,23.3373 12.3251,23.4092C12.1871,23.4452 12.0796,23.4706 12.0077,23.4869Z"
android:fillColor="#FF4B55"
android:fillColor="@color/shield_color_warning"
android:strokeColor="#ffffff"/>
<path
android:pathData="M10.8527,8.0725C10.7964,7.4313 11.2689,6.8688 11.9102,6.8238C12.5402,6.7788 13.1027,7.2513 13.1702,7.8925V8.0725L12.8102,12.5725C12.7764,12.9888 12.4277,13.3038 12.0114,13.3038H11.9439C11.5502,13.27 11.2464,12.9663 11.2127,12.5725L10.8527,8.0725ZM12.9888,15.5076C12.9888,16.0543 12.5456,16.4976 11.9988,16.4976C11.452,16.4976 11.0088,16.0543 11.0088,15.5076C11.0088,14.9608 11.452,14.5176 11.9988,14.5176C12.5456,14.5176 12.9888,14.9608 12.9888,15.5076Z"

View file

@ -6,11 +6,11 @@
<path
android:strokeWidth="1"
android:pathData="M12.0077,23.4869C12.0051,23.4875 12.0025,23.4881 12,23.4886C11.9975,23.4881 11.9949,23.4875 11.9923,23.4869C11.9204,23.4706 11.8129,23.4452 11.6749,23.4092C11.3989,23.3373 11.0015,23.2235 10.5233,23.0575C9.5654,22.725 8.2921,22.186 7.0225,21.3608C4.4897,19.7145 2,16.954 2,12.405V3.4496L12,0.521L22,3.4496V12.405C22,16.954 19.5103,19.7145 16.9775,21.3608C15.7079,22.186 14.4346,22.725 13.4767,23.0575C12.9985,23.2235 12.6011,23.3373 12.3251,23.4092C12.1871,23.4452 12.0796,23.4706 12.0077,23.4869Z"
android:fillColor="#FF4B55"
android:fillColor="@color/shield_color_warning"
android:strokeColor="#ffffff"/>
<path
android:pathData="M1.5,12.405V3.075L12,0L22.5,3.075V12.405C22.5,21.945 12,24 12,24C12,24 1.5,21.945 1.5,12.405Z"
android:fillColor="#FF4B55"/>
android:fillColor="@color/shield_color_warning"/>
<path
android:pathData="M10.8527,8.0725C10.7964,7.4313 11.2689,6.8688 11.9102,6.8238C12.5402,6.7788 13.1027,7.2513 13.1702,7.8925V8.0725L12.8102,12.5725C12.7764,12.9888 12.4277,13.3038 12.0114,13.3038H11.9439C11.5502,13.27 11.2464,12.9663 11.2127,12.5725L10.8527,8.0725ZM12.9888,15.5076C12.9888,16.0543 12.5456,16.4976 11.9988,16.4976C11.452,16.4976 11.0088,16.0543 11.0088,15.5076C11.0088,14.9608 11.452,14.5176 11.9988,14.5176C12.5456,14.5176 12.9888,14.9608 12.9888,15.5076Z"
android:fillColor="#ffffff"

View file

@ -6,7 +6,7 @@
<path
android:strokeWidth="1"
android:pathData="M6.0995,11.4627C6.0611,11.4727 6.0278,11.481 6,11.4876C5.9722,11.481 5.9389,11.4727 5.9005,11.4627C5.768,11.4282 5.5757,11.3731 5.3437,11.2926C4.8779,11.1309 4.2608,10.8694 3.6475,10.4708C2.4272,9.6776 1.25,8.3665 1.25,6.2025V1.9121L6,0.521L10.75,1.9121V6.2025C10.75,8.3665 9.5728,9.6776 8.3525,10.4708C7.7392,10.8694 7.1221,11.1309 6.6563,11.2926C6.4243,11.3731 6.232,11.4282 6.0995,11.4627Z"
android:fillColor="#FF4B55"
android:fillColor="@color/shield_color_warning"
android:strokeColor="#ffffff"/>
<path
android:pathData="M5.4263,4.0363C5.3982,3.7156 5.6345,3.4344 5.9551,3.4119C6.2701,3.3894 6.5513,3.6256 6.5851,3.9463V4.0363L6.4051,6.2863C6.3882,6.4944 6.2138,6.6519 6.0057,6.6519H5.972C5.7751,6.635 5.6232,6.4831 5.6063,6.2863L5.4263,4.0363ZM6.4944,7.7538C6.4944,8.0272 6.2728,8.2488 5.9994,8.2488C5.726,8.2488 5.5044,8.0272 5.5044,7.7538C5.5044,7.4804 5.726,7.2588 5.9994,7.2588C6.2728,7.2588 6.4944,7.4804 6.4944,7.7538Z"