chore: import upstream snapshot with attribution
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,606 @@
|
||||
/*
|
||||
* 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 workflow
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
"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/workflow"
|
||||
crossagentrun "github.com/coze-dev/coze-studio/backend/crossdomain/agentrun"
|
||||
"github.com/coze-dev/coze-studio/backend/crossdomain/agentrun/agentrunmock"
|
||||
messageentity "github.com/coze-dev/coze-studio/backend/crossdomain/message/model"
|
||||
crossupload "github.com/coze-dev/coze-studio/backend/crossdomain/upload"
|
||||
"github.com/coze-dev/coze-studio/backend/crossdomain/upload/uploadmock"
|
||||
agententity "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity"
|
||||
uploadentity "github.com/coze-dev/coze-studio/backend/domain/upload/entity"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/upload/service"
|
||||
)
|
||||
|
||||
func TestApplicationService_makeChatFlowUserInput(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
mockUpload := uploadmock.NewMockUploader(ctrl)
|
||||
crossupload.SetDefaultSVC(mockUpload)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
message *workflow.EnterMessage
|
||||
setupMock func()
|
||||
expected string
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "content type text",
|
||||
message: &workflow.EnterMessage{
|
||||
ContentType: "text",
|
||||
Content: "hello",
|
||||
},
|
||||
setupMock: func() {},
|
||||
expected: "hello",
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "content type object_string with text",
|
||||
message: &workflow.EnterMessage{
|
||||
ContentType: "object_string",
|
||||
Content: `[{"type": "text", "text": "hello world"}]`,
|
||||
},
|
||||
setupMock: func() {},
|
||||
expected: "hello world",
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "content type object_string with file",
|
||||
message: &workflow.EnterMessage{
|
||||
ContentType: "object_string",
|
||||
Content: `[{"type": "file", "file_id": "123"}]`,
|
||||
},
|
||||
setupMock: func() {
|
||||
mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 123}).Return(&service.GetFileResponse{
|
||||
File: &uploadentity.File{Url: "https://example.com/file"},
|
||||
}, nil)
|
||||
},
|
||||
expected: "https://example.com/file",
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "content type object_string with text and file",
|
||||
message: &workflow.EnterMessage{
|
||||
ContentType: "object_string",
|
||||
Content: `[{"type": "text", "text": "see this file"}, {"type": "file", "file_id": "123"}]`,
|
||||
},
|
||||
setupMock: func() {
|
||||
mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 123}).Return(&service.GetFileResponse{
|
||||
File: &uploadentity.File{Url: "https://example.com/file"},
|
||||
}, nil)
|
||||
},
|
||||
expected: "see this file,https://example.com/file",
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "get file error",
|
||||
message: &workflow.EnterMessage{
|
||||
ContentType: "object_string",
|
||||
Content: `[{"type": "file", "file_id": "123"}]`,
|
||||
},
|
||||
setupMock: func() {
|
||||
mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 123}).Return(nil, errors.New("get file error"))
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "file not found",
|
||||
message: &workflow.EnterMessage{
|
||||
ContentType: "object_string",
|
||||
Content: `[{"type": "file", "file_id": "123"}]`,
|
||||
},
|
||||
setupMock: func() {
|
||||
mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 123}).Return(&service.GetFileResponse{
|
||||
File: nil,
|
||||
}, nil)
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid content type",
|
||||
message: &workflow.EnterMessage{
|
||||
ContentType: "invalid",
|
||||
},
|
||||
setupMock: func() {},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid json",
|
||||
message: &workflow.EnterMessage{
|
||||
ContentType: "object_string",
|
||||
Content: `invalid-json`,
|
||||
},
|
||||
setupMock: func() {},
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
w := &ApplicationService{}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.setupMock()
|
||||
result, err := w.makeChatFlowUserInput(ctx, tt.message)
|
||||
if tt.expectErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_toConversationMessage(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
mockUpload := uploadmock.NewMockUploader(ctrl)
|
||||
crossupload.SetDefaultSVC(mockUpload)
|
||||
|
||||
bizID, cid, userID, roundID, sectionID := int64(2), int64(1), int64(4), int64(3), int64(5)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
msg *workflow.EnterMessage
|
||||
messageType messageentity.MessageType
|
||||
setupMock func()
|
||||
expected *messageentity.Message
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "content type text",
|
||||
msg: &workflow.EnterMessage{
|
||||
ContentType: "text",
|
||||
Content: "hello",
|
||||
},
|
||||
messageType: messageentity.MessageTypeQuestion,
|
||||
setupMock: func() {},
|
||||
expected: &messageentity.Message{
|
||||
Role: schema.User,
|
||||
ConversationID: cid,
|
||||
AgentID: bizID,
|
||||
RunID: roundID,
|
||||
Content: "hello",
|
||||
ContentType: messageentity.ContentTypeText,
|
||||
MessageType: messageentity.MessageTypeQuestion,
|
||||
UserID: strconv.FormatInt(userID, 10),
|
||||
SectionID: sectionID,
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "content type object_string with text",
|
||||
msg: &workflow.EnterMessage{
|
||||
ContentType: "object_string",
|
||||
Content: `[{"type": "text", "text": "hello"}]`,
|
||||
},
|
||||
messageType: messageentity.MessageTypeQuestion,
|
||||
setupMock: func() {},
|
||||
expected: &messageentity.Message{
|
||||
Role: schema.User,
|
||||
MessageType: messageentity.MessageTypeQuestion,
|
||||
ConversationID: cid,
|
||||
AgentID: bizID,
|
||||
UserID: strconv.FormatInt(userID, 10),
|
||||
RunID: roundID,
|
||||
ContentType: messageentity.ContentTypeMix,
|
||||
DisplayContent: `[{"type": "text", "text": "hello"}]`,
|
||||
MultiContent: []*messageentity.InputMetaData{
|
||||
{Type: messageentity.InputTypeText, Text: "hello"},
|
||||
},
|
||||
SectionID: sectionID,
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "content type object_string with file",
|
||||
msg: &workflow.EnterMessage{
|
||||
ContentType: "object_string",
|
||||
Content: `[{"type": "file", "file_id": "123"}]`,
|
||||
},
|
||||
messageType: messageentity.MessageTypeQuestion,
|
||||
setupMock: func() {
|
||||
mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 123}).Return(&service.GetFileResponse{
|
||||
File: &uploadentity.File{Url: "https://example.com/file", TosURI: "tos://uri", Name: "file.txt"},
|
||||
}, nil)
|
||||
},
|
||||
expected: &messageentity.Message{
|
||||
Role: schema.User,
|
||||
MessageType: messageentity.MessageTypeQuestion,
|
||||
ConversationID: cid,
|
||||
AgentID: bizID,
|
||||
UserID: strconv.FormatInt(userID, 10),
|
||||
RunID: roundID,
|
||||
ContentType: messageentity.ContentTypeMix,
|
||||
DisplayContent: `[{"type": "file", "file_id": "123"}]`,
|
||||
MultiContent: []*messageentity.InputMetaData{
|
||||
{
|
||||
Type: "file",
|
||||
FileData: []*messageentity.FileData{
|
||||
{Url: "https://example.com/file", URI: "tos://uri", Name: "file.txt"},
|
||||
},
|
||||
},
|
||||
},
|
||||
SectionID: sectionID,
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "get file error",
|
||||
msg: &workflow.EnterMessage{
|
||||
ContentType: "object_string",
|
||||
Content: `[{"type": "file", "file_id": "123"}]`,
|
||||
},
|
||||
setupMock: func() {
|
||||
mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 123}).Return(nil, errors.New("get file error"))
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "file not found",
|
||||
msg: &workflow.EnterMessage{
|
||||
ContentType: "object_string",
|
||||
Content: `[{"type": "file", "file_id": "123"}]`,
|
||||
},
|
||||
setupMock: func() {
|
||||
mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 123}).Return(&service.GetFileResponse{}, nil)
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid content type",
|
||||
msg: &workflow.EnterMessage{
|
||||
ContentType: "invalid",
|
||||
},
|
||||
setupMock: func() {},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid json",
|
||||
msg: &workflow.EnterMessage{
|
||||
ContentType: "object_string",
|
||||
Content: "invalid-json",
|
||||
},
|
||||
setupMock: func() {},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid input type",
|
||||
msg: &workflow.EnterMessage{
|
||||
ContentType: "object_string",
|
||||
Content: `[{"type": "invalid"}]`,
|
||||
},
|
||||
setupMock: func() {},
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.setupMock()
|
||||
result, err := toConversationMessage(ctx, bizID, cid, userID, roundID, sectionID, tt.messageType, tt.msg)
|
||||
if tt.expectErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_toSchemaMessage(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
mockUpload := uploadmock.NewMockUploader(ctrl)
|
||||
crossupload.SetDefaultSVC(mockUpload)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
msg *workflow.EnterMessage
|
||||
setupMock func()
|
||||
expected *schema.Message
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "content type text",
|
||||
msg: &workflow.EnterMessage{
|
||||
ContentType: "text",
|
||||
Content: "hello",
|
||||
},
|
||||
setupMock: func() {},
|
||||
expected: &schema.Message{
|
||||
Role: schema.User,
|
||||
Content: "hello",
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "content type object_string with text",
|
||||
msg: &workflow.EnterMessage{
|
||||
ContentType: "object_string",
|
||||
Content: `[{"type": "text", "text": "hello"}]`,
|
||||
},
|
||||
setupMock: func() {},
|
||||
expected: &schema.Message{
|
||||
Role: schema.User,
|
||||
MultiContent: []schema.ChatMessagePart{
|
||||
{Type: schema.ChatMessagePartTypeText, Text: "hello"},
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "content type object_string with image",
|
||||
msg: &workflow.EnterMessage{
|
||||
ContentType: "object_string",
|
||||
Content: `[{"type": "image", "file_id": "123"}]`,
|
||||
},
|
||||
setupMock: func() {
|
||||
mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 123}).Return(&service.GetFileResponse{
|
||||
File: &uploadentity.File{Url: "https://example.com/image.png"},
|
||||
}, nil)
|
||||
},
|
||||
expected: &schema.Message{
|
||||
Role: schema.User,
|
||||
MultiContent: []schema.ChatMessagePart{
|
||||
{
|
||||
Type: schema.ChatMessagePartTypeImageURL,
|
||||
ImageURL: &schema.ChatMessageImageURL{URL: "https://example.com/image.png"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "content type object_string with various file types",
|
||||
msg: &workflow.EnterMessage{
|
||||
ContentType: "object_string",
|
||||
Content: `[{"type": "file", "file_id": "1"}, {"type": "audio", "file_id": "2"}, {"type": "video", "file_id": "3"}]`,
|
||||
},
|
||||
setupMock: func() {
|
||||
mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 1}).Return(&service.GetFileResponse{File: &uploadentity.File{Url: "https://example.com/file"}}, nil)
|
||||
mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 2}).Return(&service.GetFileResponse{File: &uploadentity.File{Url: "https://example.com/audio"}}, nil)
|
||||
mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 3}).Return(&service.GetFileResponse{File: &uploadentity.File{Url: "https://example.com/video"}}, nil)
|
||||
},
|
||||
expected: &schema.Message{
|
||||
Role: schema.User,
|
||||
MultiContent: []schema.ChatMessagePart{
|
||||
{Type: schema.ChatMessagePartTypeFileURL, FileURL: &schema.ChatMessageFileURL{URL: "https://example.com/file"}},
|
||||
{Type: schema.ChatMessagePartTypeAudioURL, AudioURL: &schema.ChatMessageAudioURL{URL: "https://example.com/audio"}},
|
||||
{Type: schema.ChatMessagePartTypeVideoURL, VideoURL: &schema.ChatMessageVideoURL{URL: "https://example.com/video"}},
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "get file error",
|
||||
msg: &workflow.EnterMessage{
|
||||
ContentType: "object_string",
|
||||
Content: `[{"type": "file", "file_id": "123"}]`,
|
||||
},
|
||||
setupMock: func() {
|
||||
mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 123}).Return(nil, errors.New("get file error"))
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "file not found",
|
||||
msg: &workflow.EnterMessage{
|
||||
ContentType: "object_string",
|
||||
Content: `[{"type": "file", "file_id": "123"}]`,
|
||||
},
|
||||
setupMock: func() {
|
||||
mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 123}).Return(&service.GetFileResponse{}, nil)
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid content type",
|
||||
msg: &workflow.EnterMessage{
|
||||
ContentType: "invalid",
|
||||
},
|
||||
setupMock: func() {},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid json",
|
||||
msg: &workflow.EnterMessage{
|
||||
ContentType: "object_string",
|
||||
Content: "invalid-json",
|
||||
},
|
||||
setupMock: func() {},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid input type",
|
||||
msg: &workflow.EnterMessage{
|
||||
ContentType: "object_string",
|
||||
Content: `[{"type": "invalid"}]`,
|
||||
},
|
||||
setupMock: func() {},
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.setupMock()
|
||||
result, err := toSchemaMessage(ctx, tt.msg)
|
||||
if tt.expectErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_makeChatFlowHistoryMessages(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
mockAgentRun := agentrunmock.NewMockAgentRun(ctrl)
|
||||
crossagentrun.SetDefaultSVC(mockAgentRun)
|
||||
mockUpload := uploadmock.NewMockUploader(ctrl)
|
||||
crossupload.SetDefaultSVC(mockUpload)
|
||||
|
||||
bizID, conversationID, userID, sectionID, connectorID := int64(2), int64(1), int64(3), int64(4), int64(5)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
messages []*workflow.EnterMessage
|
||||
setupMock func()
|
||||
expected []*messageentity.Message
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "empty messages",
|
||||
messages: []*workflow.EnterMessage{},
|
||||
setupMock: func() {},
|
||||
expected: []*messageentity.Message{},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "one user message",
|
||||
messages: []*workflow.EnterMessage{
|
||||
{Role: "user", ContentType: "text", Content: "hello"},
|
||||
},
|
||||
setupMock: func() {
|
||||
mockAgentRun.EXPECT().Create(gomock.Any(), gomock.Any()).Return(&agententity.RunRecordMeta{ID: 100}, nil).Times(1)
|
||||
},
|
||||
expected: []*messageentity.Message{
|
||||
{
|
||||
Role: schema.User,
|
||||
ConversationID: conversationID,
|
||||
AgentID: bizID,
|
||||
RunID: 100,
|
||||
Content: "hello",
|
||||
ContentType: messageentity.ContentTypeText,
|
||||
MessageType: messageentity.MessageTypeQuestion,
|
||||
UserID: strconv.FormatInt(userID, 10),
|
||||
SectionID: sectionID,
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "user and assistant message",
|
||||
messages: []*workflow.EnterMessage{
|
||||
{Role: "user", ContentType: "text", Content: "hello"},
|
||||
{Role: "assistant", ContentType: "text", Content: "hi"},
|
||||
},
|
||||
setupMock: func() {
|
||||
mockAgentRun.EXPECT().Create(gomock.Any(), gomock.Any()).Return(&agententity.RunRecordMeta{ID: 100}, nil).Times(1)
|
||||
},
|
||||
expected: []*messageentity.Message{
|
||||
{
|
||||
Role: schema.User,
|
||||
ConversationID: conversationID,
|
||||
AgentID: bizID,
|
||||
RunID: 100,
|
||||
Content: "hello",
|
||||
ContentType: messageentity.ContentTypeText,
|
||||
MessageType: messageentity.MessageTypeQuestion,
|
||||
UserID: strconv.FormatInt(userID, 10),
|
||||
SectionID: sectionID,
|
||||
},
|
||||
{
|
||||
Role: schema.User,
|
||||
ConversationID: conversationID,
|
||||
AgentID: bizID,
|
||||
RunID: 100,
|
||||
Content: "hi",
|
||||
ContentType: messageentity.ContentTypeText,
|
||||
MessageType: messageentity.MessageTypeAnswer,
|
||||
UserID: strconv.FormatInt(userID, 10),
|
||||
SectionID: sectionID,
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "only assistant message",
|
||||
messages: []*workflow.EnterMessage{
|
||||
{Role: "assistant", ContentType: "text", Content: "hi"},
|
||||
},
|
||||
setupMock: func() {},
|
||||
expected: []*messageentity.Message{},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "create run record error",
|
||||
messages: []*workflow.EnterMessage{
|
||||
{Role: "user", ContentType: "text", Content: "hello"},
|
||||
},
|
||||
setupMock: func() {
|
||||
mockAgentRun.EXPECT().Create(gomock.Any(), gomock.Any()).Return(nil, errors.New("db error"))
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid role",
|
||||
messages: []*workflow.EnterMessage{
|
||||
{Role: "system", ContentType: "text", Content: "hello"},
|
||||
},
|
||||
setupMock: func() {},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "toConversationMessage error",
|
||||
messages: []*workflow.EnterMessage{
|
||||
{Role: "user", ContentType: "invalid", Content: "hello"},
|
||||
},
|
||||
setupMock: func() {
|
||||
mockAgentRun.EXPECT().Create(gomock.Any(), gomock.Any()).Return(&agententity.RunRecordMeta{ID: 100}, nil)
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.setupMock()
|
||||
result, err := makeChatFlowHistoryMessages(ctx, bizID, conversationID, userID, sectionID, connectorID, tt.messages)
|
||||
if tt.expectErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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 workflow
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/resource/common"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/search/entity"
|
||||
search "github.com/coze-dev/coze-studio/backend/domain/search/entity"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/search/service"
|
||||
)
|
||||
|
||||
var eventBus service.ResourceEventBus
|
||||
|
||||
func setEventBus(bus service.ResourceEventBus) {
|
||||
eventBus = bus
|
||||
}
|
||||
|
||||
func PublishWorkflowResource(ctx context.Context, workflowID int64, mode *int32, op search.OpType, r *search.ResourceDocument) error {
|
||||
if r == nil {
|
||||
r = &search.ResourceDocument{}
|
||||
}
|
||||
|
||||
r.ResType = common.ResType_Workflow
|
||||
r.ResID = workflowID
|
||||
r.ResSubType = mode
|
||||
|
||||
event := &entity.ResourceDomainEvent{
|
||||
OpType: entity.OpType(op),
|
||||
Resource: r,
|
||||
}
|
||||
|
||||
if op == search.Created {
|
||||
event.Resource.CreateTimeMS = r.CreateTimeMS
|
||||
event.Resource.UpdateTimeMS = r.UpdateTimeMS
|
||||
} else if op == search.Updated {
|
||||
event.Resource.UpdateTimeMS = r.UpdateTimeMS
|
||||
}
|
||||
|
||||
err := eventBus.PublishResources(ctx, event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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 workflow
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
|
||||
"os"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/cloudwego/eino/callbacks"
|
||||
"github.com/cloudwego/eino/compose"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/bizpkg/llm/modelbuilder"
|
||||
knowledge "github.com/coze-dev/coze-studio/backend/domain/knowledge/service"
|
||||
dbservice "github.com/coze-dev/coze-studio/backend/domain/memory/database/service"
|
||||
variables "github.com/coze-dev/coze-studio/backend/domain/memory/variables/service"
|
||||
plugin "github.com/coze-dev/coze-studio/backend/domain/plugin/service"
|
||||
search "github.com/coze-dev/coze-studio/backend/domain/search/service"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/config"
|
||||
wrapPlugin "github.com/coze-dev/coze-studio/backend/domain/workflow/plugin"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/service"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/cache"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/coderunner"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/idgen"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/imagex"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/storage"
|
||||
)
|
||||
|
||||
type ServiceComponents struct {
|
||||
IDGen idgen.IDGenerator
|
||||
DB *gorm.DB
|
||||
Cache cache.Cmdable
|
||||
DatabaseDomainSVC dbservice.Database
|
||||
VariablesDomainSVC variables.Variables
|
||||
PluginDomainSVC plugin.PluginService
|
||||
KnowledgeDomainSVC knowledge.Knowledge
|
||||
DomainNotifier search.ResourceEventBus
|
||||
Tos storage.Storage
|
||||
ImageX imagex.ImageX
|
||||
CPStore compose.CheckPointStore
|
||||
CodeRunner coderunner.Runner
|
||||
WorkflowBuildInChatModel modelbuilder.BaseChatModel
|
||||
}
|
||||
|
||||
func initWorkflowConfig() (workflow.WorkflowConfig, error) {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configBs, err := os.ReadFile(filepath.Join(wd, "resources/conf/workflow/config.yaml"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var cfg *config.WorkflowConfig
|
||||
err = yaml.Unmarshal(configBs, &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func InitService(_ context.Context, components *ServiceComponents) (*ApplicationService, error) {
|
||||
service.RegisterAllNodeAdaptors()
|
||||
|
||||
cfg, err := initWorkflowConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
workflowRepo, err := service.NewWorkflowRepository(components.IDGen, components.DB, components.Cache,
|
||||
components.Tos, components.CPStore, components.WorkflowBuildInChatModel, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
workflow.SetRepository(workflowRepo)
|
||||
|
||||
workflowDomainSVC := service.NewWorkflowService(workflowRepo)
|
||||
wrapPlugin.SetOSS(components.Tos)
|
||||
|
||||
coderunner.SetCodeRunner(components.CodeRunner)
|
||||
callbacks.AppendGlobalHandlers(service.GetTokenCallbackHandler())
|
||||
|
||||
setEventBus(components.DomainNotifier)
|
||||
|
||||
SVC.DomainSVC = workflowDomainSVC
|
||||
SVC.ImageX = components.ImageX
|
||||
SVC.TosClient = components.Tos
|
||||
SVC.IDGenerator = components.IDGen
|
||||
|
||||
err = SVC.InitNodeIconURLCache(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return SVC, nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* 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 workflow
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/workflow"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
|
||||
)
|
||||
|
||||
func TestToVariable(t *testing.T) {
|
||||
fileAssistType := workflow.AssistParameterType_DEFAULT
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
input *workflow.APIParameter
|
||||
expected *vo.Variable
|
||||
expectErr bool
|
||||
expectedErrAs any
|
||||
}{
|
||||
{
|
||||
name: "Nil Input",
|
||||
input: nil,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "Simple String",
|
||||
input: &workflow.APIParameter{
|
||||
Name: "prompt", Type: workflow.ParameterType_String, IsRequired: true,
|
||||
},
|
||||
expected: &vo.Variable{
|
||||
Name: "prompt", Type: vo.VariableTypeString, Required: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Simple Object",
|
||||
input: &workflow.APIParameter{
|
||||
Name: "user",
|
||||
Type: workflow.ParameterType_Object,
|
||||
SubParameters: []*workflow.APIParameter{
|
||||
{Name: "name", Type: workflow.ParameterType_String},
|
||||
{Name: "age", Type: workflow.ParameterType_Integer},
|
||||
},
|
||||
},
|
||||
expected: &vo.Variable{
|
||||
Name: "user",
|
||||
Type: vo.VariableTypeObject,
|
||||
Schema: []*vo.Variable{
|
||||
{Name: "name", Type: vo.VariableTypeString},
|
||||
{Name: "age", Type: vo.VariableTypeInteger},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Array of Objects",
|
||||
input: &workflow.APIParameter{
|
||||
Name: "items",
|
||||
Type: workflow.ParameterType_Array,
|
||||
SubType: ptr.Of(workflow.ParameterType_Object),
|
||||
SubParameters: []*workflow.APIParameter{
|
||||
{Name: "id", Type: workflow.ParameterType_String},
|
||||
{Name: "price", Type: workflow.ParameterType_Number},
|
||||
},
|
||||
},
|
||||
expected: &vo.Variable{
|
||||
Name: "items",
|
||||
Type: vo.VariableTypeList,
|
||||
Schema: &vo.Variable{
|
||||
Type: vo.VariableTypeObject,
|
||||
Schema: []*vo.Variable{
|
||||
{Name: "id", Type: vo.VariableTypeString},
|
||||
{Name: "price", Type: vo.VariableTypeFloat},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Array of Primitives (File)",
|
||||
input: &workflow.APIParameter{
|
||||
Name: "attachments",
|
||||
Type: workflow.ParameterType_Array,
|
||||
SubType: ptr.Of(workflow.ParameterType_String),
|
||||
SubParameters: []*workflow.APIParameter{
|
||||
{AssistType: &fileAssistType},
|
||||
},
|
||||
},
|
||||
expected: &vo.Variable{
|
||||
Name: "attachments",
|
||||
Type: vo.VariableTypeList,
|
||||
Schema: &vo.Variable{
|
||||
Type: vo.VariableTypeString,
|
||||
AssistType: vo.AssistType(fileAssistType),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Array of Primitives (String)",
|
||||
input: &workflow.APIParameter{
|
||||
Name: "tags",
|
||||
Type: workflow.ParameterType_Array,
|
||||
SubType: ptr.Of(workflow.ParameterType_String),
|
||||
},
|
||||
expected: &vo.Variable{
|
||||
Name: "tags",
|
||||
Type: vo.VariableTypeList,
|
||||
Schema: &vo.Variable{
|
||||
Type: vo.VariableTypeString,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Array with missing SubType",
|
||||
input: &workflow.APIParameter{
|
||||
Name: "bad_array",
|
||||
Type: workflow.ParameterType_Array,
|
||||
},
|
||||
expectErr: true,
|
||||
expectedErrAs: "missing a SubType",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
actual, err := toVariable(tc.input)
|
||||
|
||||
if tc.expectErr {
|
||||
require.Error(t, err)
|
||||
if tc.expectedErrAs != nil {
|
||||
assert.True(t, strings.Contains(err.Error(), fmt.Sprint(tc.expectedErrAs)))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user