github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/chat/unfurl/unfurler_test.go (about) 1 package unfurl 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 "testing" 8 "time" 9 10 "github.com/keybase/client/go/chat/attachments" 11 "github.com/keybase/client/go/chat/globals" 12 "github.com/keybase/client/go/chat/types" 13 "github.com/keybase/client/go/externalstest" 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/stretchr/testify/require" 18 ) 19 20 type dummySender struct { 21 ch chan chat1.MessagePlaintext 22 } 23 24 func makeDummySender() *dummySender { 25 return &dummySender{ 26 ch: make(chan chat1.MessagePlaintext, 1), 27 } 28 } 29 30 func (s *dummySender) SendUnfurlNonblock(ctx context.Context, convID chat1.ConversationID, 31 msg chat1.MessagePlaintext, clientPrev chat1.MessageID, outboxID chat1.OutboxID) (chat1.OutboxID, error) { 32 s.ch <- msg 33 return outboxID, nil 34 } 35 36 type promptNotification struct { 37 uid gregor1.UID 38 convID chat1.ConversationID 39 msgID chat1.MessageID 40 domain string 41 } 42 43 type dummyActivityNotifier struct { 44 types.ActivityNotifier 45 ch chan promptNotification 46 } 47 48 func makeDummyActivityNotifier() *dummyActivityNotifier { 49 return &dummyActivityNotifier{ 50 ch: make(chan promptNotification, 1), 51 } 52 } 53 54 func (d *dummyActivityNotifier) PromptUnfurl(ctx context.Context, uid gregor1.UID, 55 convID chat1.ConversationID, msgID chat1.MessageID, domain string) { 56 d.ch <- promptNotification{ 57 uid: uid, 58 convID: convID, 59 msgID: msgID, 60 domain: domain, 61 } 62 } 63 64 type dummyDeliverer struct { 65 types.MessageDeliverer 66 } 67 68 func (d dummyDeliverer) ForceDeliverLoop(ctx context.Context) {} 69 70 func TestUnfurler(t *testing.T) { 71 tc := externalstest.SetupTest(t, "unfurler", 0) 72 defer tc.Cleanup() 73 g := globals.NewContext(tc.G, &globals.ChatContext{}) 74 75 store := attachments.NewStoreTesting(g, nil) 76 s3signer := &ptsigner{} 77 notifier := makeDummyActivityNotifier() 78 g.ChatContext.ActivityNotifier = notifier 79 g.ChatContext.MessageDeliverer = dummyDeliverer{} 80 sender := makeDummySender() 81 ri := func() chat1.RemoteInterface { return paramsRemote{} } 82 storage := newMemConversationBackedStorage() 83 unfurler := NewUnfurler(g, store, s3signer, storage, sender, ri) 84 settings := NewSettings(g, storage) 85 srv := createTestCaseHTTPSrv(t) 86 addr := srv.Start() 87 defer srv.Stop() 88 89 unfurler.unfurlCh = make(chan *chat1.Unfurl, 1) 90 uid := gregor1.UID([]byte{0, 1}) 91 convID := chat1.ConversationID([]byte{0, 2}) 92 msgBody := fmt.Sprintf("check out this link! http://%s/?name=%s ", addr, "wsj0.html") 93 fromMsg := chat1.NewMessageUnboxedWithValid(chat1.MessageUnboxedValid{ 94 ClientHeader: chat1.MessageClientHeaderVerified{ 95 TlfName: "mike", 96 MessageType: chat1.MessageType_TEXT, 97 }, 98 ServerHeader: chat1.MessageServerHeader{ 99 MessageID: 4, 100 }, 101 MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{ 102 Body: msgBody, 103 }), 104 }) 105 106 // No prefetch unless we're in the whitelist 107 numPrefetched := unfurler.Prefetch(context.TODO(), uid, convID, msgBody) 108 require.Equal(t, 0, numPrefetched) 109 110 unfurler.UnfurlAndSend(context.TODO(), uid, convID, fromMsg) 111 select { 112 case <-sender.ch: 113 require.Fail(t, "no send here") 114 case n := <-notifier.ch: 115 require.Equal(t, uid, n.uid) 116 require.Equal(t, convID, n.convID) 117 require.Equal(t, fromMsg.GetMessageID(), n.msgID) 118 require.Equal(t, "0.1", n.domain) 119 case <-time.After(20 * time.Second): 120 require.Fail(t, "no notifications") 121 } 122 require.NoError(t, settings.WhitelistAdd(context.TODO(), uid, "0.1")) 123 124 // ensure we try to prefetch once per url in the msgText once we're whitelisted 125 numPrefetched = unfurler.Prefetch(context.TODO(), uid, convID, strings.Repeat(msgBody, 5)) 126 require.Equal(t, 1, numPrefetched) 127 128 for i := 0; i < 5; i++ { 129 unfurler.UnfurlAndSend(context.TODO(), uid, convID, fromMsg) 130 } 131 var outboxID chat1.OutboxID 132 select { 133 case msg := <-sender.ch: 134 require.Equal(t, chat1.MessageType_UNFURL, msg.ClientHeader.MessageType) 135 require.Equal(t, fromMsg.Valid().ClientHeader.TlfName, msg.ClientHeader.TlfName) 136 require.NotNil(t, msg.ClientHeader.OutboxID) 137 outboxID = *msg.ClientHeader.OutboxID 138 require.Equal(t, fromMsg.GetMessageID(), msg.ClientHeader.Supersedes) 139 case <-notifier.ch: 140 require.Fail(t, "no notification here") 141 case <-time.After(20 * time.Second): 142 require.Fail(t, "no notifications") 143 } 144 select { 145 case <-sender.ch: 146 require.Fail(t, "only one send should happen") 147 default: 148 } 149 select { 150 case unfurl := <-unfurler.unfurlCh: 151 require.NotNil(t, unfurl) 152 typ, err := unfurl.UnfurlType() 153 require.NoError(t, err) 154 require.Equal(t, chat1.UnfurlType_GENERIC, typ) 155 require.NotNil(t, unfurl.Generic().Image) 156 require.NotNil(t, unfurl.Generic().Favicon) 157 require.NotNil(t, unfurl.Generic().Description) 158 require.Equal(t, "U.S. Stocks Jump as Tough Month Sets to Wrap", unfurl.Generic().Title) 159 require.Equal(t, "WSJ", unfurl.Generic().SiteName) 160 case <-time.After(20 * time.Second): 161 require.Fail(t, "no unfurl") 162 } 163 select { 164 case <-unfurler.unfurlCh: 165 require.Fail(t, "only one unfurl should happen") 166 default: 167 } 168 status, _, err := unfurler.Status(context.TODO(), outboxID) 169 require.NoError(t, err) 170 require.Equal(t, types.UnfurlerTaskStatusSuccess, status) 171 unfurler.Complete(context.TODO(), outboxID) 172 status, _, err = unfurler.Status(context.TODO(), outboxID) 173 require.Error(t, err) 174 require.IsType(t, libkb.NotFoundError{}, err) 175 require.Equal(t, types.UnfurlerTaskStatusFailed, status) 176 }