1package storage
2
3import (
4	"context"
5	"sort"
6	"testing"
7	"time"
8
9	"encoding/hex"
10
11	"github.com/keybase/client/go/chat/types"
12	"github.com/keybase/client/go/chat/utils"
13	"github.com/keybase/client/go/kbtest"
14	"github.com/keybase/client/go/libkb"
15	"github.com/keybase/client/go/protocol/chat1"
16	"github.com/keybase/client/go/protocol/gregor1"
17	"github.com/keybase/client/go/protocol/keybase1"
18	"github.com/stretchr/testify/require"
19)
20
21func setupInboxTest(t testing.TB, name string) (kbtest.ChatTestContext, *Inbox, gregor1.UID) {
22	ctc := setupCommonTest(t, name)
23
24	u, err := kbtest.CreateAndSignupFakeUser("ib", ctc.TestContext.G)
25	require.NoError(t, err)
26	uid := gregor1.UID(u.User.GetUID().ToBytes())
27	return ctc, NewInbox(ctc.Context()), uid
28}
29
30func makeTlfID() chat1.TLFID {
31	return randBytes(8)
32}
33
34func makeConvo(mtime gregor1.Time, rmsg chat1.MessageID, mmsg chat1.MessageID) types.RemoteConversation {
35	conv := chat1.Conversation{
36		Metadata: chat1.ConversationMetadata{
37			ConversationID: randBytes(8),
38			IdTriple: chat1.ConversationIDTriple{
39				Tlfid:     makeTlfID(),
40				TopicType: chat1.TopicType_CHAT,
41				TopicID:   randBytes(8),
42			},
43			Visibility: keybase1.TLFVisibility_PRIVATE,
44			Status:     chat1.ConversationStatus_UNFILED,
45		},
46		ReaderInfo: &chat1.ConversationReaderInfo{
47			Mtime:     mtime,
48			ReadMsgid: rmsg,
49			MaxMsgid:  mmsg,
50		},
51		// Make it look like there's a visible message in here too
52		MaxMsgSummaries: []chat1.MessageSummary{{MessageType: chat1.MessageType_TEXT, MsgID: 1}},
53	}
54	return utils.RemoteConv(conv)
55}
56
57func makeInboxMsg(id chat1.MessageID, typ chat1.MessageType) chat1.MessageBoxed {
58	return chat1.MessageBoxed{
59		ClientHeader: chat1.MessageClientHeader{
60			MessageType: typ,
61		},
62		ServerHeader: &chat1.MessageServerHeader{
63			MessageID: id,
64			Ctime:     gregor1.ToTime(time.Now()),
65		},
66	}
67}
68
69func convListCompare(t *testing.T, ref []types.RemoteConversation, res []types.RemoteConversation,
70	name string) {
71	require.Equal(t, len(ref), len(res), name+" size mismatch")
72	refMap := make(map[chat1.ConvIDStr]types.RemoteConversation)
73	for _, conv := range ref {
74		refMap[conv.GetConvID().ConvIDStr()] = conv
75	}
76	for _, conv := range res {
77		require.Equal(t, refMap[conv.GetConvID().ConvIDStr()], conv)
78	}
79}
80
81func TestInboxBasic(t *testing.T) {
82
83	tc, inbox, uid := setupInboxTest(t, "basic")
84	defer tc.Cleanup()
85
86	// Create an inbox with a bunch of convos, merge it and read it back out
87	numConvs := 10
88	var convs []types.RemoteConversation
89	for i := numConvs - 1; i >= 0; i-- {
90		convs = append(convs, makeConvo(gregor1.Time(i), 1, 1))
91	}
92
93	// Fetch with no query parameter
94	require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(convs), nil))
95	vers, res, err := inbox.Read(context.TODO(), uid, nil)
96
97	require.NoError(t, err)
98	require.Equal(t, chat1.InboxVers(1), vers, "version mismatch")
99	convListCompare(t, convs, res, "basic")
100}
101
102func TestInboxSummarize(t *testing.T) {
103	tc, inbox, uid := setupInboxTest(t, "summarize")
104	defer tc.Cleanup()
105
106	conv := makeConvo(gregor1.Time(1), 1, 1)
107	maxMsgID := chat1.MessageID(6)
108	conv.Conv.MaxMsgs = []chat1.MessageBoxed{{
109		ClientHeader: chat1.MessageClientHeader{
110			MessageType: chat1.MessageType_TEXT,
111		},
112		ServerHeader: &chat1.MessageServerHeader{
113			MessageID: maxMsgID,
114		},
115	}}
116
117	require.NoError(t, inbox.Merge(context.TODO(), uid, 1, []chat1.Conversation{conv.Conv}, nil))
118	_, res, err := inbox.Read(context.TODO(), uid, nil)
119	require.NoError(t, err)
120	require.Zero(t, len(res[0].Conv.MaxMsgs))
121	require.Equal(t, 1, len(res[0].Conv.MaxMsgSummaries))
122	require.Equal(t, maxMsgID, res[0].Conv.MaxMsgSummaries[0].GetMessageID())
123}
124
125func TestInboxQueries(t *testing.T) {
126	tc, inbox, uid := setupInboxTest(t, "queries")
127	defer tc.Cleanup()
128
129	// Create an inbox with a bunch of convos, merge it and read it back out
130	numConvs := 20
131	var convs []types.RemoteConversation
132	for i := 0; i < numConvs; i++ {
133		conv := makeConvo(gregor1.Time(i), 1, 1)
134		convs = append(convs, conv)
135	}
136
137	// Make two dev convos
138	var devs, publics, unreads, ignored, muted, full []types.RemoteConversation
139	convs[3].Conv.Metadata.IdTriple.TopicType = chat1.TopicType_DEV
140	convs[7].Conv.Metadata.IdTriple.TopicType = chat1.TopicType_DEV
141	devs = append(devs, []types.RemoteConversation{convs[7], convs[3]}...)
142
143	// Make one public convos
144	convs[13].Conv.Metadata.Visibility = keybase1.TLFVisibility_PUBLIC
145	publics = append(publics, convs[13])
146
147	// Make three unread convos
148	makeUnread := func(ri *chat1.ConversationReaderInfo) {
149		ri.ReadMsgid = 0
150	}
151	makeUnread(convs[5].Conv.ReaderInfo)
152	makeUnread(convs[13].Conv.ReaderInfo)
153	makeUnread(convs[19].Conv.ReaderInfo)
154	unreads = append(unreads, []types.RemoteConversation{convs[19], convs[13], convs[5]}...)
155
156	// Make two ignored
157	convs[18].Conv.Metadata.Status = chat1.ConversationStatus_IGNORED
158	convs[4].Conv.Metadata.Status = chat1.ConversationStatus_IGNORED
159	ignored = append(ignored, []types.RemoteConversation{convs[18], convs[4]}...)
160
161	// Make one muted
162	convs[12].Conv.Metadata.Status = chat1.ConversationStatus_MUTED
163	muted = append(muted, []types.RemoteConversation{convs[12]}...)
164
165	// Mark one as finalized and superseded by
166	convs[6].Conv.Metadata.FinalizeInfo = &chat1.ConversationFinalizeInfo{
167		ResetFull: "reset",
168	}
169	convs[6].Conv.Metadata.SupersededBy = append(convs[6].Conv.Metadata.SupersededBy, convs[17].Conv.Metadata)
170	convs[17].Conv.Metadata.Supersedes = append(convs[17].Conv.Metadata.Supersedes, convs[6].Conv.Metadata)
171	for i := len(convs) - 1; i >= 0; i-- {
172		if i == 6 {
173			continue
174		}
175		full = append(full, convs[i])
176	}
177	for _, conv := range full {
178		t.Logf("convID: %s", conv.GetConvID())
179	}
180
181	require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(convs), nil))
182
183	// Merge in queries and try to read them back out
184	var q *chat1.GetInboxQuery
185	mergeReadAndCheck := func(t *testing.T, ref []types.RemoteConversation, name string) {
186		require.NoError(t, inbox.Merge(context.TODO(), uid, 1, []chat1.Conversation{}, q))
187		_, res, err := inbox.Read(context.TODO(), uid, q)
188		require.NoError(t, err)
189		convListCompare(t, ref, res, name)
190	}
191	t.Logf("merging all convs with nil query")
192	q = nil
193	mergeReadAndCheck(t, full, "all")
194
195	t.Logf("merging dev query")
196	devtype := chat1.TopicType_DEV
197	q = &chat1.GetInboxQuery{TopicType: &devtype}
198	mergeReadAndCheck(t, devs, "devs")
199
200	t.Logf("merging public query")
201	publicVis := keybase1.TLFVisibility_PUBLIC
202	q = &chat1.GetInboxQuery{TlfVisibility: &publicVis}
203	mergeReadAndCheck(t, publics, "public")
204
205	t.Logf("merging unread query")
206	q = &chat1.GetInboxQuery{UnreadOnly: true}
207	mergeReadAndCheck(t, unreads, "unread")
208
209	t.Logf("merging ignore query")
210	q = &chat1.GetInboxQuery{Status: []chat1.ConversationStatus{chat1.ConversationStatus_IGNORED}}
211	mergeReadAndCheck(t, ignored, "ignored")
212
213	t.Logf("merging muted query")
214	q = &chat1.GetInboxQuery{Status: []chat1.ConversationStatus{chat1.ConversationStatus_MUTED}}
215	mergeReadAndCheck(t, muted, "muted")
216
217	t.Logf("merging tlf ID query")
218	q = &chat1.GetInboxQuery{TlfID: &full[0].Conv.Metadata.IdTriple.Tlfid}
219	tlfIDs := []types.RemoteConversation{full[0]}
220	mergeReadAndCheck(t, tlfIDs, "tlfids")
221
222	t.Logf("merging after query")
223	after := full[:4]
224	atime := gregor1.Time(15)
225	q = &chat1.GetInboxQuery{After: &atime}
226	mergeReadAndCheck(t, after, "after")
227
228	t.Logf("merging before query")
229	before := full[5:]
230	var beforeConvIDs []chat1.ConversationID
231	for _, bconv := range before {
232		beforeConvIDs = append(beforeConvIDs, bconv.GetConvID())
233	}
234	btime := gregor1.Time(15)
235	q = &chat1.GetInboxQuery{Before: &btime}
236	mergeReadAndCheck(t, before, "before")
237
238	t.Logf("check conv IDs queries work")
239	q = &chat1.GetInboxQuery{Before: &btime, ConvIDs: beforeConvIDs}
240	_, cres, err := inbox.Read(context.TODO(), uid, q)
241	require.NoError(t, err)
242	convListCompare(t, before, cres, "convIDs")
243}
244
245func TestInboxEmptySuperseder(t *testing.T) {
246	tc, inbox, uid := setupInboxTest(t, "queries")
247	defer tc.Cleanup()
248
249	// Create an inbox with a bunch of convos, merge it and read it back out
250	numConvs := 20
251	var convs []types.RemoteConversation
252	for i := 0; i < numConvs; i++ {
253		conv := makeConvo(gregor1.Time(i), 1, 1)
254		conv.Conv.MaxMsgSummaries = nil
255		convs = append(convs, conv)
256	}
257	var full, superseded []types.RemoteConversation
258	convs[6].Conv.Metadata.SupersededBy = append(convs[6].Conv.Metadata.SupersededBy, convs[17].Conv.Metadata)
259	convs[17].Conv.Metadata.Supersedes = append(convs[17].Conv.Metadata.Supersedes, convs[6].Conv.Metadata)
260	for i := len(convs) - 1; i >= 0; i-- {
261		// Don't skip the superseded one, since it's not supposed to be filtered out
262		// by an empty superseder
263		full = append(full, convs[i])
264	}
265	for _, conv := range full {
266		t.Logf("convID: %s", conv.GetConvID())
267	}
268
269	require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(convs), nil))
270
271	// Merge in queries and try to read them back out
272	var q *chat1.GetInboxQuery
273	mergeReadAndCheck := func(t *testing.T, ref []types.RemoteConversation, name string) {
274		require.NoError(t, inbox.Merge(context.TODO(), uid, 1, []chat1.Conversation{}, q))
275		_, res, err := inbox.Read(context.TODO(), uid, q)
276		require.NoError(t, err)
277		convListCompare(t, ref, res, name)
278	}
279	t.Logf("merging all convs with nil query")
280	q = nil
281	mergeReadAndCheck(t, full, "all")
282
283	t.Logf("merging empty superseder query")
284	// Don't skip the superseded one, since it's not supposed to be filtered out
285	// by an empty superseder
286	superseded = append(superseded, full...)
287	q = &chat1.GetInboxQuery{}
288	// OneChatTypePerTLF
289	t.Logf("full has %d, superseded has %d", len(full), len(superseded))
290
291	mergeReadAndCheck(t, superseded, "superseded")
292
293	// Now test OneChatTypePerTLF
294	tc, inbox, uid = setupInboxTest(t, "queries2")
295	defer tc.Cleanup()
296
297	full = []types.RemoteConversation{}
298	superseded = []types.RemoteConversation{}
299	for i := len(convs) - 1; i >= 0; i-- {
300		// skip the superseded one, since it's supposed to be filtered out
301		// if not OneChatTypePerTLF
302		if i == 6 {
303			continue
304		}
305		full = append(full, convs[i])
306	}
307	require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(full), nil))
308	superseded = append(superseded, full...)
309	oneChatTypePerTLF := false
310	q = &chat1.GetInboxQuery{OneChatTypePerTLF: &oneChatTypePerTLF}
311	mergeReadAndCheck(t, superseded, "superseded")
312
313}
314
315func TestInboxNewConversation(t *testing.T) {
316	tc, inbox, uid := setupInboxTest(t, "basic")
317	defer tc.Cleanup()
318
319	// Create an inbox with a bunch of convos, merge it and read it back out
320	numConvs := 10
321	var convs []types.RemoteConversation
322	for i := numConvs - 1; i >= 0; i-- {
323		convs = append(convs, makeConvo(gregor1.Time(i), 1, 1))
324	}
325	convs[5].Conv.Metadata.FinalizeInfo = &chat1.ConversationFinalizeInfo{
326		ResetFull: "reset",
327	}
328
329	t.Logf("basic newconv")
330	require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(convs), nil))
331	newConv := makeConvo(gregor1.Time(11), 1, 1)
332	require.NoError(t, inbox.NewConversation(context.TODO(), uid, 2, newConv.Conv))
333	_, res, err := inbox.Read(context.TODO(), uid, nil)
334	require.NoError(t, err)
335	convs = append([]types.RemoteConversation{newConv}, convs...)
336	convListCompare(t, convs, res, "newconv")
337
338	t.Logf("repeat conv")
339	require.NoError(t, inbox.NewConversation(context.TODO(), uid, 3, newConv.Conv))
340	_, res, err = inbox.Read(context.TODO(), uid, nil)
341	require.NoError(t, err)
342	convListCompare(t, convs, res, "repeatconv")
343
344	t.Logf("supersede newconv")
345	newConv = makeConvo(gregor1.Time(12), 1, 1)
346	newConv.Conv.Metadata.Supersedes = append(newConv.Conv.Metadata.Supersedes, convs[6].Conv.Metadata)
347	require.NoError(t, inbox.NewConversation(context.TODO(), uid, 4, newConv.Conv))
348	_, res, err = inbox.Read(context.TODO(), uid, nil)
349	require.NoError(t, err)
350	convs = append([]types.RemoteConversation{newConv}, convs...)
351	convListCompare(t, append(convs[:7], convs[8:]...), res, "newconv finalized")
352
353	require.Equal(t, numConvs+2, len(convs), "n convs")
354
355	err = inbox.NewConversation(context.TODO(), uid, 10, newConv.Conv)
356	require.IsType(t, VersionMismatchError{}, err)
357}
358
359func TestInboxNewMessage(t *testing.T) {
360
361	tc, inbox, uid := setupInboxTest(t, "basic")
362	defer tc.Cleanup()
363
364	// Create an inbox with a bunch of convos, merge it and read it back out
365	numConvs := 10
366	var convs []types.RemoteConversation
367	for i := numConvs - 1; i >= 0; i-- {
368		convs = append(convs, makeConvo(gregor1.Time(i), 1, 1))
369	}
370
371	uid1 := uid
372	uid2, err := hex.DecodeString("22")
373	require.NoError(t, err)
374	uid3, err := hex.DecodeString("33")
375	require.NoError(t, err)
376
377	convs[5].Conv.Metadata.ActiveList = []gregor1.UID{uid2, uid3, uid1}
378	convs[6].Conv.Metadata.ActiveList = []gregor1.UID{uid2, uid3}
379	conv := convs[5]
380	msg := makeInboxMsg(2, chat1.MessageType_TEXT)
381	msg.ClientHeader.Sender = uid1
382	require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(convs), nil))
383	convID := conv.GetConvID()
384	require.NoError(t, inbox.NewMessage(context.TODO(), uid, 2, conv.GetConvID(), msg, nil))
385	_, res, err := inbox.Read(context.TODO(), uid, &chat1.GetInboxQuery{
386		ConvID: &convID,
387	})
388	require.NoError(t, err)
389	require.Equal(t, conv.GetConvID(), res[0].GetConvID(), "conv not promoted")
390	require.Equal(t, chat1.MessageID(2), res[0].Conv.ReaderInfo.MaxMsgid, "wrong max msgid")
391	require.Equal(t, chat1.MessageID(2), res[0].Conv.ReaderInfo.ReadMsgid, "wrong read msgid")
392	require.Equal(t, msg.Ctime(), res[0].Conv.ReaderInfo.LastSendTime)
393	require.Equal(t, []gregor1.UID{uid1, uid2, uid3}, res[0].Conv.Metadata.ActiveList, "active list")
394	maxMsg, err := res[0].Conv.GetMaxMessage(chat1.MessageType_TEXT)
395	require.NoError(t, err)
396	require.Equal(t, chat1.MessageID(2), maxMsg.GetMessageID(), "max msg not updated")
397
398	// Test incomplete active list
399	conv = convs[6]
400	convID = conv.GetConvID()
401	require.NoError(t, inbox.NewMessage(context.TODO(), uid, 3, conv.GetConvID(), msg, nil))
402	_, res, err = inbox.Read(context.TODO(), uid, &chat1.GetInboxQuery{
403		ConvID: &convID,
404	})
405	require.NoError(t, err)
406	require.Equal(t, []gregor1.UID{uid1, uid2, uid3}, res[0].Conv.Metadata.ActiveList, "active list")
407
408	// Send another one from a diff User
409	msg2 := makeInboxMsg(3, chat1.MessageType_TEXT)
410	msg2.ClientHeader.Sender = uid2
411	convID = conv.GetConvID()
412	require.NoError(t, inbox.NewMessage(context.TODO(), uid, 4, conv.GetConvID(), msg2, nil))
413	_, res, err = inbox.Read(context.TODO(), uid, &chat1.GetInboxQuery{
414		ConvID: &convID,
415	})
416	require.NoError(t, err)
417	require.Equal(t, chat1.MessageID(3), res[0].Conv.ReaderInfo.MaxMsgid, "wrong max msgid")
418	require.Equal(t, chat1.MessageID(2), res[0].Conv.ReaderInfo.ReadMsgid, "wrong read msgid")
419	require.Equal(t, msg.Ctime(), res[0].Conv.ReaderInfo.LastSendTime)
420	require.Equal(t, []gregor1.UID{uid2, uid1, uid3}, res[0].Conv.Metadata.ActiveList, "active list")
421	maxMsg, err = res[0].Conv.GetMaxMessage(chat1.MessageType_TEXT)
422	require.NoError(t, err)
423	require.Equal(t, chat1.MessageID(3), maxMsg.GetMessageID())
424
425	// Test delete mechanics
426	delMsg := makeInboxMsg(4, chat1.MessageType_TEXT)
427	require.NoError(t, inbox.NewMessage(context.TODO(), uid, 5, conv.GetConvID(), delMsg, nil))
428	_, res, err = inbox.Read(context.TODO(), uid, &chat1.GetInboxQuery{
429		ConvID: &convID,
430	})
431	require.NoError(t, err)
432	maxMsg, err = res[0].Conv.GetMaxMessage(chat1.MessageType_TEXT)
433	require.NoError(t, err)
434	require.Equal(t, delMsg.GetMessageID(), maxMsg.GetMessageID())
435	delete := makeInboxMsg(5, chat1.MessageType_DELETE)
436	require.NoError(t, inbox.NewMessage(context.TODO(), uid, 0, conv.GetConvID(), delete, nil))
437	require.NoError(t, inbox.NewMessage(context.TODO(), uid, 6, conv.GetConvID(), delete,
438		[]chat1.MessageSummary{msg2.Summary()}))
439	_, res, err = inbox.Read(context.TODO(), uid, &chat1.GetInboxQuery{
440		ConvID: &convID,
441	})
442	require.NoError(t, err)
443	maxMsg, err = res[0].Conv.GetMaxMessage(chat1.MessageType_TEXT)
444	require.NoError(t, err)
445	require.Equal(t, msg2.GetMessageID(), maxMsg.GetMessageID())
446	delete = makeInboxMsg(6, chat1.MessageType_DELETE)
447	err = inbox.NewMessage(context.TODO(), uid, 7, conv.GetConvID(), delete, nil)
448	require.Error(t, err)
449	require.IsType(t, VersionMismatchError{}, err)
450
451	err = inbox.NewMessage(context.TODO(), uid, 10, conv.GetConvID(), msg2, nil)
452	require.IsType(t, VersionMismatchError{}, err)
453}
454
455func TestInboxReadMessage(t *testing.T) {
456
457	tc, inbox, uid := setupInboxTest(t, "basic")
458	defer tc.Cleanup()
459
460	uid2, err := hex.DecodeString("22")
461	require.NoError(t, err)
462
463	// Create an inbox with a bunch of convos, merge it and read it back out
464	numConvs := 10
465	var convs []types.RemoteConversation
466	for i := numConvs - 1; i >= 0; i-- {
467		convs = append(convs, makeConvo(gregor1.Time(i), 1, 1))
468	}
469
470	require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(convs), nil))
471	_, _, err = inbox.Read(context.TODO(), uid, nil)
472	require.NoError(t, err)
473
474	conv := convs[5]
475	convID := conv.GetConvID()
476	msg := makeInboxMsg(2, chat1.MessageType_TEXT)
477	msg.ClientHeader.Sender = uid2
478	require.NoError(t, inbox.NewMessage(context.TODO(), uid, 2, conv.GetConvID(), msg, nil))
479	_, res, err := inbox.Read(context.TODO(), uid, &chat1.GetInboxQuery{
480		ConvID: &convID,
481	})
482	require.NoError(t, err)
483	require.Equal(t, chat1.MessageID(2), res[0].Conv.ReaderInfo.MaxMsgid, "wrong max msgid")
484	require.Equal(t, chat1.MessageID(1), res[0].Conv.ReaderInfo.ReadMsgid, "wrong read msgid")
485	require.Equal(t, gregor1.Time(0), res[0].Conv.ReaderInfo.LastSendTime)
486	require.NoError(t, inbox.ReadMessage(context.TODO(), uid, 3, conv.GetConvID(), 2))
487	_, res, err = inbox.Read(context.TODO(), uid, &chat1.GetInboxQuery{
488		ConvID: &convID,
489	})
490	require.NoError(t, err)
491	require.Equal(t, chat1.MessageID(2), res[0].Conv.ReaderInfo.MaxMsgid, "wrong max msgid")
492	require.Equal(t, chat1.MessageID(2), res[0].Conv.ReaderInfo.ReadMsgid, "wrong read msgid")
493	require.Equal(t, gregor1.Time(0), res[0].Conv.ReaderInfo.LastSendTime)
494
495	err = inbox.ReadMessage(context.TODO(), uid, 10, conv.GetConvID(), 3)
496	require.IsType(t, VersionMismatchError{}, err)
497}
498
499func TestInboxSetStatus(t *testing.T) {
500
501	tc, inbox, uid := setupInboxTest(t, "basic")
502	defer tc.Cleanup()
503
504	// Create an inbox with a bunch of convos, merge it and read it back out
505	numConvs := 10
506	var convs []types.RemoteConversation
507	for i := numConvs - 1; i >= 0; i-- {
508		convs = append(convs, makeConvo(gregor1.Time(i), 1, 1))
509	}
510
511	conv := convs[5]
512	require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(convs), nil))
513	require.NoError(t, inbox.SetStatus(context.TODO(), uid, 2, conv.GetConvID(),
514		chat1.ConversationStatus_IGNORED))
515
516	q := chat1.GetInboxQuery{
517		Status: []chat1.ConversationStatus{chat1.ConversationStatus_IGNORED},
518	}
519	require.NoError(t, inbox.Merge(context.TODO(), uid, 2, []chat1.Conversation{}, &q))
520	_, res, err := inbox.Read(context.TODO(), uid, &q)
521	require.NoError(t, err)
522	require.Equal(t, 1, len(res), "length")
523	require.Equal(t, conv.GetConvID(), res[0].GetConvID(), "id")
524
525	t.Logf("sending new message to wake up conv")
526	msg := makeInboxMsg(3, chat1.MessageType_TEXT)
527	msg.ClientHeader.Sender = uid
528	require.NoError(t, inbox.NewMessage(context.TODO(), uid, 3, conv.GetConvID(), msg, nil))
529	_, res, err = inbox.Read(context.TODO(), uid, &q)
530	require.NoError(t, err)
531	require.Equal(t, 0, len(res), "ignore not unset")
532
533	err = inbox.SetStatus(context.TODO(), uid, 10, conv.GetConvID(), chat1.ConversationStatus_BLOCKED)
534	require.IsType(t, VersionMismatchError{}, err)
535}
536
537func TestInboxSetStatusMuted(t *testing.T) {
538
539	tc, inbox, uid := setupInboxTest(t, "basic")
540	defer tc.Cleanup()
541
542	// Create an inbox with a bunch of convos, merge it and read it back out
543	numConvs := 10
544	var convs []types.RemoteConversation
545	for i := numConvs - 1; i >= 0; i-- {
546		convs = append(convs, makeConvo(gregor1.Time(i), 1, 1))
547	}
548
549	conv := convs[5]
550	require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(convs), nil))
551	require.NoError(t, inbox.SetStatus(context.TODO(), uid, 2, conv.GetConvID(),
552		chat1.ConversationStatus_MUTED))
553
554	q := chat1.GetInboxQuery{
555		Status: []chat1.ConversationStatus{chat1.ConversationStatus_MUTED},
556	}
557	require.NoError(t, inbox.Merge(context.TODO(), uid, 2, []chat1.Conversation{}, &q))
558	_, res, err := inbox.Read(context.TODO(), uid, &q)
559	require.NoError(t, err)
560	require.Equal(t, 1, len(res), "length")
561	require.Equal(t, conv.GetConvID(), res[0].GetConvID(), "id")
562
563	t.Logf("sending new message to wake up conv")
564	msg := makeInboxMsg(3, chat1.MessageType_TEXT)
565	msg.ClientHeader.Sender = uid
566	require.NoError(t, inbox.NewMessage(context.TODO(), uid, 3, conv.GetConvID(), msg, nil))
567	_, res, err = inbox.Read(context.TODO(), uid, &q)
568	require.NoError(t, err)
569	require.Equal(t, 1, len(res), "muted wrongly unset")
570
571	err = inbox.SetStatus(context.TODO(), uid, 10, conv.GetConvID(), chat1.ConversationStatus_BLOCKED)
572	require.IsType(t, VersionMismatchError{}, err)
573}
574
575func TestInboxTlfFinalize(t *testing.T) {
576
577	tc, inbox, uid := setupInboxTest(t, "basic")
578	defer tc.Cleanup()
579
580	// Create an inbox with a bunch of convos, merge it and read it back out
581	numConvs := 10
582	var convs []types.RemoteConversation
583	for i := numConvs - 1; i >= 0; i-- {
584		convs = append(convs, makeConvo(gregor1.Time(i), 1, 1))
585	}
586
587	conv := convs[5]
588	convID := conv.GetConvID()
589	require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(convs), nil))
590	require.NoError(t, inbox.TlfFinalize(context.TODO(), uid, 2, []chat1.ConversationID{conv.GetConvID()},
591		chat1.ConversationFinalizeInfo{ResetFull: "reset"}))
592	_, res, err := inbox.Read(context.TODO(), uid, &chat1.GetInboxQuery{
593		ConvID: &convID,
594	})
595	require.NoError(t, err)
596	require.Equal(t, 1, len(res), "length")
597	require.Equal(t, conv.GetConvID(), res[0].GetConvID(), "id")
598	require.NotNil(t, res[0].Conv.Metadata.FinalizeInfo, "finalize info")
599
600	err = inbox.TlfFinalize(context.TODO(), uid, 10, []chat1.ConversationID{conv.GetConvID()},
601		chat1.ConversationFinalizeInfo{ResetFull: "reset"})
602	require.IsType(t, VersionMismatchError{}, err)
603}
604
605func TestInboxSync(t *testing.T) {
606	tc, inbox, uid := setupInboxTest(t, "basic")
607	defer tc.Cleanup()
608
609	// Create an inbox with a bunch of convos, merge it and read it back out
610	numConvs := 10
611	var convs []types.RemoteConversation
612	for i := numConvs - 1; i >= 0; i-- {
613		convs = append(convs, makeConvo(gregor1.Time(i), 1, 1))
614	}
615
616	require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(convs), nil))
617	_, res, err := inbox.Read(context.TODO(), uid, nil)
618	require.NoError(t, err)
619
620	var syncConvs []chat1.Conversation
621	convs[0].Conv.Metadata.Status = chat1.ConversationStatus_MUTED
622	convs[6].Conv.Metadata.Status = chat1.ConversationStatus_MUTED
623	syncConvs = append(syncConvs, convs[0].Conv)
624	syncConvs = append(syncConvs, convs[6].Conv)
625	newConv := makeConvo(gregor1.Time(60), 1, 1)
626	syncConvs = append(syncConvs, newConv.Conv)
627
628	vers, err := inbox.Version(context.TODO(), uid)
629	require.NoError(t, err)
630	syncRes, err := inbox.Sync(context.TODO(), uid, vers+1, syncConvs)
631	require.NoError(t, err)
632	newVers, newRes, err := inbox.Read(context.TODO(), uid, nil)
633	require.NoError(t, err)
634	sort.Sort(ByDatabaseOrder(newRes))
635	require.Equal(t, vers+1, newVers)
636	require.Equal(t, len(res)+1, len(newRes))
637	require.Equal(t, newConv.GetConvID(), newRes[0].GetConvID())
638	require.Equal(t, chat1.ConversationStatus_MUTED, newRes[1].Conv.Metadata.Status)
639	require.Equal(t, chat1.ConversationStatus_MUTED, newRes[7].Conv.Metadata.Status)
640	require.Equal(t, chat1.ConversationStatus_UNFILED, newRes[4].Conv.Metadata.Status)
641	require.False(t, syncRes.TeamTypeChanged)
642	require.Len(t, syncRes.Expunges, 0)
643
644	syncConvs = nil
645	vers, err = inbox.Version(context.TODO(), uid)
646	require.NoError(t, err)
647	convs[8].Conv.Metadata.TeamType = chat1.TeamType_COMPLEX
648	syncConvs = append(syncConvs, convs[8].Conv)
649	convs[9].Conv.Expunge = chat1.Expunge{Upto: 3}
650	syncConvs = append(syncConvs, convs[9].Conv)
651	syncRes, err = inbox.Sync(context.TODO(), uid, vers+1, syncConvs)
652	require.NoError(t, err)
653	newVers, newRes, err = inbox.Read(context.TODO(), uid, nil)
654	require.NoError(t, err)
655	sort.Sort(ByDatabaseOrder(newRes))
656	require.Equal(t, vers+1, newVers)
657	require.Equal(t, chat1.TeamType_COMPLEX, newRes[9].Conv.Metadata.TeamType)
658	require.True(t, syncRes.TeamTypeChanged)
659	require.Len(t, syncRes.Expunges, 1)
660	require.True(t, convs[9].Conv.GetConvID().Eq(syncRes.Expunges[0].ConvID))
661	require.Equal(t, convs[9].Conv.Expunge, syncRes.Expunges[0].Expunge)
662}
663
664func TestInboxServerVersion(t *testing.T) {
665	tc, inbox, uid := setupInboxTest(t, "basic")
666	defer tc.Cleanup()
667
668	// Create an inbox with a bunch of convos, merge it and read it back out
669	numConvs := 10
670	var convs []types.RemoteConversation
671	for i := numConvs - 1; i >= 0; i-- {
672		convs = append(convs, makeConvo(gregor1.Time(i), 1, 1))
673	}
674
675	require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(convs), nil))
676	_, res, err := inbox.Read(context.TODO(), uid, nil)
677	require.NoError(t, err)
678	require.Equal(t, numConvs, len(res))
679
680	// Increase server version
681	cerr := tc.Context().ServerCacheVersions.Set(context.TODO(), chat1.ServerCacheVers{
682		InboxVers: 5,
683	})
684	require.NoError(t, cerr)
685
686	_, _, err = inbox.Read(context.TODO(), uid, nil)
687	require.Error(t, err)
688	require.IsType(t, MissError{}, err)
689
690	require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(convs), nil))
691	idata, err := inbox.readDiskVersions(context.TODO(), uid, true)
692	require.NoError(t, err)
693	require.Equal(t, 5, idata.ServerVersion)
694}
695
696func TestInboxKBFSUpgrade(t *testing.T) {
697	tc, inbox, uid := setupInboxTest(t, "kbfs")
698	defer tc.Cleanup()
699	numConvs := 10
700	var convs []types.RemoteConversation
701	for i := numConvs - 1; i >= 0; i-- {
702		convs = append(convs, makeConvo(gregor1.Time(i), 1, 1))
703	}
704	conv := convs[5]
705	convID := conv.GetConvID()
706	require.Equal(t, chat1.ConversationMembersType_KBFS, conv.Conv.GetMembersType())
707	require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(convs), nil))
708	require.NoError(t, inbox.UpgradeKBFSToImpteam(context.TODO(), uid, 2, conv.GetConvID()))
709	_, res, err := inbox.Read(context.TODO(), uid, &chat1.GetInboxQuery{
710		ConvID: &convID,
711	})
712	require.NoError(t, err)
713	require.Equal(t, 1, len(res), "length")
714	require.Equal(t, conv.GetConvID(), res[0].GetConvID(), "id")
715	require.Equal(t, chat1.ConversationMembersType_IMPTEAMUPGRADE, res[0].Conv.Metadata.MembersType)
716}
717
718func makeUID(t *testing.T) gregor1.UID {
719	b, err := libkb.RandBytes(16)
720	require.NoError(t, err)
721	return gregor1.UID(b)
722}
723
724func TestInboxMembershipDupUpdate(t *testing.T) {
725	ctc, inbox, uid := setupInboxTest(t, "membership")
726	defer ctc.Cleanup()
727
728	uid2 := makeUID(t)
729	conv := makeConvo(gregor1.Time(1), 1, 1)
730	conv.Conv.Metadata.AllList = []gregor1.UID{uid, uid2}
731	require.NoError(t, inbox.Merge(context.TODO(), uid, 1, []chat1.Conversation{conv.Conv}, nil))
732
733	otherJoinedConvs := []chat1.ConversationMember{{
734		Uid:    uid2,
735		ConvID: conv.GetConvID(),
736	}}
737	roleUpdates, err := inbox.MembershipUpdate(context.TODO(), uid, 2, []chat1.Conversation{conv.Conv},
738		nil, otherJoinedConvs, nil, nil, nil, nil)
739	require.NoError(t, err)
740	require.Nil(t, roleUpdates)
741
742	_, res, err := inbox.ReadAll(context.TODO(), uid, true)
743	require.NoError(t, err)
744	require.Equal(t, 1, len(res))
745	require.Equal(t, 2, len(res[0].Conv.Metadata.AllList))
746}
747
748func TestInboxMembershipUpdate(t *testing.T) {
749	ctc, inbox, uid := setupInboxTest(t, "membership")
750	defer ctc.Cleanup()
751
752	u2, err := kbtest.CreateAndSignupFakeUser("ib", ctc.G)
753	require.NoError(t, err)
754	uid2 := gregor1.UID(u2.User.GetUID().ToBytes())
755
756	u3, err := kbtest.CreateAndSignupFakeUser("ib", ctc.G)
757	require.NoError(t, err)
758	uid3 := gregor1.UID(u3.User.GetUID().ToBytes())
759
760	u4, err := kbtest.CreateAndSignupFakeUser("ib", ctc.G)
761	require.NoError(t, err)
762	uid4 := gregor1.UID(u4.User.GetUID().ToBytes())
763
764	t.Logf("uid: %s uid2: %s uid3: %s uid4: %s", uid, uid2, uid3, uid4)
765
766	// Create an inbox with a bunch of convos, merge it and read it back out
767	numConvs := 10
768	var convs []types.RemoteConversation
769	tlfID := makeTlfID()
770	for i := numConvs - 1; i >= 0; i-- {
771		conv := makeConvo(gregor1.Time(i), 1, 1)
772		conv.Conv.Metadata.IdTriple.Tlfid = tlfID
773		conv.Conv.Metadata.AllList = []gregor1.UID{uid, uid3, uid4}
774		convs = append(convs, conv)
775	}
776
777	require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(convs), nil))
778	var joinedConvs []types.RemoteConversation
779	numJoinedConvs := 5
780	for i := 0; i < numJoinedConvs; i++ {
781		conv := makeConvo(gregor1.Time(i), 1, 1)
782		conv.Conv.Metadata.IdTriple.Tlfid = tlfID
783		conv.Conv.Metadata.AllList = []gregor1.UID{uid, uid3, uid4}
784		joinedConvs = append(joinedConvs, conv)
785	}
786
787	otherJoinConvID := convs[0].GetConvID()
788	otherJoinedConvs := []chat1.ConversationMember{{
789		Uid:    uid2,
790		ConvID: otherJoinConvID,
791	}}
792	otherRemovedConvID := convs[1].GetConvID()
793	otherRemovedConvs := []chat1.ConversationMember{{
794		Uid:    uid3,
795		ConvID: otherRemovedConvID,
796	}}
797	otherResetConvID := convs[2].GetConvID()
798	otherResetConvs := []chat1.ConversationMember{{
799		Uid:    uid4,
800		ConvID: otherResetConvID,
801	}}
802	userRemovedConvID := convs[5].GetConvID()
803	userRemovedConvs := []chat1.ConversationMember{{
804		Uid:    uid,
805		ConvID: userRemovedConvID,
806	}}
807	userResetConvID := convs[6].GetConvID()
808	userResetConvs := []chat1.ConversationMember{{
809		Uid:    uid,
810		ConvID: userResetConvID,
811	}}
812
813	roleUpdates, err := inbox.MembershipUpdate(context.TODO(), uid, 2, utils.PluckConvs(joinedConvs),
814		userRemovedConvs, otherJoinedConvs, otherRemovedConvs,
815		userResetConvs, otherResetConvs, &chat1.TeamMemberRoleUpdate{
816			TlfID: tlfID,
817			Role:  keybase1.TeamRole_WRITER,
818		})
819	require.NoError(t, err)
820	require.NotNil(t, roleUpdates)
821
822	vers, res, err := inbox.ReadAll(context.TODO(), uid, true)
823	require.NoError(t, err)
824	require.Equal(t, chat1.InboxVers(2), vers)
825	for i, c := range res {
826		// make sure we bump the local version during the membership update for a role change
827		require.EqualValues(t, 1, c.Conv.Metadata.LocalVersion)
828		res[i].Conv.Metadata.LocalVersion = 0 // zero it out for later equality checks
829		if c.GetConvID().Eq(convs[5].GetConvID()) {
830			require.Equal(t, chat1.ConversationMemberStatus_LEFT, c.Conv.ReaderInfo.Status)
831			require.Equal(t, keybase1.TeamRole_WRITER, c.Conv.ReaderInfo.UntrustedTeamRole)
832			convs[5].Conv.ReaderInfo.Status = chat1.ConversationMemberStatus_LEFT
833			convs[5].Conv.Metadata.Version = chat1.ConversationVers(2)
834		} else if c.GetConvID().Eq(convs[6].GetConvID()) {
835			require.Equal(t, chat1.ConversationMemberStatus_RESET, c.Conv.ReaderInfo.Status)
836			require.Equal(t, keybase1.TeamRole_WRITER, c.Conv.ReaderInfo.UntrustedTeamRole)
837			convs[6].Conv.ReaderInfo.Status = chat1.ConversationMemberStatus_RESET
838			convs[6].Conv.Metadata.Version = chat1.ConversationVers(2)
839		}
840	}
841	expected := append(convs, joinedConvs...)
842	sort.Sort(utils.RemoteConvByConvID(expected))
843	sort.Sort(utils.ByConvID(roleUpdates))
844	sort.Sort(utils.RemoteConvByConvID(res))
845	require.Equal(t, len(expected), len(res))
846	for i := 0; i < len(res); i++ {
847		sort.Sort(chat1.ByUID(res[i].Conv.Metadata.AllList))
848		sort.Sort(chat1.ByUID(expected[i].Conv.Metadata.AllList))
849		require.Equal(t, keybase1.TeamRole_WRITER, res[i].Conv.ReaderInfo.UntrustedTeamRole)
850		require.True(t, expected[i].GetConvID().Eq(roleUpdates[i]))
851		if res[i].GetConvID().Eq(otherJoinConvID) {
852			allUsers := []gregor1.UID{uid, uid2, uid3, uid4}
853			sort.Sort(chat1.ByUID(allUsers))
854			require.Equal(t, allUsers, res[i].Conv.Metadata.AllList)
855			require.Zero(t, len(res[i].Conv.Metadata.ResetList))
856		} else if res[i].GetConvID().Eq(otherRemovedConvID) {
857			allUsers := []gregor1.UID{uid, uid4}
858			sort.Sort(chat1.ByUID(allUsers))
859			require.Equal(t, allUsers, res[i].Conv.Metadata.AllList)
860			require.Zero(t, len(res[i].Conv.Metadata.ResetList))
861		} else if res[i].GetConvID().Eq(otherResetConvID) {
862			allUsers := []gregor1.UID{uid, uid3, uid4}
863			sort.Sort(chat1.ByUID(allUsers))
864			resetUsers := []gregor1.UID{uid4}
865			require.Len(t, res[i].Conv.Metadata.AllList, len(allUsers))
866			require.Equal(t, allUsers, res[i].Conv.Metadata.AllList)
867			require.Equal(t, resetUsers, res[i].Conv.Metadata.ResetList)
868		} else if res[i].GetConvID().Eq(userRemovedConvID) {
869			allUsers := []gregor1.UID{uid3, uid4}
870			sort.Sort(chat1.ByUID(allUsers))
871			require.Len(t, res[i].Conv.Metadata.AllList, len(allUsers))
872			require.Equal(t, allUsers, res[i].Conv.Metadata.AllList)
873			require.Zero(t, len(res[i].Conv.Metadata.ResetList))
874		} else if res[i].GetConvID().Eq(userResetConvID) {
875			allUsers := []gregor1.UID{uid, uid3, uid4}
876			sort.Sort(chat1.ByUID(allUsers))
877			resetUsers := []gregor1.UID{uid}
878			require.Len(t, res[i].Conv.Metadata.AllList, len(allUsers))
879			require.Equal(t, allUsers, res[i].Conv.Metadata.AllList)
880			require.Equal(t, resetUsers, res[i].Conv.Metadata.ResetList)
881		} else {
882			allUsers := []gregor1.UID{uid, uid3, uid4}
883			sort.Sort(chat1.ByUID(allUsers))
884			require.Equal(t, allUsers, res[i].Conv.Metadata.AllList)
885			expected[i].Conv.ReaderInfo.UntrustedTeamRole = keybase1.TeamRole_WRITER
886			require.Equal(t, expected[i], res[i])
887		}
888	}
889}
890
891// TestInboxCacheOnLogout checks that calling OnLogout() clears the cache.
892func TestInboxCacheOnLogout(t *testing.T) {
893	uid := keybase1.MakeTestUID(3)
894	inboxMemCache.PutVersions(gregor1.UID(uid), &inboxDiskVersions{})
895	require.NotEmpty(t, len(inboxMemCache.versMap))
896	err := inboxMemCache.OnLogout(libkb.NewMetaContextTODO(nil))
897	require.NoError(t, err)
898	require.Nil(t, inboxMemCache.GetVersions(gregor1.UID(uid)))
899	require.Empty(t, len(inboxMemCache.versMap))
900}
901
902func TestUpdateLocalMtime(t *testing.T) {
903	tc, inbox, uid := setupInboxTest(t, "local conv")
904	defer tc.Cleanup()
905	convs := []types.RemoteConversation{
906		makeConvo(gregor1.Time(1), 1, 1),
907		makeConvo(gregor1.Time(0), 1, 1),
908	}
909	err := inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(convs), nil)
910	require.NoError(t, err)
911	mtime1 := gregor1.Time(5)
912	mtime2 := gregor1.Time(1)
913	err = inbox.UpdateLocalMtime(context.TODO(), uid, []chat1.LocalMtimeUpdate{
914		{
915			ConvID: convs[0].GetConvID(),
916			Mtime:  mtime1,
917		},
918		{
919			ConvID: convs[1].GetConvID(),
920			Mtime:  mtime2,
921		},
922	})
923	require.NoError(t, err)
924
925	diskIndex, err := inbox.readDiskIndex(context.TODO(), uid, true)
926	require.NoError(t, err)
927	convs = nil
928	for _, convID := range diskIndex.ConversationIDs {
929		conv, err := inbox.readConv(context.TODO(), uid, convID)
930		require.NoError(t, err)
931		convs = append(convs, conv)
932	}
933
934	sort.Slice(convs, func(i, j int) bool {
935		return convs[i].GetMtime() > convs[j].GetMtime()
936	})
937	require.Equal(t, mtime1, convs[0].GetMtime())
938	require.Equal(t, mtime2, convs[1].GetMtime())
939}
940