mirror of
https://github.com/coder/coder.git
synced 2026-06-07 23:18:20 +00:00
268a50c193
Fixes coder/internal#479 Fixes coder/internal#480
164 lines
4.7 KiB
Go
164 lines
4.7 KiB
Go
package agentcontainers
|
|
|
|
import (
|
|
"math/rand"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/mock/gomock"
|
|
|
|
"cdr.dev/slog"
|
|
"cdr.dev/slog/sloggers/slogtest"
|
|
"github.com/coder/coder/v2/agent/agentcontainers/acmock"
|
|
"github.com/coder/coder/v2/codersdk"
|
|
"github.com/coder/coder/v2/testutil"
|
|
"github.com/coder/quartz"
|
|
)
|
|
|
|
func TestAPI(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// List tests the API.getContainers method using a mock
|
|
// implementation. It specifically tests caching behavior.
|
|
t.Run("List", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
fakeCt := fakeContainer(t)
|
|
fakeCt2 := fakeContainer(t)
|
|
makeResponse := func(cts ...codersdk.WorkspaceAgentContainer) codersdk.WorkspaceAgentListContainersResponse {
|
|
return codersdk.WorkspaceAgentListContainersResponse{Containers: cts}
|
|
}
|
|
|
|
// Each test case is called multiple times to ensure idempotency
|
|
for _, tc := range []struct {
|
|
name string
|
|
// data to be stored in the handler
|
|
cacheData codersdk.WorkspaceAgentListContainersResponse
|
|
// duration of cache
|
|
cacheDur time.Duration
|
|
// relative age of the cached data
|
|
cacheAge time.Duration
|
|
// function to set up expectations for the mock
|
|
setupMock func(*acmock.MockLister)
|
|
// expected result
|
|
expected codersdk.WorkspaceAgentListContainersResponse
|
|
// expected error
|
|
expectedErr string
|
|
}{
|
|
{
|
|
name: "no cache",
|
|
setupMock: func(mcl *acmock.MockLister) {
|
|
mcl.EXPECT().List(gomock.Any()).Return(makeResponse(fakeCt), nil).AnyTimes()
|
|
},
|
|
expected: makeResponse(fakeCt),
|
|
},
|
|
{
|
|
name: "no data",
|
|
cacheData: makeResponse(),
|
|
cacheAge: 2 * time.Second,
|
|
cacheDur: time.Second,
|
|
setupMock: func(mcl *acmock.MockLister) {
|
|
mcl.EXPECT().List(gomock.Any()).Return(makeResponse(fakeCt), nil).AnyTimes()
|
|
},
|
|
expected: makeResponse(fakeCt),
|
|
},
|
|
{
|
|
name: "cached data",
|
|
cacheAge: time.Second,
|
|
cacheData: makeResponse(fakeCt),
|
|
cacheDur: 2 * time.Second,
|
|
expected: makeResponse(fakeCt),
|
|
},
|
|
{
|
|
name: "lister error",
|
|
setupMock: func(mcl *acmock.MockLister) {
|
|
mcl.EXPECT().List(gomock.Any()).Return(makeResponse(), assert.AnError).AnyTimes()
|
|
},
|
|
expectedErr: assert.AnError.Error(),
|
|
},
|
|
{
|
|
name: "stale cache",
|
|
cacheAge: 2 * time.Second,
|
|
cacheData: makeResponse(fakeCt),
|
|
cacheDur: time.Second,
|
|
setupMock: func(mcl *acmock.MockLister) {
|
|
mcl.EXPECT().List(gomock.Any()).Return(makeResponse(fakeCt2), nil).AnyTimes()
|
|
},
|
|
expected: makeResponse(fakeCt2),
|
|
},
|
|
} {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
var (
|
|
ctx = testutil.Context(t, testutil.WaitShort)
|
|
clk = quartz.NewMock(t)
|
|
ctrl = gomock.NewController(t)
|
|
mockLister = acmock.NewMockLister(ctrl)
|
|
now = time.Now().UTC()
|
|
logger = slogtest.Make(t, nil).Leveled(slog.LevelDebug)
|
|
api = NewAPI(logger, WithLister(mockLister))
|
|
)
|
|
defer api.Close()
|
|
|
|
api.cacheDuration = tc.cacheDur
|
|
api.clock = clk
|
|
api.containers = tc.cacheData
|
|
if tc.cacheAge != 0 {
|
|
api.mtime = now.Add(-tc.cacheAge)
|
|
}
|
|
if tc.setupMock != nil {
|
|
tc.setupMock(mockLister)
|
|
}
|
|
|
|
clk.Set(now).MustWait(ctx)
|
|
|
|
// Repeat the test to ensure idempotency
|
|
for i := 0; i < 2; i++ {
|
|
actual, err := api.getContainers(ctx)
|
|
if tc.expectedErr != "" {
|
|
require.Empty(t, actual, "expected no data (attempt %d)", i)
|
|
require.ErrorContains(t, err, tc.expectedErr, "expected error (attempt %d)", i)
|
|
} else {
|
|
require.NoError(t, err, "expected no error (attempt %d)", i)
|
|
require.Equal(t, tc.expected, actual, "expected containers to be equal (attempt %d)", i)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func fakeContainer(t *testing.T, mut ...func(*codersdk.WorkspaceAgentContainer)) codersdk.WorkspaceAgentContainer {
|
|
t.Helper()
|
|
ct := codersdk.WorkspaceAgentContainer{
|
|
CreatedAt: time.Now().UTC(),
|
|
ID: uuid.New().String(),
|
|
FriendlyName: testutil.GetRandomName(t),
|
|
Image: testutil.GetRandomName(t) + ":" + strings.Split(uuid.New().String(), "-")[0],
|
|
Labels: map[string]string{
|
|
testutil.GetRandomName(t): testutil.GetRandomName(t),
|
|
},
|
|
Running: true,
|
|
Ports: []codersdk.WorkspaceAgentContainerPort{
|
|
{
|
|
Network: "tcp",
|
|
Port: testutil.RandomPortNoListen(t),
|
|
HostPort: testutil.RandomPortNoListen(t),
|
|
//nolint:gosec // this is a test
|
|
HostIP: []string{"127.0.0.1", "[::1]", "localhost", "0.0.0.0", "[::]", testutil.GetRandomName(t)}[rand.Intn(6)],
|
|
},
|
|
},
|
|
Status: testutil.MustRandString(t, 10),
|
|
Volumes: map[string]string{testutil.GetRandomName(t): testutil.GetRandomName(t)},
|
|
}
|
|
for _, m := range mut {
|
|
m(&ct)
|
|
}
|
|
return ct
|
|
}
|