Ctrl K
ComunidadGuías…

internal/approvalgate/schedule_answer_test.go

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

Archivo original ↗Preguntar a KiBot
//go:build !nobundledharness

package approvalgate

import (
	"context"
	"strings"
	"testing"
	"time"

	acp "github.com/coder/acp-go-sdk"

	"github.com/yeomyeonggeori/blueclaw/internal/mcpserver"
	"github.com/yeomyeonggeori/blueclaw/internal/task"
	"github.com/yeomyeonggeori/blueprotocol/agentcontract"
)

type knownSchedules struct {
	*recordingScheduler
	schedules map[string]task.Schedule
}

func (schedules knownSchedules) ScheduleByID(scheduleID string) (task.Schedule, bool, error) {
	schedule, isFound := schedules.schedules[scheduleID]
	return schedule, isFound, nil
}

func scheduleOf(creatorPersonID string, isApproved bool) task.Schedule {
	schedule := task.Schedule{ScheduleID: "schedule-weekly", CreatorPersonID: creatorPersonID}
	if isApproved {
		approvedAt := time.Date(2026, 11, 3, 9, 0, 0, 0, time.UTC)
		schedule.ApprovedAt = &approvedAt
	}
	return schedule
}

func scheduledRunGate(t *testing.T, schedule task.Schedule) (*Gate, *task.TaskRunService, *choosingAsker, mcpserver.ApprovalRequest) {
	t.Helper()
	gate, taskRunService, taskRun := gateFixture(t)
	personAsker := &choosingAsker{}
	gate.UsePermissionAsker(personAsker)
	gate.UseSchedules(knownSchedules{recordingScheduler: &recordingScheduler{}, schedules: map[string]task.Schedule{schedule.ScheduleID: schedule}})
	approvalRequest := approvalRequestFixture(taskRun.TaskRunID)
	approvalRequest.ConversationID = task.ScheduleSessionID(schedule.ScheduleID)
	return gate, taskRunService, personAsker, approvalRequest
}

func ledgerOf(taskRunService *task.TaskRunService, taskRunID string) string {
	ledger := []string{}
	for _, event := range taskRunService.ListTaskEvent(taskRunID) {
		ledger = append(ledger, event.Name+"="+event.Body)
	}
	return strings.Join(ledger, "\n")
}

func TestAnApprovedScheduleAnswersTheQuestionsItsRunsRaise(t *testing.T) {
	gate, taskRunService, personAsker, approvalRequest := scheduledRunGate(t, scheduleOf("person-1", true))

	outcome, errorValue := gate.AwaitApproval(context.Background(), approvalRequest)

	if errorValue != nil {
		t.Fatal(errorValue)
	}
	if outcome.Decision != mcpserver.ApprovalDecisionApproved || outcome.HoldID == "" {
		t.Fatalf("a run of an approved schedule came back %+v, expected an approved hold", outcome)
	}
	if personAsker.offeredQuestion != "" {
		t.Fatalf("the schedule's run asked a person %q", personAsker.offeredQuestion)
	}
	ledger := ledgerOf(taskRunService, approvalRequest.TaskRunID)
	if !strings.Contains(ledger, `"decision":"approve","source":"schedule"`) || strings.Contains(ledger, agentcontract.TaskEventConfirmationRequested) {
		t.Fatalf("the ledger does not say the schedule decided without a question:\n%s", ledger)
	}
}

func TestARunOfAScheduleItsRequesterNeverApprovedStillAsksThem(t *testing.T) {
	for name, schedule := range map[string]task.Schedule{
		"never approved":              scheduleOf("person-1", false),
		"approved by someone else":    scheduleOf("person-2", true),
		"no longer kept by the agent": {},
	} {
		t.Run(name, func(t *testing.T) {
			gate, _, personAsker, approvalRequest := scheduledRunGate(t, schedule)
			personAsker.answer = ApprovalAnswer{Signal: agentcontract.ApprovalSignalReject}
			approvalRequest.ConversationID = task.ScheduleSessionID("schedule-weekly")

			outcome, _ := gate.AwaitApproval(context.Background(), approvalRequest)

			if personAsker.offeredQuestion == "" || outcome.Decision != mcpserver.ApprovalDecisionRejected {
				t.Fatalf("the run came back %+v without the requester deciding it", outcome)
			}
		})
	}
}

func TestAnApprovedScheduleAllowsTheHarnessOnceForItsRun(t *testing.T) {
	gate, _, _, approvalRequest := scheduledRunGate(t, scheduleOf("person-1", true))
	options := []acp.PermissionOption{
		{OptionId: "allow-always", Kind: acp.PermissionOptionKindAllowAlways},
		{OptionId: "allow-once", Kind: acp.PermissionOptionKindAllowOnce},
		{OptionId: "reject", Kind: acp.PermissionOptionKindRejectOnce},
	}

	outcome, status := gate.AskHarnessPermission(context.Background(), approvalRequest, HarnessPermissionQuestion{Text: "run the report script", Options: options})

	if status != AskAnswered || outcome.Selected == nil || outcome.Selected.OptionId != "allow-once" {
		t.Fatalf("the harness was answered %+v (%s), expected allow-once", outcome, status)
	}
}