yao/openapi/agent/robot/interact_test.go
Max bc4787f857 Update executor to support V2 execution model and enhance event handling
- Implement V2 execution model in the standard executor, simplifying task execution to a single call without validation loops.
- Introduce support for resuming suspended executions, allowing for human input during task processing.
- Enhance event handling by pushing task completion and failure events to the event bus for better tracking and integration.
- Update tests to reflect changes in execution flow and ensure robust handling of task statuses and results.
2026-02-25 18:40:48 +08:00

223 lines
7.6 KiB
Go

package robot
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/yaoapp/yao/openapi/response"
)
func init() {
gin.SetMode(gin.TestMode)
}
// setAuthContext sets the context values that authorized.GetInfo(c) reads
func setAuthContext(c *gin.Context) {
c.Set("__subject", "test-subject")
c.Set("__client_id", "test-client")
c.Set("__user_id", "test-user")
c.Set("__scope", "openid profile")
}
// OH1: InteractRobot with missing auth info
func TestInteractRobot_OH1_MissingAuth(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Params = gin.Params{{Key: "id", Value: "robot-123"}}
body := bytes.NewBufferString(`{"message":"hello"}`)
c.Request, _ = http.NewRequest("POST", "/v1/agent/robots/robot-123/interact", body)
c.Request.Header.Set("Content-Type", "application/json")
// No auth context set
InteractRobot(c)
require.Equal(t, http.StatusUnauthorized, w.Code)
var errResp response.ErrorResponse
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &errResp))
assert.Equal(t, response.ErrInvalidToken.Code, errResp.Code)
assert.Contains(t, errResp.ErrorDescription, "Authentication")
}
// OH2: InteractRobot with invalid JSON body
func TestInteractRobot_OH2_InvalidJSON(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
setAuthContext(c)
c.Params = gin.Params{{Key: "id", Value: "robot-123"}}
body := bytes.NewBufferString(`{invalid json`)
c.Request, _ = http.NewRequest("POST", "/v1/agent/robots/robot-123/interact", body)
c.Request.Header.Set("Content-Type", "application/json")
InteractRobot(c)
require.Equal(t, http.StatusBadRequest, w.Code)
var errResp response.ErrorResponse
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &errResp))
assert.Equal(t, response.ErrInvalidRequest.Code, errResp.Code)
assert.Contains(t, errResp.ErrorDescription, "Invalid request body")
}
// OH3: InteractRobot empty message validation
func TestInteractRobot_OH3_EmptyMessage(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
setAuthContext(c)
c.Params = gin.Params{{Key: "id", Value: "robot-123"}}
body := bytes.NewBufferString(`{}`)
c.Request, _ = http.NewRequest("POST", "/v1/agent/robots/robot-123/interact", body)
c.Request.Header.Set("Content-Type", "application/json")
InteractRobot(c)
require.Equal(t, http.StatusBadRequest, w.Code)
var errResp response.ErrorResponse
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &errResp))
assert.Equal(t, response.ErrInvalidRequest.Code, errResp.Code)
assert.Contains(t, errResp.ErrorDescription, "Invalid request body")
}
// OH4: ReplyToTask with missing auth info
func TestReplyToTask_OH4_MissingAuth(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Params = gin.Params{
{Key: "id", Value: "robot-123"},
{Key: "exec_id", Value: "exec-456"},
{Key: "task_id", Value: "task-789"},
}
body := bytes.NewBufferString(`{"message":"reply"}`)
c.Request, _ = http.NewRequest("POST", "/v1/agent/robots/robot-123/executions/exec-456/tasks/task-789/reply", body)
c.Request.Header.Set("Content-Type", "application/json")
// No auth context set
ReplyToTask(c)
require.Equal(t, http.StatusUnauthorized, w.Code)
var errResp response.ErrorResponse
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &errResp))
assert.Equal(t, response.ErrInvalidToken.Code, errResp.Code)
assert.Contains(t, errResp.ErrorDescription, "Authentication")
}
// OH5: ReplyToTask with empty robot_id parameter
func TestReplyToTask_OH5_EmptyRobotID(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
setAuthContext(c)
c.Params = gin.Params{
{Key: "id", Value: ""},
{Key: "exec_id", Value: "exec-456"},
{Key: "task_id", Value: "task-789"},
}
body := bytes.NewBufferString(`{"message":"reply"}`)
c.Request, _ = http.NewRequest("POST", "/reply", body)
c.Request.Header.Set("Content-Type", "application/json")
ReplyToTask(c)
require.Equal(t, http.StatusBadRequest, w.Code)
var errResp response.ErrorResponse
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &errResp))
assert.Equal(t, response.ErrInvalidRequest.Code, errResp.Code)
assert.Contains(t, errResp.ErrorDescription, "robot id")
}
// OH6: ReplyToTask with empty message
func TestReplyToTask_OH6_EmptyMessage(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
setAuthContext(c)
c.Params = gin.Params{
{Key: "id", Value: "robot-123"},
{Key: "exec_id", Value: "exec-456"},
{Key: "task_id", Value: "task-789"},
}
body := bytes.NewBufferString(`{}`)
c.Request, _ = http.NewRequest("POST", "/reply", body)
c.Request.Header.Set("Content-Type", "application/json")
ReplyToTask(c)
require.Equal(t, http.StatusBadRequest, w.Code)
var errResp response.ErrorResponse
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &errResp))
assert.Equal(t, response.ErrInvalidRequest.Code, errResp.Code)
assert.Contains(t, errResp.ErrorDescription, "Invalid request body")
}
// OH7: ConfirmExecution with missing auth info
func TestConfirmExecution_OH7_MissingAuth(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Params = gin.Params{
{Key: "id", Value: "robot-123"},
{Key: "exec_id", Value: "exec-456"},
}
body := bytes.NewBufferString(`{}`)
c.Request, _ = http.NewRequest("POST", "/v1/agent/robots/robot-123/executions/exec-456/confirm", body)
c.Request.Header.Set("Content-Type", "application/json")
// No auth context set
ConfirmExecution(c)
require.Equal(t, http.StatusUnauthorized, w.Code)
var errResp response.ErrorResponse
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &errResp))
assert.Equal(t, response.ErrInvalidToken.Code, errResp.Code)
assert.Contains(t, errResp.ErrorDescription, "Authentication")
}
// OH8: ConfirmExecution with empty execution_id
func TestConfirmExecution_OH8_EmptyExecutionID(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
setAuthContext(c)
c.Params = gin.Params{
{Key: "id", Value: "robot-123"},
{Key: "exec_id", Value: ""},
}
body := bytes.NewBufferString(`{}`)
c.Request, _ = http.NewRequest("POST", "/confirm", body)
c.Request.Header.Set("Content-Type", "application/json")
ConfirmExecution(c)
require.Equal(t, http.StatusBadRequest, w.Code)
var errResp response.ErrorResponse
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &errResp))
assert.Equal(t, response.ErrInvalidRequest.Code, errResp.Code)
assert.Contains(t, errResp.ErrorDescription, "execution id")
}
// OH9: ConfirmExecution with valid request (robot not found expected)
// Requires app/database to be initialized; skipped in short mode.
func TestConfirmExecution_OH9_RobotNotFound(t *testing.T) {
if testing.Short() {
t.Skip("Skipping OH9 in short mode: requires app/database for GetRobotResponse")
}
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
setAuthContext(c)
c.Params = gin.Params{
{Key: "id", Value: "non-existent-robot-999"},
{Key: "exec_id", Value: "exec-456"},
}
body := bytes.NewBufferString(`{}`)
c.Request, _ = http.NewRequest("POST", "/v1/agent/robots/non-existent-robot-999/executions/exec-456/confirm", body)
c.Request.Header.Set("Content-Type", "application/json")
ConfirmExecution(c)
require.Equal(t, http.StatusNotFound, w.Code)
var errResp response.ErrorResponse
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &errResp))
assert.Equal(t, response.ErrInvalidRequest.Code, errResp.Code)
assert.Contains(t, errResp.ErrorDescription, "Robot not found")
assert.Contains(t, errResp.ErrorDescription, "non-existent-robot-999")
}