atagen

macltok

public
Star0
Watch0
2c13c8db61741baf208b9c98b78c4a9e97d1fe61
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git

Blame: src/mentions/state_tests.rs

cbb715e11963atagen2026-10-24 23:24 UTC1
use ruma::{event_id, user_id};
cbb715e11963atagen2026-10-24 23:24 UTC2
cbb715e11963atagen2026-10-24 23:24 UTC3
use super::state::{Data, SavedEvent, Window, id};
cbb715e11963atagen2026-10-24 23:24 UTC4
use crate::{
cbb715e11963atagen2026-10-24 23:24 UTC5
    MentionCaptureState, MentionEntry, MentionKey, MentionReason, Message, MessageKind, SendState,
cbb715e11963atagen2026-10-24 23:24 UTC6
    Sender, TimelineContent, TimelineEvent, TimelineItem, TimelineItemId,
cbb715e11963atagen2026-10-24 23:24 UTC7
};
cbb715e11963atagen2026-10-24 23:24 UTC8
cbb715e11963atagen2026-10-24 23:24 UTC9
fn key(room: &str, event: &str) -> MentionKey {
cbb715e11963atagen2026-10-24 23:24 UTC10
    MentionKey {
cbb715e11963atagen2026-10-24 23:24 UTC11
        room_id: format!("!{room}:mentions.example").parse().unwrap(),
cbb715e11963atagen2026-10-24 23:24 UTC12
        event_id: format!("${event}:mentions.example").parse().unwrap(),
cbb715e11963atagen2026-10-24 23:24 UTC13
    }
cbb715e11963atagen2026-10-24 23:24 UTC14
}
cbb715e11963atagen2026-10-24 23:24 UTC15
cbb715e11963atagen2026-10-24 23:24 UTC16
fn entry(key: MentionKey, timestamp_ms: u64, discovery_revision: u64, read: bool) -> MentionEntry {
cbb715e11963atagen2026-10-24 23:24 UTC17
    MentionEntry {
cbb715e11963atagen2026-10-24 23:24 UTC18
        key,
cbb715e11963atagen2026-10-24 23:24 UTC19
        timestamp_ms,
cbb715e11963atagen2026-10-24 23:24 UTC20
        discovered_at_ms: timestamp_ms,
cbb715e11963atagen2026-10-24 23:24 UTC21
        discovery_revision,
cbb715e11963atagen2026-10-24 23:24 UTC22
        revision: discovery_revision,
cbb715e11963atagen2026-10-24 23:24 UTC23
        event: TimelineItem::DayDivider { timestamp_ms },
cbb715e11963atagen2026-10-24 23:24 UTC24
        reasons: vec![MentionReason::Direct],
cbb715e11963atagen2026-10-24 23:24 UTC25
        read,
cbb715e11963atagen2026-10-24 23:24 UTC26
        capture: MentionCaptureState::Ready,
cbb715e11963atagen2026-10-24 23:24 UTC27
        room_name: None,
cbb715e11963atagen2026-10-24 23:24 UTC28
        notification_suppressed: false,
cbb715e11963atagen2026-10-24 23:24 UTC29
    }
cbb715e11963atagen2026-10-24 23:24 UTC30
}
cbb715e11963atagen2026-10-24 23:24 UTC31
cbb715e11963atagen2026-10-24 23:24 UTC32
fn message_event(event_id: &str, timestamp_ms: u64, body: &str) -> TimelineItem {
cbb715e11963atagen2026-10-24 23:24 UTC33
    let event_id: ruma::OwnedEventId = event_id.parse().unwrap();
cbb715e11963atagen2026-10-24 23:24 UTC34
    TimelineItem::Event(Box::new(TimelineEvent {
cbb715e11963atagen2026-10-24 23:24 UTC35
        item_id: TimelineItemId::new(format!("item-{event_id}")),
cbb715e11963atagen2026-10-24 23:24 UTC36
        event_id: Some(event_id),
cbb715e11963atagen2026-10-24 23:24 UTC37
        transaction_id: None,
cbb715e11963atagen2026-10-24 23:24 UTC38
        sender: Sender {
cbb715e11963atagen2026-10-24 23:24 UTC39
            user_id: user_id!("@bob:mentions.example").to_owned(),
cbb715e11963atagen2026-10-24 23:24 UTC40
            display_name: Some("Bob".to_owned()),
cbb715e11963atagen2026-10-24 23:24 UTC41
            avatar: None,
cbb715e11963atagen2026-10-24 23:24 UTC42
            name_ambiguous: false,
cbb715e11963atagen2026-10-24 23:24 UTC43
        },
cbb715e11963atagen2026-10-24 23:24 UTC44
        timestamp_ms,
cbb715e11963atagen2026-10-24 23:24 UTC45
        own: false,
cbb715e11963atagen2026-10-24 23:24 UTC46
        send_state: SendState::Sent,
cbb715e11963atagen2026-10-24 23:24 UTC47
        content: TimelineContent::Message(Message {
cbb715e11963atagen2026-10-24 23:24 UTC48
            kind: MessageKind::Text,
cbb715e11963atagen2026-10-24 23:24 UTC49
            body: body.to_owned(),
cbb715e11963atagen2026-10-24 23:24 UTC50
            geo_uri: None,
cbb715e11963atagen2026-10-24 23:24 UTC51
            formatted_body: None,
cbb715e11963atagen2026-10-24 23:24 UTC52
            mentions: None,
cbb715e11963atagen2026-10-24 23:24 UTC53
            edited: false,
cbb715e11963atagen2026-10-24 23:24 UTC54
            media: None,
cbb715e11963atagen2026-10-24 23:24 UTC55
            link_previews: Vec::new(),
cbb715e11963atagen2026-10-24 23:24 UTC56
        }),
cbb715e11963atagen2026-10-24 23:24 UTC57
        reply_to: None,
cbb715e11963atagen2026-10-24 23:24 UTC58
        thread_root: None,
cbb715e11963atagen2026-10-24 23:24 UTC59
        thread_summary: None,
cbb715e11963atagen2026-10-24 23:24 UTC60
        reactions: Vec::new(),
cbb715e11963atagen2026-10-24 23:24 UTC61
        read_by: Vec::new(),
cbb715e11963atagen2026-10-24 23:24 UTC62
        editable: false,
cbb715e11963atagen2026-10-24 23:24 UTC63
    }))
cbb715e11963atagen2026-10-24 23:24 UTC64
}
cbb715e11963atagen2026-10-24 23:24 UTC65
cbb715e11963atagen2026-10-24 23:24 UTC66
#[cfg_attr(not(target_family = "wasm"), tokio::test)]
cbb715e11963atagen2026-10-24 23:24 UTC67
#[cfg_attr(target_family = "wasm", wasm_bindgen_test::wasm_bindgen_test)]
cbb715e11963atagen2026-10-24 23:24 UTC68
async fn pagination_keeps_its_discovery_boundary_and_stable_ties() {
cbb715e11963atagen2026-10-24 23:24 UTC69
    let account = user_id!("@alice:mentions.example").to_owned();
cbb715e11963atagen2026-10-24 23:24 UTC70
    let ordered = vec![
cbb715e11963atagen2026-10-24 23:24 UTC71
        key("room", "latest"),
cbb715e11963atagen2026-10-24 23:24 UTC72
        key("room", "tie-z"),
cbb715e11963atagen2026-10-24 23:24 UTC73
        key("room", "tie-b"),
cbb715e11963atagen2026-10-24 23:24 UTC74
        key("room", "tie-a"),
cbb715e11963atagen2026-10-24 23:24 UTC75
        key("room", "oldest"),
cbb715e11963atagen2026-10-24 23:24 UTC76
    ];
cbb715e11963atagen2026-10-24 23:24 UTC77
    let mut data = Data {
cbb715e11963atagen2026-10-24 23:24 UTC78
        revision: 1,
cbb715e11963atagen2026-10-24 23:24 UTC79
        ..Data::default()
cbb715e11963atagen2026-10-24 23:24 UTC80
    };
cbb715e11963atagen2026-10-24 23:24 UTC81
    for (mention, timestamp) in [
cbb715e11963atagen2026-10-24 23:24 UTC82
        (ordered[0].clone(), 300),
cbb715e11963atagen2026-10-24 23:24 UTC83
        (ordered[1].clone(), 200),
cbb715e11963atagen2026-10-24 23:24 UTC84
        (ordered[2].clone(), 200),
cbb715e11963atagen2026-10-24 23:24 UTC85
        (ordered[3].clone(), 200),
cbb715e11963atagen2026-10-24 23:24 UTC86
        (ordered[4].clone(), 100),
cbb715e11963atagen2026-10-24 23:24 UTC87
    ] {
cbb715e11963atagen2026-10-24 23:24 UTC88
        data.entries
cbb715e11963atagen2026-10-24 23:24 UTC89
            .insert(id(&mention), entry(mention, timestamp, 1, false));
cbb715e11963atagen2026-10-24 23:24 UTC90
    }
cbb715e11963atagen2026-10-24 23:24 UTC91
cbb715e11963atagen2026-10-24 23:24 UTC92
    let first = data.page(&account, None, 2).unwrap();
cbb715e11963atagen2026-10-24 23:24 UTC93
    assert_eq!(
cbb715e11963atagen2026-10-24 23:24 UTC94
        first
cbb715e11963atagen2026-10-24 23:24 UTC95
            .entries
cbb715e11963atagen2026-10-24 23:24 UTC96
            .iter()
cbb715e11963atagen2026-10-24 23:24 UTC97
            .map(|entry| entry.key.clone())
cbb715e11963atagen2026-10-24 23:24 UTC98
            .collect::<Vec<_>>(),
cbb715e11963atagen2026-10-24 23:24 UTC99
        ordered[..2].to_vec()
cbb715e11963atagen2026-10-24 23:24 UTC100
    );
cbb715e11963atagen2026-10-24 23:24 UTC101
    let mut cursor = first.next_cursor.unwrap();
cbb715e11963atagen2026-10-24 23:24 UTC102
cbb715e11963atagen2026-10-24 23:24 UTC103
    data.revision = 2;
cbb715e11963atagen2026-10-24 23:24 UTC104
    let later = key("room", "discovered-later");
cbb715e11963atagen2026-10-24 23:24 UTC105
    data.entries.insert(id(&later), entry(later, 50, 2, false));
cbb715e11963atagen2026-10-24 23:24 UTC106
cbb715e11963atagen2026-10-24 23:24 UTC107
    let mut paged = first
cbb715e11963atagen2026-10-24 23:24 UTC108
        .entries
cbb715e11963atagen2026-10-24 23:24 UTC109
        .into_iter()
cbb715e11963atagen2026-10-24 23:24 UTC110
        .map(|entry| entry.key)
cbb715e11963atagen2026-10-24 23:24 UTC111
        .collect::<Vec<_>>();
cbb715e11963atagen2026-10-24 23:24 UTC112
    loop {
cbb715e11963atagen2026-10-24 23:24 UTC113
        let page = data.page(&account, Some(&cursor), 2).unwrap();
cbb715e11963atagen2026-10-24 23:24 UTC114
        paged.extend(page.entries.into_iter().map(|entry| entry.key));
cbb715e11963atagen2026-10-24 23:24 UTC115
        match page.next_cursor {
cbb715e11963atagen2026-10-24 23:24 UTC116
            Some(next) => cursor = next,
cbb715e11963atagen2026-10-24 23:24 UTC117
            None => break,
cbb715e11963atagen2026-10-24 23:24 UTC118
        }
cbb715e11963atagen2026-10-24 23:24 UTC119
    }
cbb715e11963atagen2026-10-24 23:24 UTC120
    assert_eq!(paged, ordered);
cbb715e11963atagen2026-10-24 23:24 UTC121
}
cbb715e11963atagen2026-10-24 23:24 UTC122
cbb715e11963atagen2026-10-24 23:24 UTC123
#[cfg_attr(not(target_family = "wasm"), tokio::test)]
cbb715e11963atagen2026-10-24 23:24 UTC124
#[cfg_attr(target_family = "wasm", wasm_bindgen_test::wasm_bindgen_test)]
cbb715e11963atagen2026-10-24 23:24 UTC125
async fn pagination_rejects_a_forged_account_and_a_future_revision() {
cbb715e11963atagen2026-10-24 23:24 UTC126
    let account = user_id!("@alice:mentions.example").to_owned();
cbb715e11963atagen2026-10-24 23:24 UTC127
    let mut data = Data {
cbb715e11963atagen2026-10-24 23:24 UTC128
        revision: 1,
cbb715e11963atagen2026-10-24 23:24 UTC129
        ..Data::default()
cbb715e11963atagen2026-10-24 23:24 UTC130
    };
cbb715e11963atagen2026-10-24 23:24 UTC131
    for event in ["one", "two"] {
cbb715e11963atagen2026-10-24 23:24 UTC132
        let mention = key("room", event);
cbb715e11963atagen2026-10-24 23:24 UTC133
        data.entries
cbb715e11963atagen2026-10-24 23:24 UTC134
            .insert(id(&mention), entry(mention, 10, 1, false));
cbb715e11963atagen2026-10-24 23:24 UTC135
    }
cbb715e11963atagen2026-10-24 23:24 UTC136
    let cursor = data.page(&account, None, 1).unwrap().next_cursor.unwrap();
cbb715e11963atagen2026-10-24 23:24 UTC137
cbb715e11963atagen2026-10-24 23:24 UTC138
    let mut forged_account: serde_json::Value = serde_json::from_str(&cursor).unwrap();
cbb715e11963atagen2026-10-24 23:24 UTC139
    forged_account["account"] = serde_json::Value::String("@mallory:mentions.example".to_owned());
cbb715e11963atagen2026-10-24 23:24 UTC140
    assert!(
cbb715e11963atagen2026-10-24 23:24 UTC141
        data.page(&account, Some(&forged_account.to_string()), 1)
cbb715e11963atagen2026-10-24 23:24 UTC142
            .is_err()
cbb715e11963atagen2026-10-24 23:24 UTC143
    );
cbb715e11963atagen2026-10-24 23:24 UTC144
cbb715e11963atagen2026-10-24 23:24 UTC145
    let mut future_revision: serde_json::Value = serde_json::from_str(&cursor).unwrap();
cbb715e11963atagen2026-10-24 23:24 UTC146
    future_revision["revision"] = serde_json::json!(data.revision + 1);
cbb715e11963atagen2026-10-24 23:24 UTC147
    assert!(
cbb715e11963atagen2026-10-24 23:24 UTC148
        data.page(&account, Some(&future_revision.to_string()), 1)
cbb715e11963atagen2026-10-24 23:24 UTC149
            .is_err()
cbb715e11963atagen2026-10-24 23:24 UTC150
    );
cbb715e11963atagen2026-10-24 23:24 UTC151
}
cbb715e11963atagen2026-10-24 23:24 UTC152
cbb715e11963atagen2026-10-24 23:24 UTC153
#[cfg_attr(not(target_family = "wasm"), tokio::test)]
cbb715e11963atagen2026-10-24 23:24 UTC154
#[cfg_attr(target_family = "wasm", wasm_bindgen_test::wasm_bindgen_test)]
cbb715e11963atagen2026-10-24 23:24 UTC155
async fn retention_discards_read_context_first_and_keeps_shared_events_until_unused() {
cbb715e11963atagen2026-10-24 23:24 UTC156
    let read_key = key("room", "read-mention");
cbb715e11963atagen2026-10-24 23:24 UTC157
    let unread_key = key("room", "unread-mention");
cbb715e11963atagen2026-10-24 23:24 UTC158
    let shared_key = key("room", "shared-context");
cbb715e11963atagen2026-10-24 23:24 UTC159
    let read_id = id(&read_key);
cbb715e11963atagen2026-10-24 23:24 UTC160
    let unread_id = id(&unread_key);
cbb715e11963atagen2026-10-24 23:24 UTC161
    let shared_id = id(&shared_key);
cbb715e11963atagen2026-10-24 23:24 UTC162
    let mut data = Data {
cbb715e11963atagen2026-10-24 23:24 UTC163
        revision: 3,
cbb715e11963atagen2026-10-24 23:24 UTC164
        ..Data::default()
cbb715e11963atagen2026-10-24 23:24 UTC165
    };
cbb715e11963atagen2026-10-24 23:24 UTC166
    data.entries
cbb715e11963atagen2026-10-24 23:24 UTC167
        .insert(read_id.clone(), entry(read_key, 10, 1, true));
cbb715e11963atagen2026-10-24 23:24 UTC168
    data.entries
cbb715e11963atagen2026-10-24 23:24 UTC169
        .insert(unread_id.clone(), entry(unread_key, 20, 2, false));
cbb715e11963atagen2026-10-24 23:24 UTC170
    data.events.insert(
cbb715e11963atagen2026-10-24 23:24 UTC171
        shared_id.clone(),
cbb715e11963atagen2026-10-24 23:24 UTC172
        SavedEvent {
cbb715e11963atagen2026-10-24 23:24 UTC173
            item: message_event("$shared-context:mentions.example", 15, "shared"),
cbb715e11963atagen2026-10-24 23:24 UTC174
            media: Vec::new(),
cbb715e11963atagen2026-10-24 23:24 UTC175
            raw: None,
cbb715e11963atagen2026-10-24 23:24 UTC176
        },
cbb715e11963atagen2026-10-24 23:24 UTC177
    );
cbb715e11963atagen2026-10-24 23:24 UTC178
    data.windows.insert(
cbb715e11963atagen2026-10-24 23:24 UTC179
        read_id.clone(),
cbb715e11963atagen2026-10-24 23:24 UTC180
        Window {
cbb715e11963atagen2026-10-24 23:24 UTC181
            before: vec![shared_id.clone()],
cbb715e11963atagen2026-10-24 23:24 UTC182
            target: None,
cbb715e11963atagen2026-10-24 23:24 UTC183
            after: Vec::new(),
cbb715e11963atagen2026-10-24 23:24 UTC184
            start: None,
cbb715e11963atagen2026-10-24 23:24 UTC185
            end: None,
cbb715e11963atagen2026-10-24 23:24 UTC186
        },
cbb715e11963atagen2026-10-24 23:24 UTC187
    );
cbb715e11963atagen2026-10-24 23:24 UTC188
    data.windows.insert(
cbb715e11963atagen2026-10-24 23:24 UTC189
        unread_id.clone(),
cbb715e11963atagen2026-10-24 23:24 UTC190
        Window {
cbb715e11963atagen2026-10-24 23:24 UTC191
            before: vec![shared_id.clone()],
cbb715e11963atagen2026-10-24 23:24 UTC192
            target: None,
cbb715e11963atagen2026-10-24 23:24 UTC193
            after: Vec::new(),
cbb715e11963atagen2026-10-24 23:24 UTC194
            start: None,
cbb715e11963atagen2026-10-24 23:24 UTC195
            end: None,
cbb715e11963atagen2026-10-24 23:24 UTC196
        },
cbb715e11963atagen2026-10-24 23:24 UTC197
    );
cbb715e11963atagen2026-10-24 23:24 UTC198
7cdaa86b9489atagen2026-10-25 08:43 UTC199
    assert!(data.evict_one().is_some());
cbb715e11963atagen2026-10-24 23:24 UTC200
    assert!(!data.windows.contains_key(&read_id));
cbb715e11963atagen2026-10-24 23:24 UTC201
    assert!(data.windows.contains_key(&unread_id));
cbb715e11963atagen2026-10-24 23:24 UTC202
    assert_eq!(
cbb715e11963atagen2026-10-24 23:24 UTC203
        data.entries[&read_id].capture,
cbb715e11963atagen2026-10-24 23:24 UTC204
        MentionCaptureState::Unavailable
cbb715e11963atagen2026-10-24 23:24 UTC205
    );
cbb715e11963atagen2026-10-24 23:24 UTC206
    assert!(!data.entries[&unread_id].read);
cbb715e11963atagen2026-10-24 23:24 UTC207
    assert!(data.events.contains_key(&shared_id));
cbb715e11963atagen2026-10-24 23:24 UTC208
7cdaa86b9489atagen2026-10-25 08:43 UTC209
    assert!(data.evict_one().is_some());
cbb715e11963atagen2026-10-24 23:24 UTC210
    assert!(data.windows.is_empty());
cbb715e11963atagen2026-10-24 23:24 UTC211
    assert!(data.entries.contains_key(&read_id));
cbb715e11963atagen2026-10-24 23:24 UTC212
    assert!(data.entries.contains_key(&unread_id));
cbb715e11963atagen2026-10-24 23:24 UTC213
    assert!(!data.events.contains_key(&shared_id));
cbb715e11963atagen2026-10-24 23:24 UTC214
}
cbb715e11963atagen2026-10-24 23:24 UTC215
cbb715e11963atagen2026-10-24 23:24 UTC216
#[cfg_attr(not(target_family = "wasm"), tokio::test)]
cbb715e11963atagen2026-10-24 23:24 UTC217
#[cfg_attr(target_family = "wasm", wasm_bindgen_test::wasm_bindgen_test)]
cbb715e11963atagen2026-10-24 23:24 UTC218
async fn context_uses_the_redacted_saved_event_instead_of_the_entry_preview() {
cbb715e11963atagen2026-10-24 23:24 UTC219
    let mention = key("room", "redacted-target");
cbb715e11963atagen2026-10-24 23:24 UTC220
    let mention_id = id(&mention);
cbb715e11963atagen2026-10-24 23:24 UTC221
    let original = message_event(
cbb715e11963atagen2026-10-24 23:24 UTC222
        "$redacted-target:mentions.example",
cbb715e11963atagen2026-10-24 23:24 UTC223
        42,
cbb715e11963atagen2026-10-24 23:24 UTC224
        "private original body",
cbb715e11963atagen2026-10-24 23:24 UTC225
    );
cbb715e11963atagen2026-10-24 23:24 UTC226
    let mut redacted = original.clone();
cbb715e11963atagen2026-10-24 23:24 UTC227
    if let TimelineItem::Event(event) = &mut redacted {
cbb715e11963atagen2026-10-24 23:24 UTC228
        event.content = TimelineContent::Redacted;
cbb715e11963atagen2026-10-24 23:24 UTC229
        event.reply_to = None;
cbb715e11963atagen2026-10-24 23:24 UTC230
        event.reactions.clear();
cbb715e11963atagen2026-10-24 23:24 UTC231
    }
cbb715e11963atagen2026-10-24 23:24 UTC232
    let mut data = Data::default();
cbb715e11963atagen2026-10-24 23:24 UTC233
    data.entries.insert(
cbb715e11963atagen2026-10-24 23:24 UTC234
        mention_id.clone(),
cbb715e11963atagen2026-10-24 23:24 UTC235
        MentionEntry {
cbb715e11963atagen2026-10-24 23:24 UTC236
            event: original,
cbb715e11963atagen2026-10-24 23:24 UTC237
            ..entry(mention.clone(), 42, 1, false)
cbb715e11963atagen2026-10-24 23:24 UTC238
        },
cbb715e11963atagen2026-10-24 23:24 UTC239
    );
cbb715e11963atagen2026-10-24 23:24 UTC240
    data.events.insert(
cbb715e11963atagen2026-10-24 23:24 UTC241
        mention_id.clone(),
cbb715e11963atagen2026-10-24 23:24 UTC242
        SavedEvent {
cbb715e11963atagen2026-10-24 23:24 UTC243
            item: redacted,
cbb715e11963atagen2026-10-24 23:24 UTC244
            media: Vec::new(),
cbb715e11963atagen2026-10-24 23:24 UTC245
            raw: None,
cbb715e11963atagen2026-10-24 23:24 UTC246
        },
cbb715e11963atagen2026-10-24 23:24 UTC247
    );
cbb715e11963atagen2026-10-24 23:24 UTC248
    data.windows.insert(
cbb715e11963atagen2026-10-24 23:24 UTC249
        mention_id,
cbb715e11963atagen2026-10-24 23:24 UTC250
        Window {
cbb715e11963atagen2026-10-24 23:24 UTC251
            before: Vec::new(),
cbb715e11963atagen2026-10-24 23:24 UTC252
            target: Some(id(&mention)),
cbb715e11963atagen2026-10-24 23:24 UTC253
            after: Vec::new(),
cbb715e11963atagen2026-10-24 23:24 UTC254
            start: Some("start".to_owned()),
cbb715e11963atagen2026-10-24 23:24 UTC255
            end: Some("end".to_owned()),
cbb715e11963atagen2026-10-24 23:24 UTC256
        },
cbb715e11963atagen2026-10-24 23:24 UTC257
    );
cbb715e11963atagen2026-10-24 23:24 UTC258
cbb715e11963atagen2026-10-24 23:24 UTC259
    let (context, media) = data.context(&mention).unwrap();
cbb715e11963atagen2026-10-24 23:24 UTC260
    let saved = context.context.unwrap().event.unwrap();
cbb715e11963atagen2026-10-24 23:24 UTC261
    let TimelineItem::Event(saved) = saved else {
cbb715e11963atagen2026-10-24 23:24 UTC262
        panic!("the saved target should remain a timeline event");
cbb715e11963atagen2026-10-24 23:24 UTC263
    };
cbb715e11963atagen2026-10-24 23:24 UTC264
    assert_eq!(saved.content, TimelineContent::Redacted);
cbb715e11963atagen2026-10-24 23:24 UTC265
    assert_eq!(
cbb715e11963atagen2026-10-24 23:24 UTC266
        saved.event_id.as_deref(),
cbb715e11963atagen2026-10-24 23:24 UTC267
        Some(event_id!("$redacted-target:mentions.example"))
cbb715e11963atagen2026-10-24 23:24 UTC268
    );
cbb715e11963atagen2026-10-24 23:24 UTC269
    assert!(media.is_empty());
cbb715e11963atagen2026-10-24 23:24 UTC270
}
cbb715e11963atagen2026-10-24 23:24 UTC271
cbb715e11963atagen2026-10-24 23:24 UTC272
#[cfg(any(feature = "sqlite", feature = "indexeddb"))]
cbb715e11963atagen2026-10-24 23:24 UTC273
mod persistence {
cbb715e11963atagen2026-10-24 23:24 UTC274
    use std::sync::atomic::{AtomicUsize, Ordering};
cbb715e11963atagen2026-10-24 23:24 UTC275
cbb715e11963atagen2026-10-24 23:24 UTC276
    use ruma::{events::room::MediaSource, user_id};
cbb715e11963atagen2026-10-24 23:24 UTC277
    use serde_json::json;
cbb715e11963atagen2026-10-24 23:24 UTC278
cbb715e11963atagen2026-10-24 23:24 UTC279
    use super::*;
cbb715e11963atagen2026-10-24 23:24 UTC280
    use crate::{
cbb715e11963atagen2026-10-24 23:24 UTC281
        MediaId, MediaKind, StoreConfig,
cbb715e11963atagen2026-10-24 23:24 UTC282
        mentions::store::{Account, MentionsStore, Write},
cbb715e11963atagen2026-10-24 23:24 UTC283
        normalise::{MediaRegistration, MediaSourceInfo},
cbb715e11963atagen2026-10-24 23:24 UTC284
    };
cbb715e11963atagen2026-10-24 23:24 UTC285
cbb715e11963atagen2026-10-24 23:24 UTC286
    static STORES: AtomicUsize = AtomicUsize::new(0);
cbb715e11963atagen2026-10-24 23:24 UTC287
    const MAX_BYTES: usize = 64 * 1024 * 1024;
cbb715e11963atagen2026-10-24 23:24 UTC288
cbb715e11963atagen2026-10-24 23:24 UTC289
    fn account() -> Account {
cbb715e11963atagen2026-10-24 23:24 UTC290
        Account {
cbb715e11963atagen2026-10-24 23:24 UTC291
            homeserver: "https://mentions.example/".to_owned(),
cbb715e11963atagen2026-10-24 23:24 UTC292
            user_id: user_id!("@alice:mentions.example").to_owned(),
cbb715e11963atagen2026-10-24 23:24 UTC293
        }
cbb715e11963atagen2026-10-24 23:24 UTC294
    }
cbb715e11963atagen2026-10-24 23:24 UTC295
cbb715e11963atagen2026-10-24 23:24 UTC296
    fn config() -> StoreConfig {
cbb715e11963atagen2026-10-24 23:24 UTC297
        let number = STORES.fetch_add(1, Ordering::Relaxed);
cbb715e11963atagen2026-10-24 23:24 UTC298
        #[cfg(feature = "sqlite")]
cbb715e11963atagen2026-10-24 23:24 UTC299
        {
cbb715e11963atagen2026-10-24 23:24 UTC300
            StoreConfig::sqlite(
cbb715e11963atagen2026-10-24 23:24 UTC301
                std::env::temp_dir().join(format!(
cbb715e11963atagen2026-10-24 23:24 UTC302
                    "macltok-mentions-state-{}-{number}",
cbb715e11963atagen2026-10-24 23:24 UTC303
                    std::process::id()
cbb715e11963atagen2026-10-24 23:24 UTC304
                )),
cbb715e11963atagen2026-10-24 23:24 UTC305
                "state test passphrase",
cbb715e11963atagen2026-10-24 23:24 UTC306
            )
cbb715e11963atagen2026-10-24 23:24 UTC307
        }
cbb715e11963atagen2026-10-24 23:24 UTC308
        #[cfg(feature = "indexeddb")]
cbb715e11963atagen2026-10-24 23:24 UTC309
        {
cbb715e11963atagen2026-10-24 23:24 UTC310
            StoreConfig::indexeddb(
cbb715e11963atagen2026-10-24 23:24 UTC311
                format!("macltok-mentions-state-{}-{number}", js_sys::Date::now()),
cbb715e11963atagen2026-10-24 23:24 UTC312
                "state test passphrase",
cbb715e11963atagen2026-10-24 23:24 UTC313
            )
cbb715e11963atagen2026-10-24 23:24 UTC314
        }
cbb715e11963atagen2026-10-24 23:24 UTC315
    }
cbb715e11963atagen2026-10-24 23:24 UTC316
cbb715e11963atagen2026-10-24 23:24 UTC317
    fn media_registration() -> MediaRegistration {
cbb715e11963atagen2026-10-24 23:24 UTC318
        MediaRegistration {
cbb715e11963atagen2026-10-24 23:24 UTC319
            id: MediaId::new("test-scope#image".to_owned()),
cbb715e11963atagen2026-10-24 23:24 UTC320
            kind: MediaKind::Image,
cbb715e11963atagen2026-10-24 23:24 UTC321
            full: MediaSourceInfo {
cbb715e11963atagen2026-10-24 23:24 UTC322
                source: MediaSource::Plain(
cbb715e11963atagen2026-10-24 23:24 UTC323
                    ruma::mxc_uri!("mxc://mentions.example/image").to_owned(),
cbb715e11963atagen2026-10-24 23:24 UTC324
                ),
cbb715e11963atagen2026-10-24 23:24 UTC325
                mimetype: Some("image/png".to_owned()),
cbb715e11963atagen2026-10-24 23:24 UTC326
                declared_size: Some(1234),
cbb715e11963atagen2026-10-24 23:24 UTC327
            },
cbb715e11963atagen2026-10-24 23:24 UTC328
            thumbnail: None,
cbb715e11963atagen2026-10-24 23:24 UTC329
        }
cbb715e11963atagen2026-10-24 23:24 UTC330
    }
cbb715e11963atagen2026-10-24 23:24 UTC331
cbb715e11963atagen2026-10-24 23:24 UTC332
    fn saved_data() -> Data {
cbb715e11963atagen2026-10-24 23:24 UTC333
        let mention = key("persisted-room", "persisted-mention");
cbb715e11963atagen2026-10-24 23:24 UTC334
        let mention_id = id(&mention);
cbb715e11963atagen2026-10-24 23:24 UTC335
        let context_item =
cbb715e11963atagen2026-10-24 23:24 UTC336
            message_event("$persisted-mention:mentions.example", 42, "saved message");
cbb715e11963atagen2026-10-24 23:24 UTC337
        let mut data = Data {
cbb715e11963atagen2026-10-24 23:24 UTC338
            revision: 1,
cbb715e11963atagen2026-10-24 23:24 UTC339
            ..Data::default()
cbb715e11963atagen2026-10-24 23:24 UTC340
        };
cbb715e11963atagen2026-10-24 23:24 UTC341
        data.entries.insert(
cbb715e11963atagen2026-10-24 23:24 UTC342
            mention_id.clone(),
cbb715e11963atagen2026-10-24 23:24 UTC343
            MentionEntry {
cbb715e11963atagen2026-10-24 23:24 UTC344
                event: context_item.clone(),
cbb715e11963atagen2026-10-24 23:24 UTC345
                ..entry(mention.clone(), 42, 1, true)
cbb715e11963atagen2026-10-24 23:24 UTC346
            },
cbb715e11963atagen2026-10-24 23:24 UTC347
        );
cbb715e11963atagen2026-10-24 23:24 UTC348
        data.events.insert(
cbb715e11963atagen2026-10-24 23:24 UTC349
            mention_id.clone(),
cbb715e11963atagen2026-10-24 23:24 UTC350
            SavedEvent {
cbb715e11963atagen2026-10-24 23:24 UTC351
                item: context_item,
cbb715e11963atagen2026-10-24 23:24 UTC352
                media: vec![media_registration()],
cbb715e11963atagen2026-10-24 23:24 UTC353
                raw: Some(
cbb715e11963atagen2026-10-24 23:24 UTC354
                    json!({"event_id": "$persisted-mention:mentions.example", "body": "saved raw"}),
cbb715e11963atagen2026-10-24 23:24 UTC355
                ),
cbb715e11963atagen2026-10-24 23:24 UTC356
            },
cbb715e11963atagen2026-10-24 23:24 UTC357
        );
cbb715e11963atagen2026-10-24 23:24 UTC358
        data.windows.insert(
cbb715e11963atagen2026-10-24 23:24 UTC359
            mention_id.clone(),
cbb715e11963atagen2026-10-24 23:24 UTC360
            Window {
cbb715e11963atagen2026-10-24 23:24 UTC361
                before: Vec::new(),
cbb715e11963atagen2026-10-24 23:24 UTC362
                target: Some(mention_id),
cbb715e11963atagen2026-10-24 23:24 UTC363
                after: Vec::new(),
cbb715e11963atagen2026-10-24 23:24 UTC364
                start: Some("start-token".to_owned()),
cbb715e11963atagen2026-10-24 23:24 UTC365
                end: Some("end-token".to_owned()),
cbb715e11963atagen2026-10-24 23:24 UTC366
            },
cbb715e11963atagen2026-10-24 23:24 UTC367
        );
cbb715e11963atagen2026-10-24 23:24 UTC368
        data.history_cursor = Some("history-position".to_owned());
cbb715e11963atagen2026-10-24 23:24 UTC369
        data
cbb715e11963atagen2026-10-24 23:24 UTC370
    }
cbb715e11963atagen2026-10-24 23:24 UTC371
cbb715e11963atagen2026-10-24 23:24 UTC372
    async fn open(config: &StoreConfig) -> MentionsStore {
cbb715e11963atagen2026-10-24 23:24 UTC373
        MentionsStore::open(config, account(), MAX_BYTES)
cbb715e11963atagen2026-10-24 23:24 UTC374
            .await
cbb715e11963atagen2026-10-24 23:24 UTC375
            .unwrap()
cbb715e11963atagen2026-10-24 23:24 UTC376
    }
cbb715e11963atagen2026-10-24 23:24 UTC377
cbb715e11963atagen2026-10-24 23:24 UTC378
    #[cfg_attr(not(target_family = "wasm"), tokio::test)]
cbb715e11963atagen2026-10-24 23:24 UTC379
    #[cfg_attr(target_family = "wasm", wasm_bindgen_test::wasm_bindgen_test)]
7cdaa86b9489atagen2026-10-25 08:43 UTC380
    async fn encrypted_store_roundtrips_inbox_context_and_read_state() {
cbb715e11963atagen2026-10-24 23:24 UTC381
        let config = config();
cbb715e11963atagen2026-10-24 23:24 UTC382
        let data = saved_data();
cbb715e11963atagen2026-10-24 23:24 UTC383
        let mut store = open(&config).await;
cbb715e11963atagen2026-10-24 23:24 UTC384
        assert_eq!(
cbb715e11963atagen2026-10-24 23:24 UTC385
            store
cbb715e11963atagen2026-10-24 23:24 UTC386
                .commit(
cbb715e11963atagen2026-10-24 23:24 UTC387
                    0,
cbb715e11963atagen2026-10-24 23:24 UTC388
                    vec![Write::Set(
cbb715e11963atagen2026-10-24 23:24 UTC389
                        "inbox".to_owned(),
cbb715e11963atagen2026-10-24 23:24 UTC390
                        serde_json::to_value(&data).unwrap(),
cbb715e11963atagen2026-10-24 23:24 UTC391
                    )],
cbb715e11963atagen2026-10-24 23:24 UTC392
                )
cbb715e11963atagen2026-10-24 23:24 UTC393
                .await
cbb715e11963atagen2026-10-24 23:24 UTC394
                .unwrap(),
cbb715e11963atagen2026-10-24 23:24 UTC395
            1
cbb715e11963atagen2026-10-24 23:24 UTC396
        );
cbb715e11963atagen2026-10-24 23:24 UTC397
        drop(store);
cbb715e11963atagen2026-10-24 23:24 UTC398
cbb715e11963atagen2026-10-24 23:24 UTC399
        let mut restored = open(&config).await;
cbb715e11963atagen2026-10-24 23:24 UTC400
        let mut restored_data: Data = restored.read("inbox").unwrap().unwrap();
cbb715e11963atagen2026-10-24 23:24 UTC401
        assert_eq!(restored.revision(), restored_data.revision);
cbb715e11963atagen2026-10-24 23:24 UTC402
        assert!(restored_data.entries.values().next().unwrap().read);
cbb715e11963atagen2026-10-24 23:24 UTC403
        let mention = restored_data.entries.values().next().unwrap().key.clone();
cbb715e11963atagen2026-10-24 23:24 UTC404
        let (context, media) = restored_data.context(&mention).unwrap();
cbb715e11963atagen2026-10-24 23:24 UTC405
        assert_eq!(
cbb715e11963atagen2026-10-24 23:24 UTC406
            context.context.unwrap().start_token.as_deref(),
cbb715e11963atagen2026-10-24 23:24 UTC407
            Some("start-token")
cbb715e11963atagen2026-10-24 23:24 UTC408
        );
cbb715e11963atagen2026-10-24 23:24 UTC409
        assert_eq!(media.len(), 1);
cbb715e11963atagen2026-10-24 23:24 UTC410
        assert_eq!(media[0].id.as_str(), "test-scope#image");
cbb715e11963atagen2026-10-24 23:24 UTC411
        assert_eq!(media[0].full.mimetype.as_deref(), Some("image/png"));
cbb715e11963atagen2026-10-24 23:24 UTC412
        let event = restored_data.events.values().next().unwrap();
cbb715e11963atagen2026-10-24 23:24 UTC413
        assert_eq!(event.raw.as_ref().unwrap()["body"], "saved raw");
cbb715e11963atagen2026-10-24 23:24 UTC414
cbb715e11963atagen2026-10-24 23:24 UTC415
        restored_data.revision = 2;
cbb715e11963atagen2026-10-24 23:24 UTC416
        let updated_entry = restored_data.entries.values_mut().next().unwrap();
cbb715e11963atagen2026-10-24 23:24 UTC417
        updated_entry.read = false;
cbb715e11963atagen2026-10-24 23:24 UTC418
        updated_entry.revision = 2;
cbb715e11963atagen2026-10-24 23:24 UTC419
        restored
cbb715e11963atagen2026-10-24 23:24 UTC420
            .commit(
cbb715e11963atagen2026-10-24 23:24 UTC421
                1,
cbb715e11963atagen2026-10-24 23:24 UTC422
                vec![Write::Set(
cbb715e11963atagen2026-10-24 23:24 UTC423
                    "inbox".to_owned(),
cbb715e11963atagen2026-10-24 23:24 UTC424
                    serde_json::to_value(&restored_data).unwrap(),
cbb715e11963atagen2026-10-24 23:24 UTC425
                )],
cbb715e11963atagen2026-10-24 23:24 UTC426
            )
cbb715e11963atagen2026-10-24 23:24 UTC427
            .await
cbb715e11963atagen2026-10-24 23:24 UTC428
            .unwrap();
cbb715e11963atagen2026-10-24 23:24 UTC429
        drop(restored);
cbb715e11963atagen2026-10-24 23:24 UTC430
cbb715e11963atagen2026-10-24 23:24 UTC431
        let restored = open(&config).await;
cbb715e11963atagen2026-10-24 23:24 UTC432
        let restored_data: Data = restored.read("inbox").unwrap().unwrap();
cbb715e11963atagen2026-10-24 23:24 UTC433
        assert_eq!(restored.revision(), 2);
cbb715e11963atagen2026-10-24 23:24 UTC434
        assert_eq!(restored_data.revision, 2);
cbb715e11963atagen2026-10-24 23:24 UTC435
        assert!(!restored_data.entries.values().next().unwrap().read);
cbb715e11963atagen2026-10-24 23:24 UTC436
        drop(restored);
cbb715e11963atagen2026-10-24 23:24 UTC437
        config.delete().await.unwrap();
cbb715e11963atagen2026-10-24 23:24 UTC438
    }
cbb715e11963atagen2026-10-24 23:24 UTC439
}