Skip to content

Commit 2978ac6

Browse files
JAORMXclaude
andauthored
Fix flaky TestGenerateUserAgent test using dependency injection (#2561)
The test was flaky due to race conditions from parallel subtests manipulating the shared global environment variable KUBERNETES_SERVICE_HOST. This caused intermittent failures when tests executed concurrently. This fix refactors generateUserAgent() to use dependency injection with the existing env.Reader interface, allowing tests to use isolated mock environment readers instead of modifying global state. The tests now run reliably in parallel without interference. Changes: - Add generateUserAgentWithEnv() that accepts an env.Reader parameter - Keep generateUserAgent() as a wrapper using env.OSReader for production - Update tests to use gomock-based environment readers - Remove all os.Setenv/Unsetenv calls from tests - Tests remain parallel and pass consistently with race detector 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
1 parent 794100b commit 2978ac6

File tree

2 files changed

+29
-11
lines changed

2 files changed

+29
-11
lines changed

pkg/usagemetrics/client.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"time"
1010

1111
rt "github.com/stacklok/toolhive/pkg/container/runtime"
12+
"github.com/stacklok/toolhive/pkg/env"
1213
"github.com/stacklok/toolhive/pkg/logger"
1314
"github.com/stacklok/toolhive/pkg/updates"
1415
"github.com/stacklok/toolhive/pkg/versions"
@@ -87,12 +88,18 @@ func (c *Client) SendMetrics(instanceID string, record MetricRecord) error {
8788
return nil
8889
}
8990

90-
// generateUserAgent creates the user agent string
91+
// generateUserAgent creates the user agent string using the OS environment
9192
// Format: toolhive/[local|operator] [version] [build_type] (os/arch)
9293
func generateUserAgent() string {
94+
return generateUserAgentWithEnv(&env.OSReader{})
95+
}
96+
97+
// generateUserAgentWithEnv creates the user agent string using the provided environment reader
98+
// Format: toolhive/[local|operator] [version] [build_type] (os/arch)
99+
func generateUserAgentWithEnv(envReader env.Reader) string {
93100
// Determine component type
94101
envType := "local"
95-
if rt.IsKubernetesRuntime() {
102+
if rt.IsKubernetesRuntimeWithEnv(envReader) {
96103
envType = "operator"
97104
}
98105

pkg/usagemetrics/client_test.go

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ package usagemetrics
33
import (
44
"net/http"
55
"net/http/httptest"
6-
"os"
76
"strings"
87
"testing"
98

109
"github.com/stretchr/testify/assert"
1110
"github.com/stretchr/testify/require"
11+
"go.uber.org/mock/gomock"
12+
13+
envmocks "github.com/stacklok/toolhive/pkg/env/mocks"
1214
)
1315

1416
// newTestClient creates a client for testing with a pre-set anonymous ID
@@ -50,15 +52,24 @@ func TestGenerateUserAgent(t *testing.T) {
5052
t.Run(tt.name, func(t *testing.T) {
5153
t.Parallel()
5254

53-
// Set environment variable
54-
if tt.k8sEnvValue != "" {
55-
os.Setenv("KUBERNETES_SERVICE_HOST", tt.k8sEnvValue)
56-
defer os.Unsetenv("KUBERNETES_SERVICE_HOST")
57-
} else {
58-
os.Unsetenv("KUBERNETES_SERVICE_HOST")
59-
}
55+
// Create mock environment reader
56+
ctrl := gomock.NewController(t)
57+
defer ctrl.Finish()
58+
59+
mockEnv := envmocks.NewMockReader(ctrl)
60+
61+
// Set up mock expectations
62+
mockEnv.EXPECT().
63+
Getenv("TOOLHIVE_RUNTIME").
64+
Return("").
65+
AnyTimes()
66+
67+
mockEnv.EXPECT().
68+
Getenv("KUBERNETES_SERVICE_HOST").
69+
Return(tt.k8sEnvValue).
70+
AnyTimes()
6071

61-
userAgent := generateUserAgent()
72+
userAgent := generateUserAgentWithEnv(mockEnv)
6273

6374
// Verify it starts with expected prefix
6475
assert.True(t, strings.HasPrefix(userAgent, tt.expectedPrefix),

0 commit comments

Comments
 (0)