Ctrl K
ComunidadGuías…

internal/adminapi/task_retry_handler_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package adminapi

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/yeomyeonggeori/blueclaw/internal/connectors"
	"github.com/yeomyeonggeori/blueclaw/internal/task"
)

type retryTaskRunFunc func(context.Context, string) (task.TaskRun, error)

func (function retryTaskRunFunc) RetryTaskRun(contextValue context.Context, taskRunID string) (task.TaskRun, error) {
	return function(contextValue, taskRunID)
}

func TestTaskMonitorHandlerRetriesVisibleFailedTaskRun(t *testing.T) {
	taskRunService := task.NewTaskRunService(task.NewTaskEventService())
	taskRun := taskRunService.CreateTaskRun("person-1", "conversation-1", "retry this")
	if _, errorValue := taskRunService.FailTaskRun(taskRun.TaskRunID, "failed"); errorValue != nil {
		t.Fatal(errorValue)
	}
	var retriedTaskRunID string
	childTaskRun := taskRunService.CreateTaskRun("person-1", "conversation-1", "retry this")
	handler := TaskMonitorHandler{
		TaskRunService: taskRunService,
		RetryTaskRun: retryTaskRunFunc(func(_ context.Context, taskRunID string) (task.TaskRun, error) {
			retriedTaskRunID = taskRunID
			return childTaskRun, nil
		}),
	}
	request := httptest.NewRequest(http.MethodPost, "/admin/api/run/retry", strings.NewReader(`{"taskRunID":"`+taskRun.TaskRunID+`","viewerIsAdmin":true}`))
	response := httptest.NewRecorder()
	handler.HandleRetryTaskRun(response, request)
	var responseBody map[string]string
	if json.NewDecoder(response.Body).Decode(&responseBody) != nil {
		t.Fatal("retry response was not JSON")
	}
	if response.Code != http.StatusAccepted || retriedTaskRunID != taskRun.TaskRunID || responseBody["taskRunID"] != childTaskRun.TaskRunID || responseBody["status"] != string(childTaskRun.Status) {
		t.Fatalf("retry answered %d and called %q", response.Code, retriedTaskRunID)
	}
}

func TestTaskMonitorHandlerRetryVisibilityIsScopedToViewer(t *testing.T) {
	taskRunService := task.NewTaskRunService(task.NewTaskEventService())
	aliceTaskRun := taskRunService.CreateTaskRun("person-1", "conversation-1", "alice retry")
	bobTaskRun := taskRunService.CreateTaskRun("person-2", "conversation-2", "bob retry")
	for _, taskRun := range []task.TaskRun{aliceTaskRun, bobTaskRun} {
		if _, errorValue := taskRunService.FailTaskRun(taskRun.TaskRunID, "failed"); errorValue != nil {
			t.Fatal(errorValue)
		}
	}
	invocations := 0
	handler := TaskMonitorHandler{
		TaskRunService:  taskRunService,
		IdentityService: newTestIdentityService(),
		RetryTaskRun: retryTaskRunFunc(func(_ context.Context, taskRunID string) (task.TaskRun, error) {
			invocations++
			retriedTaskRun, _ := taskRunService.FindTaskRun(taskRunID)
			return retriedTaskRun, nil
		}),
	}
	otherViewerResponse := httptest.NewRecorder()
	handler.HandleRetryTaskRun(otherViewerResponse, httptest.NewRequest(http.MethodPost, "/admin/api/run/retry", strings.NewReader(`{"taskRunID":"`+bobTaskRun.TaskRunID+`","viewerEmail":"alice@example.com"}`)))
	if otherViewerResponse.Code != http.StatusNotFound || invocations != 0 {
		t.Fatalf("cross viewer retry answered %d and invoked runtime %d times", otherViewerResponse.Code, invocations)
	}
	ownViewerResponse := httptest.NewRecorder()
	handler.HandleRetryTaskRun(ownViewerResponse, httptest.NewRequest(http.MethodPost, "/admin/api/run/retry", strings.NewReader(`{"taskRunID":"`+aliceTaskRun.TaskRunID+`","viewerEmail":"alice@example.com"}`)))
	if ownViewerResponse.Code != http.StatusAccepted || invocations != 1 {
		t.Fatalf("own viewer retry answered %d and invoked runtime %d times", ownViewerResponse.Code, invocations)
	}
}

