package link import ( "context" "encoding/json" "testing" "time" ) // **A handler that waits on purpose is answered in full when it ends inside the window it asked for** // (novox/hq issue 229): `plans` asked to wait for a plan holds its caller's one answer until the plan // moves or its bound passes, rather than being answered "still running" at AnswerWithin. func TestAHandlerThatWaitsOnPurposeIsAnsweredInFullInsideItsWindow(t *testing.T) { shortWindow(t, 20*time.Millisecond) l, a := NewCallLog(), newAnswers(t) l.serveCall("mesh-controller", "plans", json.RawMessage(`{"wait":"plan-1"}`), "_INBOX.x.3", func(ctx context.Context, _ json.RawMessage) (any, error) { AnswersWithin(ctx, 200*time.Millisecond) time.Sleep(80 * time.Millisecond) // past AnswerWithin, inside what it asked for return "plan-1 moved", nil }, a.respond, nil) if got := a.only()["result"]; got != "plan-1 moved" { t.Fatalf("a waiting handler inside its window was answered %v, not in full", got) } } // What a handler may ask for is bounded: past LongestAnswer its caller is told it is running, as any // call is, so no answer is ever later than the console waits. func TestAWaitIsNeverLongerThanTheLongestAnswer(t *testing.T) { shortWindow(t, 10*time.Millisecond) was := LongestAnswer LongestAnswer = 30 * time.Millisecond t.Cleanup(func() { LongestAnswer = was }) l, a := NewCallLog(), newAnswers(t) release := make(chan struct{}) finished := make(chan struct{}) go func() { l.serveCall("mesh-controller", "plans", nil, "_INBOX.x.4", func(ctx context.Context, _ json.RawMessage) (any, error) { AnswersWithin(ctx, time.Hour) <-release return "late", nil }, a.respond, nil) close(finished) }() select { case <-a.sent: case <-time.After(2 * time.Second): t.Fatal("a handler asking for an hour held its caller past LongestAnswer") } if got, _ := a.only()["result"].(map[string]any); got["running"] != true { t.Fatalf("past LongestAnswer the caller was answered %v, not that it is running", a.only()) } close(release) <-finished }