Files
GoLoom/backend/internal/handler/handler_test.go

323 lines
9.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package handler
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"ai-agent-scaffold-go/internal/model"
"ai-agent-scaffold-go/internal/service"
"ai-agent-scaffold-go/pkg/types"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
)
// ============================================================
// Stub Runner for handler tests
// ============================================================
type stubRunner struct {
sessionID string
runResult []string
runErr error
}
func (r *stubRunner) CreateSession(userID string) (string, error) {
if r.sessionID != "" {
return r.sessionID, nil
}
return "sess:" + userID + ":1", nil
}
func (r *stubRunner) Run(userID, sessionID string, content model.ChatContent) ([]string, error) {
return r.runResult, r.runErr
}
func (r *stubRunner) Stream(userID, sessionID string, content model.ChatContent) (<-chan string, <-chan error) {
outputs := make(chan string, 4)
errs := make(chan error, 1)
go func() {
defer close(outputs)
defer close(errs)
if r.runErr != nil {
errs <- r.runErr
return
}
for _, s := range r.runResult {
outputs <- s
}
}()
return outputs, errs
}
// ============================================================
// 辅助函数
// ============================================================
func setupRouter() (*gin.Engine, *model.InMemoryAgentRegistry) {
gin.SetMode(gin.TestMode)
registry := model.NewInMemoryAgentRegistry()
sessions := model.NewInMemorySessionStore()
svc := service.NewChatService(registry, sessions)
router := gin.New()
RegisterRoutes(router, svc)
return router, registry
}
func doRequest(router http.Handler, method, path, body string) *httptest.ResponseRecorder {
var req *http.Request
if body != "" {
req = httptest.NewRequest(method, path, strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
} else {
req = httptest.NewRequest(method, path, nil)
}
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
return w
}
func parseEnvelope(t *testing.T, w *httptest.ResponseRecorder) Envelope {
t.Helper()
var resp Envelope
err := json.Unmarshal(w.Body.Bytes(), &resp)
assert.NoError(t, err)
return resp
}
// ============================================================
// healthz 测试
// ============================================================
func TestHealthz_Returns200(t *testing.T) {
gin.SetMode(gin.TestMode)
router := gin.New()
router.GET("/healthz", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"status": "ok"})
})
w := doRequest(router, "GET", "/healthz", "")
assert.Equal(t, 200, w.Code)
}
// ============================================================
// queryAgentConfigList 测试
// ============================================================
func TestQueryAgentConfigList_ReturnsList(t *testing.T) {
router, registry := setupRouter()
registry.Register(model.RegisteredAgent{AgentID: "1", AgentName: "a", AgentDesc: "desc a"})
registry.Register(model.RegisteredAgent{AgentID: "2", AgentName: "b", AgentDesc: "desc b"})
w := doRequest(router, "GET", "/api/v1/query_ai_agent_config_list", "")
assert.Equal(t, 200, w.Code)
resp := parseEnvelope(t, w)
assert.Equal(t, types.CodeSuccess, resp.Code)
data, _ := json.Marshal(resp.Data)
var agents []AiAgentConfigResponse
json.Unmarshal(data, &agents)
assert.Len(t, agents, 2)
}
func TestQueryAgentConfigList_Empty_ReturnsEmptyArray(t *testing.T) {
router, _ := setupRouter()
w := doRequest(router, "GET", "/api/v1/query_ai_agent_config_list", "")
assert.Equal(t, 200, w.Code)
resp := parseEnvelope(t, w)
assert.Equal(t, types.CodeSuccess, resp.Code)
}
// ============================================================
// createSession 测试
// ============================================================
func TestCreateSession_Success(t *testing.T) {
router, registry := setupRouter()
registry.Register(model.RegisteredAgent{
AgentID: "1",
Runner: &stubRunner{sessionID: "sess:1:1"},
})
body := `{"agentId":"1","userId":"user1"}`
w := doRequest(router, "POST", "/api/v1/create_session", body)
assert.Equal(t, 200, w.Code)
resp := parseEnvelope(t, w)
assert.Equal(t, types.CodeSuccess, resp.Code)
data, _ := json.Marshal(resp.Data)
var sessResp CreateSessionResponse
json.Unmarshal(data, &sessResp)
assert.Equal(t, "sess:1:1", sessResp.SessionID)
}
func TestCreateSession_AgentNotFound_Returns0003(t *testing.T) {
router, _ := setupRouter()
body := `{"agentId":"nonexistent","userId":"user1"}`
w := doRequest(router, "POST", "/api/v1/create_session", body)
resp := parseEnvelope(t, w)
assert.Equal(t, types.CodeAgentNotFound, resp.Code)
}
func TestCreateSession_MissingParams_Returns0002(t *testing.T) {
router, _ := setupRouter()
// 空 body
w := doRequest(router, "POST", "/api/v1/create_session", "")
resp := parseEnvelope(t, w)
assert.Equal(t, types.CodeIllegalParameter, resp.Code)
}
func TestCreateSession_Query_Success(t *testing.T) {
router, registry := setupRouter()
registry.Register(model.RegisteredAgent{
AgentID: "1",
Runner: &stubRunner{sessionID: "sess:u1:1"},
})
w := doRequest(router, "GET", "/api/v1/create_session?agentId=1&userId=user1", "")
assert.Equal(t, 200, w.Code)
resp := parseEnvelope(t, w)
assert.Equal(t, types.CodeSuccess, resp.Code)
}
func TestCreateSession_Query_AgentNotFound_Returns0003(t *testing.T) {
router, _ := setupRouter()
w := doRequest(router, "GET", "/api/v1/create_session?agentId=x&userId=u", "")
resp := parseEnvelope(t, w)
assert.Equal(t, types.CodeAgentNotFound, resp.Code)
}
// ============================================================
// chat 测试
// ============================================================
func TestChat_Success(t *testing.T) {
router, registry := setupRouter()
registry.Register(model.RegisteredAgent{
AgentID: "1",
Runner: &stubRunner{sessionID: "s1", runResult: []string{"hello"}},
})
body := `{"agentId":"1","userId":"u1","sessionId":"s1","message":"hi"}`
w := doRequest(router, "POST", "/api/v1/chat", body)
assert.Equal(t, 200, w.Code)
resp := parseEnvelope(t, w)
assert.Equal(t, types.CodeSuccess, resp.Code)
data, _ := json.Marshal(resp.Data)
var chatResp ChatResponse
json.Unmarshal(data, &chatResp)
assert.Equal(t, "hello", chatResp.Content)
}
func TestChat_AgentNotFound_Returns0003(t *testing.T) {
router, _ := setupRouter()
body := `{"agentId":"nonexistent","userId":"u1","message":"hi"}`
w := doRequest(router, "POST", "/api/v1/chat", body)
resp := parseEnvelope(t, w)
assert.Equal(t, types.CodeAgentNotFound, resp.Code)
}
func TestChat_MissingBody_Returns0002(t *testing.T) {
router, _ := setupRouter()
w := doRequest(router, "POST", "/api/v1/chat", "")
resp := parseEnvelope(t, w)
assert.Equal(t, types.CodeIllegalParameter, resp.Code)
}
func TestChat_RunnerError_ReturnsUnknownError(t *testing.T) {
router, registry := setupRouter()
registry.Register(model.RegisteredAgent{
AgentID: "1",
Runner: &stubRunner{sessionID: "s1", runErr: assert.AnError},
})
body := `{"agentId":"1","userId":"u1","sessionId":"s1","message":"hi"}`
w := doRequest(router, "POST", "/api/v1/chat", body)
resp := parseEnvelope(t, w)
assert.Equal(t, types.CodeUnknownError, resp.Code)
}
// ============================================================
// chatStream 测试
// ============================================================
func TestChatStream_SSEHeaders(t *testing.T) {
router, registry := setupRouter()
registry.Register(model.RegisteredAgent{
AgentID: "1",
Runner: &stubRunner{sessionID: "s1", runResult: []string{"chunk1", "chunk2"}},
})
body := `{"agentId":"1","userId":"u1","sessionId":"s1","message":"hi"}`
w := doRequest(router, "POST", "/api/v1/chat_stream", body)
assert.Equal(t, 200, w.Code)
assert.Contains(t, w.Header().Get("Content-Type"), "text/event-stream")
assert.Equal(t, "no-cache", w.Header().Get("Cache-Control"))
}
func TestChatStream_AgentNotFound_ReturnsError(t *testing.T) {
router, _ := setupRouter()
body := `{"agentId":"nonexistent","userId":"u1","message":"hi"}`
w := doRequest(router, "POST", "/api/v1/chat_stream", body)
// SSE 流式中错误通过 event 发送HTTP 状态码仍为 200
assert.Equal(t, 200, w.Code)
}
// ============================================================
// writeError 测试
// ============================================================
func TestWriteError_AppError(t *testing.T) {
gin.SetMode(gin.TestMode)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
writeError(c, types.NewAppError(types.CodeAgentNotFound, "not found"))
var resp Envelope
json.Unmarshal(w.Body.Bytes(), &resp)
assert.Equal(t, types.CodeAgentNotFound, resp.Code)
}
func TestWriteError_UnknownError(t *testing.T) {
gin.SetMode(gin.TestMode)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
writeError(c, assert.AnError)
var resp Envelope
json.Unmarshal(w.Body.Bytes(), &resp)
assert.Equal(t, types.CodeUnknownError, resp.Code)
}
// ============================================================
// success 测试
// ============================================================
func TestSuccess_ReturnsEnvelope(t *testing.T) {
resp := success(map[string]string{"key": "val"})
assert.Equal(t, types.CodeSuccess, resp.Code)
assert.Equal(t, types.InfoSuccess, resp.Info)
assert.NotNil(t, resp.Data)
}