Skip to content
Snippets Groups Projects

Add testing with cypher tests

Merged Jonah Husson requested to merge xx-4286/crypto-tests into release
1 file
+ 337
248
Compare changes
  • Side-by-side
  • Inline
@@ -15,6 +15,7 @@ import (
"github.com/hack-pad/go-indexeddb/idb"
"gitlab.com/elixxir/xxdk-wasm/indexedDb"
"gitlab.com/elixxir/xxdk-wasm/storage"
"gitlab.com/xx_network/crypto/csprng"
"gitlab.com/xx_network/primitives/netTime"
"os"
"strconv"
@@ -26,6 +27,7 @@ import (
"gitlab.com/elixxir/client/v4/cmix/rounds"
cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast"
"gitlab.com/elixxir/crypto/channel"
cryptoChannel "gitlab.com/elixxir/crypto/channel"
"gitlab.com/xx_network/primitives/id"
)
@@ -38,326 +40,413 @@ func dummyCallback(uint64, *id.ID, bool) {}
// Happy path, insert message and look it up
func TestWasmModel_msgIDLookup(t *testing.T) {
storage.GetLocalStorage().Clear()
testString := "test"
testMsgId := channel.MakeMessageID([]byte(testString), &id.ID{1})
eventModel, err := newWASMModel(testString, nil, dummyCallback)
cipher, err := cryptoChannel.NewCipher([]byte("testpass"), []byte("testsalt"), 128, csprng.NewSystemRNG())
if err != nil {
t.Fatalf("%+v", err)
t.Fatalf("Failed to create cipher")
}
for _, c := range []cryptoChannel.Cipher{nil, cipher} {
cs := ""
if cipher != nil {
cs = "_withCipher"
}
t.Run(fmt.Sprintf("TestWasmModel_msgIDLookup%s", cs), func(t *testing.T) {
storage.GetLocalStorage().Clear()
testString := "test"
testMsgId := channel.MakeMessageID([]byte(testString), &id.ID{1})
eventModel, err := newWASMModel(testString, c, dummyCallback)
if err != nil {
t.Fatalf("%+v", err)
}
testMsg := buildMessage([]byte(testString), testMsgId.Bytes(), nil,
testString, []byte(testString), []byte{8, 6, 7, 5}, 0, netTime.Now(),
time.Second, 0, 0, channels.Sent)
_, err = eventModel.receiveHelper(testMsg, false)
if err != nil {
t.Fatalf("%+v", err)
}
testMsg := buildMessage([]byte(testString), testMsgId.Bytes(), nil,
testString, []byte(testString), []byte{8, 6, 7, 5}, 0, netTime.Now(),
time.Second, 0, 0, channels.Sent)
_, err = eventModel.receiveHelper(testMsg, false)
if err != nil {
t.Fatalf("%+v", err)
}
uuid, err := eventModel.msgIDLookup(testMsgId)
if err != nil {
t.Fatalf("%+v", err)
}
if uuid == 0 {
t.Fatalf("Expected to get a UUID!")
uuid, err := eventModel.msgIDLookup(testMsgId)
if err != nil {
t.Fatalf("%+v", err)
}
if uuid == 0 {
t.Fatalf("Expected to get a UUID!")
}
})
}
}
// Test wasmModel.UpdateSentStatus happy path and ensure fields don't change.
func Test_wasmModel_UpdateSentStatus(t *testing.T) {
storage.GetLocalStorage().Clear()
testString := "test"
testMsgId := channel.MakeMessageID([]byte(testString), &id.ID{1})
eventModel, err := newWASMModel(testString, nil, dummyCallback)
cipher, err := cryptoChannel.NewCipher([]byte("testpass"), []byte("testsalt"), 128, csprng.NewSystemRNG())
if err != nil {
t.Fatalf("%+v", err)
t.Fatalf("Failed to create cipher")
}
for _, c := range []cryptoChannel.Cipher{nil, cipher} {
cs := ""
if cipher != nil {
cs = "_withCipher"
}
t.Run(fmt.Sprintf("Test_wasmModel_UpdateSentStatus%s", cs), func(t *testing.T) {
storage.GetLocalStorage().Clear()
testString := "test"
testMsgId := channel.MakeMessageID([]byte(testString), &id.ID{1})
eventModel, err := newWASMModel(testString, c, dummyCallback)
if err != nil {
t.Fatalf("%+v", err)
}
// Store a test message
testMsg := buildMessage([]byte(testString), testMsgId.Bytes(), nil,
testString, []byte(testString), []byte{8, 6, 7, 5}, 0, netTime.Now(),
time.Second, 0, 0, channels.Sent)
uuid, err := eventModel.receiveHelper(testMsg, false)
if err != nil {
t.Fatalf("%+v", err)
}
// Store a test message
testMsg := buildMessage([]byte(testString), testMsgId.Bytes(), nil,
testString, []byte(testString), []byte{8, 6, 7, 5}, 0, netTime.Now(),
time.Second, 0, 0, channels.Sent)
uuid, err := eventModel.receiveHelper(testMsg, false)
if err != nil {
t.Fatalf("%+v", err)
}
// Ensure one message is stored
results, err := indexedDb.Dump(eventModel.db, messageStoreName)
if err != nil {
t.Fatalf("%+v", err)
}
if len(results) != 1 {
t.Fatalf("Expected 1 message to exist")
}
// Ensure one message is stored
results, err := indexedDb.Dump(eventModel.db, messageStoreName)
if err != nil {
t.Fatalf("%+v", err)
}
if len(results) != 1 {
t.Fatalf("Expected 1 message to exist")
}
// Update the sentStatus
expectedStatus := channels.Failed
eventModel.UpdateSentStatus(uuid, testMsgId, netTime.Now(),
rounds.Round{ID: 8675309}, expectedStatus)
// Update the sentStatus
expectedStatus := channels.Failed
eventModel.UpdateSentStatus(uuid, testMsgId, netTime.Now(),
rounds.Round{ID: 8675309}, expectedStatus)
// Check the resulting status
results, err = indexedDb.Dump(eventModel.db, messageStoreName)
if err != nil {
t.Fatalf("%+v", err)
}
if len(results) != 1 {
t.Fatalf("Expected 1 message to exist")
}
resultMsg := &Message{}
err = json.Unmarshal([]byte(results[0]), resultMsg)
if err != nil {
t.Fatalf("%+v", err)
}
if resultMsg.Status != uint8(expectedStatus) {
t.Fatalf("Unexpected Status: %v", resultMsg.Status)
}
// Check the resulting status
results, err = indexedDb.Dump(eventModel.db, messageStoreName)
if err != nil {
t.Fatalf("%+v", err)
}
if len(results) != 1 {
t.Fatalf("Expected 1 message to exist")
}
resultMsg := &Message{}
err = json.Unmarshal([]byte(results[0]), resultMsg)
if err != nil {
t.Fatalf("%+v", err)
}
if resultMsg.Status != uint8(expectedStatus) {
t.Fatalf("Unexpected Status: %v", resultMsg.Status)
}
// Make sure other fields didn't change
if resultMsg.Nickname != testString {
t.Fatalf("Unexpected Nickname: %v", resultMsg.Nickname)
// Make sure other fields didn't change
if resultMsg.Nickname != testString {
t.Fatalf("Unexpected Nickname: %v", resultMsg.Nickname)
}
})
}
}
// Smoke test wasmModel.JoinChannel/wasmModel.LeaveChannel happy paths.
func Test_wasmModel_JoinChannel_LeaveChannel(t *testing.T) {
storage.GetLocalStorage().Clear()
eventModel, err := newWASMModel("test", nil, dummyCallback)
cipher, err := cryptoChannel.NewCipher([]byte("testpass"), []byte("testsalt"), 128, csprng.NewSystemRNG())
if err != nil {
t.Fatalf("%+v", err)
t.Fatalf("Failed to create cipher")
}
for _, c := range []cryptoChannel.Cipher{nil, cipher} {
cs := ""
if cipher != nil {
cs = "_withCipher"
}
t.Run(fmt.Sprintf("Test_wasmModel_JoinChannel_LeaveChannel%s", cs), func(t *testing.T) {
storage.GetLocalStorage().Clear()
eventModel, err := newWASMModel("test", c, dummyCallback)
if err != nil {
t.Fatalf("%+v", err)
}
testChannel := &cryptoBroadcast.Channel{
ReceptionID: id.NewIdFromString("test", id.Generic, t),
Name: "test",
Description: "test",
Salt: nil,
}
testChannel2 := &cryptoBroadcast.Channel{
ReceptionID: id.NewIdFromString("test2", id.Generic, t),
Name: "test2",
Description: "test2",
Salt: nil,
}
eventModel.JoinChannel(testChannel)
eventModel.JoinChannel(testChannel2)
results, err := indexedDb.Dump(eventModel.db, channelsStoreName)
if err != nil {
t.Fatalf("%+v", err)
}
if len(results) != 2 {
t.Fatalf("Expected 2 channels to exist")
}
eventModel.LeaveChannel(testChannel.ReceptionID)
results, err = indexedDb.Dump(eventModel.db, channelsStoreName)
if err != nil {
t.Fatalf("%+v", err)
}
if len(results) != 1 {
t.Fatalf("Expected 1 channels to exist")
testChannel := &cryptoBroadcast.Channel{
ReceptionID: id.NewIdFromString("test", id.Generic, t),
Name: "test",
Description: "test",
Salt: nil,
}
testChannel2 := &cryptoBroadcast.Channel{
ReceptionID: id.NewIdFromString("test2", id.Generic, t),
Name: "test2",
Description: "test2",
Salt: nil,
}
eventModel.JoinChannel(testChannel)
eventModel.JoinChannel(testChannel2)
results, err := indexedDb.Dump(eventModel.db, channelsStoreName)
if err != nil {
t.Fatalf("%+v", err)
}
if len(results) != 2 {
t.Fatalf("Expected 2 channels to exist")
}
eventModel.LeaveChannel(testChannel.ReceptionID)
results, err = indexedDb.Dump(eventModel.db, channelsStoreName)
if err != nil {
t.Fatalf("%+v", err)
}
if len(results) != 1 {
t.Fatalf("Expected 1 channels to exist")
}
})
}
}
// Test UUID gets returned when different messages are added.
func Test_wasmModel_UUIDTest(t *testing.T) {
storage.GetLocalStorage().Clear()
testString := "testHello"
eventModel, err := newWASMModel(testString, nil, dummyCallback)
cipher, err := cryptoChannel.NewCipher([]byte("testpass"), []byte("testsalt"), 128, csprng.NewSystemRNG())
if err != nil {
t.Fatalf("%+v", err)
t.Fatalf("Failed to create cipher")
}
for _, c := range []cryptoChannel.Cipher{nil, cipher} {
cs := ""
if cipher != nil {
cs = "_withCipher"
}
t.Run(fmt.Sprintf("Test_wasmModel_UUIDTest%s", cs), func(t *testing.T) {
storage.GetLocalStorage().Clear()
testString := "testHello"
eventModel, err := newWASMModel(testString, c, dummyCallback)
if err != nil {
t.Fatalf("%+v", err)
}
uuids := make([]uint64, 10)
for i := 0; i < 10; i++ {
// Store a test message
channelID := id.NewIdFromBytes([]byte(testString), t)
msgID := channel.MessageID{}
copy(msgID[:], testString+fmt.Sprintf("%d", i))
rnd := rounds.Round{ID: id.Round(42)}
uuid := eventModel.ReceiveMessage(channelID, msgID, "test",
testString+fmt.Sprintf("%d", i), []byte{8, 6, 7, 5}, 0,
netTime.Now(), time.Hour, rnd, 0, channels.Sent)
uuids[i] = uuid
}
uuids := make([]uint64, 10)
for i := 0; i < 10; i++ {
// Store a test message
channelID := id.NewIdFromBytes([]byte(testString), t)
msgID := channel.MessageID{}
copy(msgID[:], testString+fmt.Sprintf("%d", i))
rnd := rounds.Round{ID: id.Round(42)}
uuid := eventModel.ReceiveMessage(channelID, msgID, "test",
testString+fmt.Sprintf("%d", i), []byte{8, 6, 7, 5}, 0,
netTime.Now(), time.Hour, rnd, 0, channels.Sent)
uuids[i] = uuid
}
for i := 0; i < 10; i++ {
for j := i + 1; j < 10; j++ {
if uuids[i] == uuids[j] {
t.Fatalf("uuid failed: %d[%d] == %d[%d]",
uuids[i], i, uuids[j], j)
for i := 0; i < 10; i++ {
for j := i + 1; j < 10; j++ {
if uuids[i] == uuids[j] {
t.Fatalf("uuid failed: %d[%d] == %d[%d]",
uuids[i], i, uuids[j], j)
}
}
}
}
})
}
}
// Tests if the same message ID being sent always returns the same UUID.
func Test_wasmModel_DuplicateReceives(t *testing.T) {
storage.GetLocalStorage().Clear()
testString := "testHello"
eventModel, err := newWASMModel(testString, nil, dummyCallback)
cipher, err := cryptoChannel.NewCipher([]byte("testpass"), []byte("testsalt"), 128, csprng.NewSystemRNG())
if err != nil {
t.Fatalf("%+v", err)
t.Fatalf("Failed to create cipher")
}
for _, c := range []cryptoChannel.Cipher{nil, cipher} {
cs := ""
if cipher != nil {
cs = "_withCipher"
}
t.Run(fmt.Sprintf("Test_wasmModel_DuplicateReceives%s", cs), func(t *testing.T) {
storage.GetLocalStorage().Clear()
testString := "testHello"
eventModel, err := newWASMModel(testString, c, dummyCallback)
if err != nil {
t.Fatalf("%+v", err)
}
uuids := make([]uint64, 10)
msgID := channel.MessageID{}
copy(msgID[:], testString)
for i := 0; i < 10; i++ {
// Store a test message
channelID := id.NewIdFromBytes([]byte(testString), t)
rnd := rounds.Round{ID: id.Round(42)}
uuid := eventModel.ReceiveMessage(channelID, msgID, "test",
testString+fmt.Sprintf("%d", i), []byte{8, 6, 7, 5}, 0,
netTime.Now(), time.Hour, rnd, 0, channels.Sent)
uuids[i] = uuid
}
uuids := make([]uint64, 10)
msgID := channel.MessageID{}
copy(msgID[:], testString)
for i := 0; i < 10; i++ {
// Store a test message
channelID := id.NewIdFromBytes([]byte(testString), t)
rnd := rounds.Round{ID: id.Round(42)}
uuid := eventModel.ReceiveMessage(channelID, msgID, "test",
testString+fmt.Sprintf("%d", i), []byte{8, 6, 7, 5}, 0,
netTime.Now(), time.Hour, rnd, 0, channels.Sent)
uuids[i] = uuid
}
for i := 0; i < 10; i++ {
for j := i + 1; j < 10; j++ {
if uuids[i] != uuids[j] {
t.Fatalf("uuid failed: %d[%d] != %d[%d]",
uuids[i], i, uuids[j], j)
for i := 0; i < 10; i++ {
for j := i + 1; j < 10; j++ {
if uuids[i] != uuids[j] {
t.Fatalf("uuid failed: %d[%d] != %d[%d]",
uuids[i], i, uuids[j], j)
}
}
}
}
})
}
}
// Happy path: Inserts many messages, deletes some, and checks that the final
// result is as expected.
func Test_wasmModel_deleteMsgByChannel(t *testing.T) {
storage.GetLocalStorage().Clear()
testString := "test_deleteMsgByChannel"
totalMessages := 10
expectedMessages := 5
eventModel, err := newWASMModel(testString, nil, dummyCallback)
cipher, err := cryptoChannel.NewCipher([]byte("testpass"), []byte("testsalt"), 128, csprng.NewSystemRNG())
if err != nil {
t.Fatalf("%+v", err)
t.Fatalf("Failed to create cipher")
}
for _, c := range []cryptoChannel.Cipher{nil, cipher} {
cs := ""
if cipher != nil {
cs = "_withCipher"
}
t.Run(fmt.Sprintf("Test_wasmModel_deleteMsgByChannel%s", cs), func(t *testing.T) {
storage.GetLocalStorage().Clear()
testString := "test_deleteMsgByChannel"
totalMessages := 10
expectedMessages := 5
eventModel, err := newWASMModel(testString, c, dummyCallback)
if err != nil {
t.Fatalf("%+v", err)
}
// Create a test channel id
deleteChannel := id.NewIdFromString("deleteMe", id.Generic, t)
keepChannel := id.NewIdFromString("dontDeleteMe", id.Generic, t)
// Create a test channel id
deleteChannel := id.NewIdFromString("deleteMe", id.Generic, t)
keepChannel := id.NewIdFromString("dontDeleteMe", id.Generic, t)
// Store some test messages
for i := 0; i < totalMessages; i++ {
testStr := testString + strconv.Itoa(i)
// Store some test messages
for i := 0; i < totalMessages; i++ {
testStr := testString + strconv.Itoa(i)
// Interleave the channel id to ensure cursor is behaving intelligently
thisChannel := deleteChannel
if i%2 == 0 {
thisChannel = keepChannel
}
// Interleave the channel id to ensure cursor is behaving intelligently
thisChannel := deleteChannel
if i%2 == 0 {
thisChannel = keepChannel
}
testMsgId := channel.MakeMessageID([]byte(testStr), &id.ID{1})
eventModel.ReceiveMessage(thisChannel, testMsgId, testStr, testStr,
[]byte{8, 6, 7, 5}, 0, netTime.Now(), time.Second,
rounds.Round{ID: id.Round(0)}, 0, channels.Sent)
}
testMsgId := channel.MakeMessageID([]byte(testStr), &id.ID{1})
eventModel.ReceiveMessage(thisChannel, testMsgId, testStr, testStr,
[]byte{8, 6, 7, 5}, 0, netTime.Now(), time.Second,
rounds.Round{ID: id.Round(0)}, 0, channels.Sent)
}
// Check pre-results
result, err := indexedDb.Dump(eventModel.db, messageStoreName)
if err != nil {
t.Fatalf("%+v", err)
}
if len(result) != totalMessages {
t.Errorf("Expected %d messages, got %d", totalMessages, len(result))
}
// Check pre-results
result, err := indexedDb.Dump(eventModel.db, messageStoreName)
if err != nil {
t.Fatalf("%+v", err)
}
if len(result) != totalMessages {
t.Errorf("Expected %d messages, got %d", totalMessages, len(result))
}
// Do delete
err = eventModel.deleteMsgByChannel(deleteChannel)
if err != nil {
t.Error(err)
}
// Do delete
err = eventModel.deleteMsgByChannel(deleteChannel)
if err != nil {
t.Error(err)
}
// Check final results
result, err = indexedDb.Dump(eventModel.db, messageStoreName)
if err != nil {
t.Fatalf("%+v", err)
}
if len(result) != expectedMessages {
t.Errorf("Expected %d messages, got %d", expectedMessages, len(result))
// Check final results
result, err = indexedDb.Dump(eventModel.db, messageStoreName)
if err != nil {
t.Fatalf("%+v", err)
}
if len(result) != expectedMessages {
t.Errorf("Expected %d messages, got %d", expectedMessages, len(result))
}
})
}
}
// This test is designed to prove the behavior of unique indexes.
// Inserts will not fail, they simply will not happen.
func TestWasmModel_receiveHelper_UniqueIndex(t *testing.T) {
storage.GetLocalStorage().Clear()
testString := "test_receiveHelper_UniqueIndex"
eventModel, err := newWASMModel(testString, nil, dummyCallback)
cipher, err := cryptoChannel.NewCipher([]byte("testpass"), []byte("testsalt"), 128, csprng.NewSystemRNG())
if err != nil {
t.Fatal(err)
t.Fatalf("Failed to create cipher")
}
for i, c := range []cryptoChannel.Cipher{nil, cipher} {
cs := ""
if cipher != nil {
cs = "_withCipher"
}
t.Run(fmt.Sprintf("TestWasmModel_receiveHelper_UniqueIndex%s", cs), func(t *testing.T) {
storage.GetLocalStorage().Clear()
testString := fmt.Sprintf("test_receiveHelper_UniqueIndex_%d", i)
eventModel, err := newWASMModel(testString, c, dummyCallback)
if err != nil {
t.Fatal(err)
}
// Ensure index is unique
txn, err := eventModel.db.Transaction(
idb.TransactionReadOnly, messageStoreName)
if err != nil {
t.Fatal(err)
}
store, err := txn.ObjectStore(messageStoreName)
if err != nil {
t.Fatal(err)
}
idx, err := store.Index(messageStoreMessageIndex)
if err != nil {
t.Fatal(err)
}
if isUnique, err2 := idx.Unique(); !isUnique {
t.Fatalf("Index is not unique!")
} else if err2 != nil {
t.Fatal(err2)
}
// Ensure index is unique
txn, err := eventModel.db.Transaction(
idb.TransactionReadOnly, messageStoreName)
if err != nil {
t.Fatal(err)
}
store, err := txn.ObjectStore(messageStoreName)
if err != nil {
t.Fatal(err)
}
idx, err := store.Index(messageStoreMessageIndex)
if err != nil {
t.Fatal(err)
}
if isUnique, err2 := idx.Unique(); !isUnique {
t.Fatalf("Index is not unique!")
} else if err2 != nil {
t.Fatal(err2)
}
// First message insert should succeed
testMsgId := channel.MakeMessageID([]byte(testString), &id.ID{1})
testMsg := buildMessage([]byte(testString), testMsgId.Bytes(), nil,
testString, []byte(testString), []byte{8, 6, 7, 5}, 0, netTime.Now(),
time.Second, 0, 0, channels.Sent)
_, err = eventModel.receiveHelper(testMsg, false)
if err != nil {
t.Fatal(err)
}
// First message insert should succeed
testMsgId := channel.MakeMessageID([]byte(testString), &id.ID{1})
testMsg := buildMessage([]byte(testString), testMsgId.Bytes(), nil,
testString, []byte(testString), []byte{8, 6, 7, 5}, 0, netTime.Now(),
time.Second, 0, 0, channels.Sent)
_, err = eventModel.receiveHelper(testMsg, false)
if err != nil {
t.Fatal(err)
}
// The duplicate entry won't fail, but it just silently shouldn't happen
_, err = eventModel.receiveHelper(testMsg, false)
if err != nil {
t.Fatalf("%+v", err)
}
results, err := indexedDb.Dump(eventModel.db, messageStoreName)
if err != nil {
t.Fatalf("%+v", err)
}
if len(results) != 1 {
t.Fatalf("Expected only a single message, got %d", len(results))
}
// The duplicate entry won't fail, but it just silently shouldn't happen
_, err = eventModel.receiveHelper(testMsg, false)
if err != nil {
t.Fatalf("%+v", err)
}
results, err := indexedDb.Dump(eventModel.db, messageStoreName)
if err != nil {
t.Fatalf("%+v", err)
}
if len(results) != 1 {
t.Fatalf("Expected only a single message, got %d", len(results))
}
// Now insert a message with a different message ID from the first
testMsgId2 := channel.MakeMessageID([]byte(testString), &id.ID{2})
testMsg = buildMessage([]byte(testString), testMsgId2.Bytes(), nil,
testString, []byte(testString), []byte{8, 6, 7, 5}, 0, netTime.Now(),
time.Second, 0, 0, channels.Sent)
primaryKey, err := eventModel.receiveHelper(testMsg, false)
if err != nil {
t.Fatal(err)
}
// Now insert a message with a different message ID from the first
testMsgId2 := channel.MakeMessageID([]byte(testString), &id.ID{2})
testMsg = buildMessage([]byte(testString), testMsgId2.Bytes(), nil,
testString, []byte(testString), []byte{8, 6, 7, 5}, 0, netTime.Now(),
time.Second, 0, 0, channels.Sent)
primaryKey, err := eventModel.receiveHelper(testMsg, false)
if err != nil {
t.Fatal(err)
}
// Except this time, we update the second entry to have the same
// message ID as the first
testMsg.ID = primaryKey
testMsg.MessageID = testMsgId.Bytes()
_, err = eventModel.receiveHelper(testMsg, true)
if err != nil {
t.Fatal(err)
}
// Except this time, we update the second entry to have the same
// message ID as the first
testMsg.ID = primaryKey
testMsg.MessageID = testMsgId.Bytes()
_, err = eventModel.receiveHelper(testMsg, true)
if err != nil {
t.Fatal(err)
}
// The update to duplicate message ID won't fail,
// but it just silently shouldn't happen
results, err = indexedDb.Dump(eventModel.db, messageStoreName)
if err != nil {
t.Fatalf("%+v", err)
// The update to duplicate message ID won't fail,
// but it just silently shouldn't happen
results, err = indexedDb.Dump(eventModel.db, messageStoreName)
if err != nil {
t.Fatalf("%+v", err)
}
// TODO: Convert JSON to Message, ensure Message ID fields differ
})
}
// TODO: Convert JSON to Message, ensure Message ID fields differ
}
Loading