How to use context method of io.kotest.core.spec.style.scopes.ShouldSpecContainerScope class

Best Kotest code snippet using io.kotest.core.spec.style.scopes.ShouldSpecContainerScope.context

OlmEventServiceTest.kt

Source:OlmEventServiceTest.kt Github

copy

Full Screen

...145                mapOf(bob to mapOf(bobDeviceId to keysOf(bobsFakeSignedCurveKey)))146            )147        }148    }149    context(OlmEventService::handleOlmEncryptedToDeviceEvents.name) {150        context("exceptions") {151            val event = Event.ToDeviceEvent(152                OlmEncryptedEventContent(153                    mapOf(), Curve25519Key(null, "")154                ),155                UserId("sender", "server")156            )157            should("catch exceptions") {158                cut.handleOlmEncryptedToDeviceEvents(event)159            }160        }161        should("emit decrypted events") {162            freeAfter(OlmUtility.create()) { olmUtility ->163                val bobStore = InMemoryStore(storeScope).apply { init() }164                val bobOlmService =165                    OlmService("", bob, bobDeviceId, bobStore, api, json, bobAccount, olmUtility)166                store.olm.storeAccount(aliceAccount, "")167                val aliceSignService = OlmSignService(alice, aliceDeviceId, json, store, aliceAccount, olmUtility)168                val cutWithAccount = OlmEventService(169                    "",170                    alice,171                    aliceDeviceId,172                    Ed25519Key(null, aliceAccount.identityKeys.ed25519),173                    Curve25519Key(null, aliceAccount.identityKeys.curve25519),174                    json,175                    aliceAccount,176                    store,177                    api,178                    aliceSignService179                )180                store.keys.updateDeviceKeys(bob) {181                    mapOf(182                        bobDeviceId to StoredDeviceKeys(183                            Signed(184                                DeviceKeys(185                                    userId = bob,186                                    deviceId = bobDeviceId,187                                    algorithms = setOf(EncryptionAlgorithm.Olm, EncryptionAlgorithm.Megolm),188                                    keys = Keys(189                                        keysOf(190                                            bobOlmService.getSelfSignedDeviceKeys().signed.get<Curve25519Key>()!!,191                                            bobOlmService.getSelfSignedDeviceKeys().signed.get<Ed25519Key>()!!192                                        )193                                    )194                                ), mapOf()195                            ), KeySignatureTrustLevel.Valid(true)196                        )197                    )198                }199                bobStore.keys.updateDeviceKeys(alice) {200                    mapOf(201                        aliceDeviceId to StoredDeviceKeys(202                            Signed(203                                DeviceKeys(204                                    userId = alice,205                                    deviceId = aliceDeviceId,206                                    algorithms = setOf(EncryptionAlgorithm.Olm, EncryptionAlgorithm.Megolm),207                                    keys = Keys(208                                        keysOf(209                                            Curve25519Key(null, aliceAccount.identityKeys.curve25519),210                                            Ed25519Key(null, aliceAccount.identityKeys.ed25519)211                                        )212                                    )213                                ), mapOf()214                            ), KeySignatureTrustLevel.Valid(true)215                        )216                    )217                }218                apiConfig.endpoints {219                    matrixJsonEndpoint(json, mappings, ClaimKeys()) {220                        it.oneTimeKeys shouldBe mapOf(alice to mapOf(aliceDeviceId to KeyAlgorithm.SignedCurve25519))221                        ClaimKeys.Response(222                            emptyMap(),223                            mapOf(224                                alice to mapOf(225                                    aliceDeviceId to keysOf(226                                        aliceSignService.signCurve25519Key(227                                            Curve25519Key(228                                                aliceDeviceId,229                                                aliceAccount.getOneTimeKey()230                                            )231                                        )232                                    )233                                )234                            )235                        )236                    }237                }238                val outboundSession = OlmOutboundGroupSession.create()239                val eventContent = RoomKeyEventContent(240                    RoomId("room", "server"),241                    outboundSession.sessionId,242                    outboundSession.sessionKey,243                    EncryptionAlgorithm.Megolm244                )245                val encryptedEvent = Event.ToDeviceEvent(246                    bobOlmService.event.encryptOlm(247                        eventContent,248                        alice,249                        aliceDeviceId250                    ), bob251                )252                val emittedEvent = async { cutWithAccount.decryptedOlmEvents.first() }253                delay(50)254                cutWithAccount.handleOlmEncryptedToDeviceEvents(encryptedEvent)255                assertSoftly(256                    emittedEvent.await()257                ) {258                    assertNotNull(this)259                    encrypted shouldBe encryptedEvent260                    decrypted shouldBe DecryptedOlmEvent(261                        eventContent,262                        bob,263                        keysOf(bobOlmService.getSelfSignedDeviceKeys().signed.get<Ed25519Key>()!!.copy(keyId = null)),264                        alice,265                        keysOf(Ed25519Key(null, aliceAccount.identityKeys.ed25519))266                    )267                }268            }269        }270    }271    context(OlmEventService::encryptOlm.name) {272        val eventContent = RoomKeyEventContent(273            RoomId("room", "server"),274            "sessionId",275            "sessionKey",276            EncryptionAlgorithm.Megolm,277        )278        lateinit var decryptedOlmEvent: DecryptedOlmEvent<RoomKeyEventContent>279        beforeEach {280            decryptedOlmEvent = DecryptedOlmEvent(281                content = eventContent,282                sender = alice,283                senderKeys = keysOf(aliceEdKey.copy(keyId = null)),284                recipient = bob,285                recipientKeys = keysOf(bobEdKey.copy(keyId = null))286            )287        }288        context("without stored olm encrypt session") {289            beforeEach {290                apiConfig.endpoints { claimKeysEndpoint() }291            }292            should("encrypt") {293                val encryptedMessage = cut.encryptOlm(eventContent, bob, bobDeviceId)294                val encryptedCipherText = encryptedMessage.ciphertext[bobCurveKey.value]295                assertNotNull(encryptedCipherText)296                encryptedMessage.senderKey shouldBe aliceCurveKey297                encryptedCipherText.type shouldBe INITIAL_PRE_KEY298                freeAfter(299                    OlmSession.createInboundFrom(300                        account = bobAccount,301                        identityKey = aliceCurveKey.value,302                        oneTimeKeyMessage = encryptedCipherText.body303                    )304                ) { bobSession ->305                    json.decodeFromString(306                        decryptedOlmEventSerializer,307                        bobSession.decrypt(OlmMessage(encryptedCipherText.body, OlmMessageType.INITIAL_PRE_KEY))308                    ) shouldBe decryptedOlmEvent309                }310                store.olm.getOlmSessions(bobCurveKey)!! shouldHaveSize 1311            }312            should("throw exception when one time key is invalid") {313                signService.returnVerify = VerifyResult.Invalid("dino")314                shouldThrow<KeyException.KeyVerificationFailedException> {315                    cut.encryptOlm(eventContent, bob, bobDeviceId).ciphertext.entries.first().value316                }.message shouldBe "dino"317                store.olm.getOlmSessions(bobCurveKey) should beNull()318            }319        }320        context("with stored olm encrypt session") {321            should("encrypt event with stored session") {322                freeAfter(323                    OlmSession.createOutbound(324                        bobAccount,325                        aliceCurveKey.value,326                        aliceAccount.getOneTimeKey()327                    )328                ) { bobSession ->329                    val storedOlmSession = freeAfter(330                        OlmSession.createInbound(aliceAccount, bobSession.encrypt("first message").cipherText)331                    ) { aliceSession ->332                        StoredOlmSession(333                            bobCurveKey,334                            aliceSession.sessionId,335                            Clock.System.now(),336                            Clock.System.now(),337                            aliceSession.pickle("")338                        )339                    }340                    store.olm.updateOlmSessions(bobCurveKey) { setOf(storedOlmSession) }341                    val encryptedMessage = cut.encryptOlm(eventContent, bob, bobDeviceId)342                    val encryptedCipherText = encryptedMessage.ciphertext[bobCurveKey.value]343                    assertNotNull(encryptedCipherText)344                    encryptedMessage.senderKey shouldBe aliceCurveKey345                    encryptedCipherText.type shouldBe INITIAL_PRE_KEY346                    json.decodeFromString(347                        decryptedOlmEventSerializer,348                        bobSession.decrypt(349                            OlmMessage(350                                encryptedCipherText.body,351                                OlmMessageType.INITIAL_PRE_KEY352                            )353                        )354                    ) shouldBe decryptedOlmEvent355                    store.olm.getOlmSessions(bobCurveKey)!! shouldHaveSize 1356                    store.olm.getOlmSessions(bobCurveKey)?.first() shouldNotBe storedOlmSession357                }358            }359        }360    }361    context(OlmEventService::decryptOlm.name) {362        val eventContent = RoomKeyEventContent(363            RoomId("room", "server"),364            "sessionId",365            "sessionKey",366            EncryptionAlgorithm.Megolm367        )368        lateinit var decryptedOlmEvent: DecryptedOlmEvent<RoomKeyEventContent>369        beforeEach {370            decryptedOlmEvent = DecryptedOlmEvent(371                content = eventContent,372                sender = bob,373                senderKeys = keysOf(bobEdKey),374                recipient = alice,375                recipientKeys = keysOf(aliceEdKey)376            )377        }378        context("without stored decrypt olm session") {379            should("decrypt pre key message from new session") {380                val encryptedMessage = freeAfter(381                    OlmSession.createOutbound(382                        bobAccount,383                        aliceCurveKey.value,384                        aliceAccount.getOneTimeKey()385                    )386                ) { bobSession ->387                    bobSession.encrypt(json.encodeToString(decryptedOlmEventSerializer, decryptedOlmEvent))388                }389                cut.decryptOlm(390                    OlmEncryptedEventContent(391                        ciphertext = mapOf(392                            aliceCurveKey.value to CiphertextInfo(encryptedMessage.cipherText, INITIAL_PRE_KEY)393                        ),394                        senderKey = bobCurveKey395                    ), bob396                ) shouldBe decryptedOlmEvent397                store.olm.getOlmSessions(bobCurveKey)!! shouldHaveSize 1398                // we check, that the one time key cannot be used twice399                shouldThrow<OlmLibraryException> {400                    OlmSession.createInboundFrom(aliceAccount, bobCurveKey.value, encryptedMessage.cipherText)401                }402            }403            should("not decrypt pre key message, when the 5 last created sessions are not older then 1 hour") {404                val encryptedMessage = freeAfter(405                    OlmSession.createOutbound(406                        bobAccount,407                        aliceCurveKey.value,408                        aliceAccount.getOneTimeKey()409                    )410                ) { bobSession ->411                    bobSession.encrypt(json.encodeToString(decryptedOlmEventSerializer, decryptedOlmEvent))412                }413                repeat(5) { pseudoSessionId ->414                    freeAfter(OlmAccount.create()) { dummyAccount ->415                        freeAfter(416                            OlmSession.createOutbound(417                                aliceAccount,418                                dummyAccount.identityKeys.curve25519,419                                dummyAccount.getOneTimeKey()420                            )421                        ) { aliceSession ->422                            val storedOlmSession = StoredOlmSession(423                                bobCurveKey,424                                pseudoSessionId.toString(),425                                Clock.System.now(),426                                Clock.System.now(),427                                aliceSession.pickle("")428                            )429                            store.olm.updateOlmSessions(bobCurveKey) {430                                it?.plus(storedOlmSession) ?: setOf(431                                    storedOlmSession432                                )433                            }434                        }435                    }436                }437                shouldThrow<SessionException.PreventToManySessions> {438                    cut.decryptOlm(439                        OlmEncryptedEventContent(440                            ciphertext = mapOf(441                                aliceCurveKey.value to CiphertextInfo(encryptedMessage.cipherText, INITIAL_PRE_KEY)442                            ),443                            senderKey = bobCurveKey444                        ), bob445                    )446                }447            }448            should("throw on ordinary message") {449                var sendToDeviceEvents: Map<UserId, Map<String, ToDeviceEventContent>>? = null450                apiConfig.endpoints {451                    claimKeysEndpoint()452                    matrixJsonEndpoint(453                        json, mappings,454                        SendToDevice("m.room.encrypted", "txn"),455                        skipUrlCheck = true456                    ) {457                        sendToDeviceEvents = it.messages458                    }459                }460                val encryptedMessage = freeAfter(461                    OlmSession.createOutbound(462                        bobAccount,463                        aliceCurveKey.value,464                        aliceAccount.getOneTimeKey()465                    )466                ) { bobSession ->467                    bobSession.encrypt(json.encodeToString(decryptedOlmEventSerializer, decryptedOlmEvent))468                }469                shouldThrow<SessionException.CouldNotDecrypt> {470                    cut.decryptOlm(471                        OlmEncryptedEventContent(472                            ciphertext = mapOf(473                                aliceCurveKey.value to CiphertextInfo(encryptedMessage.cipherText, ORDINARY)474                            ),475                            senderKey = bobCurveKey476                        ), bob477                    )478                }479                val encryptedEventContent =480                    sendToDeviceEvents?.get(bob)?.get(bobDeviceId)?.shouldBeInstanceOf<OlmEncryptedEventContent>()481                val ciphertext = encryptedEventContent?.ciphertext?.get(bobCurveKey.value)?.body482                assertNotNull(ciphertext)483                freeAfter(OlmSession.createInbound(bobAccount, ciphertext)) { session ->484                    json.decodeFromString(485                        decryptedOlmEventSerializer,486                        session.decrypt(OlmMessage(ciphertext, OlmMessageType.INITIAL_PRE_KEY))487                    ).content shouldBe DummyEventContent488                }489            }490        }491        context("with stored decrypt olm session") {492            should("decrypt pre key message from stored session") {493                freeAfter(494                    OlmSession.createOutbound(495                        aliceAccount,496                        bobCurveKey.value,497                        bobAccount.getOneTimeKey()498                    )499                ) { aliceSession ->500                    val firstMessage = aliceSession.encrypt("first message")501                    val encryptedMessage = freeAfter(502                        OlmSession.createInbound(bobAccount, firstMessage.cipherText)503                    ) { bobSession ->504                        // we do not decrypt the message, so the next is an initial pre key message505                        bobSession.encrypt(json.encodeToString(decryptedOlmEventSerializer, decryptedOlmEvent))506                    }507                    val storedOlmSession = StoredOlmSession(508                        bobCurveKey,509                        aliceSession.sessionId,510                        Clock.System.now(),511                        Clock.System.now(),512                        aliceSession.pickle("")513                    )514                    store.olm.updateOlmSessions(bobCurveKey) { setOf(storedOlmSession) }515                    cut.decryptOlm(516                        OlmEncryptedEventContent(517                            ciphertext = mapOf(518                                aliceCurveKey.value to CiphertextInfo(encryptedMessage.cipherText, INITIAL_PRE_KEY)519                            ),520                            senderKey = bobCurveKey521                        ), bob522                    ) shouldBe decryptedOlmEvent523                    store.olm.getOlmSessions(bobCurveKey)?.first() shouldNotBe storedOlmSession524                }525            }526            should("decrypt ordinary message") {527                freeAfter(528                    OlmSession.createOutbound(529                        aliceAccount,530                        bobCurveKey.value,531                        bobAccount.getOneTimeKey()532                    )533                ) { aliceSession ->534                    val firstMessage = aliceSession.encrypt("first message")535                    val encryptedMessage = freeAfter(536                        OlmSession.createInbound(bobAccount, firstMessage.cipherText)537                    ) { bobSession ->538                        bobSession.decrypt(firstMessage)539                        bobSession.encrypt(json.encodeToString(decryptedOlmEventSerializer, decryptedOlmEvent))540                    }541                    val storedOlmSession = StoredOlmSession(542                        bobCurveKey,543                        aliceSession.sessionId,544                        Clock.System.now(),545                        Clock.System.now(),546                        aliceSession.pickle("")547                    )548                    store.olm.updateOlmSessions(bobCurveKey) { setOf(storedOlmSession) }549                    cut.decryptOlm(550                        OlmEncryptedEventContent(551                            ciphertext = mapOf(552                                aliceCurveKey.value to CiphertextInfo(encryptedMessage.cipherText, ORDINARY)553                            ),554                            senderKey = bobCurveKey555                        ), bob556                    ) shouldBe decryptedOlmEvent557                    store.olm.getOlmSessions(bobCurveKey)?.first() shouldNotBe storedOlmSession558                }559            }560            should("try multiple sessions descended by last used") {561                freeAfter(562                    OlmSession.createOutbound(aliceAccount, bobCurveKey.value, bobAccount.getOneTimeKey()),563                    OlmSession.createOutbound(aliceAccount, bobCurveKey.value, bobAccount.getOneTimeKey()),564                    OlmSession.createOutbound(aliceAccount, bobCurveKey.value, bobAccount.getOneTimeKey()),565                ) { aliceSession1, aliceSession2, aliceSession3 ->566                    val firstMessage = aliceSession1.encrypt("first message")567                    val encryptedMessage = freeAfter(568                        OlmSession.createInbound(bobAccount, firstMessage.cipherText)569                    ) { bobSession ->570                        bobSession.decrypt(firstMessage)571                        bobSession.encrypt(json.encodeToString(decryptedOlmEventSerializer, decryptedOlmEvent))572                    }573                    val storedOlmSession1 = StoredOlmSession(574                        bobCurveKey,575                        aliceSession1.sessionId,576                        Clock.System.now(),577                        Clock.System.now(),578                        aliceSession1.pickle("")579                    )580                    val storedOlmSession2 = StoredOlmSession(581                        bobCurveKey,582                        aliceSession2.sessionId,583                        fromEpochMilliseconds(24),584                        Clock.System.now(),585                        aliceSession2.pickle("")586                    )587                    val storedOlmSession3 = StoredOlmSession(588                        bobCurveKey,589                        aliceSession3.sessionId,590                        Clock.System.now(),591                        Clock.System.now(),592                        aliceSession3.pickle("")593                    )594                    store.olm.updateOlmSessions(bobCurveKey) {595                        setOf(596                            storedOlmSession2,597                            storedOlmSession1,598                            storedOlmSession3599                        )600                    }601                    cut.decryptOlm(602                        OlmEncryptedEventContent(603                            ciphertext = mapOf(604                                aliceCurveKey.value to CiphertextInfo(encryptedMessage.cipherText, ORDINARY)605                            ),606                            senderKey = bobCurveKey607                        ), bob608                    ) shouldBe decryptedOlmEvent609                    store.olm.getOlmSessions(bobCurveKey)!! shouldNotContain storedOlmSession1610                }611            }612        }613        context("handle olm event with manipulated") {614            suspend fun ContainerScope.handleManipulation(manipulatedOlmEvent: DecryptedOlmEvent<RoomKeyEventContent>) {615                val job1 = launch {616                    store.keys.outdatedKeys.first { it.isNotEmpty() }617                    store.keys.outdatedKeys.value = setOf()618                }619                freeAfter(620                    OlmSession.createOutbound(621                        aliceAccount,622                        bobCurveKey.value,623                        bobAccount.getOneTimeKey()624                    )625                ) { aliceSession ->626                    val firstMessage = aliceSession.encrypt("first message")627                    val encryptedMessage = freeAfter(628                        OlmSession.createInbound(bobAccount, firstMessage.cipherText)629                    ) { bobSession ->630                        bobSession.decrypt(firstMessage)631                        bobSession.encrypt(json.encodeToString(decryptedOlmEventSerializer, manipulatedOlmEvent))632                    }633                    val storedOlmSession = StoredOlmSession(634                        bobCurveKey,635                        aliceSession.sessionId,636                        Clock.System.now(),637                        Clock.System.now(),638                        aliceSession.pickle("")639                    )640                    store.olm.updateOlmSessions(bobCurveKey) { setOf(storedOlmSession) }641                    shouldThrow<DecryptionException> {642                        cut.decryptOlm(643                            OlmEncryptedEventContent(644                                ciphertext = mapOf(645                                    aliceCurveKey.value to CiphertextInfo(encryptedMessage.cipherText, ORDINARY)646                                ),647                                senderKey = bobCurveKey648                            ), bob649                        )650                    }651                }652                job1.cancel()653            }654            should("sender") {655                handleManipulation(decryptedOlmEvent.copy(sender = UserId("cedric", "server")))656            }657            should("senderKeys") {658                handleManipulation(decryptedOlmEvent.copy(senderKeys = keysOf(Ed25519Key("CEDRICKEY", "cedrics key"))))659            }660            should("recipient") {661                handleManipulation(decryptedOlmEvent.copy(recipient = UserId("cedric", "server")))662            }663            should("recipientKeys") {664                handleManipulation(665                    decryptedOlmEvent.copy(recipientKeys = keysOf(Ed25519Key("CEDRICKEY", "cedrics key")))666                )667            }668        }669    }670    context(OlmEventService::encryptMegolm.name) {671        val eventContent = TextMessageEventContent("Hi", relatesTo = relatesTo)672        val room = RoomId("room", "server")673        val decryptedMegolmEvent = DecryptedMegolmEvent(eventContent, room)674        beforeEach {675            store.room.update(room) { Room(room, membership = JOIN, membersLoaded = true) }676            listOf(677                StateEvent(678                    MemberEventContent(membership = JOIN),679                    EventId("\$event1"),680                    alice,681                    room,682                    1234,683                    stateKey = alice.full684                ),685                StateEvent(686                    MemberEventContent(membership = JOIN),687                    EventId("\$event2"),688                    bob,689                    room,690                    1235,691                    stateKey = bob.full692                )693            ).forEach { store.roomState.update(it) }694        }695        suspend fun ShouldSpecContainerScope.testEncryption(696            settings: EncryptionEventContent,697            expectedMessageCount: Int,698        ) {699            should("encrypt message") {700                var sendToDeviceEvents: Map<UserId, Map<String, ToDeviceEventContent>>? = null701                apiConfig.endpoints {702                    claimKeysEndpoint()703                    matrixJsonEndpoint(704                        json, mappings,705                        SendToDevice("m.room.encrypted", "txn"),706                        skipUrlCheck = true707                    ) {708                        sendToDeviceEvents = it.messages709                    }710                }711                store.keys.outdatedKeys.value = setOf(bob)712                val asyncResult = async { cut.encryptMegolm(eventContent, room, settings) }713                store.keys.outdatedKeys.subscriptionCount.takeWhile { it == 1 }.take(1).collect()714                asyncResult.isActive shouldBe true715                store.keys.outdatedKeys.value = setOf()716                val result = asyncResult.await()717                val storedOutboundSession = store.olm.getOutboundMegolmSession(room)718                assertNotNull(storedOutboundSession)719                assertSoftly(storedOutboundSession) {720                    encryptedMessageCount shouldBe expectedMessageCount721                    roomId shouldBe room722                }723                freeAfter(OlmOutboundGroupSession.unpickle("", storedOutboundSession.pickled)) { outboundSession ->724                    assertSoftly(result) {725                        senderKey shouldBe aliceCurveKey726                        deviceId shouldBe aliceDeviceId727                        sessionId shouldBe outboundSession.sessionId728                        this.relatesTo shouldBe relatesTo729                    }730                    val ciphertext =731                        sendToDeviceEvents?.get(bob)?.get(bobDeviceId)?.shouldBeInstanceOf<OlmEncryptedEventContent>()732                            ?.ciphertext?.get(bobCurveKey.value)?.body733                    assertNotNull(ciphertext)734                    freeAfter(OlmSession.createInbound(bobAccount, ciphertext)) { session ->735                        assertSoftly(736                            json.decodeFromString(737                                decryptedOlmEventSerializer,738                                session.decrypt(OlmMessage(ciphertext, OlmMessageType.INITIAL_PRE_KEY))739                            ).content740                        ) {741                            require(this is RoomKeyEventContent)742                            roomId shouldBe room743                            sessionId shouldBe outboundSession.sessionId744                        }745                    }746                    val storedInboundSession =747                        store.olm.getInboundMegolmSession(748                            aliceCurveKey,749                            outboundSession.sessionId,750                            room,751                            this752                        ).value753                    assertNotNull(storedInboundSession)754                    assertSoftly(storedInboundSession) {755                        sessionId shouldBe outboundSession.sessionId756                        senderKey shouldBe aliceCurveKey757                        roomId shouldBe room758                    }759                    freeAfter(OlmInboundGroupSession.unpickle("", storedInboundSession.pickled)) { inboundSession ->760                        json.decodeFromString(761                            decryptedMegolmEventSerializer, inboundSession.decrypt(result.ciphertext).message762                        ) shouldBe decryptedMegolmEvent763                    }764                }765            }766        }767        context("without stored megolm session") {768            testEncryption(EncryptionEventContent(), 1)769            should("not send room keys, when not possible to encrypt them due to missing one time keys") {770                val otherRoom = RoomId("otherRoom", "server")771                store.room.update(otherRoom) { Room(otherRoom, membership = JOIN, membersLoaded = true) }772                val cedric = UserId("cedric", "server")773                listOf(774                    StateEvent(775                        MemberEventContent(membership = JOIN),776                        EventId("\$event1"),777                        alice,778                        otherRoom,779                        1234,780                        stateKey = alice.full781                    ),782                    StateEvent(783                        MemberEventContent(membership = JOIN),784                        EventId("\$event2"),785                        cedric,786                        otherRoom,787                        1235,788                        stateKey = cedric.full789                    )790                ).forEach { store.roomState.update(it) }791                store.keys.outdatedKeys.value = setOf(cedric)792                val asyncResult = async { cut.encryptMegolm(eventContent, otherRoom, EncryptionEventContent()) }793                store.keys.outdatedKeys.subscriptionCount.takeWhile { it == 1 }.take(1).collect()794                asyncResult.isActive shouldBe true795                store.keys.outdatedKeys.value = setOf()796                asyncResult.await()797            }798            should("wait that room members are loaded") {799                apiConfig.endpoints {800                    matrixJsonEndpoint(801                        json, mappings,802                        SendToDevice("m.room.encrypted", "txn"),803                        skipUrlCheck = true804                    ) {805                    }806                }807                store.room.update(room) { Room(room, membership = JOIN, membersLoaded = false) }808                store.room.get(room).first { it?.membersLoaded == false }809                val cedric = UserId("cedric", "server")810                listOf(811                    StateEvent(812                        MemberEventContent(membership = JOIN),813                        EventId("\$event1"),814                        alice,815                        room,816                        1234,817                        stateKey = alice.full818                    ),819                    StateEvent(820                        MemberEventContent(membership = JOIN),821                        EventId("\$event2"),822                        cedric,823                        room,824                        1235,825                        stateKey = cedric.full826                    )827                ).forEach { store.roomState.update(it) }828                val asyncResult = async { cut.encryptMegolm(eventContent, room, EncryptionEventContent()) }829                continually(200.milliseconds) {830                    asyncResult.isActive shouldBe true831                }832                store.room.update(room) { it?.copy(membersLoaded = true) }833                asyncResult.await()834            }835        }836        context("with stored megolm session") {837            context("send sessions to new devices and encrypt") {838                beforeEach {839                    freeAfter(OlmOutboundGroupSession.create()) { session ->840                        store.olm.updateOutboundMegolmSession(room) {841                            StoredOutboundMegolmSession(842                                roomId = room,843                                encryptedMessageCount = 23,844                                newDevices = mapOf(bob to setOf(bobDeviceId)),845                                pickled = session.pickle("")846                            )847                        }848                        store.olm.storeTrustedInboundMegolmSession(849                            roomId = room,850                            senderKey = aliceCurveKey,851                            senderSigningKey = aliceEdKey,852                            sessionId = session.sessionId,853                            sessionKey = session.sessionKey,854                            pickleKey = ""855                        )856                    }857                }858                testEncryption(EncryptionEventContent(), 24)859            }860            context("when rotation period passed") {861                beforeEach {862                    store.olm.updateOutboundMegolmSession(room) {863                        StoredOutboundMegolmSession(864                            roomId = room,865                            createdAt = Clock.System.now().minus(24, DateTimeUnit.MILLISECOND),866                            pickled = "is irrelevant"867                        )868                    }869                }870                testEncryption(EncryptionEventContent(rotationPeriodMs = 24), 2)871            }872            context("when message count passed") {873                beforeEach {874                    store.olm.updateOutboundMegolmSession(room) {875                        StoredOutboundMegolmSession(876                            roomId = room,877                            encryptedMessageCount = 24,878                            pickled = "is irrelevant"879                        )880                    }881                }882                testEncryption(EncryptionEventContent(rotationPeriodMsgs = 24), 25)883            }884        }885    }886    context(OlmEventService::decryptMegolm.name) {887        val eventContent = TextMessageEventContent("Hi")888        val room = RoomId("room", "server")889        val decryptedMegolmEvent = DecryptedMegolmEvent(eventContent, room)890        should("decrypt megolm event") {891            freeAfter(OlmOutboundGroupSession.create()) { session ->892                store.olm.storeTrustedInboundMegolmSession(893                    roomId = room,894                    senderKey = bobCurveKey,895                    senderSigningKey = bobEdKey,896                    sessionId = session.sessionId,897                    sessionKey = session.sessionKey,898                    pickleKey = ""899                )900                val ciphertext =901                    session.encrypt(json.encodeToString(decryptedMegolmEventSerializer, decryptedMegolmEvent))902                cut.decryptMegolm(903                    MessageEvent(904                        MegolmEncryptedEventContent(905                            ciphertext,906                            bobCurveKey,907                            bobDeviceId,908                            session.sessionId,909                            relatesTo = relatesTo910                        ),911                        EventId("\$event"),912                        bob,913                        room,914                        1234915                    )916                ) shouldBe decryptedMegolmEvent.copy(content = decryptedMegolmEvent.content.copy(relatesTo = relatesTo))917                store.olm.updateInboundMegolmMessageIndex(bobCurveKey, session.sessionId, room, 0) {918                    it shouldBe StoredInboundMegolmMessageIndex(919                        bobCurveKey, session.sessionId, room, 0, EventId("\$event"), 1234920                    )921                    it922                }923            }924        }925        should("throw when no keys were send to us") {926            freeAfter(OlmOutboundGroupSession.create()) { session ->927                val ciphertext =928                    session.encrypt(json.encodeToString(decryptedMegolmEventSerializer, decryptedMegolmEvent))929                shouldThrow<DecryptionException> {930                    cut.decryptMegolm(931                        MessageEvent(932                            MegolmEncryptedEventContent(933                                ciphertext,934                                bobCurveKey,935                                bobDeviceId,936                                session.sessionId937                            ),938                            EventId("\$event"),939                            bob,940                            room,941                            1234942                        )943                    )944                }945            }946        }947        context("manipulation") {948            should("handle manipulated roomId in megolmEvent") {949                freeAfter(OlmOutboundGroupSession.create()) { session ->950                    store.olm.storeTrustedInboundMegolmSession(951                        roomId = room,952                        senderKey = bobCurveKey,953                        senderSigningKey = bobEdKey,954                        sessionId = session.sessionId,955                        sessionKey = session.sessionKey,956                        pickleKey = ""957                    )958                    val ciphertext = session.encrypt(959                        json.encodeToString(960                            decryptedMegolmEventSerializer,961                            decryptedMegolmEvent.copy(roomId = RoomId("other", "server"))...

Full Screen

Full Screen

KeySecretServiceTest.kt

Source:KeySecretServiceTest.kt Github

copy

Full Screen

...89            ciphertext = mapOf(),90            senderKey = Key.Curve25519Key(null, "")91        ), bob92    )93    context(KeySecretService::handleEncryptedIncomingKeyRequests.name) {94        var sendToDeviceEvents: Map<UserId, Map<String, ToDeviceEventContent>>? = null95        beforeTest {96            sendToDeviceEvents = null97            store.account.userId.value = alice98            store.keys.updateDeviceKeys(alice) {99                mapOf(100                    aliceDevice to StoredDeviceKeys(101                        Signed(DeviceKeys(alice, aliceDevice, setOf(), keysOf()), null),102                        Valid(true)103                    )104                )105            }106            apiConfig.endpoints {107                matrixJsonEndpoint(108                    json, mappings,109                    SendToDevice("m.room.encrypted", "txn"),110                    skipUrlCheck = true111                ) {112                    sendToDeviceEvents = it.messages113                }114            }115            store.keys.secrets.value =116                mapOf(117                    M_CROSS_SIGNING_USER_SIGNING to StoredSecret(118                        GlobalAccountDataEvent(UserSigningKeyEventContent(mapOf())),119                        "secretUserSigningKey"120                    )121                )122            olmEvent.returnEncryptOlm = {123                EncryptedEventContent.OlmEncryptedEventContent(124                    ciphertext = mapOf(),125                    senderKey = Key.Curve25519Key("", "")126                )127            }128        }129        should("ignore request from other user") {130            cut.handleEncryptedIncomingKeyRequests(131                IOlmService.DecryptedOlmEventContainer(132                    encryptedEvent, DecryptedOlmEvent(133                        SecretKeyRequestEventContent(134                            M_CROSS_SIGNING_USER_SIGNING.id,135                            KeyRequestAction.REQUEST,136                            bobDevice,137                            "requestId"138                        ),139                        bob, keysOf(), alice, keysOf()140                    )141                )142            )143            cut.processIncomingKeyRequests()144            sendToDeviceEvents shouldBe null145        }146        should("add request on request") {147            cut.handleEncryptedIncomingKeyRequests(148                IOlmService.DecryptedOlmEventContainer(149                    encryptedEvent, DecryptedOlmEvent(150                        SecretKeyRequestEventContent(151                            M_CROSS_SIGNING_USER_SIGNING.id,152                            KeyRequestAction.REQUEST,153                            aliceDevice,154                            "requestId"155                        ),156                        alice, keysOf(), alice, keysOf()157                    )158                )159            )160            cut.processIncomingKeyRequests()161            sendToDeviceEvents?.get(alice)?.get(aliceDevice) shouldNotBe null162        }163        should("remove request on request cancellation") {164            cut.handleEncryptedIncomingKeyRequests(165                IOlmService.DecryptedOlmEventContainer(166                    encryptedEvent, DecryptedOlmEvent(167                        SecretKeyRequestEventContent(168                            M_CROSS_SIGNING_USER_SIGNING.id,169                            KeyRequestAction.REQUEST,170                            aliceDevice,171                            "requestId"172                        ),173                        alice, keysOf(), alice, keysOf()174                    )175                )176            )177            cut.handleEncryptedIncomingKeyRequests(178                IOlmService.DecryptedOlmEventContainer(179                    encryptedEvent, DecryptedOlmEvent(180                        SecretKeyRequestEventContent(181                            M_CROSS_SIGNING_USER_SIGNING.id,182                            KeyRequestAction.REQUEST_CANCELLATION,183                            aliceDevice,184                            "requestId"185                        ),186                        alice, keysOf(), alice, keysOf()187                    )188                )189            )190            cut.processIncomingKeyRequests()191            sendToDeviceEvents shouldBe null192        }193    }194    context(KeySecretService::processIncomingKeyRequests.name) {195        var sendToDeviceEvents: Map<UserId, Map<String, ToDeviceEventContent>>? = null196        beforeTest {197            sendToDeviceEvents = null198            store.account.userId.value = alice199            apiConfig.endpoints {200                matrixJsonEndpoint(201                    json, mappings,202                    SendToDevice("m.room.encrypted", "txn"),203                    skipUrlCheck = true204                ) {205                    sendToDeviceEvents = it.messages206                }207            }208            store.keys.secrets.value =209                mapOf(210                    M_CROSS_SIGNING_USER_SIGNING to StoredSecret(211                        GlobalAccountDataEvent(UserSigningKeyEventContent(mapOf())),212                        "secretUserSigningKey"213                    )214                )215            olmEvent.returnEncryptOlm = {216                EncryptedEventContent.OlmEncryptedEventContent(217                    ciphertext = mapOf(),218                    senderKey = Key.Curve25519Key("", "")219                )220            }221        }222        suspend fun ShouldSpecContainerScope.answerRequest(returnedTrustLevel: KeySignatureTrustLevel) {223            should("answer request with trust level $returnedTrustLevel") {224                store.keys.updateDeviceKeys(alice) {225                    mapOf(226                        aliceDevice to StoredDeviceKeys(227                            SignedDeviceKeys(DeviceKeys(alice, aliceDevice, setOf(), keysOf()), mapOf()),228                            returnedTrustLevel229                        )230                    )231                }232                cut.handleEncryptedIncomingKeyRequests(233                    IOlmService.DecryptedOlmEventContainer(234                        encryptedEvent, DecryptedOlmEvent(235                            SecretKeyRequestEventContent(236                                M_CROSS_SIGNING_USER_SIGNING.id,237                                KeyRequestAction.REQUEST,238                                aliceDevice,239                                "requestId"240                            ),241                            alice, keysOf(), alice, keysOf()242                        )243                    )244                )245                cut.processIncomingKeyRequests()246                cut.processIncomingKeyRequests()247                sendToDeviceEvents?.get(alice)?.get(aliceDevice) shouldNotBe null248            }249        }250        answerRequest(Valid(true))251        answerRequest(CrossSigned(true))252        suspend fun ShouldSpecContainerScope.notAnswerRequest(returnedTrustLevel: KeySignatureTrustLevel) {253            should("not answer request with trust level $returnedTrustLevel") {254                store.keys.updateDeviceKeys(alice) {255                    mapOf(256                        aliceDevice to StoredDeviceKeys(257                            SignedDeviceKeys(DeviceKeys(alice, aliceDevice, setOf(), keysOf()), mapOf()),258                            returnedTrustLevel259                        )260                    )261                }262                cut.handleEncryptedIncomingKeyRequests(263                    IOlmService.DecryptedOlmEventContainer(264                        encryptedEvent, DecryptedOlmEvent(265                            SecretKeyRequestEventContent(266                                M_CROSS_SIGNING_USER_SIGNING.id,267                                KeyRequestAction.REQUEST,268                                aliceDevice,269                                "requestId"270                            ),271                            alice, keysOf(), alice, keysOf()272                        )273                    )274                )275                cut.processIncomingKeyRequests()276                cut.processIncomingKeyRequests()277                sendToDeviceEvents shouldBe null278            }279        }280        notAnswerRequest(Valid(false))281        notAnswerRequest(CrossSigned(false))282        notAnswerRequest(NotCrossSigned)283        notAnswerRequest(Blocked)284        notAnswerRequest(Invalid("reason"))285    }286    context(KeySecretService::handleOutgoingKeyRequestAnswer.name) {287        val (crossSigningPublicKey, crossSigningPrivateKey) = freeAfter(OlmPkSigning.create(null)) { it.publicKey to it.privateKey }288        val (keyBackupPublicKey, keyBackupPrivateKey) = freeAfter(OlmPkDecryption.create(null)) { it.publicKey to it.privateKey }289        val aliceDevice2Key = Key.Ed25519Key(aliceDevice, "aliceDevice2KeyValue")290        suspend fun setDeviceKeys(trusted: Boolean) {291            store.keys.updateDeviceKeys(alice) {292                mapOf(293                    aliceDevice to StoredDeviceKeys(294                        SignedDeviceKeys(DeviceKeys(alice, aliceDevice, setOf(), keysOf(aliceDevice2Key)), mapOf()),295                        CrossSigned(trusted)296                    )297                )298            }299        }300        suspend fun setRequest(secretType: AllowedSecretType, receiverDeviceIds: Set<String>) {301            store.keys.addSecretKeyRequest(302                StoredSecretKeyRequest(303                    SecretKeyRequestEventContent(304                        secretType.id,305                        KeyRequestAction.REQUEST,306                        "OWN_ALICE_DEVICE",307                        "requestId"308                    ), receiverDeviceIds, Clock.System.now()309                )310            )311            store.keys.allSecretKeyRequests.first { it.size == 1 }312        }313        suspend fun setCrossSigningKeys(publicKey: String) {314            store.keys.updateCrossSigningKeys(alice) {315                setOf(316                    StoredCrossSigningKeys(317                        SignedCrossSigningKeys(318                            CrossSigningKeys(319                                alice, setOf(CrossSigningKeysUsage.UserSigningKey), keysOf(320                                    Key.Ed25519Key(publicKey, publicKey)321                                )322                            ), mapOf()323                        ), CrossSigned(true)324                    )325                )326            }327        }328        fun returnRoomKeysVersion(publicKey: String? = null) {329            apiConfig.endpoints {330                matrixJsonEndpoint(json, mappings, GetRoomKeyBackupVersion()) {331                    if (publicKey == null) throw MatrixServerException(InternalServerError, ErrorResponse.Unknown(""))332                    else GetRoomKeysBackupVersionResponse.V1(333                        authData = RoomKeyBackupAuthData.RoomKeyBackupV1AuthData(334                            publicKey = Key.Curve25519Key(null, publicKey)335                        ), 1, "etag", "1"336                    )337                }338            }339        }340        should("ignore, when sender device id cannot be found") {341            cut.handleOutgoingKeyRequestAnswer(342                IOlmService.DecryptedOlmEventContainer(343                    encryptedEvent, DecryptedOlmEvent(344                        SecretKeySendEventContent("requestId", crossSigningPrivateKey),345                        alice, keysOf(aliceDevice2Key), alice, keysOf()346                    )347                )348            )349            continually(500.milliseconds) {350                store.keys.secrets.value shouldBe mapOf()351            }352        }353        should("ignore when sender was not requested") {354            setDeviceKeys(true)355            setRequest(M_CROSS_SIGNING_USER_SIGNING, setOf("OTHER_DEVICE"))356            cut.handleOutgoingKeyRequestAnswer(357                IOlmService.DecryptedOlmEventContainer(358                    encryptedEvent, DecryptedOlmEvent(359                        SecretKeySendEventContent("requestId", crossSigningPrivateKey),360                        alice, keysOf(aliceDevice2Key), alice, keysOf()361                    )362                )363            )364            continually(500.milliseconds) {365                store.keys.secrets.value shouldBe mapOf()366            }367        }368        should("ignore when sender is not trusted") {369            setDeviceKeys(false)370            cut.handleOutgoingKeyRequestAnswer(371                IOlmService.DecryptedOlmEventContainer(372                    encryptedEvent, DecryptedOlmEvent(373                        SecretKeySendEventContent("requestId", crossSigningPrivateKey),374                        alice, keysOf(aliceDevice2Key), alice, keysOf()375                    )376                )377            )378            continually(500.milliseconds) {379                store.keys.secrets.value shouldBe mapOf()380            }381        }382        should("ignore when public key of cross signing secret cannot be generated") {383            setDeviceKeys(true)384            setRequest(M_CROSS_SIGNING_USER_SIGNING, setOf(aliceDevice))385            setCrossSigningKeys(crossSigningPublicKey)386            cut.handleOutgoingKeyRequestAnswer(387                IOlmService.DecryptedOlmEventContainer(388                    encryptedEvent, DecryptedOlmEvent(389                        SecretKeySendEventContent("requestId", "dino"),390                        alice, keysOf(aliceDevice2Key), alice, keysOf()391                    )392                )393            )394            continually(500.milliseconds) {395                store.keys.secrets.value shouldBe mapOf()396            }397        }398        should("ignore when public key of key backup secret cannot be retrieved") {399            setDeviceKeys(true)400            setRequest(M_MEGOLM_BACKUP_V1, setOf(aliceDevice))401            returnRoomKeysVersion(null)402            val secretEventContent = MegolmBackupV1EventContent(mapOf())403            store.globalAccountData.update(GlobalAccountDataEvent(secretEventContent))404            cut.handleOutgoingKeyRequestAnswer(405                IOlmService.DecryptedOlmEventContainer(406                    encryptedEvent, DecryptedOlmEvent(407                        SecretKeySendEventContent("requestId", keyBackupPrivateKey),408                        alice, keysOf(aliceDevice2Key), alice, keysOf()409                    )410                )411            )412            continually(500.milliseconds) {413                store.keys.secrets.value shouldBe mapOf()414            }415        }416        should("ignore when public key of cross signing secret does not match") {417            setDeviceKeys(true)418            setRequest(M_CROSS_SIGNING_USER_SIGNING, setOf(aliceDevice))419            setCrossSigningKeys(freeAfter(OlmPkSigning.create(null)) { it.publicKey })420            val secretEventContent = UserSigningKeyEventContent(mapOf())421            store.globalAccountData.update(GlobalAccountDataEvent(secretEventContent))422            cut.handleOutgoingKeyRequestAnswer(423                IOlmService.DecryptedOlmEventContainer(424                    encryptedEvent, DecryptedOlmEvent(425                        SecretKeySendEventContent("requestId", crossSigningPrivateKey),426                        alice, keysOf(aliceDevice2Key), alice, keysOf()427                    )428                )429            )430            continually(500.milliseconds) {431                store.keys.secrets.value shouldBe mapOf()432            }433        }434        should("ignore when public key of key backup secret does not match") {435            keyBackup.returnKeyBackupCanBeTrusted = false436            setDeviceKeys(true)437            setRequest(M_MEGOLM_BACKUP_V1, setOf(aliceDevice))438            returnRoomKeysVersion(freeAfter(OlmPkDecryption.create(null)) { it.publicKey })439            val secretEventContent = MegolmBackupV1EventContent(mapOf())440            store.globalAccountData.update(GlobalAccountDataEvent(secretEventContent))441            cut.handleOutgoingKeyRequestAnswer(442                IOlmService.DecryptedOlmEventContainer(443                    encryptedEvent, DecryptedOlmEvent(444                        SecretKeySendEventContent("requestId", keyBackupPrivateKey),445                        alice, keysOf(aliceDevice2Key), alice, keysOf()446                    )447                )448            )449            continually(500.milliseconds) {450                store.keys.secrets.value shouldBe mapOf()451            }452        }453        should("ignore when encrypted secret could not be found") {454            setDeviceKeys(true)455            setRequest(M_CROSS_SIGNING_USER_SIGNING, setOf(aliceDevice))456            setCrossSigningKeys(crossSigningPublicKey)457            cut.handleOutgoingKeyRequestAnswer(458                IOlmService.DecryptedOlmEventContainer(459                    encryptedEvent, DecryptedOlmEvent(460                        SecretKeySendEventContent("requestId", crossSigningPrivateKey),461                        alice, keysOf(aliceDevice2Key), alice, keysOf()462                    )463                )464            )465            continually(500.milliseconds) {466                store.keys.secrets.value shouldBe mapOf()467            }468        }469        should("save cross signing secret") {470            setDeviceKeys(true)471            setRequest(M_CROSS_SIGNING_USER_SIGNING, setOf(aliceDevice))472            setCrossSigningKeys(crossSigningPublicKey)473            val secretEvent = GlobalAccountDataEvent(UserSigningKeyEventContent(mapOf()))474            store.globalAccountData.update(secretEvent)475            cut.handleOutgoingKeyRequestAnswer(476                IOlmService.DecryptedOlmEventContainer(477                    encryptedEvent, DecryptedOlmEvent(478                        SecretKeySendEventContent("requestId", crossSigningPrivateKey),479                        alice, keysOf(aliceDevice2Key), alice, keysOf()480                    )481                )482            )483            store.keys.secrets.first { it.size == 1 } shouldBe mapOf(484                M_CROSS_SIGNING_USER_SIGNING to StoredSecret(secretEvent, crossSigningPrivateKey)485            )486        }487        should("save cross key backup secret") {488            keyBackup.returnKeyBackupCanBeTrusted = true489            setDeviceKeys(true)490            setRequest(M_MEGOLM_BACKUP_V1, setOf(aliceDevice))491            returnRoomKeysVersion(keyBackupPublicKey)492            val secretEvent = GlobalAccountDataEvent(MegolmBackupV1EventContent(mapOf()))493            store.globalAccountData.update(secretEvent)494            cut.handleOutgoingKeyRequestAnswer(495                IOlmService.DecryptedOlmEventContainer(496                    encryptedEvent, DecryptedOlmEvent(497                        SecretKeySendEventContent("requestId", keyBackupPrivateKey),498                        alice, keysOf(aliceDevice2Key), alice, keysOf()499                    )500                )501            )502            store.keys.secrets.first { it.size == 1 } shouldBe mapOf(503                M_MEGOLM_BACKUP_V1 to StoredSecret(secretEvent, keyBackupPrivateKey)504            )505        }506        should("cancel other requests") {507            var sendToDeviceEvents: Map<UserId, Map<String, ToDeviceEventContent>>? = null508            apiConfig.endpoints {509                matrixJsonEndpoint(510                    json, mappings,511                    SendToDevice("m.secret.request", "txn"),512                    skipUrlCheck = true513                ) {514                    sendToDeviceEvents = it.messages515                }516            }517            setDeviceKeys(true)518            setRequest(M_CROSS_SIGNING_USER_SIGNING, setOf(aliceDevice, "OTHER_DEVICE"))519            setCrossSigningKeys(crossSigningPublicKey)520            val secretEvent = GlobalAccountDataEvent(UserSigningKeyEventContent(mapOf()))521            store.globalAccountData.update(secretEvent)522            cut.handleOutgoingKeyRequestAnswer(523                IOlmService.DecryptedOlmEventContainer(524                    encryptedEvent, DecryptedOlmEvent(525                        SecretKeySendEventContent("requestId", crossSigningPrivateKey),526                        alice, keysOf(aliceDevice2Key), alice, keysOf()527                    )528                )529            )530            store.keys.secrets.first { it.size == 1 } shouldBe mapOf(531                M_CROSS_SIGNING_USER_SIGNING to StoredSecret(secretEvent, crossSigningPrivateKey)532            )533            sendToDeviceEvents?.get(alice)?.get("OTHER_DEVICE") shouldBe SecretKeyRequestEventContent(534                M_CROSS_SIGNING_USER_SIGNING.id,535                KeyRequestAction.REQUEST_CANCELLATION,536                "OWN_ALICE_DEVICE",537                "requestId"538            )539        }540    }541    context(KeySecretService::cancelOldOutgoingKeyRequests.name) {542        should("only remove old requests and send cancel") {543            var sendToDeviceEvents: Map<UserId, Map<String, ToDeviceEventContent>>? = null544            apiConfig.endpoints {545                matrixJsonEndpoint(546                    json, mappings,547                    SendToDevice("m.secret.request", "txn"),548                    skipUrlCheck = true549                ) {550                    sendToDeviceEvents = it.messages551                }552            }553            val request1 = StoredSecretKeyRequest(554                SecretKeyRequestEventContent(555                    M_CROSS_SIGNING_USER_SIGNING.id,556                    KeyRequestAction.REQUEST,557                    "OWN_ALICE_DEVICE",558                    "requestId1"559                ), setOf(), Clock.System.now()560            )561            val request2 = StoredSecretKeyRequest(562                SecretKeyRequestEventContent(563                    M_CROSS_SIGNING_USER_SIGNING.id,564                    KeyRequestAction.REQUEST,565                    "OWN_ALICE_DEVICE",566                    "requestId2"567                ), setOf(aliceDevice), (Clock.System.now() - 1.days)568            )569            store.keys.addSecretKeyRequest(request1)570            store.keys.addSecretKeyRequest(request2)571            store.keys.allSecretKeyRequests.first { it.size == 2 }572            cut.cancelOldOutgoingKeyRequests()573            store.keys.allSecretKeyRequests.first { it.size == 1 } shouldBe setOf(request1)574            sendToDeviceEvents?.get(alice)?.get(aliceDevice) shouldBe SecretKeyRequestEventContent(575                M_CROSS_SIGNING_USER_SIGNING.id,576                KeyRequestAction.REQUEST_CANCELLATION,577                "OWN_ALICE_DEVICE",578                "requestId2"579            )580        }581    }582    context(KeySecretService::requestSecretKeys.name) {583        var sendToDeviceEvents: Map<UserId, Map<String, ToDeviceEventContent>>? = null584        beforeTest {585            sendToDeviceEvents = null586            apiConfig.endpoints {587                matrixJsonEndpoint(588                    json, mappings,589                    SendToDevice("m.secret.request", "txn"),590                    skipUrlCheck = true591                ) {592                    sendToDeviceEvents = it.messages593                }594            }595        }596        should("ignore when there are no missing secrets") {597            store.keys.secrets.value = mapOf(598                M_CROSS_SIGNING_USER_SIGNING to StoredSecret(599                    GlobalAccountDataEvent(UserSigningKeyEventContent(mapOf())),600                    "key1"601                ),602                M_CROSS_SIGNING_SELF_SIGNING to StoredSecret(603                    GlobalAccountDataEvent(SelfSigningKeyEventContent(mapOf())),604                    "key2"605                ),606                M_MEGOLM_BACKUP_V1 to StoredSecret(607                    GlobalAccountDataEvent(MegolmBackupV1EventContent(mapOf())),608                    "key3"609                )610            )611            cut.requestSecretKeys()612            sendToDeviceEvents shouldBe null613        }614        should("send requests to verified cross signed devices") {615            store.keys.secrets.value = mapOf(616                M_MEGOLM_BACKUP_V1 to StoredSecret(617                    GlobalAccountDataEvent(MegolmBackupV1EventContent(mapOf())),618                    "key3"619                )620            )621            store.keys.addSecretKeyRequest(622                StoredSecretKeyRequest(623                    SecretKeyRequestEventContent(624                        M_CROSS_SIGNING_SELF_SIGNING.id,625                        KeyRequestAction.REQUEST,626                        aliceDevice,627                        "requestId1"628                    ), setOf("DEVICE_2"), Clock.System.now()629                )630            )631            store.keys.allSecretKeyRequests.first { it.size == 1 }632            store.keys.updateDeviceKeys(alice) {633                mapOf(634                    "DEVICE_1" to StoredDeviceKeys(635                        SignedDeviceKeys(DeviceKeys(alice, "DEVICE_1", setOf(), keysOf()), mapOf()),636                        CrossSigned(false)637                    ),638                    "DEVICE_2" to StoredDeviceKeys(639                        SignedDeviceKeys(DeviceKeys(alice, "DEVICE_2", setOf(), keysOf()), mapOf()),640                        CrossSigned(true)641                    )642                )643            }644            cut.requestSecretKeys()645            assertSoftly(sendToDeviceEvents?.get(alice)?.get("DEVICE_2")) {646                assertNotNull(this)647                this.shouldBeInstanceOf<SecretKeyRequestEventContent>()648                this.name shouldBe M_CROSS_SIGNING_USER_SIGNING.id649                this.action shouldBe KeyRequestAction.REQUEST650                this.requestingDeviceId shouldBe aliceDevice651                this.requestId shouldNot beEmpty()652            }653            store.keys.allSecretKeyRequests.first { it.size == 2 } shouldHaveSize 2654        }655    }656    context(KeySecretService::requestSecretKeysWhenCrossSigned.name) {657        should("request secret keys, when cross signed and verified") {658            currentSyncState.value = SyncState.RUNNING659            val sendToDeviceCalled = MutableStateFlow(false)660            apiConfig.endpoints {661                matrixJsonEndpoint(json, mappings, SendToDevice("", ""), skipUrlCheck = true) {662                    sendToDeviceCalled.value = true663                }664            }665            val job = launch(start = CoroutineStart.UNDISPATCHED) {666                cut.requestSecretKeysWhenCrossSigned()667            }668            store.keys.updateDeviceKeys(alice) {669                mapOf(670                    aliceDevice to StoredDeviceKeys(671                        SignedDeviceKeys(DeviceKeys(alice, aliceDevice, setOf(), keysOf()), mapOf()),672                        CrossSigned(true)673                    ),674                    "OTHER_ALICE" to StoredDeviceKeys(675                        SignedDeviceKeys(DeviceKeys(alice, "OTHER_ALICE", setOf(), keysOf()), mapOf()),676                        CrossSigned(true)677                    ),678                )679            }680            sendToDeviceCalled.first { it }681            job.cancel()682        }683    }684    context(KeySecretService::handleChangedSecrets.name) {685        var sendToDeviceEvents: Map<UserId, Map<String, ToDeviceEventContent>>? = null686        beforeTest {687            sendToDeviceEvents = null688            apiConfig.endpoints {689                matrixJsonEndpoint(690                    json, mappings,691                    SendToDevice("m.secret.request", "txn"),692                    skipUrlCheck = true693                ) {694                    sendToDeviceEvents = it.messages695                }696            }697            store.keys.addSecretKeyRequest(698                StoredSecretKeyRequest(699                    SecretKeyRequestEventContent(700                        M_CROSS_SIGNING_USER_SIGNING.id,701                        KeyRequestAction.REQUEST,702                        aliceDevice,703                        "requestId1"704                    ), setOf("DEVICE_2"), Clock.System.now()705                )706            )707            store.keys.allSecretKeyRequests.first { it.size == 1 }708        }709        should("do nothing when secret is not allowed to cache") {710            val crossSigningPrivateKeys = mapOf(711                M_CROSS_SIGNING_USER_SIGNING to StoredSecret(712                    GlobalAccountDataEvent(UserSigningKeyEventContent(mapOf())),713                    "key"714                )715            )716            store.keys.secrets.value = crossSigningPrivateKeys717            cut.handleChangedSecrets(GlobalAccountDataEvent(MasterKeyEventContent(mapOf())))718            sendToDeviceEvents shouldBe null719            store.keys.secrets.value shouldBe crossSigningPrivateKeys720        }721        should("do nothing when event did not change") {722            val event = GlobalAccountDataEvent(UserSigningKeyEventContent(mapOf()))723            val crossSigningPrivateKeys = mapOf(724                M_CROSS_SIGNING_USER_SIGNING to StoredSecret(725                    event, "bla"726                )727            )728            store.keys.secrets.value = crossSigningPrivateKeys729            cut.handleChangedSecrets(event)730            sendToDeviceEvents shouldBe null731            store.keys.secrets.value shouldBe crossSigningPrivateKeys732        }733        should("remove cached secret and cancel ongoing requests when event did change") {734            store.keys.secrets.value = mapOf(735                M_CROSS_SIGNING_USER_SIGNING to StoredSecret(736                    GlobalAccountDataEvent(UserSigningKeyEventContent(mapOf("oh" to JsonPrimitive("change!")))),737                    "bla"738                )739            )740            cut.handleChangedSecrets(GlobalAccountDataEvent(UserSigningKeyEventContent(mapOf())))741            assertSoftly(sendToDeviceEvents?.get(alice)?.get("DEVICE_2")) {742                assertNotNull(this)743                this.shouldBeInstanceOf<SecretKeyRequestEventContent>()744                this.name shouldBe M_CROSS_SIGNING_USER_SIGNING.id745                this.action shouldBe KeyRequestAction.REQUEST_CANCELLATION746                this.requestingDeviceId shouldBe aliceDevice747                this.requestId shouldBe "requestId1"748            }749            store.keys.secrets.value shouldBe mapOf()750        }751    }752    context(KeySecretService::decryptMissingSecrets.name) {753        should("decrypt missing secrets and update secure store") {754            val existingPrivateKeys = mapOf(755                M_CROSS_SIGNING_SELF_SIGNING to StoredSecret(756                    GlobalAccountDataEvent(SelfSigningKeyEventContent(mapOf())), "key2"757                ),758                M_MEGOLM_BACKUP_V1 to StoredSecret(759                    GlobalAccountDataEvent(SelfSigningKeyEventContent(mapOf())), "key3"760                )761            )762            store.keys.secrets.value = existingPrivateKeys763            val key = Random.nextBytes(32)764            val secret = Random.nextBytes(32).encodeBase64()765            val encryptedData = encryptAesHmacSha2(766                content = secret.encodeToByteArray(),...

Full Screen

Full Screen

RoomServiceDisplayNameTest.kt

Source:RoomServiceDisplayNameTest.kt Github

copy

Full Screen

...103            1,104            stateKey = ""105        )106    }107    context(RoomService::setRoomDisplayName.name) {108        beforeTest {109            store.room.update(roomId) { simpleRoom.copy(roomId = roomId) }110        }111        suspend fun ShouldSpecContainerScope.testWithoutNameFromNameEvent() {112            context("with an existent Canonical Alias Event") {113                should("set room name to the alias field value") {114                    listOf(115                        canonicalAliasEvent(2, user2, RoomAliasId("somewhere", "localhost")),116                        memberEvent(3, user1, "User1-Display", JOIN),117                        memberEvent(4, user2, "User2-Display", INVITE),118                        memberEvent(5, user3, "User3-Display", BAN),119                        memberEvent(6, user4, "User4-Display", LEAVE)120                    ).forEach { store.roomState.update(it) }121                    val roomSummary = RoomSummary(122                        heroes = listOf(user1, user2),123                        joinedMemberCount = 1,124                        invitedMemberCount = 1,125                    )126                    cut.setRoomDisplayName(roomId, roomSummary)127                    store.room.get(roomId).value?.name shouldBe RoomDisplayName(128                        explicitName = "#somewhere:localhost",129                        summary = roomSummary130                    )131                }132            }133            context("with a non-existent Canonical Alias Event") {134                context("|joined member| + |invited member| > 1") {135                    beforeTest {136                        listOf(137                            memberEvent(3, user1, "User1-Display", JOIN),138                            memberEvent(4, user2, "User2-Display", INVITE),139                            memberEvent(7, user5, "User5-Display", BAN)140                        ).forEach { store.roomState.update(it) }141                    }142                    context("|heroes| >= |joined member| + |invited member| - 1") {143                        beforeTest {144                            listOf(145                                memberEvent(5, user3, "User3-Display", LEAVE),146                                memberEvent(6, user4, "User4-Display", LEAVE),147                            ).forEach { store.roomState.update(it) }148                        }149                        context("|heroes| = 1") {150                            should("set room name to the display name of the hero") {151                                val roomSummary = RoomSummary(152                                    heroes = listOf(user1),153                                    joinedMemberCount = 1,154                                    invitedMemberCount = 1,155                                )156                                cut.setRoomDisplayName(roomId, roomSummary)157                                store.room.get(roomId).value?.name shouldBe RoomDisplayName(158                                    summary = roomSummary159                                )160                            }161                        }162                        context("|heroes| = 2") {163                            should("set room name to the display names of the heroes concatenate with an 'und'") {164                                val roomSummary = RoomSummary(165                                    heroes = listOf(user1, user2),166                                    joinedMemberCount = 1,167                                    invitedMemberCount = 1,168                                )169                                cut.setRoomDisplayName(roomId, roomSummary)170                                store.room.get(roomId).value?.name shouldBe RoomDisplayName(171                                    summary = roomSummary172                                )173                            }174                        }175                    }176                    context("|heroes| < |joined member| + |invited member| - 1") {177                        beforeTest {178                            listOf(179                                memberEvent(5, user3, "User3-Display", JOIN),180                                memberEvent(6, user4, "User4-Display", INVITE),181                            ).forEach { store.roomState.update(it) }182                        }183                        context("|heroes| = 0") {184                            should("set room name to the count of the invited and joined users") {185                                val roomSummary = RoomSummary(186                                    heroes = listOf(),187                                    joinedMemberCount = 2,188                                    invitedMemberCount = 2,189                                )190                                cut.setRoomDisplayName(roomId, roomSummary)191                                store.room.get(roomId).value?.name shouldBe RoomDisplayName(192                                    otherUsersCount = 3,193                                    summary = roomSummary194                                )195                            }196                        }197                        context("|heroes| = 1") {198                            should("set room name to the display name of the hero and a count of the remaining users") {199                                val roomSummary = RoomSummary(200                                    heroes = listOf(user1),201                                    joinedMemberCount = 2,202                                    invitedMemberCount = 2,203                                )204                                cut.setRoomDisplayName(roomId, roomSummary)205                                store.room.get(roomId).value?.name shouldBe RoomDisplayName(206                                    otherUsersCount = 2,207                                    summary = roomSummary208                                )209                            }210                        }211                        context("|heroes| = 2") {212                            should("set room name to the display names of the heroes concatenate with an 'und'") {213                                val roomSummary = RoomSummary(214                                    heroes = listOf(user1, user2),215                                    joinedMemberCount = 2,216                                    invitedMemberCount = 2,217                                )218                                cut.setRoomDisplayName(roomId, roomSummary)219                                store.room.get(roomId).value?.name shouldBe RoomDisplayName(220                                    otherUsersCount = 1,221                                    summary = roomSummary222                                )223                            }224                        }225                    }226                }227                context("|joined member| + |invited member| = 1") {228                    beforeTest {229                        listOf(230                            memberEvent(3, user1, "User1-Display", JOIN),231                            memberEvent(4, user2, "User2-Display", BAN),232                            memberEvent(5, user3, "User3-Display", LEAVE),233                        ).forEach { store.roomState.update(it) }234                    }235                    context("|heroes| = 0") {236                        should("set room name to 'Leerer Raum'") {237                            val roomSummary = RoomSummary(238                                heroes = listOf(),239                                joinedMemberCount = 1,240                                invitedMemberCount = 0,241                            )242                            cut.setRoomDisplayName(roomId, roomSummary)243                            store.room.get(roomId).value?.name shouldBe RoomDisplayName(244                                isEmpty = true,245                                summary = roomSummary246                            )247                        }248                    }249                    context("|heroes| >= |left member| + |banned member| - 1") {250                        context("|heroes| = 1") {251                            should("set room name to the display name of the hero") {252                                val roomSummary = RoomSummary(253                                    heroes = listOf(user2),254                                    joinedMemberCount = 1,255                                    invitedMemberCount = 0,256                                )257                                cut.setRoomDisplayName(roomId, roomSummary)258                                store.room.get(roomId).value?.name shouldBe RoomDisplayName(259                                    isEmpty = true,260                                    otherUsersCount = 1,261                                    summary = roomSummary262                                )263                            }264                        }265                        context("|heroes| = 2") {266                            should("set room name to the display names of the heroes concatenate with an 'und'") {267                                val roomSummary = RoomSummary(268                                    heroes = listOf(user2, user3),269                                    joinedMemberCount = 1,270                                    invitedMemberCount = 0,271                                )272                                cut.setRoomDisplayName(roomId, roomSummary)273                                store.room.get(roomId).value?.name shouldBe RoomDisplayName(274                                    isEmpty = true,275                                    summary = roomSummary276                                )277                            }278                        }279                    }280                    context("|heroes| < |left member| + |banned member| - 1") {281                        beforeTest {282                            listOf(283                                memberEvent(6, user4, "User4-Display", LEAVE),284                                memberEvent(7, user5, "User5-Display", LEAVE),285                            ).forEach { store.roomState.update(it) }286                        }287                        context("|heroes| = 1") {288                            should("set room name to the concatenation of display names of the heroes and a count of the remaining users, enclosed by an Empty Room String") {289                                val roomSummary = RoomSummary(290                                    heroes = listOf(user2),291                                    joinedMemberCount = 1,292                                    invitedMemberCount = 0,293                                )294                                cut.setRoomDisplayName(roomId, roomSummary)295                                store.room.get(roomId).value?.name shouldBe RoomDisplayName(296                                    isEmpty = true,297                                    otherUsersCount = 3,298                                    summary = roomSummary299                                )300                            }301                        }302                        context("|heroes| = 2") {303                            should("set room name to the concatenation of display names of the heroes and a count of the remaining users, enclosed by an Empty Room String") {304                                val roomSummary = RoomSummary(305                                    heroes = listOf(user2, user3),306                                    joinedMemberCount = 1,307                                    invitedMemberCount = 0,308                                )309                                cut.setRoomDisplayName(roomId, roomSummary)310                                store.room.get(roomId).value?.name shouldBe RoomDisplayName(311                                    isEmpty = true,312                                    otherUsersCount = 2,313                                    summary = roomSummary314                                )315                            }316                        }317                    }318                }319                context("|joined member| + |invited member| = 0") {320                    beforeTest {321                        listOf(322                            memberEvent(3, user1, "User1-Display", LEAVE),323                            memberEvent(4, user2, "User2-Display", BAN),324                        ).forEach { store.roomState.update(it) }325                    }326                    context("|heroes| = 0") {327                        should("set room name to 'Leerer Raum'") {328                            val roomSummary = RoomSummary(329                                heroes = listOf(),330                                joinedMemberCount = 0,331                                invitedMemberCount = 0,332                            )333                            cut.setRoomDisplayName(roomId, roomSummary)334                            store.room.get(roomId).value?.name shouldBe RoomDisplayName(335                                isEmpty = true,336                                summary = roomSummary337                            )338                        }339                    }340                    context("|heroes| >= |left member| + |banned member| - 1") {341                        context("|heroes| = 1") {342                            should("set room name to the display name of the hero, enclosed by an Empty Room String") {343                                val roomSummary = RoomSummary(344                                    heroes = listOf(user1),345                                    joinedMemberCount = 0,346                                    invitedMemberCount = 0,347                                )348                                cut.setRoomDisplayName(roomId, roomSummary)349                                store.room.get(roomId).value?.name shouldBe RoomDisplayName(350                                    isEmpty = true,351                                    summary = roomSummary352                                )353                            }354                        }355                        context("|heroes| = 2") {356                            should("set room name to the display names of the heroes concatenate with an 'und', enclosed by an Empty Room String ") {357                                store.roomState.update(358                                    memberEvent(5, user3, "User3-Display", LEAVE),359                                )360                                val roomSummary = RoomSummary(361                                    heroes = listOf(user1, user2),362                                    joinedMemberCount = 0,363                                    invitedMemberCount = 0,364                                )365                                cut.setRoomDisplayName(roomId, roomSummary)366                                store.room.get(roomId).value?.name shouldBe RoomDisplayName(367                                    isEmpty = true,368                                    summary = roomSummary369                                )370                            }371                        }372                    }373                    context("|heroes| < |left member| + |banned member| - 1") {374                        beforeTest {375                            listOf(376                                memberEvent(5, user3, "User3-Display", LEAVE),377                                memberEvent(6, user4, "User4-Display", LEAVE),378                                memberEvent(7, user5, "User5-Display", LEAVE),379                            ).forEach { store.roomState.update(it) }380                        }381                        context("|heroes| = 1") {382                            should("set room name to the concatenation of display names of the heroes and a count of the remaining users, enclosed by an Empty Room String") {383                                val roomSummary = RoomSummary(384                                    heroes = listOf(user1),385                                    joinedMemberCount = 0,386                                    invitedMemberCount = 0,387                                )388                                cut.setRoomDisplayName(roomId, roomSummary)389                                store.room.get(roomId).value?.name shouldBe RoomDisplayName(390                                    isEmpty = true,391                                    otherUsersCount = 3,392                                    summary = roomSummary393                                )394                            }395                        }396                        context("|heroes| = 2") {397                            should("set room name to the concatenation of display names of the heroes and a count of the remaining users, enclosed by an Empty Room String") {398                                val roomSummary = RoomSummary(399                                    heroes = listOf(user1, user2),400                                    joinedMemberCount = 0,401                                    invitedMemberCount = 0,402                                )403                                cut.setRoomDisplayName(roomId, roomSummary)404                                store.room.get(roomId).value?.name shouldBe RoomDisplayName(405                                    isEmpty = true,406                                    otherUsersCount = 2,407                                    summary = roomSummary408                                )409                            }410                        }411                    }412                }413            }414        }415        context("existent room name state event") {416            context("with a non-empty name field") {417                beforeTest {418                    store.roomState.update(nameEvent(1, user1, "The room name"))419                }420                should("set room name to the name field value") {421                    listOf(422                        canonicalAliasEvent(2, user2, RoomAliasId("somewhere", "localhost")),423                        memberEvent(3, user1, "User1-Display", JOIN),424                        memberEvent(4, user2, "User2-Display", INVITE),425                        memberEvent(5, user3, "User3-Display", BAN),426                        memberEvent(6, user4, "User4-Display", LEAVE)427                    ).forEach { store.roomState.update(it) }428                    val roomSummary = RoomSummary(429                        heroes = listOf(user1, user2),430                        joinedMemberCount = 1,431                        invitedMemberCount = 2,432                    )433                    cut.setRoomDisplayName(roomId, roomSummary)434                    store.room.get(roomId).value?.name shouldBe RoomDisplayName(435                        explicitName = "The room name",436                        summary = roomSummary437                    )438                }439            }440            context("with an empty name field") {441                beforeTest {442                    store.roomState.update(nameEvent(1, user1, ""))443                }444                testWithoutNameFromNameEvent()445            }446        }447        context("non-existent room name state event") {448            testWithoutNameFromNameEvent()449        }450    }451})...

