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  }