func TestTaskMonitorHandlerRetryFailsClosedWhenViewerIdentityIsUnavailable(t *testing.T) {
	invocations := 0
	handler := TaskMonitorHandler{
		TaskRunService: task.NewTaskRunService(task.NewTaskEventService()),
		RetryTaskRun: retryTaskRunFunc(func(_ context.Context, _ string) (task.TaskRun, error) {
			invocations++
			return task.TaskRun{}, nil
		}),
	}
	taskRun := handler.TaskRunService.CreateTaskRun("person-1", "conversation-1", "retry")
	if _, errorValue := handler.TaskRunService.FailTaskRun(taskRun.TaskRunID, "failed"); errorValue != nil {
		t.Fatal(errorValue)
	}
	response := httptest.NewRecorder()
	handler.HandleRetryTaskRun(response, httptest.NewRequest(http.MethodPost, "/admin/api/run/retry", strings.NewReader(`{"taskRunID":"`+taskRun.TaskRunID+`","viewerEmail":"alice@example.com"}`)))
	if response.Code != http.StatusServiceUnavailable || invocations != 0 {
		t.Fatalf("unresolved viewer answered %d and invoked runtime %d times", response.Code, invocations)
	}
}

func TestTaskMonitorHandlerRetryMapsSourceDeletedRaceToNotFound(t *testing.T) {
	taskRunService := task.NewTaskRunService(task.NewTaskEventService())
	taskRun := taskRunService.CreateTaskRun("person-1", "conversation-1", "retry")
	if _, errorValue := taskRunService.FailTaskRun(taskRun.TaskRunID, "failed"); errorValue != nil {
		t.Fatal(errorValue)
	}
	handler := TaskMonitorHandler{
		TaskRunService: taskRunService,
		RetryTaskRun: retryTaskRunFunc(func(_ context.Context, _ string) (task.TaskRun, error) {
			return task.TaskRun{}, task.ErrTaskRunNotFound
		}),
	}
	response := httptest.NewRecorder()
	handler.HandleRetryTaskRun(response, httptest.NewRequest(http.MethodPost, "/admin/api/run/retry", strings.NewReader(`{"taskRunID":"`+taskRun.TaskRunID+`","viewerIsAdmin":true}`)))
	if response.Code != http.StatusNotFound {
		t.Fatalf("deleted source race answered %d, want 404", response.Code)
	}
}

func TestTaskMonitorHandlerRejectsMissingOrNonFailedTaskRun(t *testing.T) {
	taskRunService := task.NewTaskRunService(task.NewTaskEventService())
	runningTaskRun := taskRunService.CreateTaskRun("person-1", "conversation-1", "running")
	handler := TaskMonitorHandler{TaskRunService: taskRunService, RetryTaskRun: retryTaskRunFunc(func(_ context.Context, _ string) (task.TaskRun, error) { return task.TaskRun{}, nil })}
	for _, body := range []string{`{}`, `{"taskRunID":"` + runningTaskRun.TaskRunID + `","viewerIsAdmin":true}`} {
		response := httptest.NewRecorder()
		handler.HandleRetryTaskRun(response, httptest.NewRequest(http.MethodPost, "/admin/api/run/retry", strings.NewReader(body)))
		wantedStatus := http.StatusBadRequest
		if body != `{}` {
			wantedStatus = http.StatusConflict
		}
		if response.Code != wantedStatus {
			t.Fatalf("body %s answered %d, want %d", body, response.Code, wantedStatus)
		}
	}
}

func TestTaskMonitorHandlerMapsRetryRuntimeErrors(t *testing.T) {
	for _, testCase := range []struct {
		name       string
		errorValue error
		status     int
	}{
		{name: "conflict", errorValue: connectors.ErrTaskRetryConflict, status: http.StatusConflict},
		{name: "unavailable", errorValue: connectors.ErrTaskRetryUnavailable, status: http.StatusServiceUnavailable},
	} {
		t.Run(testCase.name, func(t *testing.T) {
			taskRunService := task.NewTaskRunService(task.NewTaskEventService())
			taskRun := taskRunService.CreateTaskRun("person-1", "conversation-1", "retry this")
			if _, errorValue := taskRunService.FailTaskRun(taskRun.TaskRunID, "failed"); errorValue != nil {
				t.Fatal(errorValue)
			}
			handler := TaskMonitorHandler{TaskRunService: taskRunService, RetryTaskRun: retryTaskRunFunc(func(_ context.Context, _ string) (task.TaskRun, error) { return task.TaskRun{}, testCase.errorValue })}
			request := httptest.NewRequest(http.MethodPost, "/admin/api/run/retry", strings.NewReader(`{"taskRunID":"`+taskRun.TaskRunID+`","viewerIsAdmin":true}`))
			response := httptest.NewRecorder()
			handler.HandleRetryTaskRun(response, request)
			if response.Code != testCase.status {
				t.Fatalf("retry answered %d, want %d", response.Code, testCase.status)
			}
		})
	}
}