Full Screen

Full Screen

ActiveSasVerificationMethodTest.kt

Source:ActiveSasVerificationMethodTest.kt Github

copy

Full Screen

...70    }71    afterTest {72        storeScope.cancel()73    }74    context("create") {75        should("not cancel when key agreement protocol is not supported") {76            val method = ActiveSasVerificationMethod.create(77                startEventContent = SasStartEventContent(78                    aliceDevice,79                    keyAgreementProtocols = setOf(),80                    relatesTo = null,81                    transactionId = "t"82                ),83                weStartedVerification = true,84                ownUserId = alice,85                ownDeviceId = aliceDevice,86                theirUserId = bob,87                theirDeviceId = bobDevice,88                relatesTo = null,89                transactionId = "t",90                sendVerificationStep = { sendVerificationStepFlow.emit(it) },91                store = store,92                keyTrustService = keyTrustService,93                json = json,94            )95            method shouldBe null96            val result = sendVerificationStepFlow.first()97            result.shouldBeInstanceOf<VerificationCancelEventContent>()98            result.code shouldBe UnknownMethod99        }100    }101    suspend fun ShouldSpecContainerScope.checkNotAllowedStateChange(vararg steps: VerificationStep) {102        steps.forEach {103            should("cancel unexpected message ${it::class.simpleName}") {104                cut.handleVerificationStep(it, false)105                val result =106                    sendVerificationStepFlow.replayCache.filterIsInstance<VerificationCancelEventContent>().first()107                result.code shouldBe UnexpectedMessage108            }109        }110    }111    context("handleVerificationStep") {112        context("current state is ${OwnSasStart::class.simpleName} or ${TheirSasStart::class.simpleName}") {113            checkNotAllowedStateChange(114                SasKeyEventContent("key", null, "t"),115                SasMacEventContent("keys", keysOf(), null, "t")116            )117            should("just set state when message is from us") {118                cut.handleVerificationStep(119                    SasAcceptEventContent("c", relatesTo = null, transactionId = "t"), true120                )121                cut.state.value shouldBe Accept(true)122                sendVerificationStepFlow.replayCache.shouldBeEmpty()123            }124            should("send ${SasKeyEventContent::class.simpleName} when sender was not us") {125                cut.handleVerificationStep(126                    SasAcceptEventContent("c", relatesTo = null, transactionId = "t"), false127                )128                cut.state.value shouldBe Accept(false)129                val result = sendVerificationStepFlow.first()130                result.shouldBeInstanceOf<SasKeyEventContent>()131                assertSoftly(result) {132                    key.shouldNotBeBlank()133                    relatesTo shouldBe null134                    transactionId shouldBe "t"135                }136            }137            should("cancel when key agreement protocol is not supported") {138                cut.handleVerificationStep(139                    SasAcceptEventContent(140                        "c",141                        keyAgreementProtocol = "c",142                        relatesTo = null,143                        transactionId = "t"144                    ), false145                )146                val result = sendVerificationStepFlow.first()147                result.shouldBeInstanceOf<VerificationCancelEventContent>()148                result.code shouldBe UnknownMethod149            }150        }151        context("current state is ${Accept::class.simpleName}") {152            context("handle unexpected") {153                beforeTest {154                    cut.handleVerificationStep(SasAcceptEventContent("c", relatesTo = null, transactionId = "t"), true)155                    cut.state.value.shouldBeInstanceOf<Accept>()156                }157                checkNotAllowedStateChange(158                    SasAcceptEventContent("c", relatesTo = null, transactionId = "t"),159                    SasMacEventContent("keys", keysOf(), null, "t")160                )161            }162            context("accept from them") {163                beforeTest {164                    cut.handleVerificationStep(SasAcceptEventContent("c", relatesTo = null, transactionId = "t"), false)165                    cut.state.value.shouldBeInstanceOf<Accept>()166                }167                should("just set state when message is from us") {168                    cut.handleVerificationStep(SasKeyEventContent("k", relatesTo = null, transactionId = "t"), true)169                    cut.state.value shouldBe WaitForKeys(true)170                }171            }172            context("accept from us") {173                beforeTest {174                    cut.handleVerificationStep(SasAcceptEventContent("c", relatesTo = null, transactionId = "t"), true)175                    cut.state.value.shouldBeInstanceOf<Accept>()176                }177                should("send ${SasKeyEventContent::class.simpleName} when sender was not us") {178                    cut.handleVerificationStep(SasKeyEventContent("k", relatesTo = null, transactionId = "t"), false)179                    cut.state.value shouldBe WaitForKeys(false)180                    val result = sendVerificationStepFlow.first()181                    result.shouldBeInstanceOf<SasKeyEventContent>()182                    assertSoftly(result) {183                        key.shouldNotBeBlank()184                        relatesTo shouldBe null185                        transactionId shouldBe "t"186                    }187                }188                should("cancel when sender it not expected") {189                    cut.handleVerificationStep(SasKeyEventContent("k", relatesTo = null, transactionId = "t"), true)190                    val result = sendVerificationStepFlow.first()191                    result.shouldBeInstanceOf<VerificationCancelEventContent>()192                    result.code shouldBe UnexpectedMessage193                }194            }195        }196        context("current state is ${WaitForKeys::class.simpleName}") {197            beforeTest {198                cut.handleVerificationStep(199                    SasAcceptEventContent(200                        "4d8Qtr63ZuKgjhdBYdm/tZ9FiNCAAU1ZEc9HoHe6kEE",201                        relatesTo = null,202                        transactionId = "t"203                    ), false204                )205                cut.handleVerificationStep(SasKeyEventContent("k", relatesTo = null, transactionId = "t"), true)206                cut.state.value.shouldBeInstanceOf<WaitForKeys>()207            }208            checkNotAllowedStateChange(209                SasAcceptEventContent("c", relatesTo = null, transactionId = "t"),210                SasMacEventContent("keys", keysOf(), null, "t")211            )212            should("create ${ComparisonByUser::class.simpleName}") {213                cut.handleVerificationStep(214                    SasKeyEventContent(215                        "3vPVpNPsVYVYuozmCrihhndEvVZUHpoHBSb5+TdkaAA",216                        relatesTo = null,217                        transactionId = "t"218                    ), false219                )220                val state = cut.state.value221                state.shouldBeInstanceOf<ComparisonByUser>()222                state.decimal shouldHaveSize 3223                state.decimal.forEach {224                    it shouldBeGreaterThanOrEqual 1000225                    it shouldBeLessThanOrEqual 9191226                }227                state.emojis shouldHaveSize 7228            }229            should("cancel when commitment does not match") {230                cut.handleVerificationStep(SasKeyEventContent("k", relatesTo = null, transactionId = "t"), false)231                val result =232                    sendVerificationStepFlow.replayCache.filterIsInstance<VerificationCancelEventContent>().first()233                result.code shouldBe MismatchedCommitment234            }235            should("cancel when sender it not expected") {236                cut.handleVerificationStep(SasKeyEventContent("k", relatesTo = null, transactionId = "t"), true)237                val result =238                    sendVerificationStepFlow.replayCache.filterIsInstance<VerificationCancelEventContent>().first()239                result.code shouldBe UnexpectedMessage240            }241        }242        context("current state is ${ComparisonByUser::class.simpleName}") {243            beforeTest {244                cut.handleVerificationStep(245                    SasAcceptEventContent(246                        "4d8Qtr63ZuKgjhdBYdm/tZ9FiNCAAU1ZEc9HoHe6kEE",247                        relatesTo = null,248                        transactionId = "t"249                    ), false250                )251                cut.handleVerificationStep(SasKeyEventContent("k", relatesTo = null, transactionId = "t"), true)252                cut.handleVerificationStep(253                    SasKeyEventContent(254                        "3vPVpNPsVYVYuozmCrihhndEvVZUHpoHBSb5+TdkaAA",255                        relatesTo = null,256                        transactionId = "t"257                    ), false258                )259                cut.state.value.shouldBeInstanceOf<ComparisonByUser>()260            }261            checkNotAllowedStateChange(262                SasAcceptEventContent("c", relatesTo = null, transactionId = "t"),263                SasKeyEventContent("key", null, "t")264            )265            should("change state to ${WaitForMacs::class.simpleName} when accepted") {266                cut.handleVerificationStep(SasMacEventContent("keys", keysOf(), null, "t"), true)267                cut.state.value shouldBe WaitForMacs268            }269            should("not change state to ${WaitForMacs::class.simpleName} when from other") {270                val oldState = cut.state.value271                cut.handleVerificationStep(SasMacEventContent("keys", keysOf(), null, "t"), false)272                cut.state.value shouldBe oldState273            }274        }275        context("current state is ${WaitForMacs::class.simpleName}") {276            var sasMacFromBob: VerificationStep? = null277            beforeTest {278                store.keys.updateDeviceKeys(bob) {279                    mapOf(280                        bobDevice to StoredDeviceKeys(281                            Signed(282                                DeviceKeys(283                                    bob, bobDevice, setOf(Megolm),284                                    keysOf(285                                        Ed25519Key(bobDevice, "bobKey"),286                                        Ed25519Key("HUHU", "buh")287                                    )288                                ), mapOf()289                            ), Valid(true)...

Full Screen

Full Screen

ActiveVerificationTest.kt

Source:ActiveVerificationTest.kt Github

copy

Full Screen

...61        lifecycleCalled = 062        sendVerificationStepFlow = MutableSharedFlow(replay = 10)63        cut = TestActiveVerification(VerificationRequestEventContent(bobDevice, setOf(Sas), 1234, "t"))64    }65    context(ActiveVerification::startLifecycle.name) {66        should("start lifecycle once") {67            cut.startLifecycle(this)68            cut.startLifecycle(this)69            lifecycleCalled shouldBe 170        }71    }72    context(ActiveVerification::cancel.name) {73        should("send user cancel event content") {74            val expectedCancelEvent =75                VerificationCancelEventContent(Code.User, "user cancelled verification", null, "t")76            cut.cancel()77            sendVerificationStepFlow.first() shouldBe expectedCancelEvent78            cut.state.value shouldBe Cancel(expectedCancelEvent, true)79        }80    }81    context("handleVerificationStep") {82        context("step is from foreign user") {83            should("cancel") {84                cut.handleStep(85                    VerificationReadyEventContent("FFFFFF", setOf(), null, "t"),86                    UserId("f", "server"),87                    false88                )89                sendVerificationStepFlow.first().shouldBeInstanceOf<VerificationCancelEventContent>()90            }91        }92        context("step has no matching transaction") {93            should("cancel") {94                cut.handleStep(VerificationReadyEventContent(aliceDevice, setOf(), null, null), alice, true)95                sendVerificationStepFlow.first().shouldBeInstanceOf<VerificationCancelEventContent>()96            }97        }98        context("current state is ${AcceptedByOtherDevice::class.simpleName}") {99            beforeTest {100                cut.setState(AcceptedByOtherDevice)101            }102            should("set state to ${Done::class.simpleName} when done") {103                cut.handleStep(VerificationDoneEventContent(null, "t"), alice, false)104                cut.state.value shouldBe Done105            }106            should("set state to ${Cancel::class.simpleName} when cancel") {107                val cancelEvent = VerificationCancelEventContent(Code.User, "user", null, "t")108                cut.handleStep(cancelEvent, alice, false)109                cut.state.value shouldBe Cancel(cancelEvent, false)110            }111        }112        suspend fun ShouldSpecContainerScope.checkNotAllowedStateChange(vararg steps: VerificationStep) {113            steps.forEach {114                should("cancel unexpected message ${it::class.simpleName}") {115                    val stateBefore = cut.state.value116                    cut.handleStep(it, bob, false)117                    val state = cut.state.value118                    state.shouldBeInstanceOf<Cancel>()119                    state.content.code shouldBe Code.UnexpectedMessage120                    if (stateBefore !is Cancel) {121                        val result = sendVerificationStepFlow.first()122                        result.shouldBeInstanceOf<VerificationCancelEventContent>()123                        result.code shouldBe Code.UnexpectedMessage124                    }125                }126            }127        }128        context("current state is ${OwnRequest::class.simpleName} or ${TheirRequest::class.simpleName}") {129            checkNotAllowedStateChange(130                SasStartEventContent(bobDevice, relatesTo = null, transactionId = "t"),131                VerificationDoneEventContent(null, "t"),132            )133            should("handle ${VerificationReadyEventContent::class.simpleName} when ${OwnRequest::class.simpleName}") {134                cut = TestActiveVerification(VerificationRequestEventContent(aliceDevice, setOf(Sas), 1234, "t"))135                cut.state.value.shouldBeInstanceOf<OwnRequest>()136                cut.handleStep(137                    VerificationReadyEventContent(bobDevice, setOf(Sas, Unknown("u")), null, "t"),138                    bob,139                    false140                )141                val state = cut.state.value142                state.shouldBeInstanceOf<Ready>()143                state.methods shouldBe setOf(Sas)144                cut.theirDeviceId shouldBe bobDevice145            }146            should("handle ${VerificationReadyEventContent::class.simpleName} when ${TheirRequest::class.simpleName}") {147                cut.state.value.shouldBeInstanceOf<TheirRequest>()148                cut.handleStep(149                    VerificationReadyEventContent(aliceDevice, setOf(Sas, Unknown("u")), null, "t"),150                    alice,151                    false152                )153                val state = cut.state.value154                state.shouldBeInstanceOf<Ready>()155                state.methods shouldBe setOf(Sas)156            }157        }158        context("current state is ${Ready::class.simpleName}") {159            beforeTest {160                cut.handleStep(161                    VerificationReadyEventContent(bobDevice, setOf(Sas, Unknown("u")), null, "t"),162                    bob,163                    false164                )165                cut.state.value.shouldBeInstanceOf<Ready>()166            }167            checkNotAllowedStateChange(168                VerificationReadyEventContent(bobDevice, setOf(), null, "t"),169                VerificationDoneEventContent(null, "t"),170            )171            should("handle ${VerificationStartEventContent::class.simpleName}") {172                val step = SasStartEventContent(bobDevice, relatesTo = null, transactionId = "t")173                cut.handleStep(step, bob, false)174                val state = cut.state.value175                state.shouldBeInstanceOf<Start>()176                state.senderUserId shouldBe bob177                state.senderDeviceId shouldBe bobDevice178                val method = state.method179                method.shouldBeInstanceOf<ActiveSasVerificationMethod>()180                val subState = method.state.value181                subState.shouldBeInstanceOf<TheirSasStart>()182                subState.content shouldBe step183            }184        }185        context("current state is ${Start::class.simpleName}") {186            beforeTest {187                cut.handleStep(188                    VerificationReadyEventContent(bobDevice, setOf(Sas, Unknown("u")), null, "t"),189                    bob,190                    false191                )192                cut.handleStep(SasStartEventContent(bobDevice, relatesTo = null, transactionId = "t"), bob, false)193                cut.state.value.shouldBeInstanceOf<Start>()194            }195            checkNotAllowedStateChange(196                VerificationReadyEventContent(bobDevice, setOf(), null, "t"),197            )198            context("handle ${VerificationStartEventContent::class.simpleName}") {199                should("keep event from lexicographically smaller user ID") {200                    val step = SasStartEventContent(aliceDevice, relatesTo = null, transactionId = "t")201                    cut.handleStep(step, alice, true)202                    cut.handleStep(SasStartEventContent(bobDevice, relatesTo = null, transactionId = "t"), bob, false)203                    val state = cut.state.value204                    state.shouldBeInstanceOf<Start>()205                    state.senderUserId shouldBe alice206                    state.senderDeviceId shouldBe aliceDevice207                    val method = state.method208                    method.shouldBeInstanceOf<ActiveSasVerificationMethod>()209                    val subState = method.state.value210                    subState.shouldBeInstanceOf<OwnSasStart>()211                    subState.content shouldBe step212                }213                should("keep event from lexicographically smaller deviceId") {214                    cut.handleStep(SasStartEventContent("CCCCCC", relatesTo = null, transactionId = "t"), bob, false)215                    val state = cut.state.value216                    state.shouldBeInstanceOf<Start>()217                    state.senderUserId shouldBe bob218                    state.senderDeviceId shouldBe bobDevice219                    val method = state.method220                    method.shouldBeInstanceOf<ActiveSasVerificationMethod>()221                    val subState = method.state.value222                    subState.shouldBeInstanceOf<TheirSasStart>()223                    subState.content shouldBe SasStartEventContent(bobDevice, relatesTo = null, transactionId = "t")224                }225                should("override event from lexicographically smaller user ID") {226                    val step = SasStartEventContent(aliceDevice, relatesTo = null, transactionId = "t")227                    cut.handleStep(step, alice, true)228                    val state = cut.state.value229                    state.shouldBeInstanceOf<Start>()230                    state.senderUserId shouldBe alice231                    state.senderDeviceId shouldBe aliceDevice232                    val method = state.method233                    method.shouldBeInstanceOf<ActiveSasVerificationMethod>()234                    val subState = method.state.value235                    subState.shouldBeInstanceOf<OwnSasStart>()236                    subState.content shouldBe step237                }238                should("override event from lexicographically smaller deviceId") {239                    val step = SasStartEventContent("AAAAAA", relatesTo = null, transactionId = "t")240                    cut.handleStep(step, bob, false)241                    val state = cut.state.value242                    state.shouldBeInstanceOf<Start>()243                    state.senderUserId shouldBe bob244                    state.senderDeviceId shouldBe "AAAAAA"245                    val method = state.method246                    method.shouldBeInstanceOf<ActiveSasVerificationMethod>()247                    val subState = method.state.value248                    subState.shouldBeInstanceOf<TheirSasStart>()249                    subState.content shouldBe step250                }251            }252            should("handle ${VerificationDoneEventContent::class.simpleName}") {253                val step = VerificationDoneEventContent(null, "t")254                cut.handleStep(step, bob, false)255                val state = cut.state.value256                state.shouldBeInstanceOf<PartlyDone>()257                state.isOurOwn shouldBe false258            }259        }260        context("current state is ${PartlyDone::class.simpleName}") {261            beforeTest {262                cut.handleStep(263                    VerificationReadyEventContent(bobDevice, setOf(Sas, Unknown("u")), null, "t"),264                    bob,265                    false266                )267                cut.handleStep(SasStartEventContent(bobDevice, relatesTo = null, transactionId = "t"), bob, false)268                cut.handleStep(VerificationDoneEventContent(null, "t"), bob, false)269                cut.state.value.shouldBeInstanceOf<PartlyDone>()270            }271            checkNotAllowedStateChange(272                VerificationReadyEventContent(bobDevice, setOf(), null, "t"),273                SasStartEventContent(bobDevice, relatesTo = null, transactionId = "t"),274            )275            should("handle ${VerificationDoneEventContent::class.simpleName}") {276                val step = VerificationDoneEventContent(null, "t")277                cut.handleStep(step, alice, true)278                val state = cut.state.value279                state shouldBe Done280            }281        }282        context("current state is ${Done::class.simpleName}") {283            beforeTest {284                cut.handleStep(285                    VerificationReadyEventContent(bobDevice, setOf(Sas, Unknown("u")), null, "t"),286                    bob,287                    false288                )289                cut.handleStep(SasStartEventContent(bobDevice, relatesTo = null, transactionId = "t"), bob, false)290                cut.handleStep(VerificationDoneEventContent(null, "t"), bob, false)291                cut.handleStep(VerificationDoneEventContent(null, "t"), alice, true)292                cut.state.value.shouldBeInstanceOf<Done>()293            }294            checkNotAllowedStateChange(295                VerificationReadyEventContent(bobDevice, setOf(), null, "t"),296                SasStartEventContent(bobDevice, relatesTo = null, transactionId = "t"),297                VerificationDoneEventContent(null, "t")298            )299        }300        context("current state is ${Cancel::class.simpleName}") {301            beforeTest {302                cut.handleStep(VerificationCancelEventContent(Code.User, "user", null, "t"), bob, false)303                cut.state.value.shouldBeInstanceOf<Cancel>()304            }305            checkNotAllowedStateChange(306                VerificationReadyEventContent(bobDevice, setOf(), null, "t"),307                SasStartEventContent(bobDevice, relatesTo = null, transactionId = "t"),308                VerificationDoneEventContent(null, "t"),309            )310            should("not send multiple cancel events") {311                cut.handleStep(VerificationDoneEventContent(null, "t"), bob, false)312                val state = cut.state.value313                state.shouldBeInstanceOf<Cancel>()314                state.content.code shouldBe Code.UnexpectedMessage...

Full Screen

Full Screen

ShouldSpecRootScope.kt

Source:ShouldSpecRootScope.kt Github

copy

Full Screen

...6typealias ShouldSpecRootContext = ShouldSpecRootScope7/**8 * Allows tests to be registered in the 'ShouldSpec' fashion.9 *10 *  context("with context") {11 *    should("do something") {12 *      // test here13 *    }14 *  }15 *16 *  or17 *18 *  should("do something") {19 *    // test here20 *  }21 */22interface ShouldSpecRootScope : RootScope {23   /**24    * Adds a top level context scope to the spec.25    */26   fun context(name: String, test: suspend ShouldSpecContainerScope.() -> Unit) {27      addContainer(TestName("context ", name, false), false, null) {28         ShouldSpecContainerScope(this).test()29      }30   }31   /**32    * Adds a top level context scope to the spec.33    */34   fun xcontext(name: String, test: suspend ShouldSpecContainerScope.() -> Unit) {35      addContainer(TestName("context ", name, false), true, null) { ShouldSpecContainerScope(this).test() }36   }37   /**38    * Adds a top level context scope accepting config to the spec.39    */40   @ExperimentalKotest41   fun context(name: String): RootContainerWithConfigBuilder<ShouldSpecContainerScope> =42      RootContainerWithConfigBuilder(TestName("context ", name, false), false, this) { ShouldSpecContainerScope(it) }43   /**44    * Adds a disabled top level context scope accepting config to the spec.45    */46   @ExperimentalKotest47   fun xcontext(name: String): RootContainerWithConfigBuilder<ShouldSpecContainerScope> =48      RootContainerWithConfigBuilder(TestName("context ", name, false), true, this) { ShouldSpecContainerScope(it) }49   /**50    * Adds a top level test, with the given name and test function, with test config supplied51    * by invoking .config on the return of this function.52    */53   fun should(name: String): RootTestWithConfigBuilder =54      RootTestWithConfigBuilder(this, TestName("should ", name, true), false)55   fun xshould(name: String): RootTestWithConfigBuilder =56      RootTestWithConfigBuilder(this, TestName("should ", name, true), true)57   /**58    * Adds a top level test, with the given name and test function, with default test config.59    */60   fun should(name: String, test: suspend TestScope.() -> Unit) {61      addTest(TestName("should ", name, false), false, null, test)62   }...

Full Screen

Full Screen

ShouldSpecContainerScope.kt

Source:ShouldSpecContainerScope.kt Github

copy

Full Screen

...10typealias ShouldSpecContainerContext = ShouldSpecContainerScope11/**12 * A scope that allows tests to be registered using the syntax:13 *14 * context("some context")15 * should("some test")16 * should("some test").config(...)17 *18 */19@KotestTestScope20class ShouldSpecContainerScope(21   val testScope: TestScope,22) : AbstractContainerScope(testScope) {23   /**24    * Adds a nested context scope to this scope.25    */26   suspend fun context(name: String, test: suspend ShouldSpecContainerScope.() -> Unit) {27      registerContainer(TestName(name), false, null) { ShouldSpecContainerScope(this).test() }28   }29   /**30    * Adds a disabled nested context scope to this scope.31    */32   suspend fun xcontext(name: String, test: suspend ShouldSpecContainerScope.() -> Unit) {33      registerContainer(TestName(name), true, null) { ShouldSpecContainerScope(this).test() }34   }35   @ExperimentalKotest36   fun context(name: String): ContainerWithConfigBuilder<ShouldSpecContainerScope> {37      return ContainerWithConfigBuilder(TestName(name), this, false) { ShouldSpecContainerScope(it) }38   }39   @ExperimentalKotest40   fun xcontext(name: String): ContainerWithConfigBuilder<ShouldSpecContainerScope> {41      return ContainerWithConfigBuilder(TestName(name), this, true) { ShouldSpecContainerScope(it) }42   }43   suspend fun should(name: String): TestWithConfigBuilder {44      TestDslState.startTest(testScope.testCase.descriptor.append(name))45      return TestWithConfigBuilder(TestName("should ", name, false), this, false)46   }47   suspend fun xshould(name: String): TestWithConfigBuilder {48      TestDslState.startTest(testScope.testCase.descriptor.append(name))49      return TestWithConfigBuilder(TestName("should ", name, false), this, true)50   }51   suspend fun should(name: String, test: suspend TestScope.() -> Unit) {52      registerTest(TestName("should ", name, false), false, null, test)53   }54   suspend fun xshould(name: String, test: suspend TestScope.() -> Unit) {...

Full Screen

Full Screen

ProfileDataShouldSpec.kt

Source:ProfileDataShouldSpec.kt Github

copy

Full Screen

...9) :10    ShouldSpec(spec@{11        for (profile in allProfiles) {12            val client = profile.makeTestEndpoint()13            context("For profile ${profile.name}") {14                test(client, profile)15            }16        }17    }) {18}19public suspend inline fun <reified DataSet : Any> ShouldSpecContainerScope.givenAll(20    from: ServiceTestProfile<*, *>,21    require: Boolean = true,22    crossinline test: suspend TestContext.(DataSet) -> Unit23) {24    val dataSets = from.testData[DataSet::class].orEmpty() as List<DataSet>25    if (dataSets.isEmpty()) {26        if (require) {27            throw IllegalStateException("No ${DataSet::class.simpleName} specified for ${from.name}")28        } else {29            println("${this::class.simpleName} did not run for ${from.name}")30        }31    }32    for (dataSet in dataSets) {33        context("Given dataSet $dataSet") {34            test(dataSet)35        }36        delay(500)37    }38}39public suspend inline fun <Profile : ServiceTestProfile<*, DataSet>, reified DataSet : Any> ShouldSpecContainerScope.givenOne(40    from: Profile,41    require: Boolean = true,42    crossinline test: suspend TestContext.(DataSet) -> Unit43) {44    val dataSet = from.testData[DataSet::class].orEmpty().firstOrNull()45    if (dataSet == null) {46        if (require) {47            throw IllegalStateException("No ${DataSet::class.simpleName} specified for ${from.name}")48        } else {49            println("${this::class.simpleName} did not run for ${from.name}")50        }51    } else {52        context("Given dataSet $dataSet") {53            test(dataSet)54        }55    }56}...

Full Screen

Full Screen

Automation Testing Tutorials

Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.

LambdaTest Learning Hubs:

YouTube

You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.

Run Kotest automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Most used method in ShouldSpecContainerScope

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful