diff options
| author | Jeff Halter <868228+jhalter@users.noreply.github.com> | 2026-05-28 16:22:24 -0700 |
|---|---|---|
| committer | Jeff Halter <868228+jhalter@users.noreply.github.com> | 2026-05-28 16:22:24 -0700 |
| commit | b772019454ebb804c313e717ae98eb68430e780e (patch) | |
| tree | a12ffc1e5de4d7e1612edaea03174043a6c55784 /internal | |
| parent | 2f46f87177234070044b5304ca3e0db471699ad8 (diff) | |
Refactor Stats to typed keys and fix peak-tracking race
Replace the map-backed Stats counter with a fixed [numStats]int array
indexed by a new StatKey enum, eliminating the hand-maintained map
initialization and the parallel string-keyed Values() map.
Fix a check-then-act race in the connection peak tracking: the old
Get-then-Set across two lock acquisitions could let concurrent
connections clobber each other's update. The new atomic Max method does
the compare-and-set under a single lock.
Values() now returns a typed StatValues struct whose JSON tags preserve
the existing /api/v1/stats wire format.
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/mobius/api_test.go | 33 |
1 files changed, 15 insertions, 18 deletions
diff --git a/internal/mobius/api_test.go b/internal/mobius/api_test.go index d448b1b..ae9916b 100644 --- a/internal/mobius/api_test.go +++ b/internal/mobius/api_test.go @@ -173,19 +173,15 @@ func (m *mockClientMgr) Delete(id hotline.ClientID) { } type mockCounter struct { - vals map[string]interface{} + vals hotline.StatValues } -func (m *mockCounter) Increment(_ ...int) {} -func (m *mockCounter) Decrement(_ int) {} -func (m *mockCounter) Set(_, _ int) {} -func (m *mockCounter) Get(_ int) int { return 0 } -func (m *mockCounter) Values() map[string]interface{} { - if m.vals == nil { - return map[string]interface{}{} - } - return m.vals -} +func (m *mockCounter) Increment(_ ...hotline.StatKey) {} +func (m *mockCounter) Decrement(_ ...hotline.StatKey) {} +func (m *mockCounter) Set(_ hotline.StatKey, _ int) {} +func (m *mockCounter) Max(_ hotline.StatKey, _ int) {} +func (m *mockCounter) Get(_ hotline.StatKey) int { return 0 } +func (m *mockCounter) Values() hotline.StatValues { return m.vals } // --- Test helper --- @@ -651,9 +647,9 @@ func TestShutdownHandler(t *testing.T) { func TestRenderStats(t *testing.T) { t.Run("returns JSON stats", func(t *testing.T) { srv, _, _, counter := newTestAPIServer(t, "") - counter.vals = map[string]interface{}{ - "connections": 42, - "downloads": 10, + counter.vals = hotline.StatValues{ + CurrentlyConnected: 42, + DownloadCounter: 10, } req := httptest.NewRequest(http.MethodGet, "/api/v1/stats", nil) @@ -666,11 +662,11 @@ func TestRenderStats(t *testing.T) { var stats map[string]interface{} err := json.Unmarshal(rr.Body.Bytes(), &stats) require.NoError(t, err) - assert.Equal(t, float64(42), stats["connections"]) - assert.Equal(t, float64(10), stats["downloads"]) + assert.Equal(t, float64(42), stats["CurrentlyConnected"]) + assert.Equal(t, float64(10), stats["DownloadCounter"]) }) - t.Run("returns empty stats when no data", func(t *testing.T) { + t.Run("returns zero-valued stats when no data", func(t *testing.T) { srv, _, _, _ := newTestAPIServer(t, "") req := httptest.NewRequest(http.MethodGet, "/api/v1/stats", nil) @@ -683,6 +679,7 @@ func TestRenderStats(t *testing.T) { var stats map[string]interface{} err := json.Unmarshal(rr.Body.Bytes(), &stats) require.NoError(t, err) - assert.Empty(t, stats) + assert.Equal(t, float64(0), stats["CurrentlyConnected"]) + assert.Contains(t, stats, "Since") }) } |