package store import ( "context" "errors" "testing" "time" ) func TestLatestJobByKind(t *testing.T) { t.Parallel() s := openTestStore(t) ctx := context.Background() hostID := makeSchedHost(t, s) // No jobs yet → ErrNotFound. if _, err := s.LatestJobByKind(ctx, hostID, "forget"); !errors.Is(err, ErrNotFound) { t.Fatalf("expected ErrNotFound on empty, got %v", err) } // Insert two finished jobs of kind=forget; the newer one should win. older := time.Now().UTC().Add(-2 * time.Hour) newer := time.Now().UTC().Add(-1 * time.Hour) if err := s.CreateJob(ctx, Job{ ID: "j-old", HostID: hostID, Kind: "forget", ActorKind: "system", CreatedAt: older, }); err != nil { t.Fatalf("create older: %v", err) } if err := s.MarkJobFinished(ctx, "j-old", "succeeded", 0, nil, "", older.Add(time.Minute)); err != nil { t.Fatalf("finish older: %v", err) } if err := s.CreateJob(ctx, Job{ ID: "j-new", HostID: hostID, Kind: "forget", ActorKind: "system", CreatedAt: newer, }); err != nil { t.Fatalf("create newer: %v", err) } if err := s.MarkJobFinished(ctx, "j-new", "failed", 1, nil, "boom", newer.Add(time.Minute)); err != nil { t.Fatalf("finish newer: %v", err) } got, err := s.LatestJobByKind(ctx, hostID, "forget") if err != nil { t.Fatalf("LatestJobByKind: %v", err) } if got.ID != "j-new" { t.Errorf("want j-new, got %q", got.ID) } // An in-flight running job must be returned — long-prune-suppresses-tick // scenario: if a prune runs >60s the next tick must not re-fire it. runningAt := time.Now().UTC() if err := s.CreateJob(ctx, Job{ ID: "j-running", HostID: hostID, Kind: "forget", ActorKind: "system", CreatedAt: runningAt, }); err != nil { t.Fatalf("create running: %v", err) } if err := s.MarkJobStarted(ctx, "j-running", runningAt); err != nil { t.Fatalf("mark started: %v", err) } got2, err := s.LatestJobByKind(ctx, hostID, "forget") if err != nil { t.Fatalf("LatestJobByKind 2: %v", err) } if got2.ID != "j-running" { t.Errorf("in-flight running job must be returned; want j-running, got %q", got2.ID) } // A queued (not-yet-started) job is also returned (it is newer than // j-running because CreatedAt is later). queuedAt := runningAt.Add(time.Millisecond) if err := s.CreateJob(ctx, Job{ ID: "j-queued", HostID: hostID, Kind: "forget", ActorKind: "system", CreatedAt: queuedAt, }); err != nil { t.Fatalf("create queued: %v", err) } got3, err := s.LatestJobByKind(ctx, hostID, "forget") if err != nil { t.Fatalf("LatestJobByKind 3: %v", err) } if got3.ID != "j-queued" { t.Errorf("queued job must be returned as newest; want j-queued, got %q", got3.ID) } // Different kind → ErrNotFound. if _, err := s.LatestJobByKind(ctx, hostID, "prune"); !errors.Is(err, ErrNotFound) { t.Fatalf("expected ErrNotFound for prune, got %v", err) } } func TestListAllMaintenance(t *testing.T) { t.Parallel() s := openTestStore(t) ctx := context.Background() // Empty case. rows, err := s.ListAllMaintenance(ctx) if err != nil { t.Fatalf("empty list: %v", err) } if len(rows) != 0 { t.Errorf("want empty, got %+v", rows) } // Seed two hosts with maintenance rows. h1 := "01HMAINTHOST00000000000A1" h2 := "01HMAINTHOST00000000000A2" for i, id := range []string{h1, h2} { if err := s.CreateHost(ctx, Host{ ID: id, Name: "maint-host-" + string(rune('a'+i)), OS: "linux", Arch: "amd64", AgentVersion: "dev", ResticVersion: "0.16.0", ProtocolVersion: 1, EnrolledAt: time.Now().UTC(), }, "th-"+id, ""); err != nil { t.Fatalf("create host %s: %v", id, err) } } if err := s.CreateDefaultRepoMaintenance(ctx, h1); err != nil { t.Fatalf("seed h1: %v", err) } if err := s.CreateDefaultRepoMaintenance(ctx, h2); err != nil { t.Fatalf("seed h2: %v", err) } rows, err = s.ListAllMaintenance(ctx) if err != nil { t.Fatalf("list: %v", err) } if len(rows) != 2 { t.Errorf("want 2 rows, got %d", len(rows)) } }