Merge pull request #150 from ouchadam/release-candidate
[Auto] Release Candidate
This commit is contained in:
commit
b3d4f79d8c
|
@ -25,7 +25,7 @@ jobs:
|
|||
|
||||
- name: Create pip requirements
|
||||
run: |
|
||||
echo "matrix-synapse==v1.60.0" > requirements.txt
|
||||
echo "matrix-synapse" > requirements.txt
|
||||
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v2
|
||||
|
|
|
@ -68,7 +68,7 @@ import java.time.Clock
|
|||
|
||||
internal class AppModule(context: Application, logger: MatrixLogger) {
|
||||
|
||||
private val buildMeta = BuildMeta(BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)
|
||||
private val buildMeta = BuildMeta(BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE, isDebug = BuildConfig.DEBUG)
|
||||
private val deviceMeta = DeviceMeta(Build.VERSION.SDK_INT)
|
||||
private val trackingModule by unsafeLazy {
|
||||
TrackingModule(
|
||||
|
@ -94,7 +94,7 @@ internal class AppModule(context: Application, logger: MatrixLogger) {
|
|||
private val workModule = WorkModule(context)
|
||||
private val imageLoaderModule = ImageLoaderModule(context)
|
||||
|
||||
private val matrixModules = MatrixModules(storeModule, trackingModule, workModule, logger, coroutineDispatchers, context.contentResolver)
|
||||
private val matrixModules = MatrixModules(storeModule, trackingModule, workModule, logger, coroutineDispatchers, context.contentResolver, buildMeta)
|
||||
val domainModules = DomainModules(matrixModules, trackingModule.errorTracker, workModule, storeModule, context, coroutineDispatchers)
|
||||
|
||||
val coreAndroidModule = CoreAndroidModule(
|
||||
|
@ -232,6 +232,7 @@ internal class MatrixModules(
|
|||
private val logger: MatrixLogger,
|
||||
private val coroutineDispatchers: CoroutineDispatchers,
|
||||
private val contentResolver: ContentResolver,
|
||||
private val buildMeta: BuildMeta,
|
||||
) {
|
||||
|
||||
val matrix by unsafeLazy {
|
||||
|
@ -240,7 +241,7 @@ internal class MatrixModules(
|
|||
MatrixClient(
|
||||
KtorMatrixHttpClientFactory(
|
||||
credentialsStore,
|
||||
includeLogging = true
|
||||
includeLogging = buildMeta.isDebug,
|
||||
),
|
||||
logger
|
||||
).also {
|
||||
|
|
|
@ -132,7 +132,7 @@ ext.kotlinTest = { dependencies ->
|
|||
dependencies.testImplementation Dependencies.mavenCentral.kluent
|
||||
dependencies.testImplementation Dependencies.mavenCentral.kotlinTest
|
||||
dependencies.testImplementation "org.jetbrains.kotlin:kotlin-test-junit:1.6.10"
|
||||
dependencies.testImplementation 'io.mockk:mockk:1.12.7'
|
||||
dependencies.testImplementation 'io.mockk:mockk:1.12.8'
|
||||
dependencies.testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4'
|
||||
|
||||
dependencies.testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0'
|
||||
|
|
|
@ -3,4 +3,5 @@ package app.dapk.st.core
|
|||
data class BuildMeta(
|
||||
val versionName: String,
|
||||
val versionCode: Int,
|
||||
val isDebug: Boolean,
|
||||
)
|
||||
|
|
|
@ -143,7 +143,7 @@ ext.Dependencies.with {
|
|||
|
||||
junit = "junit:junit:4.13.2"
|
||||
kluent = "org.amshove.kluent:kluent:1.68"
|
||||
mockk = 'io.mockk:mockk:1.12.7'
|
||||
mockk = 'io.mockk:mockk:1.12.8'
|
||||
|
||||
matrixOlm = "org.matrix.android:olm-sdk:3.2.12"
|
||||
}
|
||||
|
|
|
@ -8,3 +8,7 @@ fun OlmAccount.readIdentityKeys(): Pair<Ed25519, Curve25519> {
|
|||
val identityKeys = this.identityKeys()
|
||||
return Ed25519(identityKeys["ed25519"]!!) to Curve25519(identityKeys["curve25519"]!!)
|
||||
}
|
||||
|
||||
fun OlmAccount.oneTimeCurveKeys(): List<Pair<String, Curve25519>> {
|
||||
return this.oneTimeKeys()["curve25519"]?.map { it.key to Curve25519(it.value) } ?: emptyList()
|
||||
}
|
|
@ -67,7 +67,7 @@ class OlmWrapper(
|
|||
|
||||
private suspend fun accountCrypto(deviceCredentials: DeviceCredentials): AccountCryptoSession? {
|
||||
return olmStore.read()?.let { olmAccount ->
|
||||
createAccountCryptoSession(deviceCredentials, olmAccount)
|
||||
createAccountCryptoSession(deviceCredentials, olmAccount, isNew = false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,12 +80,12 @@ class OlmWrapper(
|
|||
val olmAccount = this.olmAccount as OlmAccount
|
||||
olmAccount.generateOneTimeKeys(count)
|
||||
|
||||
val oneTimeKeys = DeviceService.OneTimeKeys(olmAccount.oneTimeKeys()["curve25519"]!!.map {
|
||||
val oneTimeKeys = DeviceService.OneTimeKeys(olmAccount.oneTimeCurveKeys().map { (key, value) ->
|
||||
DeviceService.OneTimeKeys.Key.SignedCurve(
|
||||
keyId = it.key,
|
||||
value = it.value,
|
||||
keyId = key,
|
||||
value = value.value,
|
||||
signature = DeviceService.OneTimeKeys.Key.SignedCurve.Ed25519Signature(
|
||||
value = it.value.toSignedJson(olmAccount),
|
||||
value = value.value.toSignedJson(olmAccount),
|
||||
deviceId = credentials.deviceId,
|
||||
userId = credentials.userId,
|
||||
)
|
||||
|
@ -98,20 +98,21 @@ class OlmWrapper(
|
|||
|
||||
private suspend fun createAccountCrypto(deviceCredentials: DeviceCredentials, action: suspend (AccountCryptoSession) -> Unit): AccountCryptoSession {
|
||||
val olmAccount = OlmAccount()
|
||||
return createAccountCryptoSession(deviceCredentials, olmAccount).also {
|
||||
return createAccountCryptoSession(deviceCredentials, olmAccount, isNew = true).also {
|
||||
action(it)
|
||||
olmStore.persist(olmAccount)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createAccountCryptoSession(credentials: DeviceCredentials, olmAccount: OlmAccount): AccountCryptoSession {
|
||||
private fun createAccountCryptoSession(credentials: DeviceCredentials, olmAccount: OlmAccount, isNew: Boolean): AccountCryptoSession {
|
||||
val (identityKey, senderKey) = olmAccount.readIdentityKeys()
|
||||
return AccountCryptoSession(
|
||||
fingerprint = identityKey,
|
||||
senderKey = senderKey,
|
||||
deviceKeys = deviceKeyFactory.create(credentials.userId, credentials.deviceId, identityKey, senderKey, olmAccount),
|
||||
olmAccount = olmAccount,
|
||||
maxKeys = olmAccount.maxOneTimeKeys().toInt()
|
||||
maxKeys = olmAccount.maxOneTimeKeys().toInt(),
|
||||
hasKeys = !isNew,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -136,6 +137,7 @@ class OlmWrapper(
|
|||
singletonFlows.update("room-${roomId.value}", rotatedSession)
|
||||
}
|
||||
}
|
||||
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
|
@ -277,10 +279,12 @@ class OlmWrapper(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
OlmMessage.MESSAGE_TYPE_MESSAGE -> {
|
||||
logger.crypto("decrypting olm message type")
|
||||
session.decryptMessage(olmMessage)?.let { JsonString(it) }
|
||||
}
|
||||
|
||||
else -> throw IllegalArgumentException("Unknown message type: $type")
|
||||
}
|
||||
}.onFailure {
|
||||
|
@ -297,7 +301,7 @@ class OlmWrapper(
|
|||
}
|
||||
|
||||
private suspend fun AccountCryptoSession.updateAccountInstance(olmAccount: OlmAccount) {
|
||||
singletonFlows.update("account-crypto", this.copy(olmAccount = olmAccount))
|
||||
singletonFlows.update("account-crypto", this.copy(olmAccount = olmAccount, hasKeys = true))
|
||||
olmStore.persist(olmAccount)
|
||||
}
|
||||
|
||||
|
|
|
@ -100,19 +100,27 @@ class HomeViewModel(
|
|||
Loading -> current
|
||||
is SignedIn -> {
|
||||
when (page) {
|
||||
Page.Directory -> {
|
||||
// do nothing
|
||||
current.page -> current
|
||||
else -> current.copy(page = page).also {
|
||||
pageChangeSideEffects(page)
|
||||
}
|
||||
|
||||
Page.Profile -> profileViewModel.reset()
|
||||
}
|
||||
current.copy(page = page)
|
||||
}
|
||||
|
||||
SignedOut -> current
|
||||
}
|
||||
}
|
||||
|
||||
private fun pageChangeSideEffects(page: Page) {
|
||||
when (page) {
|
||||
Page.Directory -> {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
Page.Profile -> profileViewModel.reset()
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
viewModelScope.cancel()
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ private const val ENABLED_MATERIAL_YOU = true
|
|||
|
||||
class SettingsItemFactoryTest {
|
||||
|
||||
private val buildMeta = BuildMeta(versionName = "a-version-name", versionCode = 100)
|
||||
private val buildMeta = BuildMeta(versionName = "a-version-name", versionCode = 100, isDebug = false)
|
||||
private val deviceMeta = DeviceMeta(apiVersion = 31)
|
||||
private val fakePushTokenRegistrars = FakePushRegistrars()
|
||||
private val fakeThemeStore = FakeThemeStore()
|
||||
|
|
|
@ -72,6 +72,7 @@ interface Olm {
|
|||
val fingerprint: Ed25519,
|
||||
val senderKey: Curve25519,
|
||||
val deviceKeys: DeviceKeys,
|
||||
val hasKeys: Boolean,
|
||||
val maxKeys: Int,
|
||||
val olmAccount: Any,
|
||||
)
|
||||
|
|
|
@ -15,16 +15,28 @@ internal class MaybeCreateAndUploadOneTimeKeysUseCaseImpl(
|
|||
private val credentialsStore: CredentialsStore,
|
||||
private val deviceService: DeviceService,
|
||||
private val logger: MatrixLogger,
|
||||
): MaybeCreateAndUploadOneTimeKeysUseCase {
|
||||
) : MaybeCreateAndUploadOneTimeKeysUseCase {
|
||||
|
||||
override suspend fun invoke(currentServerKeyCount: ServerKeyCount) {
|
||||
val ensureCryptoAccount = fetchAccountCryptoUseCase.invoke()
|
||||
val keysDiff = (ensureCryptoAccount.maxKeys / 2) - currentServerKeyCount.value
|
||||
if (keysDiff > 0) {
|
||||
logger.crypto("current otk: $currentServerKeyCount, creating: $keysDiff")
|
||||
ensureCryptoAccount.createAndUploadOneTimeKeys(countToCreate = keysDiff + (ensureCryptoAccount.maxKeys / 4))
|
||||
} else {
|
||||
logger.crypto("current otk: $currentServerKeyCount, not creating new keys")
|
||||
val cryptoAccount = fetchAccountCryptoUseCase.invoke()
|
||||
when {
|
||||
currentServerKeyCount.value == 0 && cryptoAccount.hasKeys -> {
|
||||
logger.crypto("Server has no keys but a crypto instance exists, waiting for next update")
|
||||
}
|
||||
|
||||
else -> {
|
||||
val keysDiff = (cryptoAccount.maxKeys / 2) - currentServerKeyCount.value
|
||||
when {
|
||||
keysDiff > 0 -> {
|
||||
logger.crypto("current otk: $currentServerKeyCount, creating: $keysDiff")
|
||||
cryptoAccount.createAndUploadOneTimeKeys(countToCreate = keysDiff + (cryptoAccount.maxKeys / 4))
|
||||
}
|
||||
|
||||
else -> {
|
||||
logger.crypto("current otk: $currentServerKeyCount, not creating new keys")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,12 +22,11 @@ class MaybeCreateAndUploadOneTimeKeysUseCaseTest {
|
|||
|
||||
private val fakeDeviceService = FakeDeviceService()
|
||||
private val fakeOlm = FakeOlm()
|
||||
private val fakeCredentialsStore = FakeCredentialsStore().also {
|
||||
it.givenCredentials().returns(A_USER_CREDENTIALS)
|
||||
}
|
||||
private val fakeCredentialsStore = FakeCredentialsStore().also { it.givenCredentials().returns(A_USER_CREDENTIALS) }
|
||||
private val fakeFetchAccountCryptoUseCase = FakeFetchAccountCryptoUseCase()
|
||||
|
||||
private val maybeCreateAndUploadOneTimeKeysUseCase = MaybeCreateAndUploadOneTimeKeysUseCaseImpl(
|
||||
FakeFetchAccountCryptoUseCase().also { it.givenFetch().returns(AN_ACCOUNT_CRYPTO_SESSION) },
|
||||
fakeFetchAccountCryptoUseCase.also { it.givenFetch().returns(AN_ACCOUNT_CRYPTO_SESSION) },
|
||||
fakeOlm,
|
||||
fakeCredentialsStore,
|
||||
fakeDeviceService,
|
||||
|
@ -43,6 +42,16 @@ class MaybeCreateAndUploadOneTimeKeysUseCaseTest {
|
|||
fakeDeviceService.verifyDidntUploadOneTimeKeys()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given account has keys and server count is 0 then does nothing`() = runTest {
|
||||
fakeFetchAccountCryptoUseCase.givenFetch().returns(AN_ACCOUNT_CRYPTO_SESSION.copy(hasKeys = true))
|
||||
val zeroServiceKeys = ServerKeyCount(0)
|
||||
|
||||
maybeCreateAndUploadOneTimeKeysUseCase.invoke(zeroServiceKeys)
|
||||
|
||||
fakeDeviceService.verifyDidntUploadOneTimeKeys()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given 0 current keys than generates and uploads 75 percent of the max key capacity`() = runTest {
|
||||
fakeDeviceService.expect { it.uploadOneTimeKeys(GENERATED_ONE_TIME_KEYS) }
|
||||
|
|
|
@ -10,8 +10,9 @@ fun anAccountCryptoSession(
|
|||
senderKey: Curve25519 = aCurve25519(),
|
||||
deviceKeys: DeviceKeys = aDeviceKeys(),
|
||||
maxKeys: Int = 5,
|
||||
hasKeys: Boolean = false,
|
||||
olmAccount: Any = mockk(),
|
||||
) = Olm.AccountCryptoSession(fingerprint, senderKey, deviceKeys, maxKeys, olmAccount)
|
||||
) = Olm.AccountCryptoSession(fingerprint, senderKey, deviceKeys, hasKeys, maxKeys, olmAccount)
|
||||
|
||||
fun aRoomCryptoSession(
|
||||
creationTimestampUtc: Long = 0L,
|
||||
|
|
|
@ -56,6 +56,12 @@ internal data class ApiSyncRoom(
|
|||
@SerialName("state") val state: ApiSyncRoomState,
|
||||
@SerialName("account_data") val accountData: ApiAccountData? = null,
|
||||
@SerialName("ephemeral") val ephemeral: ApiEphemeral? = null,
|
||||
@SerialName("summary") val summary: ApiRoomSummary? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
internal data class ApiRoomSummary(
|
||||
@SerialName("m.heroes") val heroes: List<UserId>? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
|
|
@ -21,7 +21,13 @@ internal sealed class ApiTimelineEvent {
|
|||
@Serializable
|
||||
internal data class Content(
|
||||
@SerialName("type") val type: String? = null
|
||||
)
|
||||
) {
|
||||
|
||||
object Type {
|
||||
const val SPACE = "m.space"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
@ -50,6 +56,18 @@ internal sealed class ApiTimelineEvent {
|
|||
)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("m.room.canonical_alias")
|
||||
internal data class CanonicalAlias(
|
||||
@SerialName("event_id") val id: EventId,
|
||||
@SerialName("content") val content: Content,
|
||||
) : ApiTimelineEvent() {
|
||||
|
||||
@Serializable
|
||||
internal data class Content(
|
||||
@SerialName("alias") val alias: String
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("m.room.avatar")
|
||||
|
|
|
@ -27,6 +27,7 @@ internal class SyncSideEffects(
|
|||
response.deviceLists?.changed?.ifEmpty { null }?.let {
|
||||
notifyDevicesUpdated.notifyChanges(it, requestToken)
|
||||
}
|
||||
|
||||
oneTimeKeyProducer.onServerKeyCount(response.oneTimeKeysCount["signed_curve25519"] ?: ServerKeyCount(0))
|
||||
|
||||
val decryptedToDeviceEvents = decryptedToDeviceEvents(response)
|
||||
|
|
|
@ -12,37 +12,43 @@ internal class RoomOverviewProcessor(
|
|||
private val roomMembersService: RoomMembersService,
|
||||
) {
|
||||
|
||||
suspend fun process(roomToProcess: RoomToProcess, previousState: RoomOverview?, lastMessage: LastMessage?): RoomOverview {
|
||||
suspend fun process(roomToProcess: RoomToProcess, previousState: RoomOverview?, lastMessage: LastMessage?): RoomOverview? {
|
||||
val combinedEvents = roomToProcess.apiSyncRoom.state.stateEvents + roomToProcess.apiSyncRoom.timeline.apiTimelineEvents
|
||||
val isEncrypted = combinedEvents.any { it is ApiTimelineEvent.Encryption }
|
||||
|
||||
val readMarker = roomToProcess.apiSyncRoom.accountData?.events?.filterIsInstance<ApiAccountEvent.FullyRead>()?.firstOrNull()?.content?.eventId
|
||||
return when (previousState) {
|
||||
null -> combinedEvents.filterIsInstance<ApiTimelineEvent.RoomCreate>().first().let { roomCreate ->
|
||||
val roomName = roomDisplayName(combinedEvents)
|
||||
val isGroup = roomToProcess.directMessage == null
|
||||
RoomOverview(
|
||||
roomName = roomName ?: roomToProcess.directMessage?.let {
|
||||
roomMembersService.find(roomToProcess.roomId, it)?.let { it.displayName ?: it.id.value }
|
||||
},
|
||||
roomCreationUtc = roomCreate.utcTimestamp,
|
||||
lastMessage = lastMessage,
|
||||
roomId = roomToProcess.roomId,
|
||||
isGroup = isGroup,
|
||||
roomAvatarUrl = roomAvatar(
|
||||
roomToProcess.roomId,
|
||||
roomMembersService,
|
||||
roomToProcess.directMessage,
|
||||
combinedEvents,
|
||||
roomToProcess.userCredentials.homeServer
|
||||
),
|
||||
readMarker = readMarker,
|
||||
isEncrypted = isEncrypted,
|
||||
)
|
||||
when (roomCreate.content.type) {
|
||||
ApiTimelineEvent.RoomCreate.Content.Type.SPACE -> null
|
||||
else -> {
|
||||
val roomName = roomDisplayName(roomToProcess, combinedEvents)
|
||||
val isGroup = roomToProcess.directMessage == null
|
||||
val processedName = roomName ?: roomToProcess.directMessage?.let {
|
||||
roomMembersService.find(roomToProcess.roomId, it)?.let { it.displayName ?: it.id.value }
|
||||
}
|
||||
RoomOverview(
|
||||
roomName = processedName,
|
||||
roomCreationUtc = roomCreate.utcTimestamp,
|
||||
lastMessage = lastMessage,
|
||||
roomId = roomToProcess.roomId,
|
||||
isGroup = isGroup,
|
||||
roomAvatarUrl = roomAvatar(
|
||||
roomToProcess.roomId,
|
||||
roomMembersService,
|
||||
roomToProcess.directMessage,
|
||||
combinedEvents,
|
||||
roomToProcess.userCredentials.homeServer
|
||||
),
|
||||
readMarker = readMarker,
|
||||
isEncrypted = isEncrypted,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
previousState.copy(
|
||||
roomName = previousState.roomName ?: roomDisplayName(combinedEvents),
|
||||
roomName = previousState.roomName ?: roomDisplayName(roomToProcess, combinedEvents),
|
||||
lastMessage = lastMessage ?: previousState.lastMessage,
|
||||
roomAvatarUrl = previousState.roomAvatarUrl ?: roomAvatar(
|
||||
roomToProcess.roomId,
|
||||
|
@ -58,9 +64,13 @@ internal class RoomOverviewProcessor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun roomDisplayName(combinedEvents: List<ApiTimelineEvent>): String? {
|
||||
val roomName = combinedEvents.filterIsInstance<ApiTimelineEvent.RoomName>().lastOrNull()
|
||||
return roomName?.content?.name
|
||||
private suspend fun roomDisplayName(roomToProcess: RoomToProcess, combinedEvents: List<ApiTimelineEvent>): String? {
|
||||
val roomName = combinedEvents.filterIsInstance<ApiTimelineEvent.RoomName>().lastOrNull()?.content?.name
|
||||
?: combinedEvents.filterIsInstance<ApiTimelineEvent.CanonicalAlias>().lastOrNull()?.content?.alias
|
||||
?: roomToProcess.heroes?.let {
|
||||
roomMembersService.find(roomToProcess.roomId, it).joinToString { it.displayName ?: it.id.value }
|
||||
}
|
||||
return roomName?.takeIf { it.isNotEmpty() }
|
||||
}
|
||||
|
||||
private suspend fun roomAvatar(
|
||||
|
@ -75,6 +85,7 @@ internal class RoomOverviewProcessor(
|
|||
val filterIsInstance = combinedEvents.filterIsInstance<ApiTimelineEvent.RoomAvatar>()
|
||||
filterIsInstance.lastOrNull()?.content?.url?.convertMxUrToUrl(homeServerUrl)?.let { AvatarUrl(it) }
|
||||
}
|
||||
|
||||
else -> membersService.find(roomId, dmUser)?.avatarUrl
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ internal class RoomProcessor(
|
|||
private val ephemeralEventsUseCase: EphemeralEventsUseCase,
|
||||
) {
|
||||
|
||||
suspend fun processRoom(roomToProcess: RoomToProcess, isInitialSync: Boolean): RoomState {
|
||||
suspend fun processRoom(roomToProcess: RoomToProcess, isInitialSync: Boolean): RoomState? {
|
||||
val members = roomToProcess.apiSyncRoom.collectMembers(roomToProcess.userCredentials)
|
||||
roomMembersService.insert(roomToProcess.roomId, members)
|
||||
|
||||
|
@ -28,16 +28,17 @@ internal class RoomProcessor(
|
|||
previousState?.events ?: emptyList(),
|
||||
)
|
||||
|
||||
val overview = createRoomOverview(distinctEvents, roomToProcess, previousState)
|
||||
unreadEventsProcessor.processUnreadState(overview, previousState?.roomOverview, newEvents, roomToProcess.userCredentials.userId, isInitialSync)
|
||||
return createRoomOverview(distinctEvents, roomToProcess, previousState)?.let {
|
||||
unreadEventsProcessor.processUnreadState(it, previousState?.roomOverview, newEvents, roomToProcess.userCredentials.userId, isInitialSync)
|
||||
|
||||
return RoomState(overview, distinctEvents).also {
|
||||
roomDataSource.persist(roomToProcess.roomId, previousState, it)
|
||||
ephemeralEventsUseCase.processEvents(roomToProcess)
|
||||
RoomState(it, distinctEvents).also {
|
||||
roomDataSource.persist(roomToProcess.roomId, previousState, it)
|
||||
ephemeralEventsUseCase.processEvents(roomToProcess)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun createRoomOverview(distinctEvents: List<RoomEvent>, roomToProcess: RoomToProcess, previousState: RoomState?): RoomOverview {
|
||||
private suspend fun createRoomOverview(distinctEvents: List<RoomEvent>, roomToProcess: RoomToProcess, previousState: RoomState?): RoomOverview? {
|
||||
val lastMessage = distinctEvents.sortedByDescending { it.utcTimestamp }.findLastMessage()
|
||||
return roomOverviewProcessor.process(roomToProcess, previousState?.roomOverview, lastMessage)
|
||||
}
|
||||
|
@ -56,6 +57,7 @@ private fun ApiSyncRoom.collectMembers(userCredentials: UserCredentials): List<R
|
|||
avatarUrl = it.content.avatarUrl?.convertMxUrToUrl(userCredentials.homeServer)?.let { AvatarUrl(it) },
|
||||
)
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,4 +10,5 @@ internal data class RoomToProcess(
|
|||
val apiSyncRoom: ApiSyncRoom,
|
||||
val directMessage: UserId?,
|
||||
val userCredentials: UserCredentials,
|
||||
val heroes: List<UserId>?,
|
||||
)
|
|
@ -30,7 +30,6 @@ internal class SyncReducer(
|
|||
|
||||
suspend fun reduce(isInitialSync: Boolean, sideEffects: SideEffectResult, response: ApiSyncResponse, userCredentials: UserCredentials): ReducerResult {
|
||||
val directMessages = response.directMessages()
|
||||
|
||||
val invites = response.rooms?.invite?.map { roomInvite(it, userCredentials) } ?: emptyList()
|
||||
val roomsLeft = findRoomsLeft(response, userCredentials)
|
||||
val newRooms = response.rooms?.join?.keys?.filterNot { roomDataSource.contains(it) } ?: emptyList()
|
||||
|
@ -46,6 +45,7 @@ internal class SyncReducer(
|
|||
apiSyncRoom = apiRoom,
|
||||
directMessage = directMessages[roomId],
|
||||
userCredentials = userCredentials,
|
||||
heroes = apiRoom.summary?.heroes,
|
||||
),
|
||||
isInitialSync = isInitialSync
|
||||
)
|
||||
|
|
|
@ -38,6 +38,7 @@ internal class TimelineEventsProcessor(
|
|||
is ApiTimelineEvent.RoomMember -> null
|
||||
is ApiTimelineEvent.RoomName -> null
|
||||
is ApiTimelineEvent.RoomTopic -> null
|
||||
is ApiTimelineEvent.CanonicalAlias -> null
|
||||
ApiTimelineEvent.Ignored -> null
|
||||
}
|
||||
roomEvent
|
||||
|
|
|
@ -70,4 +70,5 @@ private fun aRoomToProcess(ephemeral: ApiEphemeral? = null) = RoomToProcess(
|
|||
anApiSyncRoom(ephemeral = ephemeral),
|
||||
directMessage = null,
|
||||
userCredentials = aUserCredentials(),
|
||||
heroes = null,
|
||||
)
|
||||
|
|
|
@ -101,4 +101,4 @@ internal fun aRoomToProcess(
|
|||
apiSyncRoom: ApiSyncRoom = anApiSyncRoom(),
|
||||
directMessage: UserId? = null,
|
||||
userCredentials: UserCredentials = aUserCredentials(),
|
||||
) = RoomToProcess(roomId, apiSyncRoom, directMessage, userCredentials)
|
||||
) = RoomToProcess(roomId, apiSyncRoom, directMessage, userCredentials, heroes = null)
|
||||
|
|
|
@ -9,7 +9,7 @@ test {
|
|||
|
||||
dependencies {
|
||||
kotlinTest(it)
|
||||
testImplementation 'app.cash.turbine:turbine:0.9.0'
|
||||
testImplementation 'app.cash.turbine:turbine:0.10.0'
|
||||
|
||||
testImplementation Dependencies.mavenCentral.kotlinSerializationJson
|
||||
|
||||
|
|
|
@ -20,10 +20,7 @@ import org.junit.jupiter.api.MethodOrderer
|
|||
import org.junit.jupiter.api.Order
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestMethodOrder
|
||||
import test.MatrixTestScope
|
||||
import test.TestMatrix
|
||||
import test.flowTest
|
||||
import test.restoreLoginAndInitialSync
|
||||
import test.*
|
||||
import java.nio.file.Paths
|
||||
import java.util.*
|
||||
|
||||
|
@ -35,8 +32,8 @@ class SmokeTest {
|
|||
@Test
|
||||
@Order(1)
|
||||
fun `can register accounts`() = runTest {
|
||||
SharedState._alice = createAndRegisterAccount()
|
||||
SharedState._bob = createAndRegisterAccount()
|
||||
SharedState._alice = createAndRegisterAccount("alice")
|
||||
SharedState._bob = createAndRegisterAccount("bob")
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -94,7 +91,7 @@ class SmokeTest {
|
|||
|
||||
@Test
|
||||
fun `can import E2E room keys file`() = runTest {
|
||||
val ignoredUser = TestUser("ignored", RoomMember(UserId("ignored"), null, null), "ignored")
|
||||
val ignoredUser = TestUser("ignored", RoomMember(UserId("ignored"), null, null), "ignored", "ignored")
|
||||
val cryptoService = TestMatrix(ignoredUser, includeLogging = true).client.cryptoService()
|
||||
val stream = loadResourceStream("element-keys.txt")
|
||||
|
||||
|
@ -133,10 +130,10 @@ class SmokeTest {
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun createAndRegisterAccount(): TestUser {
|
||||
private suspend fun createAndRegisterAccount(testUsername: String): TestUser {
|
||||
val aUserName = "${UUID.randomUUID()}"
|
||||
val userId = UserId("@$aUserName:localhost:8080")
|
||||
val aUser = TestUser("aaaa11111zzzz", RoomMember(userId, aUserName, null), HTTPS_TEST_SERVER_URL)
|
||||
val aUser = TestUser("aaaa11111zzzz", RoomMember(userId, aUserName, null), HTTPS_TEST_SERVER_URL, testUsername)
|
||||
|
||||
val result = TestMatrix(aUser, includeLogging = true, includeHttpLogging = true)
|
||||
.client
|
||||
|
@ -167,26 +164,35 @@ private suspend fun login(user: TestUser) {
|
|||
}
|
||||
|
||||
object SharedState {
|
||||
|
||||
val alice: TestUser
|
||||
get() = _alice!!
|
||||
var _alice: TestUser? = null
|
||||
set(value) {
|
||||
field = value!!
|
||||
TestUsers.users.add(value)
|
||||
}
|
||||
|
||||
val bob: TestUser
|
||||
get() = _bob!!
|
||||
var _bob: TestUser? = null
|
||||
set(value) {
|
||||
field = value!!
|
||||
TestUsers.users.add(value)
|
||||
}
|
||||
|
||||
val sharedRoom: RoomId
|
||||
get() = _sharedRoom!!
|
||||
var _sharedRoom: RoomId? = null
|
||||
}
|
||||
|
||||
data class TestUser(val password: String, val roomMember: RoomMember, val homeServer: String)
|
||||
data class TestUser(val password: String, val roomMember: RoomMember, val homeServer: String, val testName: String)
|
||||
data class TestMessage(val content: String, val author: RoomMember)
|
||||
|
||||
fun String.from(roomMember: RoomMember) = TestMessage("$this - ${UUID.randomUUID()}", roomMember)
|
||||
|
||||
fun testAfterInitialSync(block: suspend MatrixTestScope.(TestMatrix, TestMatrix) -> Unit) {
|
||||
restoreLoginAndInitialSync(TestMatrix(SharedState.alice, includeLogging = false), TestMatrix(SharedState.bob, includeLogging = false), block)
|
||||
restoreLoginAndInitialSync(TestMatrix(SharedState.alice, includeLogging = true), TestMatrix(SharedState.bob, includeLogging = false), block)
|
||||
}
|
||||
|
||||
private fun Flow<Verification.State>.automaticVerification(testMatrix: TestMatrix) = this.onEach {
|
||||
|
|
|
@ -5,17 +5,13 @@ package test
|
|||
import TestMessage
|
||||
import TestUser
|
||||
import app.dapk.st.core.extensions.ifNull
|
||||
import app.dapk.st.matrix.common.MxUrl
|
||||
import app.dapk.st.matrix.common.RoomId
|
||||
import app.dapk.st.matrix.common.RoomMember
|
||||
import app.dapk.st.matrix.common.convertMxUrToUrl
|
||||
import app.dapk.st.matrix.http.MatrixHttpClient
|
||||
import app.dapk.st.matrix.message.MessageService
|
||||
import app.dapk.st.matrix.message.messageService
|
||||
import app.dapk.st.matrix.sync.RoomEvent
|
||||
import app.dapk.st.matrix.sync.syncService
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.statement.*
|
||||
import io.ktor.util.cio.*
|
||||
|
@ -28,7 +24,6 @@ import org.amshove.kluent.fail
|
|||
import org.amshove.kluent.shouldBeEqualTo
|
||||
import java.io.File
|
||||
import java.math.BigInteger
|
||||
import java.net.URL
|
||||
import java.security.MessageDigest
|
||||
import java.util.*
|
||||
|
||||
|
|
|
@ -46,6 +46,12 @@ import java.io.File
|
|||
import java.time.Clock
|
||||
import javax.imageio.ImageIO
|
||||
|
||||
object TestUsers {
|
||||
|
||||
val users = mutableSetOf<TestUser>()
|
||||
|
||||
}
|
||||
|
||||
class TestMatrix(
|
||||
private val user: TestUser,
|
||||
temporaryDatabase: Boolean = false,
|
||||
|
@ -53,10 +59,11 @@ class TestMatrix(
|
|||
includeLogging: Boolean = false,
|
||||
) {
|
||||
|
||||
private val errorTracker = PrintingErrorTracking(prefix = user.roomMember.id.value.split(":")[0])
|
||||
private val errorTracker = PrintingErrorTracking(prefix = user.testName)
|
||||
private val logger: MatrixLogger = { tag, message ->
|
||||
if (includeLogging) {
|
||||
println("${user.roomMember.id.value.split(":")[0]} $tag $message")
|
||||
val messageWithIdReplaceByName = TestUsers.users.fold(message) { acc, user -> acc.replace(user.roomMember.id.value, "*${user.testName}") }
|
||||
println("${user.testName} $tag $messageWithIdReplaceByName")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"code": 16,
|
||||
"name": "15/09/2022-V1"
|
||||
"code": 17,
|
||||
"name": "19/09/2022-V1"
|
||||
}
|
Loading…
Reference in New Issue