Files
coder/coderd/debug_test.go
T
Spike Curtis bddb808b25 chore: arrange imports in a standard way (#21452)
Fixes all our Go file imports to match the preferred spec that we've _mostly_ been using. For example:

```
import (
	"context"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"golang.org/x/xerrors"
	"gopkg.in/natefinch/lumberjack.v2"

	"cdr.dev/slog/v3"
	"github.com/coder/coder/v2/codersdk/agentsdk"
	"github.com/coder/serpent"
)
```

3 groups: standard library, 3rd partly libs, Coder libs.

This PR makes the change across the codebase. The PR in the stack above modifies our formatting to maintain this state of affairs, and is a separate PR so it's possible to review that one in detail.
2026-01-08 15:24:11 +04:00

370 lines
10 KiB
Go

package coderd_test
import (
"context"
"encoding/json"
"io"
"net/http"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"cdr.dev/slog/v3/sloggers/slogtest"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/codersdk/healthsdk"
"github.com/coder/coder/v2/testutil"
)
func TestDebugHealth(t *testing.T) {
t.Parallel()
t.Run("OK", func(t *testing.T) {
t.Parallel()
var (
calls = atomic.Int64{}
ctx, cancel = context.WithTimeout(context.Background(), testutil.WaitShort)
sessionToken string
client = coderdtest.New(t, &coderdtest.Options{
HealthcheckFunc: func(_ context.Context, apiKey string) *healthsdk.HealthcheckReport {
calls.Add(1)
assert.Equal(t, sessionToken, apiKey)
return &healthsdk.HealthcheckReport{
Time: time.Now(),
}
},
HealthcheckRefresh: time.Hour, // Avoid flakes.
})
_ = coderdtest.CreateFirstUser(t, client)
)
defer cancel()
sessionToken = client.SessionToken()
for i := 0; i < 10; i++ {
res, err := client.Request(ctx, "GET", "/api/v2/debug/health", nil)
require.NoError(t, err)
_, _ = io.ReadAll(res.Body)
res.Body.Close()
require.Equal(t, http.StatusOK, res.StatusCode)
}
// The healthcheck should only have been called once.
require.EqualValues(t, 1, calls.Load())
})
t.Run("Forced", func(t *testing.T) {
t.Parallel()
var (
calls = atomic.Int64{}
ctx, cancel = context.WithTimeout(context.Background(), testutil.WaitShort)
sessionToken string
client = coderdtest.New(t, &coderdtest.Options{
HealthcheckFunc: func(_ context.Context, apiKey string) *healthsdk.HealthcheckReport {
calls.Add(1)
assert.Equal(t, sessionToken, apiKey)
return &healthsdk.HealthcheckReport{
Time: time.Now(),
}
},
HealthcheckRefresh: time.Hour, // Avoid flakes.
})
_ = coderdtest.CreateFirstUser(t, client)
)
defer cancel()
sessionToken = client.SessionToken()
for i := 0; i < 10; i++ {
res, err := client.Request(ctx, "GET", "/api/v2/debug/health?force=true", nil)
require.NoError(t, err)
_, _ = io.ReadAll(res.Body)
res.Body.Close()
require.Equal(t, http.StatusOK, res.StatusCode)
}
// The healthcheck func should have been called each time.
require.EqualValues(t, 10, calls.Load())
})
t.Run("Timeout", func(t *testing.T) {
t.Parallel()
var (
// Need to ignore errors due to ctx timeout
logger = slogtest.Make(t, &slogtest.Options{IgnoreErrors: true})
ctx, cancel = context.WithTimeout(context.Background(), testutil.WaitShort)
client = coderdtest.New(t, &coderdtest.Options{
Logger: &logger,
HealthcheckTimeout: time.Microsecond,
HealthcheckFunc: func(context.Context, string) *healthsdk.HealthcheckReport {
t := time.NewTimer(time.Second)
defer t.Stop()
select {
case <-ctx.Done():
return &healthsdk.HealthcheckReport{}
case <-t.C:
return &healthsdk.HealthcheckReport{}
}
},
})
_ = coderdtest.CreateFirstUser(t, client)
)
defer cancel()
res, err := client.Request(ctx, "GET", "/api/v2/debug/health", nil)
require.NoError(t, err)
defer res.Body.Close()
_, _ = io.ReadAll(res.Body)
require.Equal(t, http.StatusServiceUnavailable, res.StatusCode)
})
t.Run("Refresh", func(t *testing.T) {
t.Parallel()
var (
calls = make(chan struct{})
callsDone = make(chan struct{})
ctx, cancel = context.WithTimeout(context.Background(), testutil.WaitShort)
client = coderdtest.New(t, &coderdtest.Options{
HealthcheckRefresh: time.Microsecond,
HealthcheckFunc: func(context.Context, string) *healthsdk.HealthcheckReport {
calls <- struct{}{}
return &healthsdk.HealthcheckReport{}
},
})
_ = coderdtest.CreateFirstUser(t, client)
)
defer cancel()
go func() {
defer close(callsDone)
<-calls
<-time.After(testutil.IntervalFast)
<-calls
}()
res, err := client.Request(ctx, "GET", "/api/v2/debug/health", nil)
require.NoError(t, err)
defer res.Body.Close()
_, _ = io.ReadAll(res.Body)
require.Equal(t, http.StatusOK, res.StatusCode)
res, err = client.Request(ctx, "GET", "/api/v2/debug/health", nil)
require.NoError(t, err)
defer res.Body.Close()
_, _ = io.ReadAll(res.Body)
require.Equal(t, http.StatusOK, res.StatusCode)
select {
case <-callsDone:
case <-ctx.Done():
t.Fatal("timed out waiting for calls to finish")
}
})
t.Run("Deduplicated", func(t *testing.T) {
t.Parallel()
var (
ctx, cancel = context.WithTimeout(context.Background(), testutil.WaitShort)
calls int
client = coderdtest.New(t, &coderdtest.Options{
HealthcheckRefresh: time.Hour,
HealthcheckTimeout: time.Hour,
HealthcheckFunc: func(context.Context, string) *healthsdk.HealthcheckReport {
calls++
return &healthsdk.HealthcheckReport{
Time: time.Now(),
}
},
})
_ = coderdtest.CreateFirstUser(t, client)
)
defer cancel()
res, err := client.Request(ctx, "GET", "/api/v2/debug/health", nil)
require.NoError(t, err)
defer res.Body.Close()
_, _ = io.ReadAll(res.Body)
require.Equal(t, http.StatusOK, res.StatusCode)
res, err = client.Request(ctx, "GET", "/api/v2/debug/health", nil)
require.NoError(t, err)
defer res.Body.Close()
_, _ = io.ReadAll(res.Body)
require.Equal(t, http.StatusOK, res.StatusCode)
require.Equal(t, 1, calls)
})
t.Run("Text", func(t *testing.T) {
t.Parallel()
var (
ctx, cancel = context.WithTimeout(context.Background(), testutil.WaitShort)
sessionToken string
client = coderdtest.New(t, &coderdtest.Options{
HealthcheckFunc: func(_ context.Context, apiKey string) *healthsdk.HealthcheckReport {
assert.Equal(t, sessionToken, apiKey)
return &healthsdk.HealthcheckReport{
Time: time.Now(),
Healthy: true,
DERP: healthsdk.DERPHealthReport{Healthy: true},
}
},
})
_ = coderdtest.CreateFirstUser(t, client)
)
defer cancel()
sessionToken = client.SessionToken()
res, err := client.Request(ctx, "GET", "/api/v2/debug/health?format=text", nil)
require.NoError(t, err)
defer res.Body.Close()
resB, _ := io.ReadAll(res.Body)
require.Equal(t, http.StatusOK, res.StatusCode)
resStr := string(resB)
assert.Contains(t, resStr, "healthy: true")
assert.Contains(t, resStr, "derp: true")
assert.Contains(t, resStr, "access_url: false")
assert.Contains(t, resStr, "websocket: false")
assert.Contains(t, resStr, "database: false")
})
}
func TestHealthSettings(t *testing.T) {
t.Parallel()
t.Run("InitialState", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
defer cancel()
// given
adminClient := coderdtest.New(t, nil)
_ = coderdtest.CreateFirstUser(t, adminClient)
// when
settings, err := healthsdk.New(adminClient).HealthSettings(ctx)
require.NoError(t, err)
// then
require.Equal(t, healthsdk.HealthSettings{DismissedHealthchecks: []healthsdk.HealthSection{}}, settings)
})
t.Run("DismissSection", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
defer cancel()
// given
adminClient := coderdtest.New(t, nil)
_ = coderdtest.CreateFirstUser(t, adminClient)
expected := healthsdk.HealthSettings{
DismissedHealthchecks: []healthsdk.HealthSection{healthsdk.HealthSectionDERP, healthsdk.HealthSectionWebsocket},
}
// when: dismiss "derp" and "websocket"
err := healthsdk.New(adminClient).PutHealthSettings(ctx, expected)
require.NoError(t, err)
// then
settings, err := healthsdk.New(adminClient).HealthSettings(ctx)
require.NoError(t, err)
require.Equal(t, expected, settings)
// then
res, err := adminClient.Request(ctx, "GET", "/api/v2/debug/health", nil)
require.NoError(t, err)
bs, err := io.ReadAll(res.Body)
require.NoError(t, err)
defer res.Body.Close()
var hc healthsdk.HealthcheckReport
require.NoError(t, json.Unmarshal(bs, &hc))
require.True(t, hc.DERP.Dismissed)
require.True(t, hc.Websocket.Dismissed)
})
t.Run("UnDismissSection", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
defer cancel()
// given
adminClient := coderdtest.New(t, nil)
_ = coderdtest.CreateFirstUser(t, adminClient)
initial := healthsdk.HealthSettings{
DismissedHealthchecks: []healthsdk.HealthSection{healthsdk.HealthSectionDERP, healthsdk.HealthSectionWebsocket},
}
err := healthsdk.New(adminClient).PutHealthSettings(ctx, initial)
require.NoError(t, err)
expected := healthsdk.HealthSettings{
DismissedHealthchecks: []healthsdk.HealthSection{healthsdk.HealthSectionDERP},
}
// when: undismiss "websocket"
err = healthsdk.New(adminClient).PutHealthSettings(ctx, expected)
require.NoError(t, err)
// then
settings, err := healthsdk.New(adminClient).HealthSettings(ctx)
require.NoError(t, err)
require.Equal(t, expected, settings)
// then
res, err := adminClient.Request(ctx, "GET", "/api/v2/debug/health", nil)
require.NoError(t, err)
bs, err := io.ReadAll(res.Body)
require.NoError(t, err)
defer res.Body.Close()
var hc healthsdk.HealthcheckReport
require.NoError(t, json.Unmarshal(bs, &hc))
require.True(t, hc.DERP.Dismissed)
require.False(t, hc.Websocket.Dismissed)
})
t.Run("NotModified", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
defer cancel()
// given
adminClient := coderdtest.New(t, nil)
_ = coderdtest.CreateFirstUser(t, adminClient)
expected := healthsdk.HealthSettings{
DismissedHealthchecks: []healthsdk.HealthSection{healthsdk.HealthSectionDERP, healthsdk.HealthSectionWebsocket},
}
err := healthsdk.New(adminClient).PutHealthSettings(ctx, expected)
require.NoError(t, err)
// when
err = healthsdk.New(adminClient).PutHealthSettings(ctx, expected)
// then
require.Error(t, err)
require.Contains(t, err.Error(), "health settings not modified")
})
}
func TestDebugWebsocket(t *testing.T) {
t.Parallel()
t.Run("OK", func(t *testing.T) {
t.Parallel()
})
}