Files
coder/enterprise/coderd/prebuilds/controller_test.go
2025-02-21 14:34:58 +00:00

131 lines
4.5 KiB
Go

package prebuilds
import (
"testing"
"time"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/coderd/database"
)
var (
templateID = uuid.New()
templateVersionID = uuid.New()
presetID = uuid.New()
preset2ID = uuid.New()
prebuildID = uuid.New()
)
func TestReconciliationActions(t *testing.T) {
cases := map[string]struct {
preset database.GetTemplatePresetsWithPrebuildsRow // TODO: make own structs; reusing these types is lame
running []database.GetRunningPrebuildsRow
inProgress []database.GetPrebuildsInProgressRow
expected reconciliationActions
}{
// New template version created which adds a new preset with prebuilds configured.
"CreateNetNew": {
preset: preset(true, 1),
expected: reconciliationActions{
desired: 1,
create: 1,
},
},
// New template version created, making an existing preset and its prebuilds outdated.
"DeleteOutdated": {
preset: preset(false, 1),
running: []database.GetRunningPrebuildsRow{
{
WorkspaceID: prebuildID,
TemplateID: templateID,
TemplateVersionID: templateVersionID,
CurrentPresetID: uuid.NullUUID{UUID: presetID, Valid: true},
DesiredPresetID: uuid.NullUUID{UUID: uuid.New(), Valid: true},
Ready: true,
},
},
expected: reconciliationActions{
outdated: 1,
deleteIDs: []uuid.UUID{prebuildID},
},
},
// Somehow an additional prebuild is running, delete it.
// This can happen if an operator messes with a prebuild's state (stop, start).
"DeleteOldestExtraneous": {
preset: preset(true, 1),
running: []database.GetRunningPrebuildsRow{
{
WorkspaceID: prebuildID,
TemplateID: templateID,
TemplateVersionID: templateVersionID,
CurrentPresetID: uuid.NullUUID{UUID: presetID, Valid: true},
DesiredPresetID: uuid.NullUUID{UUID: uuid.New(), Valid: true},
CreatedAt: time.Now().Add(-time.Hour),
},
{
WorkspaceID: uuid.New(),
TemplateID: templateID,
TemplateVersionID: templateVersionID,
CurrentPresetID: uuid.NullUUID{UUID: presetID, Valid: true},
DesiredPresetID: uuid.NullUUID{UUID: uuid.New(), Valid: true},
CreatedAt: time.Now(),
},
},
expected: reconciliationActions{
desired: 1,
extraneous: 1,
actual: 2,
deleteIDs: []uuid.UUID{prebuildID},
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
t.Parallel()
ps := presetState{
preset: tc.preset,
running: tc.running,
inProgress: tc.inProgress,
}
actions, err := ps.calculateActions()
require.NoError(t, err, "could not calculate reconciliation actions")
validateActions(t, tc.expected, *actions)
})
}
}
func preset(active bool, instances int32) database.GetTemplatePresetsWithPrebuildsRow {
return database.GetTemplatePresetsWithPrebuildsRow{
TemplateID: templateID,
TemplateVersionID: templateVersionID,
UsingActiveVersion: active,
PresetID: presetID,
Name: "bob",
DesiredInstances: instances,
Deleted: false,
Deprecated: false,
}
}
// validateActions is a convenience func to make tests more readable; it exploits the fact that the default states for
// prebuilds align with zero values.
func validateActions(t *testing.T, expected, actual reconciliationActions) bool {
return assert.EqualValuesf(t, expected.deleteIDs, actual.deleteIDs, "'deleteIDs' did not match expectation") &&
assert.EqualValuesf(t, expected.create, actual.create, "'create' did not match expectation") &&
assert.EqualValuesf(t, expected.desired, actual.desired, "'desired' did not match expectation") &&
assert.EqualValuesf(t, expected.actual, actual.actual, "'actual' did not match expectation") &&
assert.EqualValuesf(t, expected.eligible, actual.eligible, "'eligible' did not match expectation") &&
assert.EqualValuesf(t, expected.extraneous, actual.extraneous, "'extraneous' did not match expectation") &&
assert.EqualValuesf(t, expected.outdated, actual.outdated, "'outdated' did not match expectation") &&
assert.EqualValuesf(t, expected.starting, actual.starting, "'starting' did not match expectation") &&
assert.EqualValuesf(t, expected.stopping, actual.stopping, "'stopping' did not match expectation") &&
assert.EqualValuesf(t, expected.deleting, actual.deleting, "'deleting' did not match expectation")
}