Files
promptkit/internal/usecase/coordinate_test.go

110 lines
3.6 KiB
Go
Raw Normal View History

package usecase
import (
"context"
"errors"
"fmt"
"testing"
"go.maximumdirect.net/promptkit/internal/domain"
)
func TestCoordinateBudgetAndDelivery(t *testing.T) {
for _, tc := range []struct {
name string
budget int
stop bool
outcome CoordinationOutcome
runs, callbacks int
}{
{"zero stop", 0, true, CoordinationStopped, 0, 1},
{"zero exhausted", 0, false, CoordinationExhausted, 0, 1},
{"stop after last", 2, true, CoordinationStopped, 2, 3},
{"request after last", 2, false, CoordinationExhausted, 2, 3},
} {
t.Run(tc.name, func(t *testing.T) {
start := domain.CaptureExchange(nil, "initial", nil, "start")
runs, callbacks := 0, 0
result, err := Coordinate(context.Background(), start, tc.budget, func(_ context.Context, turn ConversationTurn[string]) (*string, error) {
callbacks++
if turn.RemainingTurns != tc.budget-runs || (runs == 0 && turn.Result != nil) || (runs > 0 && (turn.Result == nil || *turn.Result != fmt.Sprint(runs))) {
t.Fatalf("unexpected turn: %#v", turn)
}
if tc.stop && turn.RemainingTurns == 0 {
return nil, nil
}
value := "next"
return &value, nil
}, func(_ context.Context, current *domain.Exchange, _ *string) (*string, *domain.Exchange, error) {
runs++
value := fmt.Sprint(runs)
return &value, domain.CaptureExchange(current, value, nil, value), nil
})
if err != nil || result.Outcome != tc.outcome || result.Undelivered != nil || runs != tc.runs || callbacks != tc.callbacks {
t.Fatalf("result=%#v error=%v runs=%d callbacks=%d", result, err, runs, callbacks)
}
})
}
}
func TestCoordinateCancellationAndErrors(t *testing.T) {
start := domain.CaptureExchange(nil, "initial", nil, "start")
request := "continue"
for _, mode := range []string{"before", "after run", "inside callback", "callback error", "run error"} {
t.Run(mode, func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
sentinel := errors.New("sentinel")
calls := 0
if mode == "before" {
cancel()
}
result, err := Coordinate(ctx, start, 1, func(_ context.Context, turn ConversationTurn[string]) (*string, error) {
calls++
if mode == "inside callback" && turn.Result != nil {
cancel()
return nil, sentinel
}
if mode == "callback error" {
return nil, fmt.Errorf("callback: %w", sentinel)
}
return &request, nil
}, func(_ context.Context, current *domain.Exchange, _ *string) (*string, *domain.Exchange, error) {
if mode == "run error" {
return nil, nil, fmt.Errorf("run: %w", sentinel)
}
value := "response"
if mode == "after run" {
cancel()
}
return &value, domain.CaptureExchange(current, "next", nil, value), nil
})
if mode == "before" || mode == "after run" || mode == "inside callback" {
if !errors.Is(err, context.Canceled) {
t.Fatalf("error = %v", err)
}
} else if !errors.Is(err, sentinel) {
t.Fatalf("error = %v", err)
}
if result.Outcome != "" || (mode == "after run") != (result.Undelivered != nil) {
t.Fatalf("result = %#v", result)
}
if mode == "before" && calls != 0 {
t.Fatal("callback invoked after cancellation")
}
if mode == "inside callback" && calls != 2 {
t.Fatalf("callbacks = %d", calls)
}
})
}
}
func TestCoordinatePropagatesPanic(t *testing.T) {
defer func() {
if recover() != "callback panic" {
t.Error("panic did not propagate")
}
}()
Coordinate[string, string](context.Background(), nil, 0, func(context.Context, ConversationTurn[string]) (*string, error) { panic("callback panic") }, nil)
}