/* * Copyright 2025 coze-dev Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package conversation import ( "context" "errors" "io" "testing" "github.com/cloudwego/eino/schema" "github.com/stretchr/testify/assert" "go.uber.org/mock/gomock" "github.com/coze-dev/coze-studio/backend/api/model/conversation/common" "github.com/coze-dev/coze-studio/backend/api/model/conversation/run" singleagent "github.com/coze-dev/coze-studio/backend/crossdomain/agent/model" agentrun "github.com/coze-dev/coze-studio/backend/crossdomain/agentrun/model" saEntity "github.com/coze-dev/coze-studio/backend/domain/agent/singleagent/entity" "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity" convEntity "github.com/coze-dev/coze-studio/backend/domain/conversation/conversation/entity" openapiEntity "github.com/coze-dev/coze-studio/backend/domain/openauth/openapiauth/entity" cmdEntity "github.com/coze-dev/coze-studio/backend/domain/shortcutcmd/entity" uploadEntity "github.com/coze-dev/coze-studio/backend/domain/upload/entity" uploadService "github.com/coze-dev/coze-studio/backend/domain/upload/service" sseImpl "github.com/coze-dev/coze-studio/backend/infra/sse/impl/sse" mockSingleAgent "github.com/coze-dev/coze-studio/backend/internal/mock/domain/agent/singleagent" mockAgentRun "github.com/coze-dev/coze-studio/backend/internal/mock/domain/conversation/agentrun" mockConversation "github.com/coze-dev/coze-studio/backend/internal/mock/domain/conversation/conversation" mockShortcut "github.com/coze-dev/coze-studio/backend/internal/mock/domain/shortcutcmd" mockUpload "github.com/coze-dev/coze-studio/backend/internal/mock/domain/upload" "github.com/coze-dev/coze-studio/backend/pkg/ctxcache" "github.com/coze-dev/coze-studio/backend/pkg/lang/ptr" "github.com/coze-dev/coze-studio/backend/types/consts" ) func setupMocks(t *testing.T) (*OpenapiAgentRunApplication, *mockShortcut.MockShortcutCmd, *mockUpload.MockUploadService, *mockAgentRun.MockRun, *mockConversation.MockConversation, *mockSingleAgent.MockSingleAgent, *gomock.Controller) { ctrl := gomock.NewController(t) mockShortcutSvc := mockShortcut.NewMockShortcutCmd(ctrl) mockUploadSvc := mockUpload.NewMockUploadService(ctrl) mockAgentRunSvc := mockAgentRun.NewMockRun(ctrl) mockConversationSvc := mockConversation.NewMockConversation(ctrl) mockSingleAgentSvc := mockSingleAgent.NewMockSingleAgent(ctrl) app := &OpenapiAgentRunApplication{ ShortcutDomainSVC: mockShortcutSvc, UploaodDomainSVC: mockUploadSvc, } // Setup ConversationSVC mocks originalConversationSVC := ConversationSVC ConversationSVC = &ConversationApplicationService{ AgentRunDomainSVC: mockAgentRunSvc, ConversationDomainSVC: mockConversationSvc, appContext: &ServiceComponents{ SingleAgentDomainSVC: mockSingleAgentSvc, }, } t.Cleanup(func() { ConversationSVC = originalConversationSVC ctrl.Finish() }) return app, mockShortcutSvc, mockUploadSvc, mockAgentRunSvc, mockConversationSvc, mockSingleAgentSvc, ctrl } func createTestContext() context.Context { ctx := context.Background() ctx = ctxcache.Init(ctx) apiKey := &openapiEntity.ApiKey{ UserID: 12345, ConnectorID: consts.CozeConnectorID, } ctxcache.Store(ctx, consts.OpenapiAuthKeyInCtx, apiKey) return ctx } func createTestRequest() *run.ChatV3Request { return &run.ChatV3Request{ BotID: 67890, ConversationID: ptr.Of(int64(11111)), User: "test-user", AdditionalMessages: []*run.EnterMessage{ { Role: "user", Content: "Hello, world!", ContentType: run.ContentTypeText, }, }, } } func createTestRequestWithMultipleMessages() *run.ChatV3Request { return &run.ChatV3Request{ BotID: 67890, ConversationID: ptr.Of(int64(11111)), User: "test-user", AdditionalMessages: []*run.EnterMessage{ { Role: "user", Content: "Hello, I need help with something.", ContentType: run.ContentTypeText, }, { Role: "assistant", Content: "Sure, I'd be happy to help! What do you need assistance with?", ContentType: run.ContentTypeText, }, { Role: "user", Content: `{"type": "image", "url": "https://example.com/image.jpg"}`, ContentType: run.ContentTypeImage, }, { Role: "user", Content: `{"type": "file", "name": "document.pdf", "url": "https://example.com/doc.pdf"}`, ContentType: run.ContentTypeFile, }, }, } } func createTestRequestWithAssistantOnly() *run.ChatV3Request { return &run.ChatV3Request{ BotID: 67890, ConversationID: ptr.Of(int64(11111)), User: "test-user", AdditionalMessages: []*run.EnterMessage{ { Role: "assistant", Content: "I'm here to help you with any questions you might have.", ContentType: run.ContentTypeText, // assistant role only supports text content type }, }, } } func TestOpenapiAgentRun_Success(t *testing.T) { app, _, _, mockAgentRun, mockConversation, mockSingleAgent, _ := setupMocks(t) ctx := createTestContext() req := createTestRequest() // Mock agent check mockAgent := &saEntity.SingleAgent{ SingleAgent: &singleagent.SingleAgent{ AgentID: 67890, SpaceID: 54321, CreatorID: 12345, }, } mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil) // Mock conversation check mockConv := &convEntity.Conversation{ ID: 11111, CreatorID: 12345, SectionID: 98765, } mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil) // Mock agent run failure to avoid pullStream complexity mockAgentRun.EXPECT().AgentRun(ctx, gomock.Any()).Return(nil, errors.New("mock stream error")) err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req) assert.Error(t, err) assert.Contains(t, err.Error(), "mock stream error") } func TestOpenapiAgentRun_CheckAgentError(t *testing.T) { app, _, _, _, _, mockSingleAgent, _ := setupMocks(t) ctx := createTestContext() req := createTestRequest() // Mock agent check failure mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(nil, errors.New("agent not found")) err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req) assert.Error(t, err) assert.Contains(t, err.Error(), "agent not found") } func TestOpenapiAgentRun_AgentNotExists(t *testing.T) { app, _, _, _, _, mockSingleAgent, _ := setupMocks(t) ctx := createTestContext() req := createTestRequest() // Mock agent check returns nil (agent not exists) mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(nil, nil) err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req) assert.Error(t, err) } func TestOpenapiAgentRun_CheckConversationError(t *testing.T) { app, _, _, _, mockConversation, mockSingleAgent, _ := setupMocks(t) ctx := createTestContext() req := createTestRequest() // Mock agent check success mockAgent := &saEntity.SingleAgent{ SingleAgent: &singleagent.SingleAgent{ AgentID: 67890, SpaceID: 54321, CreatorID: 12345, }, } mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil) // Mock conversation check failure mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(nil, errors.New("conversation not found")) err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req) assert.Error(t, err) assert.Contains(t, err.Error(), "conversation not found") } func TestOpenapiAgentRun_ConversationPermissionError(t *testing.T) { app, _, _, _, mockConversation, mockSingleAgent, _ := setupMocks(t) ctx := createTestContext() req := createTestRequest() // Mock agent check success mockAgent := &saEntity.SingleAgent{ SingleAgent: &singleagent.SingleAgent{ AgentID: 67890, SpaceID: 54321, CreatorID: 12345, }, } mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil) // Mock conversation with different creator mockConv := &convEntity.Conversation{ ID: 11111, CreatorID: 99999, // Different from user ID (12345) SectionID: 98765, } mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil) err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req) assert.Error(t, err) } func TestOpenapiAgentRun_CreateNewConversation(t *testing.T) { app, _, _, mockAgentRun, mockConversation, mockSingleAgent, _ := setupMocks(t) ctx := createTestContext() req := createTestRequest() req.ConversationID = ptr.Of(int64(0)) // No conversation ID // Mock agent check success mockAgent := &saEntity.SingleAgent{ SingleAgent: &singleagent.SingleAgent{ AgentID: 67890, SpaceID: 54321, CreatorID: 12345, }, } mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil) // Mock create new conversation mockConv := &convEntity.Conversation{ ID: 22222, CreatorID: 12345, SectionID: 98765, UserID: ptr.Of("test-user"), } mockConversation.EXPECT().Create(ctx, gomock.Any()).DoAndReturn(func(ctx context.Context, meta *convEntity.CreateMeta) (*convEntity.Conversation, error) { assert.Equal(t, int64(67890), meta.AgentID) assert.Equal(t, "test-user", ptr.From(meta.UserID)) assert.Equal(t, common.Scene_SceneOpenApi, meta.Scene) return mockConv, nil }) // Mock agent run failure to avoid pullStream complexity mockAgentRun.EXPECT().AgentRun(ctx, gomock.Any()).Return(nil, errors.New("mock stream error")) err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req) assert.Error(t, err) assert.Equal(t, int64(22222), *req.ConversationID) // Should be updated } func TestOpenapiAgentRun_AgentRunError(t *testing.T) { app, _, _, mockAgentRun, mockConversation, mockSingleAgent, _ := setupMocks(t) ctx := createTestContext() req := createTestRequest() // Mock agent check success mockAgent := &saEntity.SingleAgent{ SingleAgent: &singleagent.SingleAgent{ AgentID: 67890, SpaceID: 54321, CreatorID: 12345, }, } mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil) // Mock conversation check success mockConv := &convEntity.Conversation{ ID: 11111, CreatorID: 12345, SectionID: 98765, } mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil) // Mock agent run failure mockAgentRun.EXPECT().AgentRun(ctx, gomock.Any()).Return(nil, errors.New("agent run failed")) err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req) assert.Error(t, err) assert.Contains(t, err.Error(), "agent run failed") } func TestOpenapiAgentRun_WithShortcutCommand(t *testing.T) { app, mockShortcut, _, mockAgentRun, mockConversation, mockSingleAgent, _ := setupMocks(t) ctx := createTestContext() req := createTestRequest() req.ShortcutCommand = &run.ShortcutCommandDetail{ CommandID: 123, Parameters: map[string]string{"param1": "value1"}, } // Mock agent check success mockAgent := &saEntity.SingleAgent{ SingleAgent: &singleagent.SingleAgent{ AgentID: 67890, SpaceID: 54321, CreatorID: 12345, }, } mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil) // Mock conversation check success mockConv := &convEntity.Conversation{ ID: 11111, CreatorID: 12345, SectionID: 98765, } mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil) // Mock shortcut command mockCmd := &cmdEntity.ShortcutCmd{ ID: 123, PluginID: 456, PluginToolName: "test-tool", PluginToolID: 789, ToolType: 1, } mockShortcut.EXPECT().GetByCmdID(ctx, int64(123), int32(0)).Return(mockCmd, nil) // Mock agent run failure to avoid pullStream complexity mockAgentRun.EXPECT().AgentRun(ctx, gomock.Any()).Return(nil, errors.New("mock stream error")) err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req) assert.Error(t, err) } func TestOpenapiAgentRun_WithMultipleMessages(t *testing.T) { app, _, _, mockAgentRun, mockConversation, mockSingleAgent, _ := setupMocks(t) ctx := createTestContext() req := createTestRequestWithMultipleMessages() // Mock agent check success mockAgent := &saEntity.SingleAgent{ SingleAgent: &singleagent.SingleAgent{ AgentID: 67890, SpaceID: 54321, CreatorID: 12345, }, } mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil) // Mock conversation check success mockConv := &convEntity.Conversation{ ID: 11111, CreatorID: 12345, SectionID: 98765, } mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil) // Mock agent run failure to avoid pullStream complexity mockAgentRun.EXPECT().AgentRun(ctx, gomock.Any()).Return(nil, errors.New("mock stream error")) err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req) assert.Error(t, err) assert.Contains(t, err.Error(), "mock stream error") // Verify that the request contains multiple messages with different roles and content types assert.Len(t, req.AdditionalMessages, 4) assert.Equal(t, "user", req.AdditionalMessages[0].Role) assert.Equal(t, run.ContentTypeText, req.AdditionalMessages[0].ContentType) assert.Equal(t, "assistant", req.AdditionalMessages[1].Role) assert.Equal(t, run.ContentTypeText, req.AdditionalMessages[1].ContentType) assert.Equal(t, "user", req.AdditionalMessages[2].Role) assert.Equal(t, run.ContentTypeImage, req.AdditionalMessages[2].ContentType) assert.Equal(t, "user", req.AdditionalMessages[3].Role) assert.Equal(t, run.ContentTypeFile, req.AdditionalMessages[3].ContentType) } func TestOpenapiAgentRun_WithAssistantMessage(t *testing.T) { app, _, _, mockAgentRun, mockConversation, mockSingleAgent, _ := setupMocks(t) ctx := createTestContext() req := createTestRequestWithAssistantOnly() // Mock agent check success mockAgent := &saEntity.SingleAgent{ SingleAgent: &singleagent.SingleAgent{ AgentID: 67890, SpaceID: 54321, CreatorID: 12345, }, } mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil) // Mock conversation check success mockConv := &convEntity.Conversation{ ID: 11111, CreatorID: 12345, SectionID: 98765, } mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil) // Mock agent run failure to avoid pullStream complexity mockAgentRun.EXPECT().AgentRun(ctx, gomock.Any()).Return(nil, errors.New("mock stream error")) err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req) assert.Error(t, err) assert.Contains(t, err.Error(), "mock stream error") // Verify that the assistant message only supports text content type assert.Len(t, req.AdditionalMessages, 1) assert.Equal(t, "assistant", req.AdditionalMessages[0].Role) assert.Equal(t, run.ContentTypeText, req.AdditionalMessages[0].ContentType) } func TestOpenapiAgentRun_WithMixedContentTypes(t *testing.T) { app, _, _, mockAgentRun, mockConversation, mockSingleAgent, _ := setupMocks(t) ctx := createTestContext() // Create request with various content types for user role req := &run.ChatV3Request{ BotID: 67890, ConversationID: ptr.Of(int64(11111)), User: "test-user", AdditionalMessages: []*run.EnterMessage{ { Role: "user", Content: "Here's a text message", ContentType: run.ContentTypeText, }, { Role: "user", Content: `{"type": "audio", "url": "https://example.com/audio.mp3"}`, ContentType: run.ContentTypeAudio, }, { Role: "user", Content: `{"type": "video", "url": "https://example.com/video.mp4"}`, ContentType: run.ContentTypeVideo, }, { Role: "assistant", Content: "I can only respond with text content.", ContentType: run.ContentTypeText, // assistant must use text }, { Role: "user", Content: `{"type": "link", "url": "https://example.com"}`, ContentType: run.ContentTypeLink, }, }, } // Mock agent check success mockAgent := &saEntity.SingleAgent{ SingleAgent: &singleagent.SingleAgent{ AgentID: 67890, SpaceID: 54321, CreatorID: 12345, }, } mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil) // Mock conversation check success mockConv := &convEntity.Conversation{ ID: 11111, CreatorID: 12345, SectionID: 98765, } mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil) // Mock agent run failure to avoid pullStream complexity mockAgentRun.EXPECT().AgentRun(ctx, gomock.Any()).Return(nil, errors.New("mock stream error")) err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req) assert.Error(t, err) assert.Contains(t, err.Error(), "mock stream error") // Verify various content types are preserved assert.Len(t, req.AdditionalMessages, 5) // Check user messages with different content types assert.Equal(t, "user", req.AdditionalMessages[0].Role) assert.Equal(t, run.ContentTypeText, req.AdditionalMessages[0].ContentType) assert.Equal(t, "user", req.AdditionalMessages[1].Role) assert.Equal(t, run.ContentTypeAudio, req.AdditionalMessages[1].ContentType) assert.Equal(t, "user", req.AdditionalMessages[2].Role) assert.Equal(t, run.ContentTypeVideo, req.AdditionalMessages[2].ContentType) // Check assistant message (must be text) assert.Equal(t, "assistant", req.AdditionalMessages[3].Role) assert.Equal(t, run.ContentTypeText, req.AdditionalMessages[3].ContentType) assert.Equal(t, "user", req.AdditionalMessages[4].Role) assert.Equal(t, run.ContentTypeLink, req.AdditionalMessages[4].ContentType) } func TestOpenapiAgentRun_ParseAdditionalMessages_InvalidRole(t *testing.T) { app, _, _, _, mockConversation, mockSingleAgent, _ := setupMocks(t) ctx := createTestContext() // Create request with invalid role req := &run.ChatV3Request{ BotID: 67890, ConversationID: ptr.Of(int64(11111)), User: "test-user", AdditionalMessages: []*run.EnterMessage{ { Role: "system", // Invalid role Content: "System message", ContentType: run.ContentTypeText, }, }, } // Mock agent check success mockAgent := &saEntity.SingleAgent{ SingleAgent: &singleagent.SingleAgent{ AgentID: 67890, SpaceID: 54321, CreatorID: 12345, }, } mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil) // Mock conversation check success to reach parseAdditionalMessages mockConv := &convEntity.Conversation{ ID: 11111, CreatorID: 12345, SectionID: 98765, } mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil) err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req) assert.Error(t, err) assert.Contains(t, err.Error(), "additional message role only support user and assistant") } func TestOpenapiAgentRun_ParseAdditionalMessages_InvalidType(t *testing.T) { app, _, _, _, mockConversation, mockSingleAgent, _ := setupMocks(t) ctx := createTestContext() // Create request with invalid message type invalidType := "invalid_type" req := &run.ChatV3Request{ BotID: 67890, ConversationID: ptr.Of(int64(11111)), User: "test-user", AdditionalMessages: []*run.EnterMessage{ { Role: "user", Content: "Test message", ContentType: run.ContentTypeText, Type: &invalidType, // Invalid type }, }, } // Mock agent check success mockAgent := &saEntity.SingleAgent{ SingleAgent: &singleagent.SingleAgent{ AgentID: 67890, SpaceID: 54321, CreatorID: 12345, }, } mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil) // Mock conversation check success to reach parseAdditionalMessages mockConv := &convEntity.Conversation{ ID: 11111, CreatorID: 12345, SectionID: 98765, } mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil) err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req) assert.Error(t, err) assert.Contains(t, err.Error(), "additional message type only support question and answer now") } func TestOpenapiAgentRun_ParseAdditionalMessages_AnswerWithNonTextContent(t *testing.T) { app, _, _, _, mockConversation, mockSingleAgent, _ := setupMocks(t) ctx := createTestContext() // Create request with answer type but non-text content answerType := "answer" req := &run.ChatV3Request{ BotID: 67890, ConversationID: ptr.Of(int64(11111)), User: "test-user", AdditionalMessages: []*run.EnterMessage{ { Role: "assistant", Content: `[{"type": "image", "file_url": "https://example.com/image.jpg"}]`, ContentType: run.ContentTypeMixApi, // object_string Type: &answerType, }, }, } // Mock agent check success mockAgent := &saEntity.SingleAgent{ SingleAgent: &singleagent.SingleAgent{ AgentID: 67890, SpaceID: 54321, CreatorID: 12345, }, } mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil) // Mock conversation check success to reach parseAdditionalMessages mockConv := &convEntity.Conversation{ ID: 11111, CreatorID: 12345, SectionID: 98765, } mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil) err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req) assert.Error(t, err) assert.Contains(t, err.Error(), "answer messages only support text content") } func TestOpenapiAgentRun_ParseAdditionalMessages_MixApiWithFileURL(t *testing.T) { app, _, _, mockAgentRun, mockConversation, mockSingleAgent, _ := setupMocks(t) ctx := createTestContext() // Create request with object_string content type and file URL req := &run.ChatV3Request{ BotID: 67890, ConversationID: ptr.Of(int64(11111)), User: "test-user", AdditionalMessages: []*run.EnterMessage{ { Role: "user", Content: `[{"type": "text", "text": "Here's an image:"}, {"type": "image", "file_url": "https://example.com/image.jpg"}]`, ContentType: run.ContentTypeMixApi, }, }, } // Mock agent check success mockAgent := &saEntity.SingleAgent{ SingleAgent: &singleagent.SingleAgent{ AgentID: 67890, SpaceID: 54321, CreatorID: 12345, }, } mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil) // Mock conversation check success mockConv := &convEntity.Conversation{ ID: 11111, CreatorID: 12345, SectionID: 98765, } mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil) // Mock agent run failure to avoid pullStream complexity mockAgentRun.EXPECT().AgentRun(ctx, gomock.Any()).Return(nil, errors.New("mock stream error")) err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req) assert.Error(t, err) assert.Contains(t, err.Error(), "mock stream error") } func TestOpenapiAgentRun_ParseAdditionalMessages_MixApiWithFileID(t *testing.T) { app, _, mockUpload, mockAgentRun, mockConversation, mockSingleAgent, _ := setupMocks(t) ctx := createTestContext() // Create request with object_string content type and file ID req := &run.ChatV3Request{ BotID: 67890, ConversationID: ptr.Of(int64(11111)), User: "test-user", AdditionalMessages: []*run.EnterMessage{ { Role: "user", Content: `[{"type": "file", "file_id": "12345"}]`, ContentType: run.ContentTypeMixApi, }, }, } // Mock agent check success mockAgent := &saEntity.SingleAgent{ SingleAgent: &singleagent.SingleAgent{ AgentID: 67890, SpaceID: 54321, CreatorID: 12345, }, } mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil) // Mock conversation check success mockConv := &convEntity.Conversation{ ID: 11111, CreatorID: 12345, SectionID: 98765, } mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil) // Mock upload service to return file info mockUpload.EXPECT().GetFile(ctx, gomock.Any()).DoAndReturn(func(ctx context.Context, req *uploadService.GetFileRequest) (*uploadService.GetFileResponse, error) { assert.Equal(t, int64(12345), req.ID) return &uploadService.GetFileResponse{ File: &uploadEntity.File{ Url: "https://example.com/file.pdf", TosURI: "tos://bucket/file.pdf", }, }, nil }) // Mock agent run failure to avoid pullStream complexity mockAgentRun.EXPECT().AgentRun(ctx, gomock.Any()).Return(nil, errors.New("mock stream error")) err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req) assert.Error(t, err) assert.Contains(t, err.Error(), "mock stream error") } func TestOpenapiAgentRun_ParseAdditionalMessages_FileIDError(t *testing.T) { app, _, mockUpload, _, mockConversation, mockSingleAgent, _ := setupMocks(t) ctx := createTestContext() // Create request with object_string content type and file ID that will fail req := &run.ChatV3Request{ BotID: 67890, ConversationID: ptr.Of(int64(11111)), User: "test-user", AdditionalMessages: []*run.EnterMessage{ { Role: "user", Content: `[{"type": "file", "file_id": "99999"}]`, ContentType: run.ContentTypeMixApi, }, }, } // Mock agent check success mockAgent := &saEntity.SingleAgent{ SingleAgent: &singleagent.SingleAgent{ AgentID: 67890, SpaceID: 54321, CreatorID: 12345, }, } mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil) // Mock conversation check success mockConv := &convEntity.Conversation{ ID: 11111, CreatorID: 12345, SectionID: 98765, } mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil) // Mock upload service to return error mockUpload.EXPECT().GetFile(ctx, gomock.Any()).Return(nil, errors.New("file not found")) err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req) assert.Error(t, err) assert.Contains(t, err.Error(), "file not found") } func TestOpenapiAgentRun_ParseAdditionalMessages_EmptyContent(t *testing.T) { app, _, _, mockAgentRun, mockConversation, mockSingleAgent, _ := setupMocks(t) ctx := createTestContext() // Create request with empty text content (should be skipped) req := &run.ChatV3Request{ BotID: 67890, ConversationID: ptr.Of(int64(11111)), User: "test-user", AdditionalMessages: []*run.EnterMessage{ { Role: "user", Content: "", // Empty content ContentType: run.ContentTypeText, }, { Role: "user", Content: "Valid content", ContentType: run.ContentTypeText, }, }, } // Mock agent check success mockAgent := &saEntity.SingleAgent{ SingleAgent: &singleagent.SingleAgent{ AgentID: 67890, SpaceID: 54321, CreatorID: 12345, }, } mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil) // Mock conversation check success mockConv := &convEntity.Conversation{ ID: 11111, CreatorID: 12345, SectionID: 98765, } mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil) // Mock agent run failure to avoid pullStream complexity mockAgentRun.EXPECT().AgentRun(ctx, gomock.Any()).Return(nil, errors.New("mock stream error")) err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req) assert.Error(t, err) assert.Contains(t, err.Error(), "mock stream error") // Verify that only the non-empty message is included assert.Len(t, req.AdditionalMessages, 2) // Original request still has 2 messages } func TestOpenapiAgentRun_ParseAdditionalMessages_NilMessage(t *testing.T) { app, _, _, mockAgentRun, mockConversation, mockSingleAgent, _ := setupMocks(t) ctx := createTestContext() // Create request with empty content message (should be skipped) req := &run.ChatV3Request{ BotID: 67890, ConversationID: ptr.Of(int64(11111)), User: "test-user", AdditionalMessages: []*run.EnterMessage{ { Role: "user", Content: "", // Empty content message ContentType: run.ContentTypeText, }, { Role: "user", Content: "Valid content", ContentType: run.ContentTypeText, }, }, } // Mock agent check success mockAgent := &saEntity.SingleAgent{ SingleAgent: &singleagent.SingleAgent{ AgentID: 67890, SpaceID: 54321, CreatorID: 12345, }, } mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil) // Mock conversation check success mockConv := &convEntity.Conversation{ ID: 11111, CreatorID: 12345, SectionID: 98765, } mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil) // Mock agent run failure to avoid pullStream complexity mockAgentRun.EXPECT().AgentRun(ctx, gomock.Any()).Return(nil, errors.New("mock stream error")) err := app.OpenapiAgentRun(ctx, &sseImpl.SSenderImpl{}, req) assert.Error(t, err) assert.Contains(t, err.Error(), "mock stream error") } type MockStreamReader struct { chunks []*entity.AgentRunResponse index int } func (m *MockStreamReader) Recv() (*entity.AgentRunResponse, error) { if m.index >= len(m.chunks) { return nil, io.EOF } response := m.chunks[m.index] m.index++ return response, nil } func newMockStreamReader(chunks []*entity.AgentRunResponse) *schema.StreamReader[*entity.AgentRunResponse] { sr, sw := schema.Pipe[*entity.AgentRunResponse](10) go func() { defer sw.Close() for _, chunk := range chunks { sw.Send(chunk, nil) } }() return sr } func TestOpenapiAgentRunSync_Success(t *testing.T) { app, _, _, mockAgentRun, mockConversation, mockSingleAgent, _ := setupMocks(t) ctx := createTestContext() req := &run.ChatV3Request{ BotID: 67890, ConversationID: ptr.Of(int64(11111)), User: "test-user", AdditionalMessages: []*run.EnterMessage{ { Role: "user", Content: "test query", ContentType: run.ContentTypeText, }, }, } // Mock agent check success mockAgent := &saEntity.SingleAgent{ SingleAgent: &singleagent.SingleAgent{ AgentID: 67890, SpaceID: 54321, }, } mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil) // Mock conversation check success mockConv := &convEntity.Conversation{ ID: 11111, CreatorID: 12345, SectionID: 98765, } mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil) // Mock successful agent run with stream mockStream := newMockStreamReader([]*entity.AgentRunResponse{ { Event: entity.RunEventCreated, ChunkRunItem: &entity.RunRecordMeta{ ID: 999, ConversationID: 11111, AgentID: 67890, Status: entity.RunStatusCompleted, SectionID: 98765, CreatedAt: 1640995200000, // 2022-01-01 00:00:00 CompletedAt: 1640995260000, // 2022-01-01 00:01:00 Usage: &agentrun.Usage{ LlmTotalTokens: 100, LlmPromptTokens: 60, LlmCompletionTokens: 40, }, }, }, }) mockAgentRun.EXPECT().AgentRun(ctx, gomock.Any()).Return(mockStream, nil) result, err := app.OpenapiAgentRunSync(ctx, req) assert.NoError(t, err) assert.NotNil(t, result) assert.NotNil(t, result.ChatDetail) assert.Equal(t, int64(999), result.ChatDetail.ID) assert.Equal(t, int64(11111), result.ChatDetail.ConversationID) assert.Equal(t, int64(67890), result.ChatDetail.BotID) assert.Equal(t, string(entity.RunStatusCompleted), result.ChatDetail.Status) assert.Equal(t, ptr.Of(int64(98765)), result.ChatDetail.SectionID) assert.Equal(t, ptr.Of(int32(1640995200)), result.ChatDetail.CreatedAt) assert.Equal(t, ptr.Of(int32(1640995260)), result.ChatDetail.CompletedAt) assert.NotNil(t, result.ChatDetail.Usage) assert.Equal(t, ptr.Of(int32(100)), result.ChatDetail.Usage.TokenCount) assert.Equal(t, ptr.Of(int32(60)), result.ChatDetail.Usage.InputTokens) assert.Equal(t, ptr.Of(int32(40)), result.ChatDetail.Usage.OutputTokens) } func TestOpenapiAgentRunSync_AgentNotFound(t *testing.T) { app, _, _, _, _, mockSingleAgent, _ := setupMocks(t) ctx := createTestContext() req := &run.ChatV3Request{ BotID: 67890, AdditionalMessages: []*run.EnterMessage{ { Role: "user", Content: "test query", ContentType: run.ContentTypeText, }, }, } // Mock agent check failure mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(nil, nil) result, err := app.OpenapiAgentRunSync(ctx, req) assert.Error(t, err) assert.Nil(t, result) assert.Contains(t, err.Error(), "agent not exists") } func TestOpenapiAgentRunSync_ConversationPermissionError(t *testing.T) { app, _, _, _, mockConversation, mockSingleAgent, _ := setupMocks(t) ctx := createTestContext() req := &run.ChatV3Request{ BotID: 67890, ConversationID: ptr.Of(int64(11111)), AdditionalMessages: []*run.EnterMessage{ { Role: "user", Content: "test query", ContentType: run.ContentTypeText, }, }, } // Mock agent check success mockAgent := &saEntity.SingleAgent{ SingleAgent: &singleagent.SingleAgent{ AgentID: 67890, SpaceID: 54321, }, } mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil) // Mock conversation check with wrong user mockConv := &convEntity.Conversation{ ID: 11111, CreatorID: 99999, // Different user ID SectionID: 98765, } mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil) result, err := app.OpenapiAgentRunSync(ctx, req) assert.Error(t, err) assert.Nil(t, result) assert.Contains(t, err.Error(), "user not match") } func TestOpenapiAgentRunSync_StreamError(t *testing.T) { app, _, _, mockAgentRun, mockConversation, mockSingleAgent, _ := setupMocks(t) ctx := createTestContext() req := &run.ChatV3Request{ BotID: 67890, ConversationID: ptr.Of(int64(11111)), User: "test-user", AdditionalMessages: []*run.EnterMessage{ { Role: "user", Content: "test query", ContentType: run.ContentTypeText, }, }, } // Mock agent check success mockAgent := &saEntity.SingleAgent{ SingleAgent: &singleagent.SingleAgent{ AgentID: 67890, SpaceID: 54321, }, } mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil) // Mock conversation check success mockConv := &convEntity.Conversation{ ID: 11111, CreatorID: 12345, SectionID: 98765, } mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil) // Mock stream with error event mockStream := newMockStreamReader([]*entity.AgentRunResponse{ { Event: entity.RunEventError, Error: &entity.RunError{ Code: 500, Msg: "agent run failed", }, }, }) mockAgentRun.EXPECT().AgentRun(ctx, gomock.Any()).Return(mockStream, nil) result, err := app.OpenapiAgentRunSync(ctx, req) assert.Error(t, err) assert.Nil(t, result) assert.Contains(t, err.Error(), "code=500") } func TestOpenapiAgentRunSync_NoFinalResult(t *testing.T) { app, _, _, mockAgentRun, mockConversation, mockSingleAgent, _ := setupMocks(t) ctx := createTestContext() req := &run.ChatV3Request{ BotID: 67890, ConversationID: ptr.Of(int64(11111)), User: "test-user", AdditionalMessages: []*run.EnterMessage{ { Role: "user", Content: "test query", ContentType: run.ContentTypeText, }, }, } // Mock agent check success mockAgent := &saEntity.SingleAgent{ SingleAgent: &singleagent.SingleAgent{ AgentID: 67890, SpaceID: 54321, }, } mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil) // Mock conversation check success mockConv := &convEntity.Conversation{ ID: 11111, CreatorID: 12345, SectionID: 98765, } mockConversation.EXPECT().GetByID(ctx, int64(11111)).Return(mockConv, nil) // Mock stream with no RunEventCreated event mockStream := newMockStreamReader([]*entity.AgentRunResponse{ { Event: entity.RunEventMessageDelta, // Different event type }, }) mockAgentRun.EXPECT().AgentRun(ctx, gomock.Any()).Return(mockStream, nil) result, err := app.OpenapiAgentRunSync(ctx, req) assert.Error(t, err) assert.Nil(t, result) assert.Contains(t, err.Error(), "no final result received") }