diff --git a/session/sql_migration.go b/session/sql_migration.go index 5e280d50c..a2498beda 100644 --- a/session/sql_migration.go +++ b/session/sql_migration.go @@ -198,6 +198,7 @@ func migrateSessionsToSQLAndValidate(ctx context.Context, overrideSessionTimeZone(migratedSession) overrideMacaroonRecipe(kvSession, migratedSession) overrideRemovedAccount(kvSession, migratedSession) + overrideFeatureConfig(kvSession, migratedSession) if !reflect.DeepEqual(kvSession, migratedSession) { diff := difflib.UnifiedDiff{ @@ -530,3 +531,28 @@ func overrideRemovedAccount(kvSession *Session, migratedSession *Session) { }, ) } + +// overrideFeatureConfig overrides a specific feature's config for the SQL +// session in a certain scenario: +// +// In the bbolt store, an empty config for a feature is represented as an empty +// array, while in for the SQL store, the same config is represented as nil. +// Therefore, in the scenario where a specific feature has an empty config, we +// override the SQL FeatureConfig for that feature to also be set to an empty +// array. This is needed to ensure that the deep equals check in the migration +// validation does not fail in this scenario. +func overrideFeatureConfig(kvSession *Session, mSession *Session) { + // If FeatureConfig is not set for both sessions, we return early. + if kvSession.FeatureConfig == nil || mSession.FeatureConfig == nil { + return + } + + migratedConf := *mSession.FeatureConfig + for featureName, config := range *kvSession.FeatureConfig { + // If the config is empty for the feature, we override + // the SQL version. + if len(config) == 0 { + migratedConf[featureName] = make([]byte, 0) + } + } +} diff --git a/session/sql_migration_test.go b/session/sql_migration_test.go index 1cb7ba751..fb8763808 100644 --- a/session/sql_migration_test.go +++ b/session/sql_migration_test.go @@ -81,6 +81,10 @@ func TestSessionsStoreMigration(t *testing.T) { // session doesn't. overrideRemovedAccount(kvSession, sqlSession) + // Finally, we also need to override specific feature's + // config if they are empty. + overrideFeatureConfig(kvSession, sqlSession) + assertEqualSessions(t, kvSession, sqlSession) } @@ -200,6 +204,48 @@ func TestSessionsStoreMigration(t *testing.T) { return getBoltStoreSessions(t, store) }, }, + { + name: "one session with a feature config with empty", + populateDB: func(t *testing.T, store *BoltStore, + _ accounts.Store) []*Session { + + // Set a feature config, which has an empty + // entry for a specific feature. + featureConfig := map[string][]byte{ + "AutoFees": {}, + } + + _, err := store.NewSession( + ctx, "test", TypeMacaroonAdmin, + time.Unix(1000, 0), "", + WithFeatureConfig(featureConfig), + ) + require.NoError(t, err) + + return getBoltStoreSessions(t, store) + }, + }, + { + name: "one session with a feature config with nil", + populateDB: func(t *testing.T, store *BoltStore, + _ accounts.Store) []*Session { + + // Set a feature config, which has a nil entry + // for a specific feature. + featureConfig := map[string][]byte{ + "AutoFees": nil, + } + + _, err := store.NewSession( + ctx, "test", TypeMacaroonAdmin, + time.Unix(1000, 0), "", + WithFeatureConfig(featureConfig), + ) + require.NoError(t, err) + + return getBoltStoreSessions(t, store) + }, + }, { name: "one session with dev server", populateDB: func(t *testing.T, store *BoltStore, @@ -801,9 +847,18 @@ func randomPrivacyFlags() PrivacyFlags { func randomFeatureConfig() FeaturesConfig { featureConfig := make(FeaturesConfig) for i := range rand.Intn(10) { - featureName := fmt.Sprintf("feature%d", i) - featureValue := []byte{byte(rand.Int31())} - featureConfig[featureName] = featureValue + var ( + featureName = fmt.Sprintf("feature%d", i) + configValue []byte + ) + + // For the vast majority of the features, we set a value for the + // feature's config. But in 1/5 of the cases, we set an empty + // config. + if rand.Intn(5) != 0 { + configValue = []byte{byte(rand.Int31())} + } + featureConfig[featureName] = configValue } return featureConfig