github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/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 }