internal/approvalgate/redeemed_call_test.go
Rama main · réplica de lectura del repositorio original.
package approvalgate
import (
"encoding/json"
"errors"
"sync"
"testing"
"github.com/yeomyeonggeori/blueclaw/internal/task"
"github.com/yeomyeonggeori/blueprotocol/agentcontract"
"github.com/yeomyeonggeori/blueprotocol/holdrecord"
)
type heldCallFixture struct {
taskRunService *task.TaskRunService
taskRunID string
holdID string
}
func aHeldMessage(t *testing.T, decision string) heldCallFixture {
t.Helper()
taskRunService := task.NewTaskRunService(task.NewTaskEventService())
taskRun := taskRunService.CreateTaskRun("person-1", "conversation-1", "박예시에게 보내줘")
holdID := holdrecord.Open(taskRunService, taskRun.TaskRunID, agentcontract.HeldCall{
ToolName: "message_send",
ToolInput: json.RawMessage(`{"recipientHint":"박예시","message":"내일 10시 어때요?"}`),
ApprovedToolInput: json.RawMessage(`{"message":"내일 10시 어때요?","recipientHint":"person-2"}`),
}, nil).ID
if decision != "" {
holdrecord.Decide(taskRunService, taskRun.TaskRunID, holdID, decision, "chat_reply")
}
return heldCallFixture{taskRunService: taskRunService, taskRunID: taskRun.TaskRunID, holdID: holdID}
}
func (fixture heldCallFixture) approvedCall() HeldCallRedemption {
return HeldCallRedemption{
TaskRunID: fixture.taskRunID,
HoldID: fixture.holdID,
RequesterPersonID: "person-1",
ToolName: "message_send",
ToolInput: json.RawMessage(`{ "recipientHint": "person-2", "message": "내일 10시 어때요?" }`),
}
}
func TestAnApprovedHoldIsRedeemedByExactlyTheCallItApproved(t *testing.T) {
fixture := aHeldMessage(t, holdrecord.DecisionApprove)
if errorValue := RedeemHeldCall(fixture.taskRunService, fixture.approvedCall()); errorValue != nil {
t.Fatalf("the approved call was refused: %v", errorValue)
}
if errorValue := RedeemHeldCall(fixture.taskRunService, fixture.approvedCall()); !errors.Is(errorValue, ErrHoldWasRedeemedAlready) {
t.Fatalf("a second redemption of one approval answered %v", errorValue)
}
}
func TestAHoldIsNotRedeemedForAnythingButTheCallItsRequesterApproved(t *testing.T) {
for _, testCase := range []struct {
name string
decision string
change func(*HeldCallRedemption)
expected error
}{
{"an invented hold id", holdrecord.DecisionApprove, func(call *HeldCallRedemption) { call.HoldID = "held-test" }, ErrHoldIsNotInThisRun},
{"no hold id", holdrecord.DecisionApprove, func(call *HeldCallRedemption) { call.HoldID = "" }, ErrHoldIsNotInThisRun},
{"a run that does not exist", holdrecord.DecisionApprove, func(call *HeldCallRedemption) { call.TaskRunID = "run-missing" }, ErrHoldBelongsToNoRun},
{"another person's run", holdrecord.DecisionApprove, func(call *HeldCallRedemption) { call.RequesterPersonID = "person-2" }, ErrHoldIsAnotherPersons},
{"a hold still waiting", "", func(*HeldCallRedemption) {}, ErrHoldWasNotApproved},
{"a rejected hold", holdrecord.DecisionReject, func(*HeldCallRedemption) {}, ErrHoldWasNotApproved},
{"a different message", holdrecord.DecisionApprove, func(call *HeldCallRedemption) {
call.ToolInput = json.RawMessage(`{"recipientHint":"person-2","message":"회의 취소합니다"}`)
}, ErrHoldApprovedAnotherCall},
{"a different recipient", holdrecord.DecisionApprove, func(call *HeldCallRedemption) {
call.ToolInput = json.RawMessage(`{"recipientHint":"person-3","message":"내일 10시 어때요?"}`)
}, ErrHoldApprovedAnotherCall},
{"a different tool", holdrecord.DecisionApprove, func(call *HeldCallRedemption) { call.ToolName = "mail_message_send" }, ErrHoldApprovedAnotherCall},
} {
t.Run(testCase.name, func(t *testing.T) {
fixture := aHeldMessage(t, testCase.decision)
call := fixture.approvedCall()
testCase.change(&call)
if errorValue := RedeemHeldCall(fixture.taskRunService, call); !errors.Is(errorValue, testCase.expected) {
t.Fatalf("expected %v, got %v", testCase.expected, errorValue)
}
if errorValue := RedeemHeldCall(fixture.taskRunService, fixture.approvedCall()); testCase.decision == holdrecord.DecisionApprove && errorValue != nil {
t.Fatalf("a refused redemption spent the approval anyway: %v", errorValue)
}
})
}
}
func TestOneApprovalCarriedOutByTwoCallsAtOnceRedeemsOnce(t *testing.T) {
fixture := aHeldMessage(t, holdrecord.DecisionApprove)
results := make(chan error, 8)
var started sync.WaitGroup
for range 8 {
started.Add(1)
go func() {
defer started.Done()
results <- RedeemHeldCall(fixture.taskRunService, fixture.approvedCall())
}()
}
started.Wait()
close(results)
redeemed := 0
for errorValue := range results {
if errorValue == nil {
redeemed++
}
}
if redeemed != 1 {
t.Fatalf("one approval was carried out %d times", redeemed)
}
}