async def test_local_echo(e2e_room: Room): new = [] cb = lambda room, event: new.append(event) # noqa e2e_room.client.rooms.callbacks[Text].append(cb) initial_events = len(e2e_room.timeline) await e2e_room.timeline.send(Text("hi"), transaction_id="abc") assert len(e2e_room.timeline) == initial_events + 1 for event in new: assert event.content == Text("hi") assert event.sender == e2e_room.client.user_id assert not event.decryption assert new[0].id == "$echo.abc" assert new[0].sending == SendStep.sending assert new[0].id not in e2e_room.timeline assert new[1].id != new[0].id assert new[1].sending == SendStep.sent assert new[1].id in e2e_room.timeline await e2e_room.client.sync.once() assert new[2].sending == SendStep.synced assert len(new) == 3 assert len(e2e_room.timeline) == initial_events + 1
def test_textual_no_reply_fallback(): assert Text("plain").html_no_reply_fallback is None reply = Text.from_html("<b>foo</b>") # from_html will strip mx-reply reply.formatted_body = HTML_REPLY_FALLBACK + reply.formatted_body assert reply.html_no_reply_fallback == "<b>foo</b>" not_reply = Text.from_html("<b>foo</b>") assert not_reply.html_no_reply_fallback == "<b>foo</b>"
async def test_sending_failure(room: Room, bob: Client): new = [] cb = lambda room, event: new.append((event.id, event.sending)) # noqa room.client.rooms.callbacks[TimelineEvent].append(cb) await room.timeline.send(Text("abc")) await bob.rooms.join(room.id) # prevent room from becoming inaccessible await room.leave() # Event that has failed sending in timeline with raises(MatrixError): await room.timeline.send(Text("def"), transaction_id="123") unsent_id = EventId("$echo.123") assert new[-1] == (unsent_id, SendStep.failed) assert unsent_id in room.timeline assert list(room.timeline.unsent) == [unsent_id] assert room.timeline.unsent[unsent_id].content == Text("def") assert room.timeline.unsent[unsent_id].sending == SendStep.failed assert not room.timeline.unsent[unsent_id].historic # Failed in unsent but not timeline._data after client restart await room.client.terminate() client2 = Client(room.client.base_dir) new = [] cb = lambda room, event: new.append((event.id, event.sending)) # noqa client2.rooms.callbacks[TimelineEvent].append(cb) await client2.load() assert new == [(unsent_id, SendStep.failed)] timeline2 = client2.rooms[room.id].timeline assert unsent_id in timeline2 assert list(timeline2.unsent) == [unsent_id] assert timeline2.unsent[unsent_id].content == Text("def") assert timeline2.unsent[unsent_id].sending == SendStep.failed assert timeline2.unsent[unsent_id].historic # Retry sending that failed await client2.rooms.join(room.id) event_id = await timeline2.resend_failed(timeline2.unsent[unsent_id]) assert timeline2[event_id].sending == SendStep.sent assert not timeline2.unsent assert new[-1] == (event_id, SendStep.sent) with raises(AssertionError): await timeline2.resend_failed(timeline2[event_id])
async def test_timeline_redaction(e2e_room: Room): new = [] cb = lambda room, event: new.append(event) # noqa e2e_room.client.rooms.callbacks[TimelineEvent].append(cb) # Send original message await e2e_room.timeline.send(Text("hi")) await e2e_room.client.sync.once() # Redact + test local echo await e2e_room.timeline[-1].redact("bye") assert isinstance(new[-2].content, Redaction) assert isinstance(new[-1].content, Redacted) assert new[-1].redacted_by == new[-2] assert new[-1].redacted_by.sending == SendStep.sent await e2e_room.client.sync.once() # Check Redaction in timeline redaction = e2e_room.timeline[-1] assert isinstance(redaction.content, Redaction) assert redaction.content.reason == "bye" # Check message now redacted in timeline redacted = e2e_room.timeline[-2] assert isinstance(redacted.content, Redacted) assert redacted.redacted_by == redaction # Check callback results for real events assert isinstance(new[-2].content, Redaction) assert isinstance(new[-1].content, Redacted) assert new[2].sending == SendStep.synced
async def test_cancel_session_forward(alice: Client, e2e_room: Room, tmp_path): await e2e_room.timeline.send(Text("sent before other devices exist")) alice2 = await new_device_from(alice, tmp_path / "alice2") alice3 = await new_device_from(alice, tmp_path / "alice3") # Make alice3 send a request to both alice and alice2 for other in (alice2, alice3): await other.sync.once() await other.rooms[e2e_room.id].timeline.load_history(1) sent_to = {alice.user_id, alice2.user_id} assert next(iter(alice3.e2e._sent_session_requests.values()))[1] == sent_to # Make alice respond to alice3's request before alice2 await alice.sync.once() await alice.devices.own[alice3.device_id].trust() await alice3.sync.once() # alice3 got alice's response, so it should have cancelled alice2's request cancelled = False sync_data = await alice2.sync.once() assert sync_data for event in sync_data["to_device"]["events"]: if event["content"]["action"] == "request_cancellation": cancelled = True assert cancelled
async def test_redact_failure(room: Room): await room.timeline.send(Text("hi")) await room.client.sync.once() await room.leave() new = [] cb = lambda room, event: new.append(( # noqa type(event.content), event.sending, event.redacted_by.sending if event.redacted_by else None, )) room.client.rooms.callbacks[TimelineEvent].append(cb) with raises(MatrixError): await room.timeline[-1].redact() assert isinstance(room.timeline[-2].content, Redacted) assert room.timeline[-2].redacted_by assert room.timeline[-2].redacted_by.sending == SendStep.failed assert isinstance(room.timeline[-1].content, Redaction) assert room.timeline[-1].sending == SendStep.failed assert room.timeline[-1].id not in room.timeline.unsent assert new == [ (Redaction, SendStep.sending, None), (Redacted, SendStep.synced, SendStep.sending), (Redaction, SendStep.failed, None), (Redacted, SendStep.synced, SendStep.failed), ]
async def test_session_forwarding(alice: Client, e2e_room: Room, tmp_path): # https://github.com/matrix-org/synapse/pull/8675 cross-user sharing broken await e2e_room.timeline.send(Text("sent before other devices exist")) untrusted = await new_device_from(alice, tmp_path / "unstrusted") blocked = await new_device_from(alice, tmp_path / "blocked") for other in (untrusted, blocked): await other.sync.once() await other.rooms[e2e_room.id].timeline.load_history(1) await alice.sync.once() await alice.devices.own[blocked.device_id].block() assert alice.devices.own[untrusted.device_id].trusted is None assert alice.devices.own[blocked.device_id].trusted is False for other in (untrusted, blocked): # Ensure session requests from untrusted or blocked device are pended assert alice.devices.own[other.device_id].pending_session_requests assert len(other.e2e._sent_session_requests) == 1 other_event = other.rooms[e2e_room.id].timeline[-1] assert isinstance(other_event.content, Megolm) # Test replying to pending forward request of previously untrusted dev. await alice.devices.own[other.device_id].trust() await other.sync.once() assert not alice.devices.own[other.device_id].pending_session_requests assert not other.e2e._sent_session_requests other_event = other.rooms[e2e_room.id].timeline[-1] assert isinstance(other_event.content, Text)
async def test_broken_olm_lost_forwarded_session_recovery( alice: Client, e2e_room: Room, tmp_path, ): await e2e_room.timeline.send(Text("sent before other devices exist")) # Receive an undecryptable megolm and send a group session request for it alice2 = await new_device_from(alice, tmp_path / "second") await alice2.sync.once() assert isinstance(alice2.rooms[e2e_room.id].timeline[-1].content, Megolm) # Respond to alice2's group session request await alice.sync.once() await alice.devices.own[alice2.device_id].trust() # Fail to decrypt alice's response to our group session request, # alice2 will create a new olm session and send a new request sync_data = await alice2.sync.once(_handle=False) assert sync_data sync_data["to_device"]["events"][0]["content"]["ciphertext"].clear() await alice2.sync._handle_sync(sync_data) assert isinstance(alice2.rooms[e2e_room.id].timeline[-1].content, Megolm) # Alice responds to the new requerst, then alice2 should be able to decrypt await alice.sync.once() await alice2.sync.once() assert isinstance(alice2.rooms[e2e_room.id].timeline[-1].content, Text)
async def test_leave(e2e_room: Room): await e2e_room.timeline.send(Text("make a session")) assert e2e_room.id in e2e_room.client.e2e._out_group_sessions await e2e_room.leave(reason="bye") await e2e_room.client.sync.once() assert e2e_room.left assert e2e_room.id not in e2e_room.client.e2e._out_group_sessions assert e2e_room.state.me.membership_reason == "bye"
async def test_tracking(alice: Client, e2e_room: Room, bob: Client, tmp_path): # Get initial devices of users we share an encrypted room with: await bob.rooms.join(e2e_room.id) await bob.sync.once() await bob.rooms[e2e_room.id].timeline.send(Text("makes alice get my key")) bob_dev1 = bob.devices.current assert bob.user_id not in alice.devices assert bob_dev1.curve25519 not in alice.devices.by_curve await alice.sync.once() assert alice.devices[bob.user_id] == {bob_dev1.device_id: bob_dev1} assert alice.devices.by_curve[bob_dev1.curve25519] == bob_dev1 # Notice a user's new devices at runtime and share session with it: bob2 = await new_device_from(bob, tmp_path) bob_dev2 = bob2.devices.current await alice.sync.once() await bob.sync.once() await e2e_room.timeline.send(Text("notice bob's new device")) await bob.rooms[e2e_room.id].timeline.send(Text("notice my new device")) assert alice.devices[bob.user_id] == bob.devices.own == { bob_dev1.device_id: bob_dev1, bob_dev2.device_id: bob_dev2, } assert alice.devices.by_curve[bob_dev1.curve25519] == bob_dev1 assert alice.devices.by_curve[bob_dev2.curve25519] == bob_dev2 await bob2.sync.once() assert isinstance(bob2.rooms[e2e_room.id].timeline[-1].content, Text) # Stop tracking devices of users we no longer share an encrypted room with: await bob.rooms[e2e_room.id].leave() await alice.sync.once() assert bob.user_id not in alice.devices assert bob_dev1.curve25519 not in alice.devices.by_curve assert bob_dev2.curve25519 not in alice.devices.by_curve
async def test_timeline_event_callback(alice: Client, room: Room): got = [] cb = lambda r, e: got.extend([r, type(e.content)]) # noqa alice.rooms.callbacks[TimelineEvent].append(cb) await room.timeline.send(Text("This is a test"), local_echo_to=[]) await room.timeline.send(Emote("tests"), local_echo_to=[]) # We parse a corresponding timeline event on sync for new states await room.state.send(CanonicalAlias()) await alice.sync.once() assert got == [room, Text, room, Emote, room, CanonicalAlias]
async def test_send_to_lazy_encrypted_room(e2e_room: Room, bob: Client): await e2e_room.invite(bob.user_id) await bob.rooms.join(e2e_room.id) await e2e_room.client.sync.once() assert not e2e_room.state.all_users_loaded await e2e_room.timeline.send(Text("hi")) assert e2e_room.state.all_users_loaded assert len(e2e_room.state.users) == 2 await bob.sync.once() assert isinstance(bob.rooms[e2e_room.id].timeline[-1].content, Text)
async def test_textual_replying_to(room: Room): await room.timeline.send(Text("plain\nmsg")) await room.timeline.send(Text.from_html("<b>html</b><br>msg")) await room.state.send(Name("Test room")) await room.client.sync.once() plain, html, other = list(room.timeline.values())[-3:] def check_reply_attrs(first_event, reply, fallback_content, reply_content): assert reply.replies_to == first_event.id assert reply.format == HTML_FORMAT assert reply.formatted_body == HTML_REPLY_FALLBACK.format( matrix_to=MATRIX_TO, room_id=room.id, user_id=room.client.user_id, event_id=first_event.id, content=fallback_content, ) + reply_content reply = Text("nice").replying_to(plain) assert reply.body == f"> <{plain.sender}> plain\n> msg\n\nnice" check_reply_attrs(plain, reply, plain2html(plain.content.body), "nice") reply = Text("nice").replying_to(html) assert reply.body == f"> <{plain.sender}> **html** \n> msg\n\nnice" check_reply_attrs(html, reply, html.content.formatted_body, "nice") reply = Text.from_html("<i>nice</i>").replying_to(html) assert reply.body == f"> <{plain.sender}> **html** \n> msg\n\n*nice*" check_reply_attrs(html, reply, html.content.formatted_body, "<i>nice</i>") reply = Text("nice").replying_to(other) assert reply.body == f"> <{plain.sender}> {Name.type}\n\nnice" assert other.type check_reply_attrs(other, reply, plain2html(other.type), "nice")
async def test_multiclient_local_echo(e2e_room: Room, bob: Client): new1 = [] cb1 = lambda room, event: new1.append(event) # noqa e2e_room.client.rooms.callbacks[Text].append(cb1) new2 = [] cb2 = lambda room, event: new2.append(event) # noqa bob.rooms.callbacks[Text].append(cb2) await bob.rooms.join(e2e_room.id) await bob.sync.once() await e2e_room.timeline.send(Text("hi"), [e2e_room.client, bob]) assert new1 == new2
async def test_trust(alice: Client, e2e_room: Room, bob: Client): bob_dev = bob.devices.current await bob.rooms.join(e2e_room.id) await bob.sync.once() await bob.rooms[e2e_room.id].timeline.send(Text("makes alice get my key")) await alice.sync.once() assert alice.devices[bob.user_id][bob_dev.device_id].trusted is None await e2e_room.timeline.send(Text("unset")) await bob.sync.once() assert isinstance(bob.rooms[e2e_room.id].timeline[-1].content, Text) await alice.devices[bob.user_id][bob_dev.device_id].trust() assert alice.devices[bob.user_id][bob_dev.device_id].trusted is True await e2e_room.timeline.send(Text("trusted")) await bob.sync.once() assert isinstance(bob.rooms[e2e_room.id].timeline[-1].content, Text) await alice.devices[bob.user_id][bob_dev.device_id].block() assert alice.devices[bob.user_id][bob_dev.device_id].trusted is False await e2e_room.timeline.send(Text("blocked")) await bob.sync.once() assert isinstance(bob.rooms[e2e_room.id].timeline[-1].content, Megolm)
async def test_timeline_event_callback_group(alice: Client, room: Room): cb_group = CallbackGroupTest() alice.rooms.callback_groups.append(cb_group) text = Text("This is a test") emote = Emote("tests") await room.timeline.send(text, local_echo_to=[]) await room.timeline.send(emote, local_echo_to=[]) # We parse a corresponding timeline event on sync for new states await room.state.send(CanonicalAlias()) await alice.sync.once() expected = [room, text, room, emote, room, CanonicalAlias()] assert cb_group.timeline_result == expected
async def test_trust_megolm_validation(alice: Client, e2e_room, bob: Client): bob_dev = bob.devices.current await bob.rooms.join(e2e_room.id) await bob.sync.once() await bob.rooms[e2e_room.id].timeline.send(Text("makes alice get my key")) await alice.sync.once() assert alice.devices[bob.user_id][bob_dev.device_id].trusted is None await bob.rooms[e2e_room.id].timeline.send(Text("unset")) await alice.sync.once() error = e2e_room.timeline[-1].decryption.verification_errors[0] assert isinstance(error, MegolmFromUntrustedDevice) await alice.devices[bob.user_id][bob_dev.device_id].trust() await bob.rooms[e2e_room.id].timeline.send(Text("trusted")) await alice.sync.once() assert not e2e_room.timeline[-1].decryption.verification_errors await alice.devices[bob.user_id][bob_dev.device_id].block() await bob.rooms[e2e_room.id].timeline.send(Text("blocked")) await alice.sync.once() error = e2e_room.timeline[-1].decryption.verification_errors[0] assert isinstance(error, MegolmFromBlockedDevice) assert error.device is alice.devices[bob.user_id][bob_dev.device_id]
async def test_session_forwarding_already_trusted_device( alice: Client, e2e_room: Room, tmp_path, ): await e2e_room.timeline.send(Text("sent before other devices exist")) alice2 = await new_device_from(alice, tmp_path / "unstrusted") await alice.sync.once() await alice.devices.own[alice2.device_id].trust() await alice2.sync.once() await alice2.rooms[e2e_room.id].timeline.load_history(1) await alice.sync.once() # get session request await alice2.sync.once() # get forwarded session event = alice2.rooms[e2e_room.id].timeline[-1] assert isinstance(event.content, Text)
def test_textual_same_html_plaintext(): text = Text.from_html("abc", plaintext="abc") assert text.body == "abc" assert text.format is None assert text.formatted_body is None
def test_textual_from_html_manual_plaintext(): text = Text.from_html("<p>abc</p>", plaintext="123") assert text.body == "123" assert text.format == HTML_FORMAT assert text.formatted_body == "<p>abc</p>"
async def test_forwarding_chains(alice: Client, e2e_room: Room, tmp_path): await e2e_room.timeline.send(Text("sent before other devices exist")) # [no forward chain] alice/trusted → alice2 alice2 = await new_device_from(alice, tmp_path / "alice2") await alice.sync.once() await alice.devices.own[alice2.device_id].trust() await alice2.devices.own[alice.device_id].trust() await alice2.sync.once() await alice2.rooms[e2e_room.id].timeline.load_history(1) await alice.sync.once() # get session request await alice2.sync.once() # get forwarded session event = alice2.rooms[e2e_room.id].timeline[-1] assert isinstance(event.content, Text) and event.decryption assert not event.decryption.forward_chain assert not event.decryption.verification_errors # [forward chain: alice/trusted →] alice2/trusted → alice3 alice3 = await new_device_from(alice, tmp_path / "alice3") await alice2.sync.once() await alice2.devices.own[alice3.device_id].trust() await alice3.devices.own[alice.device_id].trust() await alice3.devices.own[alice2.device_id].trust() await alice3.sync.once() await alice3.rooms[e2e_room.id].timeline.load_history(1) await alice2.sync.once() # get session request await alice3.sync.once() # get forwarded session event = alice3.rooms[e2e_room.id].timeline[-1] assert isinstance(event.content, Text) and event.decryption assert event.decryption.forward_chain == [alice.devices.current] assert not event.decryption.verification_errors # [forward chain: alice/blocked →] alice2/trusted → alice3 await alice3.devices.own[alice.device_id].block() event = await event.decryption.original._decrypted() assert isinstance(event.content, Text) and event.decryption assert event.decryption.forward_chain == [alice.devices.current] assert event.decryption.verification_errors == [ err.MegolmFromBlockedDevice(alice.devices.current), err.MegolmBlockedDeviceInForwardChain(alice.devices.current), ] # [forward chain: alice/untrusted →] alice2/trusted → alice3 alice3.devices.own[alice.device_id].trusted = None event = await event.decryption.original._decrypted() assert isinstance(event.content, Text) and event.decryption assert event.decryption.forward_chain == [alice.devices.current] assert event.decryption.verification_errors == [ err.MegolmFromUntrustedDevice(alice.devices.current), err.MegolmUntrustedDeviceInForwardChain(alice.devices.current), ]
async def test_push_rules_triggering(alice: Client, bob: Client, room: Room): await room.timeline.send(Text.from_html("<b>abc</b>, def")) await room.timeline.send(Text(f"...{alice.profile.name}...")) await alice.sync.once() assert isinstance(room.timeline[0].content, Creation) creation = room.timeline[0] abc = room.timeline[-2] mention = room.timeline[-1] # Individual conditions assert PushEventMatch({}, "content.format", "org.*.hTmL").triggered_by(abc) assert not PushEventMatch({}, "content.format", "html").triggered_by(abc) assert not PushEventMatch({}, "bad field", "blah").triggered_by(abc) assert PushEventMatch({}, "content.body", "abc").triggered_by(abc) assert PushEventMatch({}, "content.body", "Ab[cd]").triggered_by(abc) assert not PushEventMatch({}, "content.body", "ab").triggered_by(abc) assert PushContainsDisplayName({}).triggered_by(mention) assert not PushContainsDisplayName({}).triggered_by(abc) assert not PushContainsDisplayName({}).triggered_by(creation) ops = PushRoomMemberCount.Operator assert PushRoomMemberCount({}, 1).triggered_by(creation) assert PushRoomMemberCount({}, 2, ops.lt).triggered_by(abc) assert PushRoomMemberCount({}, 0, ops.gt).triggered_by(abc) assert PushRoomMemberCount({}, 1, ops.le).triggered_by(abc) assert PushRoomMemberCount({}, 1, ops.ge).triggered_by(abc) assert not PushRoomMemberCount({}, 1, ops.gt).triggered_by(abc) assert not PushRoomMemberCount({}, 1, ops.lt).triggered_by(abc) assert not PushRoomMemberCount({}, 2, ops.eq).triggered_by(abc) assert PushSenderNotificationPermission({}, "room").triggered_by(abc) assert PushSenderNotificationPermission({}, "unknown").triggered_by(abc) users = {alice.user_id: 49} await room.state.send(room.state.power_levels.but(users=users)) await alice.sync.once() assert not PushSenderNotificationPermission({}, "room").triggered_by(abc) assert not PushSenderNotificationPermission({}, "xyz").triggered_by(abc) # PushRule kinds = PushRule.Kind assert PushRule("test").triggered_by(abc) assert not PushRule("test", enabled=False).triggered_by(abc) mention_1 = PushRule("test", conditions=[ PushContainsDisplayName({}), PushRoomMemberCount({}, 1), ]) assert mention_1.triggered_by(mention) assert not mention_1.triggered_by(abc) assert PushRule("c", kind=kinds.content, pattern="abc").triggered_by(abc) assert not PushRule("c", kind=kinds.content, pattern="a").triggered_by(abc) assert PushRule(room.id, kind=kinds.room).triggered_by(abc) assert not PushRule(room.id + "a", kind=kinds.room).triggered_by(abc) assert PushRule(alice.user_id, kind=kinds.sender).triggered_by(abc) assert not PushRule(bob.user_id, kind=kinds.sender).triggered_by(abc) # PushRuleset rule = alice.account_data.push_rules.main.triggered(abc) assert rule and rule.id == ".m.rule.message"
async def test_session_export(alice: Client, e2e_room: Room, bob: Client): alice_ses = alice.e2e._in_group_sessions bob_ses = bob.e2e._in_group_sessions # Alice won't auto-share her session to Bob since his device isn't trusted await e2e_room.timeline.send(Text("undecryptable to bob")) await bob.rooms.join(e2e_room.id) await bob.sync.once() await e2e_room.timeline.send(Text("make a session")) await alice.sync.once() assert not bob_ses assert len(alice_ses) == 1 assert isinstance(bob.rooms[e2e_room.id].timeline[-2].content, Megolm) exported = await alice.e2e.export_sessions(passphrase="test") # 100% successful import and previous message decrypted as a result await bob.e2e.import_sessions(exported, "test") assert alice_ses.keys() == bob_ses.keys() assert isinstance(bob.rooms[e2e_room.id].timeline[-2].content, Text) for session1, sender_ed1, _, forward_chain1 in alice_ses.values(): for session2, sender_ed2, _, forward_chain2 in bob_ses.values(): export1 = session1.export_session(session1.first_known_index) export2 = session2.export_session(session2.first_known_index) assert export1 == export2 assert sender_ed1 == sender_ed2 assert forward_chain1 == forward_chain2 # Total import failures with raises(err.SessionFileMissingHeader): await bob.e2e.import_sessions(exported[1:], "test") with raises(err.SessionFileMissingFooter): await bob.e2e.import_sessions(exported[:-1], "test") with raises(err.SessionFileInvalidBase64): bad = SESSION_FILE_HEADER + "abcdE" + SESSION_FILE_FOOTER await bob.e2e.import_sessions(bad, "test") with raises(err.SessionFileInvalidDataSize): bad = SESSION_FILE_HEADER + "abcd" + SESSION_FILE_FOOTER await bob.e2e.import_sessions(bad, "test") with raises(err.SessionFileUnsupportedVersion): base64 = "aaab" * err.SessionFileInvalidDataSize.minimum bad = SESSION_FILE_HEADER + base64 + SESSION_FILE_FOOTER await bob.e2e.import_sessions(bad, "test") with raises(err.SessionFileInvalidHMAC): await bob.e2e.import_sessions(exported, "incorrect passphrase") with raises(err.SessionFileInvalidJSON): bad = await alice.e2e.export_sessions("test", lambda j: j + "break") await bob.e2e.import_sessions(bad, "test") with raises(err.SessionFileInvalidJSON): bad = await alice.e2e.export_sessions("test", lambda j: "{}") await bob.e2e.import_sessions(bad, "test") # Skipped session due to older/same version of it already being present current = list(bob_ses.values())[0][0] await bob.e2e.import_sessions(exported, "test") assert list(bob_ses.values())[0][0] == current # Skipped session due to unsupported algo def corrupt_session0_algo(json_data): data = json.loads(json_data) data[0]["algorithm"] = "123" return json.dumps(data) bad = await alice.e2e.export_sessions("test", corrupt_session0_algo) bob_ses.clear() await bob.e2e.import_sessions(bad, "test") assert not bob_ses # Skipped session due to general error, in this case a missing dict key def kill_session0_essential_key(json_data): data = json.loads(json_data) del data[0]["algorithm"] return json.dumps(data) bad = await alice.e2e.export_sessions("test", kill_session0_essential_key) bob_ses.clear() await bob.e2e.import_sessions(bad, "test") assert not bob_ses