package settings_test import ( "context" "errors" "sync" "testing" "github.com/dariuszjaniak/warp/server/internal/settings" ) // memStore is an in-memory storage.SettingsStore. type memStore struct { mu sync.Mutex values map[string]string failSet error } func newMemStore(initial map[string]string) *memStore { values := map[string]string{} for k, v := range initial { values[k] = v } return &memStore{values: values} } func (m *memStore) Get(_ context.Context, key string) (string, bool, error) { m.mu.Lock() defer m.mu.Unlock() v, ok := m.values[key] return v, ok, nil } func (m *memStore) GetAll(context.Context) (map[string]string, error) { m.mu.Lock() defer m.mu.Unlock() out := map[string]string{} for k, v := range m.values { out[k] = v } return out, nil } func (m *memStore) Set(_ context.Context, key, value string) error { m.mu.Lock() defer m.mu.Unlock() if m.failSet != nil { return m.failSet } m.values[key] = value return nil } func (m *memStore) Delete(_ context.Context, key string) error { m.mu.Lock() defer m.mu.Unlock() delete(m.values, key) return nil } func togglableDefaults() settings.Defaults { return settings.Defaults{ FederationEnabled: false, FederationAllowRuntimeToggle: true, RegistrationEnabled: false, RegistrationAllowRuntimeToggle: true, } } func TestStoredValueOverridesConfig(t *testing.T) { store := newMemStore(map[string]string{settings.KeyFederationEnabled: "true"}) svc, err := settings.New(t.Context(), store, togglableDefaults(), nil) if err != nil { t.Fatalf("New: %v", err) } if !svc.FederationEnabled() { t.Error("the persisted value should win over the config default") } } func TestPinnedSettingIgnoresStoredValue(t *testing.T) { store := newMemStore(map[string]string{settings.KeyFederationEnabled: "true"}) defaults := togglableDefaults() defaults.FederationAllowRuntimeToggle = false svc, err := settings.New(t.Context(), store, defaults, nil) if err != nil { t.Fatalf("New: %v", err) } if svc.FederationEnabled() { t.Error("with allow_runtime_toggle false the config file is authoritative") } if err := svc.SetFederationEnabled(t.Context(), true); !errors.Is(err, settings.ErrNotToggleable) { t.Errorf("SetFederationEnabled = %v, want ErrNotToggleable", err) } if _, found, _ := store.Get(t.Context(), settings.KeyFederationEnabled); !found { t.Error("the stored value should be kept for when the toggle is re-enabled") } } func TestUnparseableStoredValueFallsBackToConfig(t *testing.T) { store := newMemStore(map[string]string{settings.KeyRegistrationEnabled: "maybe"}) defaults := togglableDefaults() defaults.RegistrationEnabled = true svc, err := settings.New(t.Context(), store, defaults, nil) if err != nil { t.Fatalf("New: %v", err) } if !svc.RegistrationEnabled() { t.Error("a corrupt stored value must fall back to the config value, not to false") } } func TestSetPersistsAndNotifies(t *testing.T) { store := newMemStore(nil) svc, err := settings.New(t.Context(), store, togglableDefaults(), nil) if err != nil { t.Fatal(err) } var got []settings.Snapshot svc.Subscribe(func(s settings.Snapshot) { got = append(got, s) }) if err := svc.SetFederationEnabled(t.Context(), true); err != nil { t.Fatalf("SetFederationEnabled: %v", err) } if !svc.FederationEnabled() { t.Error("the cached value was not updated") } if v, _, _ := store.Get(t.Context(), settings.KeyFederationEnabled); v != "true" { t.Errorf("stored value = %q, want \"true\"", v) } if len(got) != 1 || !got[0].FederationEnabled { t.Fatalf("subscriber saw %v, want one snapshot with federation on", got) } // Setting the same value again changes nothing and must not notify. if err := svc.SetFederationEnabled(t.Context(), true); err != nil { t.Fatal(err) } if len(got) != 1 { t.Errorf("a no-op change notified subscribers: %v", got) } } func TestSetKeepsCacheOnStoreFailure(t *testing.T) { store := newMemStore(nil) store.failSet = errors.New("disk on fire") svc, err := settings.New(t.Context(), store, togglableDefaults(), nil) if err != nil { t.Fatal(err) } if err := svc.SetFederationEnabled(t.Context(), true); err == nil { t.Fatal("expected the store failure to surface") } if svc.FederationEnabled() { t.Error("the cached value must not change when persistence fails") } } func TestSnapshotIsConsistent(t *testing.T) { store := newMemStore(map[string]string{ settings.KeyFederationEnabled: "true", settings.KeyRegistrationEnabled: "true", }) svc, err := settings.New(t.Context(), store, togglableDefaults(), nil) if err != nil { t.Fatal(err) } snap := svc.Snapshot() if !snap.FederationEnabled || !snap.RegistrationEnabled { t.Errorf("Snapshot() = %+v, want both enabled", snap) } } func TestConcurrentReadsAndWrites(t *testing.T) { store := newMemStore(nil) svc, err := settings.New(t.Context(), store, togglableDefaults(), nil) if err != nil { t.Fatal(err) } var wg sync.WaitGroup for i := range 8 { wg.Add(1) go func() { defer wg.Done() if i%2 == 0 { _ = svc.SetFederationEnabled(t.Context(), i%4 == 0) return } _ = svc.FederationEnabled() _ = svc.Snapshot() }() } wg.Wait() }