channel_store_tests.rs

  1use crate::channel_chat::ChannelChatEvent;
  2
  3use super::*;
  4use client::{test::FakeServer, Client, UserStore};
  5use gpui::{AppContext, Context, Model, TestAppContext};
  6use rpc::proto::{self};
  7use settings::SettingsStore;
  8use util::http::FakeHttpClient;
  9
 10#[gpui::test]
 11fn test_update_channels(cx: &mut AppContext) {
 12    let channel_store = init_test(cx);
 13
 14    update_channels(
 15        &channel_store,
 16        proto::UpdateChannels {
 17            channels: vec![
 18                proto::Channel {
 19                    id: 1,
 20                    name: "b".to_string(),
 21                    visibility: proto::ChannelVisibility::Members as i32,
 22                    role: proto::ChannelRole::Admin.into(),
 23                    parent_path: Vec::new(),
 24                },
 25                proto::Channel {
 26                    id: 2,
 27                    name: "a".to_string(),
 28                    visibility: proto::ChannelVisibility::Members as i32,
 29                    role: proto::ChannelRole::Member.into(),
 30                    parent_path: Vec::new(),
 31                },
 32            ],
 33            ..Default::default()
 34        },
 35        cx,
 36    );
 37    assert_channels(
 38        &channel_store,
 39        &[
 40            //
 41            (0, "a".to_string(), proto::ChannelRole::Member),
 42            (0, "b".to_string(), proto::ChannelRole::Admin),
 43        ],
 44        cx,
 45    );
 46
 47    update_channels(
 48        &channel_store,
 49        proto::UpdateChannels {
 50            channels: vec![
 51                proto::Channel {
 52                    id: 3,
 53                    name: "x".to_string(),
 54                    visibility: proto::ChannelVisibility::Members as i32,
 55                    role: proto::ChannelRole::Admin.into(),
 56                    parent_path: vec![1],
 57                },
 58                proto::Channel {
 59                    id: 4,
 60                    name: "y".to_string(),
 61                    visibility: proto::ChannelVisibility::Members as i32,
 62                    role: proto::ChannelRole::Member.into(),
 63                    parent_path: vec![2],
 64                },
 65            ],
 66            ..Default::default()
 67        },
 68        cx,
 69    );
 70    assert_channels(
 71        &channel_store,
 72        &[
 73            (0, "a".to_string(), proto::ChannelRole::Member),
 74            (1, "y".to_string(), proto::ChannelRole::Member),
 75            (0, "b".to_string(), proto::ChannelRole::Admin),
 76            (1, "x".to_string(), proto::ChannelRole::Admin),
 77        ],
 78        cx,
 79    );
 80}
 81
 82#[gpui::test]
 83fn test_dangling_channel_paths(cx: &mut AppContext) {
 84    let channel_store = init_test(cx);
 85
 86    update_channels(
 87        &channel_store,
 88        proto::UpdateChannels {
 89            channels: vec![
 90                proto::Channel {
 91                    id: 0,
 92                    name: "a".to_string(),
 93                    visibility: proto::ChannelVisibility::Members as i32,
 94                    role: proto::ChannelRole::Admin.into(),
 95                    parent_path: vec![],
 96                },
 97                proto::Channel {
 98                    id: 1,
 99                    name: "b".to_string(),
100                    visibility: proto::ChannelVisibility::Members as i32,
101                    role: proto::ChannelRole::Admin.into(),
102                    parent_path: vec![0],
103                },
104                proto::Channel {
105                    id: 2,
106                    name: "c".to_string(),
107                    visibility: proto::ChannelVisibility::Members as i32,
108                    role: proto::ChannelRole::Admin.into(),
109                    parent_path: vec![0, 1],
110                },
111            ],
112            ..Default::default()
113        },
114        cx,
115    );
116    // Sanity check
117    assert_channels(
118        &channel_store,
119        &[
120            //
121            (0, "a".to_string(), proto::ChannelRole::Admin),
122            (1, "b".to_string(), proto::ChannelRole::Admin),
123            (2, "c".to_string(), proto::ChannelRole::Admin),
124        ],
125        cx,
126    );
127
128    update_channels(
129        &channel_store,
130        proto::UpdateChannels {
131            delete_channels: vec![1, 2],
132            ..Default::default()
133        },
134        cx,
135    );
136
137    // Make sure that the 1/2/3 path is gone
138    assert_channels(
139        &channel_store,
140        &[(0, "a".to_string(), proto::ChannelRole::Admin)],
141        cx,
142    );
143}
144
145#[gpui::test]
146async fn test_channel_messages(cx: &mut TestAppContext) {
147    let user_id = 5;
148    let channel_id = 5;
149    let channel_store = cx.update(init_test);
150    let client = channel_store.update(cx, |s, _| s.client());
151    let server = FakeServer::for_client(user_id, &client, cx).await;
152
153    // Get the available channels.
154    server.send(proto::UpdateChannels {
155        channels: vec![proto::Channel {
156            id: channel_id,
157            name: "the-channel".to_string(),
158            visibility: proto::ChannelVisibility::Members as i32,
159            role: proto::ChannelRole::Member.into(),
160            parent_path: vec![],
161        }],
162        ..Default::default()
163    });
164    cx.executor().run_until_parked();
165    cx.update(|cx| {
166        assert_channels(
167            &channel_store,
168            &[(0, "the-channel".to_string(), proto::ChannelRole::Member)],
169            cx,
170        );
171    });
172
173    let get_users = server.receive::<proto::GetUsers>().await.unwrap();
174    assert_eq!(get_users.payload.user_ids, vec![5]);
175    server.respond(
176        get_users.receipt(),
177        proto::UsersResponse {
178            users: vec![proto::User {
179                id: 5,
180                github_login: "nathansobo".into(),
181                avatar_url: "http://avatar.com/nathansobo".into(),
182            }],
183        },
184    );
185
186    // Join a channel and populate its existing messages.
187    let channel = channel_store.update(cx, |store, cx| {
188        let channel_id = store.ordered_channels().next().unwrap().1.id;
189        store.open_channel_chat(channel_id, cx)
190    });
191    let join_channel = server.receive::<proto::JoinChannelChat>().await.unwrap();
192    server.respond(
193        join_channel.receipt(),
194        proto::JoinChannelChatResponse {
195            messages: vec![
196                proto::ChannelMessage {
197                    id: 10,
198                    body: "a".into(),
199                    timestamp: 1000,
200                    sender_id: 5,
201                    mentions: vec![],
202                    nonce: Some(1.into()),
203                },
204                proto::ChannelMessage {
205                    id: 11,
206                    body: "b".into(),
207                    timestamp: 1001,
208                    sender_id: 6,
209                    mentions: vec![],
210                    nonce: Some(2.into()),
211                },
212            ],
213            done: false,
214        },
215    );
216
217    cx.executor().start_waiting();
218
219    // Client requests all users for the received messages
220    let mut get_users = server.receive::<proto::GetUsers>().await.unwrap();
221    get_users.payload.user_ids.sort();
222    assert_eq!(get_users.payload.user_ids, vec![6]);
223    server.respond(
224        get_users.receipt(),
225        proto::UsersResponse {
226            users: vec![proto::User {
227                id: 6,
228                github_login: "maxbrunsfeld".into(),
229                avatar_url: "http://avatar.com/maxbrunsfeld".into(),
230            }],
231        },
232    );
233
234    let channel = channel.await.unwrap();
235    channel.update(cx, |channel, _| {
236        assert_eq!(
237            channel
238                .messages_in_range(0..2)
239                .map(|message| (message.sender.github_login.clone(), message.body.clone()))
240                .collect::<Vec<_>>(),
241            &[
242                ("nathansobo".into(), "a".into()),
243                ("maxbrunsfeld".into(), "b".into())
244            ]
245        );
246    });
247
248    // Receive a new message.
249    server.send(proto::ChannelMessageSent {
250        channel_id,
251        message: Some(proto::ChannelMessage {
252            id: 12,
253            body: "c".into(),
254            timestamp: 1002,
255            sender_id: 7,
256            mentions: vec![],
257            nonce: Some(3.into()),
258        }),
259    });
260
261    // Client requests user for message since they haven't seen them yet
262    let get_users = server.receive::<proto::GetUsers>().await.unwrap();
263    assert_eq!(get_users.payload.user_ids, vec![7]);
264    server.respond(
265        get_users.receipt(),
266        proto::UsersResponse {
267            users: vec![proto::User {
268                id: 7,
269                github_login: "as-cii".into(),
270                avatar_url: "http://avatar.com/as-cii".into(),
271            }],
272        },
273    );
274
275    assert_eq!(
276        channel.next_event(cx),
277        ChannelChatEvent::MessagesUpdated {
278            old_range: 2..2,
279            new_count: 1,
280        }
281    );
282    channel.update(cx, |channel, _| {
283        assert_eq!(
284            channel
285                .messages_in_range(2..3)
286                .map(|message| (message.sender.github_login.clone(), message.body.clone()))
287                .collect::<Vec<_>>(),
288            &[("as-cii".into(), "c".into())]
289        )
290    });
291
292    // Scroll up to view older messages.
293    channel.update(cx, |channel, cx| {
294        channel.load_more_messages(cx).unwrap().detach();
295    });
296    let get_messages = server.receive::<proto::GetChannelMessages>().await.unwrap();
297    assert_eq!(get_messages.payload.channel_id, 5);
298    assert_eq!(get_messages.payload.before_message_id, 10);
299    server.respond(
300        get_messages.receipt(),
301        proto::GetChannelMessagesResponse {
302            done: true,
303            messages: vec![
304                proto::ChannelMessage {
305                    id: 8,
306                    body: "y".into(),
307                    timestamp: 998,
308                    sender_id: 5,
309                    nonce: Some(4.into()),
310                    mentions: vec![],
311                },
312                proto::ChannelMessage {
313                    id: 9,
314                    body: "z".into(),
315                    timestamp: 999,
316                    sender_id: 6,
317                    nonce: Some(5.into()),
318                    mentions: vec![],
319                },
320            ],
321        },
322    );
323
324    assert_eq!(
325        channel.next_event(cx),
326        ChannelChatEvent::MessagesUpdated {
327            old_range: 0..0,
328            new_count: 2,
329        }
330    );
331    channel.update(cx, |channel, _| {
332        assert_eq!(
333            channel
334                .messages_in_range(0..2)
335                .map(|message| (message.sender.github_login.clone(), message.body.clone()))
336                .collect::<Vec<_>>(),
337            &[
338                ("nathansobo".into(), "y".into()),
339                ("maxbrunsfeld".into(), "z".into())
340            ]
341        );
342    });
343}
344
345fn init_test(cx: &mut AppContext) -> Model<ChannelStore> {
346    let settings_store = SettingsStore::test(cx);
347    cx.set_global(settings_store);
348
349    let http = FakeHttpClient::with_404_response();
350    let client = Client::new(http.clone(), cx);
351    let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
352
353    client::init(&client, cx);
354    crate::init(&client, user_store, cx);
355
356    ChannelStore::global(cx)
357}
358
359fn update_channels(
360    channel_store: &Model<ChannelStore>,
361    message: proto::UpdateChannels,
362    cx: &mut AppContext,
363) {
364    let task = channel_store.update(cx, |store, cx| store.update_channels(message, cx));
365    assert!(task.is_none());
366}
367
368#[track_caller]
369fn assert_channels(
370    channel_store: &Model<ChannelStore>,
371    expected_channels: &[(usize, String, proto::ChannelRole)],
372    cx: &mut AppContext,
373) {
374    let actual = channel_store.update(cx, |store, _| {
375        store
376            .ordered_channels()
377            .map(|(depth, channel)| (depth, channel.name.to_string(), channel.role))
378            .collect::<Vec<_>>()
379    });
380    assert_eq!(actual, expected_channels);
381}