| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221 |
- package service
- import (
- "context"
- "errors"
- "sync"
- "sync/atomic"
- "testing"
- "time"
- )
- var (
- benchmarkOpsMonitoringEnabled bool
- benchmarkOpsAdvancedSettings OpsAdvancedSettings
- )
- type opsRuntimeRefreshRepo struct {
- SettingRepository
- mu sync.RWMutex
- values map[string]string
- fail atomic.Bool
- calls atomic.Int64
- }
- func (r *opsRuntimeRefreshRepo) GetMultiple(ctx context.Context, keys []string) (map[string]string, error) {
- r.calls.Add(1)
- select {
- case <-ctx.Done():
- return nil, ctx.Err()
- default:
- }
- if r.fail.Load() {
- return nil, errors.New("settings unavailable")
- }
- r.mu.RLock()
- defer r.mu.RUnlock()
- out := make(map[string]string, len(keys))
- for _, key := range keys {
- if value, ok := r.values[key]; ok {
- out[key] = value
- }
- }
- return out, nil
- }
- func (r *opsRuntimeRefreshRepo) set(key, value string) {
- r.mu.Lock()
- r.values[key] = value
- r.mu.Unlock()
- }
- func waitForOpsRefresh(t *testing.T, timeout time.Duration, condition func() bool) {
- t.Helper()
- deadline := time.Now().Add(timeout)
- for time.Now().Before(deadline) {
- if condition() {
- return
- }
- time.Sleep(time.Millisecond)
- }
- t.Fatal("condition not satisfied before timeout")
- }
- func TestOpsRuntimeSettingsSnapshotLoadsOnceAndServesHotPath(t *testing.T) {
- repo := newRuntimeSettingRepoStub()
- repo.values[SettingKeyOpsMonitoringEnabled] = "false"
- repo.values[SettingKeyOpsAdvancedSettings] = `{"ignore_context_canceled":false,"auto_refresh_interval_seconds":45}`
- svc := &OpsService{settingRepo: repo}
- svc.initRuntimeSettings(context.Background())
- if repo.getMultipleCalls != 1 {
- t.Fatalf("startup GetMultiple calls = %d, want 1", repo.getMultipleCalls)
- }
- for range 1000 {
- if svc.IsMonitoringEnabled(context.Background()) {
- t.Fatal("monitoring enabled, want false")
- }
- cfg, err := svc.GetOpsAdvancedSettings(context.Background())
- if err != nil {
- t.Fatalf("GetOpsAdvancedSettings() error = %v", err)
- }
- if cfg.AutoRefreshIntervalSec != 45 {
- t.Fatalf("AutoRefreshIntervalSec = %d, want 45", cfg.AutoRefreshIntervalSec)
- }
- }
- if repo.getValueCalls != 0 || repo.getMultipleCalls != 1 {
- t.Fatalf("hot path touched repository: get=%d get_multiple=%d", repo.getValueCalls, repo.getMultipleCalls)
- }
- }
- func TestOpsRuntimeSettingsAdministrativeUpdatesAreImmediatelyVisible(t *testing.T) {
- svc := &OpsService{}
- svc.initRuntimeSettings(context.Background())
- svc.SetMonitoringEnabled(false)
- if svc.IsMonitoringEnabled(context.Background()) {
- t.Fatal("monitoring update was not visible")
- }
- cfg := defaultOpsAdvancedSettings()
- cfg.IgnoreNoAvailableAccounts = true
- svc.storeAdvancedSettingsSnapshot(cfg)
- got, err := svc.GetOpsAdvancedSettings(context.Background())
- if err != nil {
- t.Fatalf("GetOpsAdvancedSettings() error = %v", err)
- }
- if !got.IgnoreNoAvailableAccounts {
- t.Fatal("advanced settings update was not visible")
- }
- if svc.IsMonitoringEnabled(context.Background()) {
- t.Fatal("advanced update overwrote monitoring setting")
- }
- }
- func TestOpsRuntimeSettingsBackgroundRefreshConverges(t *testing.T) {
- repo := &opsRuntimeRefreshRepo{values: map[string]string{
- SettingKeyOpsMonitoringEnabled: "false",
- SettingKeyOpsRuntimeLogConfig: `{"persist_access_logs":false}`,
- }}
- sink := &OpsSystemLogSink{}
- svc := &OpsService{settingRepo: repo, systemLogSink: sink}
- svc.initRuntimeSettings(context.Background())
- if svc.IsMonitoringEnabled(context.Background()) {
- t.Fatal("initial monitoring state = true, want false")
- }
- repo.set(SettingKeyOpsMonitoringEnabled, "true")
- repo.set(SettingKeyOpsRuntimeLogConfig, `{"persist_access_logs":true}`)
- svc.startRuntimeSettingsRefresh(context.Background(), 5*time.Millisecond, 0, 50*time.Millisecond)
- t.Cleanup(svc.StopRuntimeSettingsRefresh)
- waitForOpsRefresh(t, time.Second, func() bool {
- return svc.IsMonitoringEnabled(context.Background()) &&
- sink.persistAccessLogs.Load() &&
- svc.RuntimeSettingsRefreshHealth().SuccessTotal > 0
- })
- }
- func TestOpsRuntimeSettingsRefreshUsesSafeAccessLogDefault(t *testing.T) {
- repo := &opsRuntimeRefreshRepo{values: map[string]string{
- SettingKeyOpsRuntimeLogConfig: `{"persist_access_logs":true}`,
- }}
- sink := &OpsSystemLogSink{}
- svc := &OpsService{settingRepo: repo, systemLogSink: sink}
- svc.initRuntimeSettings(context.Background())
- if !sink.persistAccessLogs.Load() {
- t.Fatal("valid runtime config did not enable access-log persistence")
- }
- repo.set(SettingKeyOpsRuntimeLogConfig, `{invalid-json}`)
- if err := svc.RefreshRuntimeSettings(context.Background()); err != nil {
- t.Fatalf("RefreshRuntimeSettings() error = %v", err)
- }
- if sink.persistAccessLogs.Load() {
- t.Fatal("invalid runtime config should fail closed for access-log persistence")
- }
- }
- func TestOpsRuntimeSettingsRefreshFailuresKeepLastKnownGoodSnapshot(t *testing.T) {
- repo := &opsRuntimeRefreshRepo{values: map[string]string{
- SettingKeyOpsMonitoringEnabled: "false",
- SettingKeyOpsAdvancedSettings: `{"ignore_no_available_accounts":true}`,
- SettingKeyOpsRuntimeLogConfig: `{"persist_access_logs":true}`,
- }}
- sink := &OpsSystemLogSink{}
- svc := &OpsService{settingRepo: repo, systemLogSink: sink}
- svc.initRuntimeSettings(context.Background())
- if !sink.persistAccessLogs.Load() {
- t.Fatal("initial access-log setting was not applied")
- }
- repo.fail.Store(true)
- svc.startRuntimeSettingsRefresh(context.Background(), 5*time.Millisecond, 0, 50*time.Millisecond)
- t.Cleanup(svc.StopRuntimeSettingsRefresh)
- waitForOpsRefresh(t, time.Second, func() bool {
- return svc.RuntimeSettingsRefreshHealth().FailureTotal >= 3
- })
- if svc.IsMonitoringEnabled(context.Background()) {
- t.Fatal("failed refresh overwrote last known monitoring state")
- }
- if !svc.OpsAdvancedSettingsSnapshot().IgnoreNoAvailableAccounts {
- t.Fatal("failed refresh overwrote last known advanced settings")
- }
- if !sink.persistAccessLogs.Load() {
- t.Fatal("failed refresh overwrote last known access-log setting")
- }
- }
- func TestOpsRuntimeSettingsRefreshStopEndsLifecycle(t *testing.T) {
- repo := &opsRuntimeRefreshRepo{values: map[string]string{}}
- svc := &OpsService{settingRepo: repo}
- svc.initRuntimeSettings(context.Background())
- svc.startRuntimeSettingsRefresh(context.Background(), 5*time.Millisecond, 0, 50*time.Millisecond)
- waitForOpsRefresh(t, time.Second, func() bool {
- return svc.RuntimeSettingsRefreshHealth().SuccessTotal > 0
- })
- svc.StopRuntimeSettingsRefresh()
- callsAfterStop := repo.calls.Load()
- time.Sleep(20 * time.Millisecond)
- if got := repo.calls.Load(); got != callsAfterStop {
- t.Fatalf("refresh continued after Stop: before=%d after=%d", callsAfterStop, got)
- }
- if svc.RuntimeSettingsRefreshHealth().Running {
- t.Fatal("refresh health still reports running after Stop")
- }
- // Idempotence is part of the cleanup contract.
- svc.StopRuntimeSettingsRefresh()
- }
- func BenchmarkOpsRuntimeSettingsSnapshotRead(b *testing.B) {
- svc := &OpsService{}
- svc.initRuntimeSettings(context.Background())
- ctx := context.Background()
- b.ReportAllocs()
- b.ResetTimer()
- for range b.N {
- benchmarkOpsMonitoringEnabled = svc.IsMonitoringEnabled(ctx)
- benchmarkOpsAdvancedSettings = svc.OpsAdvancedSettingsSnapshot()
- }
- }
|