atagen

macltok

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