Fetch today's tasks asynchronously on entering planning
The controller fetches the Marvin task list when entering planning, mirroring the planning coach: generation-guarded goroutine, status enum (idle/pending/ready/error), projected into State only while planning and only when a provider is set. nil provider degrades to no tasks view. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
"antidrift/internal/domain"
|
||||
"antidrift/internal/evidence"
|
||||
"antidrift/internal/store"
|
||||
"antidrift/internal/tasks"
|
||||
)
|
||||
|
||||
func newTestController(t *testing.T) (*Controller, string) {
|
||||
@@ -772,3 +773,73 @@ func TestRecentTitlesRingCapsAtTen(t *testing.T) {
|
||||
t.Fatalf("ring should drop oldest first, got first=%q", titles[0])
|
||||
}
|
||||
}
|
||||
|
||||
type fakeProvider struct {
|
||||
list []tasks.Task
|
||||
err error
|
||||
gate chan struct{} // if non-nil, Today blocks until it receives
|
||||
}
|
||||
|
||||
func (f *fakeProvider) Today(ctx context.Context) ([]tasks.Task, error) {
|
||||
if f.gate != nil {
|
||||
<-f.gate
|
||||
}
|
||||
return f.list, f.err
|
||||
}
|
||||
|
||||
// waitTasksStatus polls until the tasks view reaches want, or fails after 2s.
|
||||
func waitTasksStatus(t *testing.T, c *Controller, want string) State {
|
||||
t.Helper()
|
||||
deadline := time.Now().Add(2 * time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
st := c.State()
|
||||
if st.Tasks != nil && st.Tasks.Status == want {
|
||||
return st
|
||||
}
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
t.Fatalf("tasks status never reached %q (last: %+v)", want, c.State().Tasks)
|
||||
return State{}
|
||||
}
|
||||
|
||||
func TestEnterPlanningFetchesTasks(t *testing.T) {
|
||||
c, _ := newTestController(t)
|
||||
c.SetTasks(&fakeProvider{list: []tasks.Task{{ID: "a", Title: "Write spec", Day: "2026-05-31"}}})
|
||||
if err := c.EnterPlanning(); err != nil {
|
||||
t.Fatalf("enter planning: %v", err)
|
||||
}
|
||||
st := waitTasksStatus(t, c, "ready")
|
||||
if len(st.Tasks.Tasks) != 1 || st.Tasks.Tasks[0].Title != "Write spec" {
|
||||
t.Fatalf("tasks list wrong: %+v", st.Tasks)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTasksFetchError(t *testing.T) {
|
||||
c, _ := newTestController(t)
|
||||
c.SetTasks(&fakeProvider{err: errors.New("am down")})
|
||||
if err := c.EnterPlanning(); err != nil {
|
||||
t.Fatalf("enter planning: %v", err)
|
||||
}
|
||||
st := waitTasksStatus(t, c, "error")
|
||||
if len(st.Tasks.Tasks) != 0 {
|
||||
t.Fatalf("error state should carry no tasks: %+v", st.Tasks)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoProviderNoTasksView(t *testing.T) {
|
||||
c, _ := newTestController(t)
|
||||
if err := c.EnterPlanning(); err != nil {
|
||||
t.Fatalf("enter planning: %v", err)
|
||||
}
|
||||
if c.State().Tasks != nil {
|
||||
t.Fatalf("nil provider should yield no tasks view: %+v", c.State().Tasks)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTasksViewAbsentOutsidePlanning(t *testing.T) {
|
||||
c, _ := newTestController(t)
|
||||
c.SetTasks(&fakeProvider{list: []tasks.Task{{ID: "a", Title: "x"}}})
|
||||
if c.State().Tasks != nil {
|
||||
t.Fatalf("no tasks view while Locked: %+v", c.State().Tasks)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user