518 lines
13 KiB
Go
518 lines
13 KiB
Go
/*
|
|
* 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 internal
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/cloudwego/eino/schema"
|
|
"github.com/stretchr/testify/assert"
|
|
"go.uber.org/mock/gomock"
|
|
|
|
"github.com/coze-dev/coze-studio/backend/infra/imagex"
|
|
mockImagex "github.com/coze-dev/coze-studio/backend/internal/mock/infra/imagex"
|
|
)
|
|
|
|
func TestParseMessageURI(t *testing.T) {
|
|
|
|
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case strings.Contains(r.URL.Path, "image.jpg"):
|
|
w.Header().Set("Content-Type", "image/jpeg")
|
|
w.Write([]byte("fake-image-data"))
|
|
case strings.Contains(r.URL.Path, "file.pdf"):
|
|
w.Header().Set("Content-Type", "application/pdf")
|
|
w.Write([]byte("fake-pdf-data"))
|
|
case strings.Contains(r.URL.Path, "audio.mp3"):
|
|
w.Header().Set("Content-Type", "audio/mpeg")
|
|
w.Write([]byte("fake-audio-data"))
|
|
case strings.Contains(r.URL.Path, "video.mp4"):
|
|
w.Header().Set("Content-Type", "video/mp4")
|
|
w.Write([]byte("fake-video-data"))
|
|
default:
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}
|
|
}))
|
|
defer testServer.Close()
|
|
|
|
tests := []struct {
|
|
name string
|
|
mcMsg *schema.Message
|
|
setupMock func(mock *mockImagex.MockImageX, serverURL string)
|
|
expectedResult *schema.Message
|
|
}{
|
|
{
|
|
name: "nil MultiContent should not be processed",
|
|
mcMsg: &schema.Message{
|
|
Role: schema.User,
|
|
Content: "test message",
|
|
MultiContent: nil,
|
|
},
|
|
setupMock: func(mock *mockImagex.MockImageX, serverURL string) {
|
|
// No mock calls expected
|
|
},
|
|
expectedResult: &schema.Message{
|
|
Role: schema.User,
|
|
Content: "test message",
|
|
MultiContent: nil,
|
|
},
|
|
},
|
|
{
|
|
name: "empty MultiContent should not be processed",
|
|
mcMsg: &schema.Message{
|
|
Role: schema.User,
|
|
Content: "test message",
|
|
MultiContent: []schema.ChatMessagePart{},
|
|
},
|
|
setupMock: func(mock *mockImagex.MockImageX, serverURL string) {
|
|
// No mock calls expected
|
|
},
|
|
expectedResult: &schema.Message{
|
|
Role: schema.User,
|
|
Content: "test message",
|
|
MultiContent: []schema.ChatMessagePart{},
|
|
},
|
|
},
|
|
{
|
|
name: "ImageURL with valid URI should be processed",
|
|
mcMsg: &schema.Message{
|
|
Role: schema.User,
|
|
MultiContent: []schema.ChatMessagePart{
|
|
{
|
|
Type: schema.ChatMessagePartTypeImageURL,
|
|
ImageURL: &schema.ChatMessageImageURL{
|
|
URI: "test-image-uri",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
setupMock: func(mock *mockImagex.MockImageX, serverURL string) {
|
|
mock.EXPECT().GetResourceURL(
|
|
gomock.Any(),
|
|
"test-image-uri",
|
|
).Return(&imagex.ResourceURL{
|
|
URL: serverURL + "/image.jpg",
|
|
}, nil)
|
|
},
|
|
expectedResult: &schema.Message{
|
|
Role: schema.User,
|
|
MultiContent: []schema.ChatMessagePart{
|
|
{
|
|
Type: schema.ChatMessagePartTypeImageURL,
|
|
ImageURL: &schema.ChatMessageImageURL{
|
|
URI: "test-image-uri",
|
|
URL: "",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "ImageURL with empty URI should not be processed",
|
|
mcMsg: &schema.Message{
|
|
Role: schema.User,
|
|
MultiContent: []schema.ChatMessagePart{
|
|
{
|
|
Type: schema.ChatMessagePartTypeImageURL,
|
|
ImageURL: &schema.ChatMessageImageURL{
|
|
URI: "",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
setupMock: func(mock *mockImagex.MockImageX, serverURL string) {
|
|
// No mock calls expected
|
|
},
|
|
expectedResult: &schema.Message{
|
|
Role: schema.User,
|
|
MultiContent: []schema.ChatMessagePart{
|
|
{
|
|
Type: schema.ChatMessagePartTypeImageURL,
|
|
ImageURL: &schema.ChatMessageImageURL{
|
|
URI: "",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "ImageURL with GetResourceURL error should keep original",
|
|
mcMsg: &schema.Message{
|
|
Role: schema.User,
|
|
MultiContent: []schema.ChatMessagePart{
|
|
{
|
|
Type: schema.ChatMessagePartTypeImageURL,
|
|
ImageURL: &schema.ChatMessageImageURL{
|
|
URI: "invalid-uri",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
setupMock: func(mock *mockImagex.MockImageX, serverURL string) {
|
|
mock.EXPECT().GetResourceURL(
|
|
gomock.Any(),
|
|
"invalid-uri",
|
|
).Return(nil, errors.New("resource not found"))
|
|
},
|
|
expectedResult: &schema.Message{
|
|
Role: schema.User,
|
|
MultiContent: []schema.ChatMessagePart{
|
|
{
|
|
Type: schema.ChatMessagePartTypeImageURL,
|
|
ImageURL: &schema.ChatMessageImageURL{
|
|
URI: "invalid-uri",
|
|
URL: "",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// FileURL
|
|
{
|
|
name: "FileURL with valid URI should be processed",
|
|
mcMsg: &schema.Message{
|
|
Role: schema.User,
|
|
MultiContent: []schema.ChatMessagePart{
|
|
{
|
|
Type: schema.ChatMessagePartTypeFileURL,
|
|
FileURL: &schema.ChatMessageFileURL{
|
|
URI: "test-file-uri",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
setupMock: func(mock *mockImagex.MockImageX, serverURL string) {
|
|
mock.EXPECT().GetResourceURL(
|
|
gomock.Any(),
|
|
"test-file-uri",
|
|
).Return(&imagex.ResourceURL{
|
|
URL: serverURL + "/file.pdf",
|
|
}, nil)
|
|
},
|
|
expectedResult: &schema.Message{
|
|
Role: schema.User,
|
|
MultiContent: []schema.ChatMessagePart{
|
|
{
|
|
Type: schema.ChatMessagePartTypeFileURL,
|
|
FileURL: &schema.ChatMessageFileURL{
|
|
URI: "test-file-uri",
|
|
URL: "",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "FileURL with GetResourceURL error should keep original",
|
|
mcMsg: &schema.Message{
|
|
Role: schema.User,
|
|
MultiContent: []schema.ChatMessagePart{
|
|
{
|
|
Type: schema.ChatMessagePartTypeFileURL,
|
|
FileURL: &schema.ChatMessageFileURL{
|
|
URI: "invalid-file-uri",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
setupMock: func(mock *mockImagex.MockImageX, serverURL string) {
|
|
mock.EXPECT().GetResourceURL(
|
|
gomock.Any(),
|
|
"invalid-file-uri",
|
|
).Return(nil, errors.New("resource not found"))
|
|
},
|
|
expectedResult: &schema.Message{
|
|
Role: schema.User,
|
|
MultiContent: []schema.ChatMessagePart{
|
|
{
|
|
Type: schema.ChatMessagePartTypeFileURL,
|
|
FileURL: &schema.ChatMessageFileURL{
|
|
URI: "invalid-file-uri",
|
|
URL: "",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// AudioURL
|
|
{
|
|
name: "AudioURL with valid URI should be processed",
|
|
mcMsg: &schema.Message{
|
|
Role: schema.User,
|
|
MultiContent: []schema.ChatMessagePart{
|
|
{
|
|
Type: schema.ChatMessagePartTypeAudioURL,
|
|
AudioURL: &schema.ChatMessageAudioURL{
|
|
URI: "test-audio-uri",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
setupMock: func(mock *mockImagex.MockImageX, serverURL string) {
|
|
mock.EXPECT().GetResourceURL(
|
|
gomock.Any(),
|
|
"test-audio-uri",
|
|
).Return(&imagex.ResourceURL{
|
|
URL: serverURL + "/audio.mp3",
|
|
}, nil)
|
|
},
|
|
expectedResult: &schema.Message{
|
|
Role: schema.User,
|
|
MultiContent: []schema.ChatMessagePart{
|
|
{
|
|
Type: schema.ChatMessagePartTypeAudioURL,
|
|
AudioURL: &schema.ChatMessageAudioURL{
|
|
URI: "test-audio-uri",
|
|
URL: "",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "AudioURL with GetResourceURL error should keep original",
|
|
mcMsg: &schema.Message{
|
|
Role: schema.User,
|
|
MultiContent: []schema.ChatMessagePart{
|
|
{
|
|
Type: schema.ChatMessagePartTypeAudioURL,
|
|
AudioURL: &schema.ChatMessageAudioURL{
|
|
URI: "invalid-audio-uri",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
setupMock: func(mock *mockImagex.MockImageX, serverURL string) {
|
|
mock.EXPECT().GetResourceURL(
|
|
gomock.Any(),
|
|
"invalid-audio-uri",
|
|
).Return(nil, errors.New("resource not found"))
|
|
},
|
|
expectedResult: &schema.Message{
|
|
Role: schema.User,
|
|
MultiContent: []schema.ChatMessagePart{
|
|
{
|
|
Type: schema.ChatMessagePartTypeAudioURL,
|
|
AudioURL: &schema.ChatMessageAudioURL{
|
|
URI: "invalid-audio-uri",
|
|
URL: "",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// VideoURL
|
|
{
|
|
name: "VideoURL with valid URI should be processed",
|
|
mcMsg: &schema.Message{
|
|
Role: schema.User,
|
|
MultiContent: []schema.ChatMessagePart{
|
|
{
|
|
Type: schema.ChatMessagePartTypeVideoURL,
|
|
VideoURL: &schema.ChatMessageVideoURL{
|
|
URI: "test-video-uri",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
setupMock: func(mock *mockImagex.MockImageX, serverURL string) {
|
|
mock.EXPECT().GetResourceURL(
|
|
gomock.Any(),
|
|
"test-video-uri",
|
|
).Return(&imagex.ResourceURL{
|
|
URL: serverURL + "/video.mp4",
|
|
}, nil)
|
|
},
|
|
expectedResult: &schema.Message{
|
|
Role: schema.User,
|
|
MultiContent: []schema.ChatMessagePart{
|
|
{
|
|
Type: schema.ChatMessagePartTypeVideoURL,
|
|
VideoURL: &schema.ChatMessageVideoURL{
|
|
URI: "test-video-uri",
|
|
URL: "",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "VideoURL with GetResourceURL error should keep original",
|
|
mcMsg: &schema.Message{
|
|
Role: schema.User,
|
|
MultiContent: []schema.ChatMessagePart{
|
|
{
|
|
Type: schema.ChatMessagePartTypeVideoURL,
|
|
VideoURL: &schema.ChatMessageVideoURL{
|
|
URI: "invalid-video-uri",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
setupMock: func(mock *mockImagex.MockImageX, serverURL string) {
|
|
mock.EXPECT().GetResourceURL(
|
|
gomock.Any(),
|
|
"invalid-video-uri",
|
|
).Return(nil, errors.New("resource not found"))
|
|
},
|
|
expectedResult: &schema.Message{
|
|
Role: schema.User,
|
|
MultiContent: []schema.ChatMessagePart{
|
|
{
|
|
Type: schema.ChatMessagePartTypeVideoURL,
|
|
VideoURL: &schema.ChatMessageVideoURL{
|
|
URI: "invalid-video-uri",
|
|
URL: "",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// mix content types
|
|
{
|
|
name: "Mixed content types should be processed correctly",
|
|
mcMsg: &schema.Message{
|
|
Role: schema.User,
|
|
MultiContent: []schema.ChatMessagePart{
|
|
{
|
|
Type: schema.ChatMessagePartTypeImageURL,
|
|
ImageURL: &schema.ChatMessageImageURL{
|
|
URI: "test-image-uri",
|
|
},
|
|
},
|
|
{
|
|
Type: schema.ChatMessagePartTypeFileURL,
|
|
FileURL: &schema.ChatMessageFileURL{
|
|
URI: "test-file-uri",
|
|
},
|
|
},
|
|
{
|
|
Type: schema.ChatMessagePartTypeText,
|
|
Text: "This is text content",
|
|
},
|
|
},
|
|
},
|
|
setupMock: func(mock *mockImagex.MockImageX, serverURL string) {
|
|
mock.EXPECT().GetResourceURL(
|
|
gomock.Any(),
|
|
"test-image-uri",
|
|
).Return(&imagex.ResourceURL{
|
|
URL: serverURL + "/image.jpg",
|
|
}, nil)
|
|
mock.EXPECT().GetResourceURL(
|
|
gomock.Any(),
|
|
"test-file-uri",
|
|
).Return(&imagex.ResourceURL{
|
|
URL: serverURL + "/file.pdf",
|
|
}, nil)
|
|
},
|
|
expectedResult: &schema.Message{
|
|
Role: schema.User,
|
|
MultiContent: []schema.ChatMessagePart{
|
|
{
|
|
Type: schema.ChatMessagePartTypeImageURL,
|
|
ImageURL: &schema.ChatMessageImageURL{
|
|
URI: "test-image-uri",
|
|
URL: "",
|
|
},
|
|
},
|
|
{
|
|
Type: schema.ChatMessagePartTypeFileURL,
|
|
FileURL: &schema.ChatMessageFileURL{
|
|
URI: "test-file-uri",
|
|
URL: "",
|
|
},
|
|
},
|
|
{
|
|
Type: schema.ChatMessagePartTypeText,
|
|
Text: "This is text content",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Unsupported content type should be ignored",
|
|
mcMsg: &schema.Message{
|
|
Role: schema.User,
|
|
MultiContent: []schema.ChatMessagePart{
|
|
{
|
|
Type: schema.ChatMessagePartTypeText,
|
|
Text: "This is text content",
|
|
},
|
|
},
|
|
},
|
|
setupMock: func(mock *mockImagex.MockImageX, serverURL string) {
|
|
// No mock calls expected
|
|
},
|
|
expectedResult: &schema.Message{
|
|
Role: schema.User,
|
|
MultiContent: []schema.ChatMessagePart{
|
|
{
|
|
Type: schema.ChatMessagePartTypeText,
|
|
Text: "This is text content",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
|
|
mockImagexClient := mockImagex.NewMockImageX(ctrl)
|
|
tt.setupMock(mockImagexClient, testServer.URL)
|
|
|
|
ctx := context.Background()
|
|
result := parseMessageURI(ctx, tt.mcMsg, mockImagexClient)
|
|
|
|
// For tests that expect dynamic URL setting, update expected results
|
|
// Only set URLs for successful cases (where mock returns no error)
|
|
if !strings.Contains(tt.name, "error") {
|
|
for i, part := range tt.expectedResult.MultiContent {
|
|
switch part.Type {
|
|
case schema.ChatMessagePartTypeImageURL:
|
|
if part.ImageURL != nil && part.ImageURL.URL == "" && part.ImageURL.URI != "" {
|
|
tt.expectedResult.MultiContent[i].ImageURL.URL = testServer.URL + "/image.jpg"
|
|
}
|
|
case schema.ChatMessagePartTypeFileURL:
|
|
if part.FileURL != nil && part.FileURL.URL == "" && part.FileURL.URI != "" {
|
|
tt.expectedResult.MultiContent[i].FileURL.URL = testServer.URL + "/file.pdf"
|
|
}
|
|
case schema.ChatMessagePartTypeAudioURL:
|
|
if part.AudioURL != nil && part.AudioURL.URL == "" && part.AudioURL.URI != "" {
|
|
tt.expectedResult.MultiContent[i].AudioURL.URL = testServer.URL + "/audio.mp3"
|
|
}
|
|
case schema.ChatMessagePartTypeVideoURL:
|
|
if part.VideoURL != nil && part.VideoURL.URL == "" && part.VideoURL.URI != "" {
|
|
tt.expectedResult.MultiContent[i].VideoURL.URL = testServer.URL + "/video.mp4"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
assert.Equal(t, tt.expectedResult, result)
|
|
})
|
|
}
|
|
}
|