181 lines
5.5 KiB
Go
181 lines
5.5 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/oklog/ulid/v2"
|
|
)
|
|
|
|
func ptrStr(s string) *string { return &s }
|
|
|
|
func seedFleetUser(t *testing.T, s *Store) string {
|
|
t.Helper()
|
|
id := ulid.Make().String()
|
|
if err := s.CreateUser(context.Background(), User{
|
|
ID: id, Username: "u-" + id[:6], PasswordHash: "x", Role: RoleAdmin,
|
|
}); err != nil {
|
|
t.Fatalf("create user: %v", err)
|
|
}
|
|
return id
|
|
}
|
|
|
|
func seedFleetHost(t *testing.T, s *Store, name string) string {
|
|
t.Helper()
|
|
id := ulid.Make().String()
|
|
if err := s.CreateHost(context.Background(), Host{
|
|
ID: id, Name: name, OS: "linux", Arch: "amd64",
|
|
EnrolledAt: time.Now().UTC(),
|
|
}, "tokenhash-"+id[:6], ""); err != nil {
|
|
t.Fatalf("create host: %v", err)
|
|
}
|
|
return id
|
|
}
|
|
|
|
func TestCreateFleetUpdate_RefusesIfRunning(t *testing.T) {
|
|
t.Parallel()
|
|
s := openTestStore(t)
|
|
uid := seedFleetUser(t, s)
|
|
h1 := seedFleetHost(t, s, "h1")
|
|
|
|
fu1 := FleetUpdate{ID: ulid.Make().String(), StartedByUserID: uid, TargetVersion: "v1"}
|
|
if err := s.CreateFleetUpdate(context.Background(), fu1, []string{h1}); err != nil {
|
|
t.Fatalf("create #1: %v", err)
|
|
}
|
|
fu2 := FleetUpdate{ID: ulid.Make().String(), StartedByUserID: uid, TargetVersion: "v2"}
|
|
err := s.CreateFleetUpdate(context.Background(), fu2, []string{h1})
|
|
if !errors.Is(err, ErrFleetUpdateRunning) {
|
|
t.Fatalf("want ErrFleetUpdateRunning, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCreateFleetUpdate_HydrateRoundTrip(t *testing.T) {
|
|
t.Parallel()
|
|
s := openTestStore(t)
|
|
uid := seedFleetUser(t, s)
|
|
h1 := seedFleetHost(t, s, "h1")
|
|
h2 := seedFleetHost(t, s, "h2")
|
|
|
|
fu := FleetUpdate{ID: ulid.Make().String(), StartedByUserID: uid, TargetVersion: "v1.2.3"}
|
|
if err := s.CreateFleetUpdate(context.Background(), fu, []string{h1, h2}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
got, hosts, err := s.GetFleetUpdate(context.Background(), fu.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got.Status != "running" || got.TargetVersion != "v1.2.3" {
|
|
t.Fatalf("parent: %+v", got)
|
|
}
|
|
if len(hosts) != 2 || hosts[0].Position != 0 || hosts[1].Position != 1 {
|
|
t.Fatalf("hosts: %+v", hosts)
|
|
}
|
|
if hosts[0].Status != "pending" || hosts[1].Status != "pending" {
|
|
t.Fatalf("hosts status: %+v", hosts)
|
|
}
|
|
}
|
|
|
|
func TestSetFleetUpdateHostStatus_ProgressesAndStoresJobID(t *testing.T) {
|
|
t.Parallel()
|
|
s := openTestStore(t)
|
|
uid := seedFleetUser(t, s)
|
|
h := seedFleetHost(t, s, "h1")
|
|
fu := FleetUpdate{ID: ulid.Make().String(), StartedByUserID: uid, TargetVersion: "v1"}
|
|
_ = s.CreateFleetUpdate(context.Background(), fu, []string{h})
|
|
|
|
jobID := ulid.Make().String()
|
|
if err := s.CreateJob(context.Background(), Job{
|
|
ID: jobID, HostID: h, Kind: "update",
|
|
ActorKind: "user", ActorID: ptrStr(uid), CreatedAt: time.Now().UTC(),
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := s.SetFleetUpdateHostStatus(context.Background(), fu.ID, h, "running", "", ""); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.SetFleetUpdateHostStatus(context.Background(), fu.ID, h, "succeeded", "", jobID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, hs, _ := s.GetFleetUpdate(context.Background(), fu.ID)
|
|
if hs[0].Status != "succeeded" || hs[0].JobID != jobID {
|
|
t.Fatalf("after succeed: %+v", hs[0])
|
|
}
|
|
|
|
pending, _ := s.ListPendingFleetUpdateHosts(context.Background(), fu.ID)
|
|
if len(pending) != 0 {
|
|
t.Fatalf("pending should be empty: %+v", pending)
|
|
}
|
|
}
|
|
|
|
func TestHaltAndCompleteFleetUpdate(t *testing.T) {
|
|
t.Parallel()
|
|
s := openTestStore(t)
|
|
uid := seedFleetUser(t, s)
|
|
h := seedFleetHost(t, s, "h1")
|
|
|
|
fu1 := FleetUpdate{ID: ulid.Make().String(), StartedByUserID: uid, TargetVersion: "v1"}
|
|
_ = s.CreateFleetUpdate(context.Background(), fu1, []string{h})
|
|
if err := s.HaltFleetUpdate(context.Background(), fu1.ID, "boom", time.Now().UTC()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, _, _ := s.GetFleetUpdate(context.Background(), fu1.ID)
|
|
if got.Status != "halted" || got.HaltedReason != "boom" {
|
|
t.Fatalf("after halt: %+v", got)
|
|
}
|
|
if got.CompletedAt == nil {
|
|
t.Fatal("halted must stamp completed_at")
|
|
}
|
|
if active, _ := s.ActiveFleetUpdate(context.Background()); active != nil {
|
|
t.Fatalf("halted should clear active: %+v", active)
|
|
}
|
|
|
|
// Now a fresh run can start.
|
|
fu2 := FleetUpdate{ID: ulid.Make().String(), StartedByUserID: uid, TargetVersion: "v2"}
|
|
if err := s.CreateFleetUpdate(context.Background(), fu2, []string{h}); err != nil {
|
|
t.Fatalf("create after halt: %v", err)
|
|
}
|
|
if err := s.CompleteFleetUpdate(context.Background(), fu2.ID, time.Now().UTC()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, _, _ = s.GetFleetUpdate(context.Background(), fu2.ID)
|
|
if got.Status != "completed" {
|
|
t.Fatalf("after complete: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestRunningUpdateJobForHost(t *testing.T) {
|
|
t.Parallel()
|
|
s := openTestStore(t)
|
|
h := seedFleetHost(t, s, "h1")
|
|
|
|
got, err := s.RunningUpdateJobForHost(context.Background(), h)
|
|
if err != nil || got != "" {
|
|
t.Fatalf("empty case: got=%q err=%v", got, err)
|
|
}
|
|
|
|
jobID := ulid.Make().String()
|
|
if err := s.CreateJob(context.Background(), Job{
|
|
ID: jobID, HostID: h, Kind: "update",
|
|
ActorKind: "user", ActorID: ptrStr("u-1"), CreatedAt: time.Now().UTC(),
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err = s.RunningUpdateJobForHost(context.Background(), h)
|
|
if err != nil || got != jobID {
|
|
t.Fatalf("queued case: got=%q err=%v", got, err)
|
|
}
|
|
|
|
// Mark succeeded → no longer "in flight".
|
|
if err := s.MarkJobFinished(context.Background(), jobID, "succeeded", 0, nil, "", time.Now().UTC()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err = s.RunningUpdateJobForHost(context.Background(), h)
|
|
if err != nil || got != "" {
|
|
t.Fatalf("after succeed: got=%q err=%v", got, err)
|
|
}
|
|
}
|