test: 添加各模块单元测试(config/llm/service/handler)

This commit is contained in:
hhs
2026-06-10 16:11:02 +08:00
parent 1f8ba61444
commit 92c6c27b25
8 changed files with 1847 additions and 4 deletions

View File

@@ -0,0 +1,322 @@
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)
}