package walletconnect import ( "strconv" "testing" "database/sql" "github.com/status-im/status-go/services/wallet/common" "github.com/stretchr/testify/require" ) type urlOverride *string type timestampOverride *int64 // testSession will override defaults for the fields that are not null type testSession struct { url urlOverride created timestampOverride expiry timestampOverride disconnected *bool testChains *bool } const testDappUrl = "https://test.url/" // generateTestData generates alternative disconnected and active sessions starting with the active one // timestamps start with 1234567890 and increase by 1 for each session // all sessions will share the same two pairing sessions (roll over after index 1) // testChains is false if not overridden func generateTestData(sessions []testSession) []DBSession { res := make([]DBSession, len(sessions)) pairingIdx := 0 for i := 0; i < len(res); i++ { strI := strconv.Itoa(i) if i%2 == 0 { pairingIdx++ } pairingIdxStr := strconv.Itoa(pairingIdx) s := sessions[i] url := testDappUrl + strI if s.url != nil { url = *s.url } createdTimestamp := 1234567890 + int64(i) if s.created != nil { createdTimestamp = *s.created } expiryTimestamp := createdTimestamp + 1000 + int64(i) if s.expiry != nil { expiryTimestamp = *s.expiry } disconnected := (i % 2) != 0 if s.disconnected != nil { disconnected = *s.disconnected } testChains := false if s.testChains != nil { testChains = *s.testChains } res[i] = DBSession{ Topic: Topic(strI + "aaaaaa1234567890"), Disconnected: disconnected, SessionJSON: "{}", Expiry: expiryTimestamp, CreatedTimestamp: createdTimestamp, PairingTopic: Topic(pairingIdxStr + "bbbbbb1234567890"), TestChains: testChains, DBDApp: DBDApp{ URL: url, Name: "TestApp" + strI, IconURL: "https://test.icon" + strI, }, } } return res } func insertTestData(t *testing.T, db *sql.DB, entries []DBSession) { for _, entry := range entries { err := UpsertSession(db, entry) require.NoError(t, err) } } func TestInsertUpdateAndGetSession(t *testing.T) { db, close := SetupTestDB(t) defer close() entry := generateTestData(make([]testSession, 1))[0] err := UpsertSession(db, entry) require.NoError(t, err) retrievedSession, err := GetSessionByTopic(db, entry.Topic) require.NoError(t, err) require.Equal(t, entry, *retrievedSession) updatedEntry := entry updatedEntry.Disconnected = true updatedEntry.Expiry = 1111111111 err = UpsertSession(db, updatedEntry) require.NoError(t, err) retrievedSession, err = GetSessionByTopic(db, updatedEntry.Topic) require.NoError(t, err) require.Equal(t, updatedEntry, *retrievedSession) } func TestInsertAndGetSessionsByPairingTopic(t *testing.T) { db, close := SetupTestDB(t) defer close() generatedSessions := generateTestData(make([]testSession, 4)) for _, session := range generatedSessions { err := UpsertSession(db, session) require.NoError(t, err) } retrievedSessions, err := GetSessionsByPairingTopic(db, generatedSessions[2].Topic) require.NoError(t, err) require.Equal(t, 0, len(retrievedSessions)) retrievedSessions, err = GetSessionsByPairingTopic(db, generatedSessions[2].PairingTopic) require.NoError(t, err) require.Equal(t, 2, len(retrievedSessions)) for i := 2; i < 4; i++ { found := false for _, session := range retrievedSessions { if session.Topic == generatedSessions[i].Topic { found = true require.Equal(t, generatedSessions[i], session) } } require.True(t, found) } } func TestGet(t *testing.T) { db, close := SetupTestDB(t) defer close() entries := generateTestData(make([]testSession, 3)) insertTestData(t, db, entries) retrievedSession, err := GetSessionByTopic(db, entries[1].Topic) require.NoError(t, err) require.Equal(t, entries[1], *retrievedSession) err = DeleteSession(db, entries[1].Topic) require.NoError(t, err) deletedSession, err := GetSessionByTopic(db, entries[1].Topic) require.ErrorIs(t, err, sql.ErrNoRows) require.Nil(t, deletedSession) } func TestGetActiveSessions(t *testing.T) { db, close := SetupTestDB(t) defer close() // insert two disconnected and three active sessions entries := generateTestData(make([]testSession, 5)) insertTestData(t, db, entries) activeSessions, err := GetActiveSessions(db, entries[2].Expiry) require.NoError(t, err) require.Equal(t, 2, len(activeSessions)) // Expect newest on top require.Equal(t, entries[4], activeSessions[0]) require.Equal(t, entries[2], activeSessions[1]) } func TestDeleteSession(t *testing.T) { db, close := SetupTestDB(t) defer close() entries := generateTestData(make([]testSession, 3)) insertTestData(t, db, entries) err := DeleteSession(db, entries[1].Topic) require.NoError(t, err) sessions, err := GetSessions(db) require.NoError(t, err) require.Equal(t, 2, len(sessions)) require.Equal(t, entries[0], sessions[1]) require.Equal(t, entries[2], sessions[0]) err = DeleteSession(db, entries[0].Topic) require.NoError(t, err) err = DeleteSession(db, entries[2].Topic) require.NoError(t, err) sessions, err = GetSessions(db) require.NoError(t, err) require.Equal(t, 0, len(sessions)) } // urlFor prepares a value to be used in testSession func urlFor(i int) urlOverride { return common.NewAndSet(testDappUrl + strconv.Itoa(i)) } // at prepares a value to be used in testSession func at(i int) timestampOverride { return common.NewAndSet(int64(i)) } // TestGetActiveDapps_JoinWorksAsExpected also validates that GetActiveDapps returns the dapps in the order of the last first time added func TestGetActiveDapps_JoinWorksAsExpected(t *testing.T) { db, close := SetupTestDB(t) defer close() not := common.NewAndSet(false) // The first creation date is 1, 2, 3 but the last name update is, respectively, 1, 4, 5 entries := generateTestData([]testSession{ {url: urlFor(1), created: at(1), disconnected: not}, {url: urlFor(1), created: at(2), disconnected: not}, {url: urlFor(2), created: at(3), disconnected: not}, {url: urlFor(3), created: at(4), disconnected: not}, {url: urlFor(2), created: at(5), disconnected: not}, {url: urlFor(3), created: at(6), disconnected: not}, }) insertTestData(t, db, entries) getTestnet := false validAtTimestamp := entries[0].Expiry dapps, err := GetActiveDapps(db, validAtTimestamp, getTestnet) require.NoError(t, err) require.Equal(t, 3, len(dapps)) require.Equal(t, 3, len(dapps)) require.Equal(t, entries[5].Name, dapps[0].Name) require.Equal(t, entries[4].Name, dapps[1].Name) require.Equal(t, entries[1].Name, dapps[2].Name) } // TestGetActiveDapps_ActiveWorksAsExpected tests the combination of disconnected and expired sessions func TestGetActiveDapps_ActiveWorksAsExpected(t *testing.T) { db, close := SetupTestDB(t) defer close() not := common.NewAndSet(false) yes := common.NewAndSet(true) timeNow := 4 entries := generateTestData([]testSession{ {url: urlFor(1), expiry: at(timeNow - 3), disconnected: not}, {url: urlFor(1), expiry: at(timeNow - 2), disconnected: yes}, {url: urlFor(2), expiry: at(timeNow - 2), disconnected: not}, {url: urlFor(3), expiry: at(timeNow - 1), disconnected: yes}, // ----- timeNow {url: urlFor(3), expiry: at(timeNow + 1), disconnected: not}, {url: urlFor(4), expiry: at(timeNow + 1), disconnected: yes}, {url: urlFor(4), expiry: at(timeNow + 2), disconnected: not}, {url: urlFor(5), expiry: at(timeNow + 2), disconnected: yes}, {url: urlFor(6), expiry: at(timeNow + 3), disconnected: not}, }) insertTestData(t, db, entries) getTestnet := false dapps, err := GetActiveDapps(db, int64(timeNow), getTestnet) require.NoError(t, err) require.Equal(t, 3, len(dapps)) } // TestGetActiveDapps_TestChainsWorksAsExpected tests the combination of disconnected and expired sessions func TestGetActiveDapps_TestChainsWorksAsExpected(t *testing.T) { db, close := SetupTestDB(t) defer close() not := common.NewAndSet(false) yes := common.NewAndSet(true) timeNow := 4 entries := generateTestData([]testSession{ {url: urlFor(1), testChains: not, expiry: at(timeNow - 3), disconnected: not}, {url: urlFor(2), testChains: yes, expiry: at(timeNow - 2), disconnected: not}, {url: urlFor(2), testChains: not, expiry: at(timeNow - 1), disconnected: not}, // ----- timeNow {url: urlFor(3), testChains: not, expiry: at(timeNow + 1), disconnected: not}, {url: urlFor(4), testChains: not, expiry: at(timeNow + 2), disconnected: not}, {url: urlFor(4), testChains: yes, expiry: at(timeNow + 3), disconnected: not}, {url: urlFor(5), testChains: yes, expiry: at(timeNow + 4), disconnected: not}, }) insertTestData(t, db, entries) getTestnet := true dapps, err := GetActiveDapps(db, int64(timeNow), getTestnet) require.NoError(t, err) require.Equal(t, 2, len(dapps)) } // TestGetDapps_EmptyDB tests that an empty database will return an empty list func TestGetDapps_EmptyDB(t *testing.T) { db, close := SetupTestDB(t) defer close() entries := generateTestData([]testSession{}) insertTestData(t, db, entries) getTestnet := false validAtTimestamp := int64(0) dapps, err := GetActiveDapps(db, validAtTimestamp, getTestnet) require.NoError(t, err) require.Equal(t, 0, len(dapps)) } // TestGetDapps_OrphanDapps tests that missing session will place the dapp at the end func TestGetDapps_OrphanDapps(t *testing.T) { db, close := SetupTestDB(t) defer close() not := common.NewAndSet(false) entries := generateTestData([]testSession{ {url: urlFor(1), disconnected: not}, {url: urlFor(2), disconnected: not}, {url: urlFor(2), disconnected: not}, }) insertTestData(t, db, entries) err := DeleteSession(db, entries[1].Topic) require.NoError(t, err) err = DeleteSession(db, entries[2].Topic) require.NoError(t, err) getTestnet := false validAtTimestamp := entries[0].Expiry dapps, err := GetActiveDapps(db, validAtTimestamp, getTestnet) require.NoError(t, err) // The orphan dapp is not considered active require.Equal(t, 1, len(dapps)) require.Equal(t, entries[0].Name, dapps[0].Name) } func TestDisconnectSession(t *testing.T) { db, close := SetupTestDB(t) defer close() not := common.NewAndSet(false) entries := generateTestData([]testSession{ {url: urlFor(1), disconnected: not}, {url: urlFor(2), disconnected: not}, {url: urlFor(2), disconnected: not}, }) insertTestData(t, db, entries) activeSessions, err := GetActiveSessions(db, 0) require.NoError(t, err) require.Equal(t, 3, len(activeSessions)) getTestnet := false validAtTimestamp := entries[0].Expiry dapps, err := GetActiveDapps(db, validAtTimestamp, getTestnet) require.NoError(t, err) require.Equal(t, 2, len(dapps)) err = DisconnectSession(db, entries[1].Topic) require.NoError(t, err) activeSessions, err = GetActiveSessions(db, 0) require.NoError(t, err) require.Equal(t, 2, len(activeSessions)) err = DisconnectSession(db, entries[2].Topic) require.NoError(t, err) activeSessions, err = GetActiveSessions(db, 0) require.NoError(t, err) require.Equal(t, 1, len(activeSessions)) dapps, err = GetActiveDapps(db, validAtTimestamp, getTestnet) require.NoError(t, err) require.Equal(t, 1, len(dapps)) require.Equal(t, entries[0].Name, dapps[0].Name) }