33package client
44
55import (
6- "bytes"
76 "context"
8- "io"
97 "os"
108 "path/filepath"
119 "strings"
1210 "testing"
1311
12+ "github.com/spf13/viper"
1413 "github.com/stretchr/testify/assert"
1514 "github.com/stretchr/testify/require"
15+ "go.uber.org/zap"
16+ "go.uber.org/zap/zaptest/observer"
1617
1718 "github.com/stacklok/toolhive/pkg/config"
1819 "github.com/stacklok/toolhive/pkg/logger"
@@ -186,27 +187,14 @@ func CreateTestConfigProvider(t *testing.T, cfg *config.Config) (config.Provider
186187 }
187188}
188189
189- func TestFindClientConfigs (t * testing.T ) {
190- t .Parallel ()
191- logger .Initialize ()
192-
190+ //nolint:paralleltest // This test modifies global logger
191+ func TestFindClientConfigs (t * testing.T ) { // Can't run in parallel because it uses global logger
193192 // Setup a temporary home directory for testing
194193 tempHome := t .TempDir ()
195194
196195 t .Run ("InvalidConfigFileFormat" , func (t * testing.T ) {
197- t .Parallel () // Now we can use parallel since we don't modify global state
198- // Set up environment for unstructured logs and capture stderr before initializing logger
199- originalUnstructuredLogs := os .Getenv ("UNSTRUCTURED_LOGS" )
200- os .Setenv ("UNSTRUCTURED_LOGS" , "true" )
201- defer os .Setenv ("UNSTRUCTURED_LOGS" , originalUnstructuredLogs )
202-
203- // Capture log output to verify error logging
204- originalStderr := os .Stderr
205- r , w , _ := os .Pipe ()
206- os .Stderr = w
207-
208- // Re-initialize logger to use the captured stderr
209- logger .Initialize ()
196+ // Initialize in-memory test logger
197+ observerLogs := initializeTest (t )
210198
211199 // Create an invalid JSON file
212200 invalidPath := filepath .Join (tempHome , ".cursor" , "invalid.json" )
@@ -266,13 +254,13 @@ func TestFindClientConfigs(t *testing.T) {
266254 // We expect 1 config (VSCode) since cursor with invalid JSON should be skipped
267255 assert .Len (t , configs , 1 , "Should find configs for valid clients only, skipping invalid ones" )
268256
269- // Restore stderr and capture log output
270- w .Close ()
271- os .Stderr = originalStderr
257+ // Read all log entries
258+ var sb strings.Builder
259+ for _ , entry := range observerLogs .All () {
260+ sb .WriteString (entry .Message )
261+ }
272262
273- var capturedOutput bytes.Buffer
274- io .Copy (& capturedOutput , r )
275- logOutput := capturedOutput .String ()
263+ logOutput := sb .String ()
276264
277265 // Verify that the error was logged
278266 assert .Contains (t , logOutput , "Unable to process client config for cursor" , "Should log warning about cursor client config" )
@@ -281,6 +269,33 @@ func TestFindClientConfigs(t *testing.T) {
281269 })
282270}
283271
272+ func initializeTest (t * testing.T ) * observer.ObservedLogs {
273+ t .Helper ()
274+
275+ // Set log level based on current debug flag
276+ var level zap.AtomicLevel
277+ if viper .GetBool ("debug" ) {
278+ level = zap .NewAtomicLevelAt (zap .DebugLevel )
279+ } else {
280+ level = zap .NewAtomicLevelAt (zap .InfoLevel )
281+ }
282+
283+ core , observedLogs := observer .New (level )
284+ testLogger := zap .New (core )
285+
286+ // Save original logger for restoring
287+ originalLogger := zap .L ()
288+
289+ zap .ReplaceGlobals (testLogger )
290+
291+ // Restore original logger
292+ t .Cleanup (func () {
293+ zap .ReplaceGlobals (originalLogger )
294+ })
295+
296+ return observedLogs
297+ }
298+
284299func TestSuccessfulClientConfigOperations (t * testing.T ) {
285300 t .Parallel ()
286301 logger .Initialize ()
0 commit comments