github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/chat/prev_test.go (about)

     1  package chat
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/keybase/client/go/protocol/chat1"
     7  	"github.com/stretchr/testify/require"
     8  )
     9  
    10  type dummyMessage struct {
    11  	id        chat1.MessageID
    12  	hash      chat1.Hash
    13  	prevs     []chat1.MessagePreviousPointer
    14  	exploding bool
    15  }
    16  
    17  func expectCode(t *testing.T, err ChatThreadConsistencyError, code ConsistencyErrorCode) {
    18  	if err == nil {
    19  		t.Fatalf("Expected an error. Got nil.")
    20  	}
    21  	if err.Code() != code {
    22  		t.Fatalf("Expected a code %d, but found %d.", code, err.Code())
    23  	}
    24  }
    25  
    26  func threadViewFromDummies(dummies []dummyMessage) chat1.ThreadView {
    27  	threadView := chat1.ThreadView{}
    28  	for _, dummy := range dummies {
    29  		messageValid := chat1.MessageUnboxedValid{
    30  			HeaderHash: dummy.hash,
    31  			ServerHeader: chat1.MessageServerHeader{
    32  				MessageID: dummy.id,
    33  			},
    34  			ClientHeader: chat1.MessageClientHeaderVerified{
    35  				Prev: dummy.prevs,
    36  			},
    37  			// no need for a body
    38  		}
    39  		if dummy.exploding {
    40  			// This just needs to be non-nil.
    41  			messageValid.ClientHeader.EphemeralMetadata = &chat1.MsgEphemeralMetadata{}
    42  		}
    43  		msg := chat1.NewMessageUnboxedWithValid(messageValid)
    44  		threadView.Messages = append(threadView.Messages, msg)
    45  	}
    46  	return threadView
    47  }
    48  
    49  func TestPrevGood(t *testing.T) {
    50  	thread := threadViewFromDummies([]dummyMessage{
    51  		{
    52  			id:    1,
    53  			hash:  []byte("placeholder"),
    54  			prevs: nil,
    55  		},
    56  		{
    57  			id:   2,
    58  			hash: []byte("placeholder"),
    59  			prevs: []chat1.MessagePreviousPointer{
    60  				{
    61  					Id:   1,
    62  					Hash: []byte("placeholder"),
    63  				},
    64  				{
    65  					Id:   0, // This one won't exist locally.
    66  					Hash: []byte("nonexistent message hash"),
    67  				},
    68  			},
    69  		},
    70  	})
    71  
    72  	unpreved, _, err := CheckPrevPointersAndGetUnpreved(&thread)
    73  	if err != nil {
    74  		t.Fatal(err)
    75  	}
    76  
    77  	if len(unpreved) != 1 {
    78  		t.Fatalf("Expected 1 unpreved message, found %d", len(unpreved))
    79  	}
    80  }
    81  
    82  func TestPrevDuplicateID(t *testing.T) {
    83  	thread := threadViewFromDummies([]dummyMessage{
    84  		{
    85  			id:    1,
    86  			hash:  []byte("placeholder"),
    87  			prevs: nil,
    88  		},
    89  		{
    90  			id:   1,
    91  			hash: []byte("placeholder"),
    92  		},
    93  	})
    94  
    95  	_, _, err := CheckPrevPointersAndGetUnpreved(&thread)
    96  	expectCode(t, err, DuplicateID)
    97  }
    98  
    99  func TestPrevInconsistentHash(t *testing.T) {
   100  	thread := threadViewFromDummies([]dummyMessage{
   101  		{
   102  			id:   1,
   103  			hash: []byte("placeholder"),
   104  			prevs: []chat1.MessagePreviousPointer{
   105  				{
   106  					Id: 0,
   107  					// We don't have the "real" has for this message, but we
   108  					// can still cause an error by failing to match another
   109  					// prev pointer.
   110  					Hash: []byte("ONE THING"),
   111  				},
   112  			},
   113  		},
   114  		{
   115  			id:   2,
   116  			hash: []byte("placeholder"),
   117  			prevs: []chat1.MessagePreviousPointer{
   118  				{
   119  					Id:   0,
   120  					Hash: []byte("ANOTHER THING"), // This doesn't match above!
   121  				},
   122  			},
   123  		},
   124  	})
   125  
   126  	_, _, err := CheckPrevPointersAndGetUnpreved(&thread)
   127  	expectCode(t, err, InconsistentHash)
   128  }
   129  
   130  func TestPrevOutOfOrder(t *testing.T) {
   131  	thread := threadViewFromDummies([]dummyMessage{
   132  		{
   133  			id:   1,
   134  			hash: []byte("placeholder"),
   135  			prevs: []chat1.MessagePreviousPointer{
   136  				{
   137  					Id:   2, // Out of order!
   138  					Hash: []byte("placeholder"),
   139  				},
   140  			},
   141  		},
   142  		{
   143  			id:    2,
   144  			hash:  []byte("placeholder"),
   145  			prevs: []chat1.MessagePreviousPointer{},
   146  		},
   147  	})
   148  
   149  	_, _, err := CheckPrevPointersAndGetUnpreved(&thread)
   150  	expectCode(t, err, OutOfOrderID)
   151  }
   152  
   153  func TestPrevOutOfOrderEq(t *testing.T) {
   154  	thread := threadViewFromDummies([]dummyMessage{
   155  		{
   156  			id:   1,
   157  			hash: []byte("placeholder"),
   158  			prevs: []chat1.MessagePreviousPointer{
   159  				{
   160  					Id:   1, // Points to self!
   161  					Hash: []byte("placeholder"),
   162  				},
   163  			},
   164  		},
   165  	})
   166  
   167  	_, _, err := CheckPrevPointersAndGetUnpreved(&thread)
   168  	expectCode(t, err, OutOfOrderID)
   169  }
   170  
   171  func TestPrevIncorrectHash(t *testing.T) {
   172  	thread := threadViewFromDummies([]dummyMessage{
   173  		{
   174  			id:   1,
   175  			hash: []byte("placeholder"),
   176  		},
   177  		{
   178  			id:   2,
   179  			hash: []byte("placeholder"),
   180  			prevs: []chat1.MessagePreviousPointer{
   181  				{
   182  					Id:   1,
   183  					Hash: []byte("THE WRONG THING"), // This doesn't match above!
   184  				},
   185  			},
   186  		},
   187  	})
   188  
   189  	_, _, err := CheckPrevPointersAndGetUnpreved(&thread)
   190  	expectCode(t, err, IncorrectHash)
   191  }
   192  
   193  func TestPrevExploding(t *testing.T) {
   194  	thread := threadViewFromDummies([]dummyMessage{
   195  		{
   196  			id:   1,
   197  			hash: []byte("placeholder"),
   198  		},
   199  		{
   200  			exploding: true,
   201  			id:        2,
   202  			hash:      []byte("placeholder"),
   203  			prevs: []chat1.MessagePreviousPointer{
   204  				{
   205  					Id:   1,
   206  					Hash: []byte("placeholder"),
   207  				},
   208  			},
   209  		},
   210  		{
   211  			exploding: true,
   212  			id:        3,
   213  			hash:      []byte("placeholder"),
   214  			prevs: []chat1.MessagePreviousPointer{
   215  				{
   216  					Id:   2,
   217  					Hash: []byte("placeholder"),
   218  				},
   219  			},
   220  		},
   221  	})
   222  
   223  	unprevedRegular, unprevedExploding, err := CheckPrevPointersAndGetUnpreved(&thread)
   224  	require.NoError(t, err)
   225  
   226  	// The regular set of unpreved messages shouldn't respect the exploding
   227  	// messages. It should treat message 1 as unpreved, and it should not
   228  	// include message 3.
   229  	require.Equal(t, 1, len(unprevedRegular))
   230  	require.EqualValues(t, 1, unprevedRegular[0].Id)
   231  
   232  	// In the exploding messages' view, messages 1 and 2 have both been preved
   233  	// already.
   234  	require.Equal(t, 1, len(unprevedExploding))
   235  	require.EqualValues(t, 3, unprevedExploding[0].Id)
   236  }