diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e1a8ef567bb5b16fa78d4bf6b5537c5103d45dc9..b145fc985b6ea48f9ef9ba9d06c0ba082b0f8955 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -25,6 +25,8 @@ stages: build: stage: build + except: + - tags script: - go mod vendor -v - mkdir -p release @@ -36,6 +38,8 @@ build: wasm-test: stage: test + except: + - tags script: - export PATH=/root/go/bin:$PATH - echo > utils/utils_js.s @@ -44,23 +48,29 @@ wasm-test: go-test: stage: test + except: + - tags script: - go mod vendor -v - go test ./... -v version_check: stage: version_check + except: + - tags only: - master - release image: $DOCKER_IMAGE script: - GITTAG=$(git describe --tags) - - CODEVERS=$(cat utils/version.go | grep "const SEMVER =" | cut -d ' ' -f4 | tr -d '"') + - CODEVERS=$(cat storage/version.go | grep "const SEMVER =" | cut -d ' ' -f4 | tr -d '"') - if [[ $GITTAG != $CODEVERS ]]; then echo "VERSION NUMBER BAD $GITTAG != $CODEVER"; exit -1; fi tag: stage: build + except: + - tags image: $DOCKER_IMAGE script: - git remote add origin_tags git@$GITLAB_SERVER:elixxir/xxdk-wasm.git || true @@ -76,6 +86,8 @@ tag: # master/release, this will fail to pull the latest client, and the docs will not update. doc-update: stage: doc-update + except: + - tags image: $DOCKER_IMAGE script: # We use GOPRIVATE blank because not want to directly pull client, we want to use the public cache. diff --git a/indexedDb/channels/implementation.go b/indexedDb/channels/implementation.go index 36c5980921de39d694c1ab0b0df3ecb702bb439c..bcd957f13863d47e2ce493f6c00b5e5bfd4775b4 100644 --- a/indexedDb/channels/implementation.go +++ b/indexedDb/channels/implementation.go @@ -288,15 +288,20 @@ func (w *wasmModel) UpdateSentStatus(uuid uint64, // Use the key to get the existing Message currentMsg, err := indexedDb.Get(w.db, messageStoreName, key) if err != nil { + jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr, + "Unable to get message: %+v", err)) return } // Extract the existing Message and update the Status newMessage := &Message{} - err = json.Unmarshal([]byte(currentMsg), newMessage) + err = json.Unmarshal([]byte(utils.JsToJson(currentMsg)), newMessage) if err != nil { + jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr, + "Could not JSON unmarshal message: %+v", err)) return } + newMessage.Status = uint8(status) if !messageID.Equals(cryptoChannel.MessageID{}) { newMessage.MessageID = messageID.Bytes() diff --git a/indexedDb/channels/implementation_test.go b/indexedDb/channels/implementation_test.go index a14de4f928575189915a04d84a634d160e44d1d8..27f33649eac4d02514a09b6e2117fb148c6b764e 100644 --- a/indexedDb/channels/implementation_test.go +++ b/indexedDb/channels/implementation_test.go @@ -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" ) @@ -36,297 +38,415 @@ func TestMain(m *testing.M) { func dummyCallback(uint64, *id.ID, bool) {} -// Test wasmModel.UpdateSentStatus happy path and ensure fields don't change. -func Test_wasmModel_UpdateSentStatus(t *testing.T) { - testString := "test" - testMsgId := channel.MakeMessageID([]byte(testString), &id.ID{1}) - eventModel, err := newWASMModel(testString, nil, dummyCallback) +// Happy path, insert message and look it up +func TestWasmModel_msgIDLookup(t *testing.T) { + 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) + } - // Store a test message - testMsg := buildMessage([]byte(testString), testMsgId.Bytes(), nil, - testString, []byte(testString), []byte{8, 6, 7, 5}, 0, 0, netTime.Now(), - time.Second, 0, 0, channels.Sent) - uuid, 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!") + } + }) } +} - // Ensure one message is stored - results, err := indexedDb.Dump(eventModel.db, messageStoreName) +// Test wasmModel.UpdateSentStatus happy path and ensure fields don't change. +func Test_wasmModel_UpdateSentStatus(t *testing.T) { + cipher, err := cryptoChannel.NewCipher([]byte("testpass"), []byte("testsalt"), 128, csprng.NewSystemRNG()) if err != nil { - t.Fatalf("%+v", err) - } - if len(results) != 1 { - t.Fatalf("Expected 1 message to exist") + 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) + } - // Update the sentStatus - expectedStatus := channels.Failed - eventModel.UpdateSentStatus(uuid, testMsgId, netTime.Now(), - rounds.Round{ID: 8675309}, expectedStatus) + // 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) + } - // 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) - } + // 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) + + // 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) { - 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, 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, 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) { - 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, 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) { - 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, 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, 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 } diff --git a/indexedDb/channels/init.go b/indexedDb/channels/init.go index 0b52434ff260ee3df42c8b0f201527f08377205b..12d6bb8840f3d75c8778b871ac883a84ad008ada 100644 --- a/indexedDb/channels/init.go +++ b/indexedDb/channels/init.go @@ -211,7 +211,7 @@ func (w *wasmModel) hackTestDb() error { if err != nil { return err } - if len(result) == 0 { + if result.IsUndefined() { return errors.Errorf("Failed to test db, record not present") } return nil diff --git a/indexedDb/channels/model.go b/indexedDb/channels/model.go index 078d6bd67f7c43d9e50373b7801f69937cfce2ac..629c9f43353ade792f024f88aba769c3c89f22c0 100644 --- a/indexedDb/channels/model.go +++ b/indexedDb/channels/model.go @@ -61,7 +61,7 @@ type Message struct { // User cryptographic Identity struct -- could be pulled out Pubkey []byte `json:"pubkey"` - DmToken uint32 `json:"dm_token"` + DmToken []byte `json:"dm_token"` CodesetVersion uint8 `json:"codeset_version"` } diff --git a/indexedDb/utils.go b/indexedDb/utils.go index 7d7e7e6b394eb492ea09519f5939f4fd5c79ca59..1aa6ff45805c1c65b9ad27e51a8b0590e7b8201d 100644 --- a/indexedDb/utils.go +++ b/indexedDb/utils.go @@ -32,25 +32,25 @@ func NewContext() (context.Context, context.CancelFunc) { } // Get is a generic helper for getting values from the given [idb.ObjectStore]. -func Get(db *idb.Database, objectStoreName string, key js.Value) (string, error) { +func Get(db *idb.Database, objectStoreName string, key js.Value) (js.Value, error) { parentErr := errors.Errorf("failed to Get %s/%s", objectStoreName, key) // Prepare the Transaction txn, err := db.Transaction(idb.TransactionReadOnly, objectStoreName) if err != nil { - return "", errors.WithMessagef(parentErr, + return js.Undefined(), errors.WithMessagef(parentErr, "Unable to create Transaction: %+v", err) } store, err := txn.ObjectStore(objectStoreName) if err != nil { - return "", errors.WithMessagef(parentErr, + return js.Undefined(), errors.WithMessagef(parentErr, "Unable to get ObjectStore: %+v", err) } // Perform the operation getRequest, err := store.Get(key) if err != nil { - return "", errors.WithMessagef(parentErr, + return js.Undefined(), errors.WithMessagef(parentErr, "Unable to Get from ObjectStore: %+v", err) } @@ -59,14 +59,17 @@ func Get(db *idb.Database, objectStoreName string, key js.Value) (string, error) resultObj, err := getRequest.Await(ctx) cancel() if err != nil { - return "", errors.WithMessagef(parentErr, + return js.Undefined(), errors.WithMessagef(parentErr, "Unable to get from ObjectStore: %+v", err) + } else if resultObj.IsUndefined() { + return js.Undefined(), errors.WithMessage(parentErr, + "Unable to get from ObjectStore: result is undefined") } // Process result into string - resultStr := utils.JsToJson(resultObj) - jww.DEBUG.Printf("Got from %s/%s: %s", objectStoreName, key, resultStr) - return resultStr, nil + jww.DEBUG.Printf("Got from %s/%s: %s", + objectStoreName, key, utils.JsToJson(resultObj)) + return resultObj, nil } // GetIndex is a generic helper for getting values from the given @@ -79,24 +82,24 @@ func GetIndex(db *idb.Database, objectStoreName string, // Prepare the Transaction txn, err := db.Transaction(idb.TransactionReadOnly, objectStoreName) if err != nil { - return js.Value{}, errors.WithMessagef(parentErr, + return js.Undefined(), errors.WithMessagef(parentErr, "Unable to create Transaction: %+v", err) } store, err := txn.ObjectStore(objectStoreName) if err != nil { - return js.Value{}, errors.WithMessagef(parentErr, + return js.Undefined(), errors.WithMessagef(parentErr, "Unable to get ObjectStore: %+v", err) } idx, err := store.Index(indexName) if err != nil { - return js.Value{}, errors.WithMessagef(parentErr, + return js.Undefined(), errors.WithMessagef(parentErr, "Unable to get Index: %+v", err) } // Perform the operation getRequest, err := idx.Get(key) if err != nil { - return js.Value{}, errors.WithMessagef(parentErr, + return js.Undefined(), errors.WithMessagef(parentErr, "Unable to Get from ObjectStore: %+v", err) } @@ -105,14 +108,16 @@ func GetIndex(db *idb.Database, objectStoreName string, resultObj, err := getRequest.Await(ctx) cancel() if err != nil { - return js.Value{}, errors.WithMessagef(parentErr, + return js.Undefined(), errors.WithMessagef(parentErr, "Unable to get from ObjectStore: %+v", err) + } else if resultObj.IsUndefined() { + return js.Undefined(), errors.WithMessage(parentErr, + "Unable to get from ObjectStore: result is undefined") } // Process result into string - resultStr := utils.JsToJson(resultObj) jww.DEBUG.Printf("Got from %s/%s/%s: %s", - objectStoreName, indexName, key, resultStr) + objectStoreName, indexName, key, utils.JsToJson(resultObj)) return resultObj, nil } @@ -143,7 +148,7 @@ func Put(db *idb.Database, objectStoreName string, value js.Value) (*idb.Request return nil, errors.Errorf("Putting value failed: %+v", err) } jww.DEBUG.Printf("Successfully put value in %s: %v", - objectStoreName, value.String()) + objectStoreName, utils.JsToJson(value)) return request, nil } diff --git a/indexedDb/utils_test.go b/indexedDb/utils_test.go new file mode 100644 index 0000000000000000000000000000000000000000..1c657ebd5dbb4607c977323dc8883042989f05f9 --- /dev/null +++ b/indexedDb/utils_test.go @@ -0,0 +1,80 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2022 xx foundation // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file. // +//////////////////////////////////////////////////////////////////////////////// + +//go:build js && wasm + +package indexedDb + +import ( + "github.com/hack-pad/go-indexeddb/idb" + "strings" + "syscall/js" + "testing" +) + +// Error path: Tests that Get returns an error when trying to get a message that +// does not exist. +func TestGet_NoMessageError(t *testing.T) { + db := newTestDB("messages", "index", t) + + _, err := Get(db, "messages", js.ValueOf(5)) + if err == nil || !strings.Contains(err.Error(), "undefined") { + t.Errorf("Did not get expected error when getting a message that "+ + "does not exist: %+v", err) + } +} + +// Error path: Tests that GetIndex returns an error when trying to get a message +// that does not exist. +func TestGetIndex_NoMessageError(t *testing.T) { + db := newTestDB("messages", "index", t) + + _, err := GetIndex(db, "messages", "index", js.ValueOf(5)) + if err == nil || !strings.Contains(err.Error(), "undefined") { + t.Errorf("Did not get expected error when getting a message that "+ + "does not exist: %+v", err) + } +} + +// newTestDB creates a new idb.Database for testing. +func newTestDB(name, index string, t *testing.T) *idb.Database { + // Attempt to open database object + ctx, cancel := NewContext() + defer cancel() + openRequest, err := idb.Global().Open(ctx, "databaseName", 0, + func(db *idb.Database, _ uint, _ uint) error { + storeOpts := idb.ObjectStoreOptions{ + KeyPath: js.ValueOf("id"), + AutoIncrement: true, + } + + // Build Message ObjectStore and Indexes + messageStore, err := db.CreateObjectStore(name, storeOpts) + if err != nil { + return err + } + + _, err = messageStore.CreateIndex( + index, js.ValueOf("id"), idb.IndexOptions{}) + if err != nil { + return err + } + + return nil + }) + if err != nil { + t.Fatal(err) + } + + // Wait for database open to finish + db, err := openRequest.Await(ctx) + if err != nil { + t.Fatal(err) + } + + return db +} diff --git a/storage/version.go b/storage/version.go index 61bc23937a227f407b059b08362938c62222d558..9530aca55472b737e1905fe233f70a78689a52b5 100644 --- a/storage/version.go +++ b/storage/version.go @@ -18,7 +18,7 @@ import ( ) // SEMVER is the current semantic version of xxDK WASM. -const SEMVER = "0.1.8" +const SEMVER = "0.1.13" // Storage keys. const (