session: fix empty config for features in sql mig

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.
This commit is contained in:
Viktor Torstensson 2025-10-26 22:29:00 -04:00
parent e3a8589283
commit 3d10572280
No known key found for this signature in database
GPG key ID: 961CC8259AE675D4
2 changed files with 85 additions and 3 deletions

View file

@ -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,29 @@ 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 bbolt feature, and nil for the
// SQL version, we override the SQL version to match the bbolt
// version.
if len(config) == 0 && migratedConf[featureName] == nil {
migratedConf[featureName] = make([]byte, 0)
}
}
}

View file

@ -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