atagen

macltok

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

Blame: src/mentions/state_tests.rs

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