github.com/kaleido-io/firefly@v0.0.0-20210622132723-8b4b6aacb971/internal/database/sqlcommon/message_sql_test.go (about) 1 // Copyright © 2021 Kaleido, Inc. 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 // 5 // Licensed under the Apache License, Version 2.0 (the "License"); 6 // you may not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, software 12 // distributed under the License is distributed on an "AS IS" BASIS, 13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 // See the License for the specific language governing permissions and 15 // limitations under the License. 16 17 package sqlcommon 18 19 import ( 20 "context" 21 "database/sql/driver" 22 "encoding/json" 23 "fmt" 24 "testing" 25 26 "github.com/DATA-DOG/go-sqlmock" 27 "github.com/kaleido-io/firefly/internal/log" 28 "github.com/kaleido-io/firefly/pkg/database" 29 "github.com/kaleido-io/firefly/pkg/fftypes" 30 "github.com/stretchr/testify/assert" 31 "github.com/stretchr/testify/mock" 32 ) 33 34 func TestUpsertE2EWithDB(t *testing.T) { 35 log.SetLevel("trace") 36 37 s := newQLTestProvider(t) 38 defer s.Close() 39 ctx := context.Background() 40 41 s.callbacks.On("MessageCreated", mock.Anything).Return() 42 43 // Create a new message 44 msgID := fftypes.NewUUID() 45 dataID1 := fftypes.NewUUID() 46 dataID2 := fftypes.NewUUID() 47 rand1 := fftypes.NewRandB32() 48 rand2 := fftypes.NewRandB32() 49 msg := &fftypes.Message{ 50 Header: fftypes.MessageHeader{ 51 ID: msgID, 52 CID: nil, 53 Type: fftypes.MessageTypeBroadcast, 54 Author: "0x12345", 55 Created: fftypes.Now(), 56 Namespace: "ns12345", 57 Topics: []string{"test1"}, 58 Group: nil, 59 DataHash: fftypes.NewRandB32(), 60 TxType: fftypes.TransactionTypeNone, 61 }, 62 Hash: fftypes.NewRandB32(), 63 Confirmed: nil, 64 Data: []*fftypes.DataRef{ 65 {ID: dataID1, Hash: rand1}, 66 {ID: dataID2, Hash: rand2}, 67 }, 68 } 69 err := s.InsertMessageLocal(ctx, msg) 70 assert.NoError(t, err) 71 72 // Check we get the exact same message back 73 msgRead, err := s.GetMessageByID(ctx, msgID) 74 assert.NoError(t, err) 75 assert.True(t, msgRead.Local) 76 // The generated sequence will have been added 77 msg.Sequence = msgRead.Sequence 78 assert.NoError(t, err) 79 msgJson, _ := json.Marshal(&msg) 80 msgReadJson, _ := json.Marshal(&msgRead) 81 assert.Equal(t, string(msgJson), string(msgReadJson)) 82 83 // Update the message (this is testing what's possible at the database layer, 84 // and does not account for the verification that happens at the higher level) 85 dataID3 := fftypes.NewUUID() 86 rand3 := fftypes.NewRandB32() 87 cid := fftypes.NewUUID() 88 gid := fftypes.NewRandB32() 89 bid := fftypes.NewUUID() 90 msgUpdated := &fftypes.Message{ 91 Header: fftypes.MessageHeader{ 92 ID: msgID, 93 CID: cid, 94 Type: fftypes.MessageTypeBroadcast, 95 Author: "0x12345", 96 Created: fftypes.Now(), 97 Namespace: "ns12345", 98 Topics: []string{"topic1", "topic2"}, 99 Tag: "tag1", 100 Group: gid, 101 DataHash: fftypes.NewRandB32(), 102 TxType: fftypes.TransactionTypeBatchPin, 103 }, 104 Hash: fftypes.NewRandB32(), 105 Pins: []string{fftypes.NewRandB32().String(), fftypes.NewRandB32().String()}, 106 Confirmed: fftypes.Now(), 107 BatchID: bid, 108 Data: []*fftypes.DataRef{ 109 {ID: dataID2, Hash: rand2}, 110 {ID: dataID3, Hash: rand3}, 111 }, 112 Local: false, // must be ignored 113 } 114 115 // Ensure hash change rejected 116 err = s.UpsertMessage(context.Background(), msgUpdated, true, false) 117 assert.Equal(t, database.HashMismatch, err) 118 119 err = s.UpsertMessage(context.Background(), msgUpdated, true, true) 120 assert.NoError(t, err) 121 122 // Check we get the exact same message back - note the removal of one of the data elements 123 msgRead, err = s.GetMessageByID(ctx, msgID) 124 assert.True(t, msgRead.Local) // Must not have been overridden with the update 125 // The generated sequence will have been added 126 msgUpdated.Sequence = msgRead.Sequence 127 msgUpdated.Local = true // retained 128 assert.NoError(t, err) 129 msgJson, _ = json.Marshal(&msgUpdated) 130 msgReadJson, _ = json.Marshal(&msgRead) 131 assert.Equal(t, string(msgJson), string(msgReadJson)) 132 133 // Query back the message 134 fb := database.MessageQueryFactory.NewFilter(ctx) 135 filter := fb.And( 136 fb.Eq("id", msgUpdated.Header.ID.String()), 137 fb.Eq("namespace", msgUpdated.Header.Namespace), 138 fb.Eq("type", string(msgUpdated.Header.Type)), 139 fb.Eq("author", msgUpdated.Header.Author), 140 fb.Eq("topics", msgUpdated.Header.Topics), 141 fb.Eq("group", msgUpdated.Header.Group), 142 fb.Eq("cid", msgUpdated.Header.CID), 143 fb.Eq("local", true), 144 fb.Gt("created", "0"), 145 fb.Gt("confirmed", "0"), 146 ) 147 msgs, err := s.GetMessages(ctx, filter) 148 assert.NoError(t, err) 149 assert.Equal(t, 1, len(msgs)) 150 msgReadJson, _ = json.Marshal(msgs[0]) 151 assert.Equal(t, string(msgJson), string(msgReadJson)) 152 153 // Check just getting hte refs 154 msgRefs, err := s.GetMessageRefs(ctx, filter) 155 assert.NoError(t, err) 156 assert.Equal(t, 1, len(msgs)) 157 assert.Equal(t, msgUpdated.Header.ID, msgRefs[0].ID) 158 assert.Equal(t, msgUpdated.Hash, msgRefs[0].Hash) 159 assert.Equal(t, msgUpdated.Sequence, msgRefs[0].Sequence) 160 161 // Check we can get it with a filter on only mesasges with a particular data ref 162 msgs, err = s.GetMessagesForData(ctx, dataID3, filter) 163 assert.NoError(t, err) 164 assert.Equal(t, 1, len(msgs)) 165 msgReadJson, _ = json.Marshal(msgs[0]) 166 assert.Equal(t, string(msgJson), string(msgReadJson)) 167 168 // Negative test on filter 169 filter = fb.And( 170 fb.Eq("id", msgUpdated.Header.ID.String()), 171 fb.Eq("created", "0"), 172 ) 173 msgs, err = s.GetMessages(ctx, filter) 174 assert.NoError(t, err) 175 assert.Equal(t, 0, len(msgs)) 176 177 // Update 178 gid2 := fftypes.NewRandB32() 179 bid2 := fftypes.NewUUID() 180 up := database.MessageQueryFactory.NewUpdate(ctx). 181 Set("group", gid2). 182 Set("batch", bid2) 183 err = s.UpdateMessage(ctx, msgID, up) 184 assert.NoError(t, err) 185 186 // Test find updated value 187 filter = fb.And( 188 fb.Eq("id", msgUpdated.Header.ID.String()), 189 fb.Eq("group", gid2), 190 ) 191 msgs, err = s.GetMessages(ctx, filter) 192 assert.NoError(t, err) 193 assert.Equal(t, 1, len(msgs)) 194 assert.Equal(t, *bid2, *msgs[0].BatchID) 195 196 s.callbacks.AssertExpectations(t) 197 } 198 199 func TestUpsertMessageFailBegin(t *testing.T) { 200 s, mock := newMockProvider().init() 201 mock.ExpectBegin().WillReturnError(fmt.Errorf("pop")) 202 err := s.UpsertMessage(context.Background(), &fftypes.Message{}, true, true) 203 assert.Regexp(t, "FF10114", err) 204 assert.NoError(t, mock.ExpectationsWereMet()) 205 } 206 207 func TestUpsertMessageFailSelect(t *testing.T) { 208 s, mock := newMockProvider().init() 209 mock.ExpectBegin() 210 mock.ExpectQuery("SELECT .*").WillReturnError(fmt.Errorf("pop")) 211 mock.ExpectRollback() 212 msgID := fftypes.NewUUID() 213 err := s.UpsertMessage(context.Background(), &fftypes.Message{Header: fftypes.MessageHeader{ID: msgID}}, true, true) 214 assert.Regexp(t, "FF10115", err) 215 assert.NoError(t, mock.ExpectationsWereMet()) 216 } 217 218 func TestUpsertMessageFailInsert(t *testing.T) { 219 s, mock := newMockProvider().init() 220 mock.ExpectBegin() 221 mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{})) 222 mock.ExpectExec("INSERT .*").WillReturnError(fmt.Errorf("pop")) 223 mock.ExpectRollback() 224 msgID := fftypes.NewUUID() 225 err := s.UpsertMessage(context.Background(), &fftypes.Message{Header: fftypes.MessageHeader{ID: msgID}}, true, true) 226 assert.Regexp(t, "FF10116", err) 227 assert.NoError(t, mock.ExpectationsWereMet()) 228 } 229 230 func TestUpsertMessageFailUpdate(t *testing.T) { 231 s, mock := newMockProvider().init() 232 msgID := fftypes.NewUUID() 233 mock.ExpectBegin() 234 mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(msgID.String())) 235 mock.ExpectExec("UPDATE .*").WillReturnError(fmt.Errorf("pop")) 236 mock.ExpectRollback() 237 err := s.UpsertMessage(context.Background(), &fftypes.Message{Header: fftypes.MessageHeader{ID: msgID}}, true, true) 238 assert.Regexp(t, "FF10117", err) 239 assert.NoError(t, mock.ExpectationsWereMet()) 240 } 241 242 func TestUpsertMessageFailUpdateRefs(t *testing.T) { 243 s, mock := newMockProvider().init() 244 msgID := fftypes.NewUUID() 245 mock.ExpectBegin() 246 mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(msgID.String())) 247 mock.ExpectExec("UPDATE .*").WillReturnResult(driver.ResultNoRows) 248 mock.ExpectExec("DELETE .*").WillReturnError(fmt.Errorf("pop")) 249 mock.ExpectRollback() 250 err := s.UpsertMessage(context.Background(), &fftypes.Message{Header: fftypes.MessageHeader{ID: msgID}}, true, true) 251 assert.Regexp(t, "FF10118", err) 252 assert.NoError(t, mock.ExpectationsWereMet()) 253 } 254 255 func TestUpsertMessageFailCommit(t *testing.T) { 256 s, mock := newMockProvider().init() 257 msgID := fftypes.NewUUID() 258 mock.ExpectBegin() 259 mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"id"})) 260 mock.ExpectExec("INSERT .*").WillReturnResult(sqlmock.NewResult(1, 1)) 261 mock.ExpectCommit().WillReturnError(fmt.Errorf("pop")) 262 err := s.UpsertMessage(context.Background(), &fftypes.Message{Header: fftypes.MessageHeader{ID: msgID}}, true, true) 263 assert.Regexp(t, "FF10119", err) 264 assert.NoError(t, mock.ExpectationsWereMet()) 265 } 266 267 func TestUpdateMessageDataRefsNilID(t *testing.T) { 268 s, mock := newMockProvider().init() 269 msgID := fftypes.NewUUID() 270 mock.ExpectBegin() 271 tx, _ := s.db.Begin() 272 err := s.updateMessageDataRefs(context.Background(), &txWrapper{sqlTX: tx}, &fftypes.Message{ 273 Header: fftypes.MessageHeader{ID: msgID}, 274 Data: []*fftypes.DataRef{{ID: nil}}, 275 }, false) 276 assert.Regexp(t, "FF10123", err) 277 assert.NoError(t, mock.ExpectationsWereMet()) 278 } 279 280 func TestUpdateMessageDataRefsNilHash(t *testing.T) { 281 s, mock := newMockProvider().init() 282 msgID := fftypes.NewUUID() 283 mock.ExpectBegin() 284 tx, _ := s.db.Begin() 285 err := s.updateMessageDataRefs(context.Background(), &txWrapper{sqlTX: tx}, &fftypes.Message{ 286 Header: fftypes.MessageHeader{ID: msgID}, 287 Data: []*fftypes.DataRef{{ID: fftypes.NewUUID()}}, 288 }, false) 289 assert.Regexp(t, "FF10139", err) 290 assert.NoError(t, mock.ExpectationsWereMet()) 291 } 292 293 func TestUpdateMessageDataDeleteFail(t *testing.T) { 294 s, mock := newMockProvider().init() 295 msgID := fftypes.NewUUID() 296 mock.ExpectBegin() 297 tx, _ := s.db.Begin() 298 mock.ExpectExec("DELETE .*").WillReturnError(fmt.Errorf("pop")) 299 err := s.updateMessageDataRefs(context.Background(), &txWrapper{sqlTX: tx}, &fftypes.Message{ 300 Header: fftypes.MessageHeader{ID: msgID}, 301 }, true) 302 assert.Regexp(t, "FF10118", err) 303 assert.NoError(t, mock.ExpectationsWereMet()) 304 } 305 306 func TestUpdateMessageDataAddFail(t *testing.T) { 307 s, mock := newMockProvider().init() 308 msgID := fftypes.NewUUID() 309 dataID := fftypes.NewUUID() 310 dataHash := fftypes.NewRandB32() 311 mock.ExpectBegin() 312 tx, _ := s.db.Begin() 313 mock.ExpectExec("INSERT .*").WillReturnError(fmt.Errorf("pop")) 314 err := s.updateMessageDataRefs(context.Background(), &txWrapper{sqlTX: tx}, &fftypes.Message{ 315 Header: fftypes.MessageHeader{ID: msgID}, 316 Data: []*fftypes.DataRef{{ID: dataID, Hash: dataHash}}, 317 }, false) 318 assert.Regexp(t, "FF10116", err) 319 assert.NoError(t, mock.ExpectationsWereMet()) 320 } 321 322 func TestLoadMessageDataRefsQueryFail(t *testing.T) { 323 s, mock := newMockProvider().init() 324 msgID := fftypes.NewUUID() 325 mock.ExpectQuery("SELECT .*").WillReturnError(fmt.Errorf("pop")) 326 err := s.loadDataRefs(context.Background(), []*fftypes.Message{ 327 { 328 Header: fftypes.MessageHeader{ID: msgID}, 329 }, 330 }) 331 assert.Regexp(t, "FF10115", err) 332 assert.NoError(t, mock.ExpectationsWereMet()) 333 } 334 335 func TestLoadMessageDataRefsScanFail(t *testing.T) { 336 s, mock := newMockProvider().init() 337 msgID := fftypes.NewUUID() 338 mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"data_id"}).AddRow("only one")) 339 err := s.loadDataRefs(context.Background(), []*fftypes.Message{ 340 { 341 Header: fftypes.MessageHeader{ID: msgID}, 342 }, 343 }) 344 assert.Regexp(t, "FF10121", err) 345 assert.NoError(t, mock.ExpectationsWereMet()) 346 } 347 348 func TestLoadMessageDataRefsEmpty(t *testing.T) { 349 s, mock := newMockProvider().init() 350 msgID := fftypes.NewUUID() 351 msg := &fftypes.Message{Header: fftypes.MessageHeader{ID: msgID}} 352 mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"data_id", "data_hash"})) 353 err := s.loadDataRefs(context.Background(), []*fftypes.Message{msg}) 354 assert.NoError(t, err) 355 assert.Equal(t, fftypes.DataRefs{}, msg.Data) 356 assert.NoError(t, mock.ExpectationsWereMet()) 357 } 358 359 func TestGetMessageByIDSelectFail(t *testing.T) { 360 s, mock := newMockProvider().init() 361 msgID := fftypes.NewUUID() 362 mock.ExpectQuery("SELECT .*").WillReturnError(fmt.Errorf("pop")) 363 _, err := s.GetMessageByID(context.Background(), msgID) 364 assert.Regexp(t, "FF10115", err) 365 assert.NoError(t, mock.ExpectationsWereMet()) 366 } 367 368 func TestGetMessageByIDNotFound(t *testing.T) { 369 s, mock := newMockProvider().init() 370 msgID := fftypes.NewUUID() 371 mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"id"})) 372 msg, err := s.GetMessageByID(context.Background(), msgID) 373 assert.NoError(t, err) 374 assert.Nil(t, msg) 375 assert.NoError(t, mock.ExpectationsWereMet()) 376 } 377 378 func TestGetMessageByIDScanFail(t *testing.T) { 379 s, mock := newMockProvider().init() 380 msgID := fftypes.NewUUID() 381 mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow("only one")) 382 _, err := s.GetMessageByID(context.Background(), msgID) 383 assert.Regexp(t, "FF10121", err) 384 assert.NoError(t, mock.ExpectationsWereMet()) 385 } 386 387 func TestGetMessageByIDLoadRefsFail(t *testing.T) { 388 s, mock := newMockProvider().init() 389 msgID := fftypes.NewUUID() 390 b32 := fftypes.NewRandB32() 391 cols := append([]string{}, msgColumns...) 392 cols = append(cols, "id()") 393 mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows(cols). 394 AddRow(msgID.String(), nil, fftypes.MessageTypeBroadcast, "0x12345", 0, "ns1", "t1", "c1", nil, b32.String(), b32.String(), b32.String(), 0, "pin", nil, false, 0)) 395 mock.ExpectQuery("SELECT .*").WillReturnError(fmt.Errorf("pop")) 396 _, err := s.GetMessageByID(context.Background(), msgID) 397 assert.Regexp(t, "FF10115", err) 398 assert.NoError(t, mock.ExpectationsWereMet()) 399 } 400 401 func TestGetMessagesBuildQueryFail(t *testing.T) { 402 s, _ := newMockProvider().init() 403 f := database.MessageQueryFactory.NewFilter(context.Background()).Eq("id", map[bool]bool{true: false}) 404 _, err := s.GetMessages(context.Background(), f) 405 assert.Regexp(t, "FF10149.*id", err) 406 } 407 408 func TestGetMessagesQueryFail(t *testing.T) { 409 s, mock := newMockProvider().init() 410 mock.ExpectQuery("SELECT .*").WillReturnError(fmt.Errorf("pop")) 411 f := database.MessageQueryFactory.NewFilter(context.Background()).Eq("id", "") 412 _, err := s.GetMessages(context.Background(), f) 413 assert.Regexp(t, "FF10115", err) 414 assert.NoError(t, mock.ExpectationsWereMet()) 415 } 416 417 func TestGetMessagesForDataBadQuery(t *testing.T) { 418 s, mock := newMockProvider().init() 419 f := database.MessageQueryFactory.NewFilter(context.Background()).Eq("!wrong", "") 420 _, err := s.GetMessagesForData(context.Background(), fftypes.NewUUID(), f) 421 assert.Regexp(t, "FF10148", err) 422 assert.NoError(t, mock.ExpectationsWereMet()) 423 } 424 425 func TestGetMessagesReadMessageFail(t *testing.T) { 426 s, mock := newMockProvider().init() 427 mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow("only one")) 428 f := database.MessageQueryFactory.NewFilter(context.Background()).Eq("id", "") 429 _, err := s.GetMessages(context.Background(), f) 430 assert.Regexp(t, "FF10121", err) 431 assert.NoError(t, mock.ExpectationsWereMet()) 432 } 433 434 func TestGetMessagesLoadRefsFail(t *testing.T) { 435 s, mock := newMockProvider().init() 436 msgID := fftypes.NewUUID() 437 b32 := fftypes.NewRandB32() 438 cols := append([]string{}, msgColumns...) 439 cols = append(cols, "id()") 440 mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows(cols). 441 AddRow(msgID.String(), nil, fftypes.MessageTypeBroadcast, "0x12345", 0, "ns1", "t1", "c1", nil, b32.String(), b32.String(), b32.String(), 0, "pin", nil, false, 0)) 442 mock.ExpectQuery("SELECT .*").WillReturnError(fmt.Errorf("pop")) 443 f := database.MessageQueryFactory.NewFilter(context.Background()).Gt("confirmed", "0") 444 _, err := s.GetMessages(context.Background(), f) 445 assert.Regexp(t, "FF10115", err) 446 assert.NoError(t, mock.ExpectationsWereMet()) 447 } 448 449 func TestGetMessageRefsBuildQueryFail(t *testing.T) { 450 s, _ := newMockProvider().init() 451 f := database.MessageQueryFactory.NewFilter(context.Background()).Eq("id", map[bool]bool{true: false}) 452 _, err := s.GetMessageRefs(context.Background(), f) 453 assert.Regexp(t, "FF10149.*id", err) 454 } 455 456 func TestGetMessageRefsQueryFail(t *testing.T) { 457 s, mock := newMockProvider().init() 458 mock.ExpectQuery("SELECT .*").WillReturnError(fmt.Errorf("pop")) 459 f := database.MessageQueryFactory.NewFilter(context.Background()).Eq("id", "") 460 _, err := s.GetMessageRefs(context.Background(), f) 461 assert.Regexp(t, "FF10115", err) 462 assert.NoError(t, mock.ExpectationsWereMet()) 463 } 464 465 func TestGetMessageRefsReadMessageFail(t *testing.T) { 466 s, mock := newMockProvider().init() 467 mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow("only one")) 468 f := database.MessageQueryFactory.NewFilter(context.Background()).Eq("id", "") 469 _, err := s.GetMessageRefs(context.Background(), f) 470 assert.Regexp(t, "FF10121", err) 471 assert.NoError(t, mock.ExpectationsWereMet()) 472 } 473 474 func TestMessageUpdateBeginFail(t *testing.T) { 475 s, mock := newMockProvider().init() 476 mock.ExpectBegin().WillReturnError(fmt.Errorf("pop")) 477 u := database.MessageQueryFactory.NewUpdate(context.Background()).Set("id", "anything") 478 err := s.UpdateMessage(context.Background(), fftypes.NewUUID(), u) 479 assert.Regexp(t, "FF10114", err) 480 } 481 482 func TestMessageUpdateBuildQueryFail(t *testing.T) { 483 s, mock := newMockProvider().init() 484 mock.ExpectBegin() 485 u := database.MessageQueryFactory.NewUpdate(context.Background()).Set("id", map[bool]bool{true: false}) 486 err := s.UpdateMessage(context.Background(), fftypes.NewUUID(), u) 487 assert.Regexp(t, "FF10149.*id", err) 488 } 489 490 func TestMessagesUpdateBuildFilterFail(t *testing.T) { 491 s, mock := newMockProvider().init() 492 mock.ExpectBegin() 493 f := database.MessageQueryFactory.NewFilter(context.Background()).Eq("id", map[bool]bool{true: false}) 494 u := database.MessageQueryFactory.NewUpdate(context.Background()).Set("type", fftypes.MessageTypeBroadcast) 495 err := s.UpdateMessages(context.Background(), f, u) 496 assert.Regexp(t, "FF10149.*id", err) 497 } 498 499 func TestMessageUpdateFail(t *testing.T) { 500 s, mock := newMockProvider().init() 501 mock.ExpectBegin() 502 mock.ExpectExec("UPDATE .*").WillReturnError(fmt.Errorf("pop")) 503 mock.ExpectRollback() 504 u := database.MessageQueryFactory.NewUpdate(context.Background()).Set("group", fftypes.NewRandB32()) 505 err := s.UpdateMessage(context.Background(), fftypes.NewUUID(), u) 506 assert.Regexp(t, "FF10117", err) 507 }